2024年上半年刚过,很多人开始盘算自己的财务状况,想削减开支、制定更合理的理财计划。但直接拿ChatGPT这类工具来处理个人财务数据,隐私风险实在太大——谁也不愿意把自己的银&行流水、消费记录上传到云端吧。于是,一个完全在本地运行的AI财务分析助手应运而生:它无需联网、完全免费,数据始终留在你自己的电脑上。
这个本地AI助手会先导入财务数据,接着分析收入和支出,最后根据你的生活目标给出个性化的财务规划。下面,就一步步演示如何从零搭建这样一个工具。
免责声明:本文仅用于学习,不作为个人财务、投资建议。
整体介绍
目标及架构
整个应用使用Streamlit搭建用户界面,核心逻辑则依赖LangChain与Ollama中的本地开源大模型。项目中用到了Mistral和LLA VA这类前沿开源模型,来实现多模态功能——比如直接看懂图表。
通过精心设计的提示词,大模型被设定为一个“专业财务规划师”。项目的具体目标包括:处理和分类财务数据;分析总收入、支出和结余,同时可视化收入与支出的变化趋势;利用多模态能力理解图表,发现财务规律;最后,根据用户的生活方式生成个性化的投资建议。
需要的工具
Ollama:目前运行开源大模型最简单、最趁手的工具之一。支持Llama 2、Mistral、LLA VA等一系列模型,你可以在ollama.ai/library上找到所有可下载的模型。Ollama在MacOS、Windows和Linux上都能安装。
LangChain:围绕大模型构建的开源框架,极大简化了AI应用的设计和开发。它与Ollama中的开源模型集成得很好。
Streamlit:开源框架,开发者只需少量Python代码就能快速创建和共享数据应用。它特别适合原型设计以及开发复杂的数据仪表盘项目。
Step1:安装应用和准备财务数据
安装Ollama
访问Ollama下载页面,选择与你的操作系统匹配的版本,下载并安装。

安装后,打开终端(Mac用户搜索“终端”,Windows用户搜索“cmd”),输入以下命令。这些命令会把开源大模型下载到你的电脑上。本项目需要Mistral和LLA VA。
ollama serve ollama pull mistral ollama pull lla va ollama run mistral ollama run lla va
准备数据集
这里用合成数据代替真实财务数据。用ChatGPT生成了1000笔财务记录,你也可以直接使用自己的真实数据。生成测试数据的提示词如下:
生成一个年轻金融专业人士在欧洲生活的财务数据集,涵盖2022年1月至2023年12月的1000笔交易。确保收入和支出在各个类别中均衡分布。数据集应包括以下四列:
日期:交易日期(格式:YYYY-MM-DD)
名称/描述:每笔交易的独特详细描述(例如:"工资存款","每月房租支付","与朋友的餐馆晚餐")
支出/收入:明确标明是"支出"还是"收入"
金额(欧元):交易金额(单位:欧元)
生成的数据集包含四列:日期、名称/描述、支出/收入、金额(欧元)。

生成的交易数据示例
安装依赖项
现在安装Langchain和Streamlit的相关依赖:
pip install langchain-community pip install streamlit
Step2:上传并处理数据
上传数据
创建一个新的Python文件“Upload.py”并添加以下代码。
步骤如下:
- 导入必要的库
- 初始化用于分类交易的大模型
- 定义类别:涵盖各种收入和支出类型,帮助大模型准确分类
import streamlit as st
import pandas as pd
from langchain_community.llms import Ollama
llm = Ollama(model="mistral")
categories = [
"Salary/Wages", "Investment Income", "Freelance Income", "Business Revenue","Rental Income",
"Housing", "Utilities","Groceries","Transportation","Insurance","Healthcare","Entertainment",
"Personal Care","Education","Sa vings/Investments","Loans/Debt","Taxes","Childcare",
"Gifts/Donations","Dining Out","Tra vel","Shopping","Subscriptions","Pet Care",
"Home Improvement","Clothing","Tech/Gadgets", "Fitness/Sports",
]
categories_string = ",".join(categories)
构建交易分类函数
1. 分类交易
编写categorize_transactions函数,接收交易名称。通过提示工程引导大模型输出,要求它根据预定义的类别进行分类。收到输出后,将数据组织成结构化的pandas DataFrame。
def categorize_transactions(transaction_names, llm):
prompt = f"""把以下费用分到适当的类别中。
请记住,类别应从以下列表中选择一个,根据它们的主要目的或性质选择最相关的类别:{categories_string}。
输出格式应始终为:transaction name - category。例如:Spotify #2 - Entertainment, Basic Fit Amsterdam Nld #3 - Fitness/Sports
以下是待分类的交易:{transaction_names}
"""
print(prompt)
filtered_response = []
# retry is the LLM output is not consistent
while len(filtered_response) < 2:
response = llm.invoke(prompt).split("n")
print(response)
# Remove items that do not contain "transaction: category" pairs
filtered_response = [item for item in response if '-' in item]
print(filtered_response)
# Put in dataframe
categories_df = pd.DataFrame({"Transaction vs category": filtered_response})
size_dif = len(categories_df) - len(transaction_names.split(","))
if size_dif >= 0:
categories_df["Transaction"] = transaction_names.split(",") + [None] * size_dif
else:
categories_df["Transaction"] = transaction_names.split(",")[:len(categories_df)]
categories_df["Category"] = categories_df["Transaction vs category"].str.split("-", expand=True)[1]
return categories_df
2. 创建数据处理函数
创建process_data函数,处理上传的数据文件,使用categorize_transactions对交易进行分类,并将分类后的数据合并到全局DataFrame中。
def hop(start, stop, step):
for i in range(start, stop, step):
yield i
yield stop
def process_data(df: pd.DataFrame):
unique_transactions = df["Name/Description"].unique()
index_list = list(hop(0, len(unique_transactions), 30))
# Intialise the categories_df_all dataframe
categories_df_all = pd.DataFrame()
# Loop through the index_list
for i in range(0, len(index_list) - 1):
print(f"Looping: {i}")
transaction_names = unique_transactions[index_list[i] : index_list[i + 1]]
transaction_names = ",".join(transaction_names)
categories_df = categorize_transactions(transaction_names, llm)
categories_df_all = pd.concat(
[categories_df_all, categories_df], ignore_index=True
)
# futher clean data:
# Drop NA values
categories_df_all = categories_df_all.dropna()
# Remove the numbering eg "1. " from Transaction column
categories_df_all["Transaction"] = categories_df_all["Transaction"].str.replace(
r"d+.s?", "", regex=True
).str.strip()
new_df = pd.merge(
df,
categories_df_all,
left_on="Name/Description",
right_on="Transaction",
how="left",
)
new_df.to_csv(f"data/{uploaded_file.name}_categorized.csv", index=False)
return new_df
3. 创建Streamlit Web应用程序
设置应用程序标题,并添加文件上传小部件。
st.title("