基于OpenAI的自动记账工具:从自然语言到结构化账目
简介这是一份面向个人开发者与财务自动化爱好者的智能记账工具源码包基于 OpenAI 接口与 Python 实现用于将银行流水、发票、账单等非结构化财务文档自动解析为可用的记账数据适合具备一定 Python 基础、希望实践 AI 财务场景的读者。压缩包共 51 个文件约 37KB以 36 个 py 脚本为核心辅以 yaml、yml 配置定义任务与账单流程另有 json、ini、makefile、pylintrc 等工程化文件整体结构轻量、便于二次开发。目录中可见 prompts、routers、models、utils、tests 等模块分别承担提示词管理、接口路由、数据模型、日志与错误处理、单元测试等职责并预留 notion 集成入口方便对接外部记账服务。目前已有 238 人学习下载。通过该资源可掌握从数据获取、预处理、信息抽取到结构化落库与报告可视化的完整链路理解 OpenAI 自然语言理解在财务场景中的落地方式并借鉴其工程组织与测试思路。1. 自动记账这件事难的不是记账而是「记」很多人第一次听到「基于 OpenAI 实现的自动记账工具」脑子里浮现的是又一个记账 App。但真正动手做过的人会告诉你记账类工具最劝退的环节从来不是账本本身而是「记」这个动作——手动选分类、填金额、挑账户三步下来十几秒坚持不过两周。我见过太多人下载了记账软件三天热度之后账本停在第一页。这个方案要解决的就是这个断点你只需要用自然语言说一句「昨天打车 38午饭跟同事 AA 付了 65」剩下的金额提取、分类判断、账户归属、时间解析全部交给模型和一段 Python 逻辑去完成。它适合两类人一类是想自己搭一套私人记账流水、又不想被 App 绑架的开发者另一类是手里已经有一堆微信/支付宝导出的 CSV想批量清洗成结构化账目的数据党。核心链路其实很短——文本进、结构化 JSON 出、落库、对账难的是让模型稳定地吐出你要的字段。2. 从一句话到一条账目解析链路怎么搭2.1 为什么用「函数调用」而不是让模型直接写 SQL新手最容易踩的坑是直接给模型一句「把这句话转成 INSERT 语句」。看起来省事实际上你会遇到三个问题模型偶尔会编造不存在的字段名、金额单位时有时无、日期格式每次都不一样。更麻烦的是一旦模型输出被拼进 SQL注入风险就跟着来了。常见做法是用 OpenAI 的 function calling现在叫 tool calling能力把「记一笔账」定义成一个带严格 schema 的函数让模型只负责填参数不负责拼语句。这样模型输出天然是 JSON字段类型由 schema 约束落库那一步由你自己的代码控制。我一般会把 schema 写成这样# tools.py # 定义记账工具的参数 schema模型只能按这个结构填 RECORD_TOOL { type: function, function: { name: record_transaction, description: 记录一笔个人收支流水, parameters: { type: object, properties: { amount: { type: number, description: 金额正数表示收入负数表示支出单位元 }, category: { type: string, enum: [餐饮, 交通, 购物, 居住, 医疗, 娱乐, 工资, 理财, 其他] }, account: { type: string, description: 支付账户如 微信/支付宝/银行卡/现金 }, occurred_at: { type: string, description: 发生时间ISO8601 格式缺省用当前时间 }, note: { type: string, description: 原始描述或补充备注 } }, required: [amount, category, account] } } }这段 schema 的关键在enum和required。enum把分类锁死在一个闭集里模型没法自由发挥造出「吃喝玩乐」这种新分类required保证金额、分类、账户三个字段一定存在缺了模型会被强制重试。amount用正负号区分收支比单独加一个type字段更省事后面统计时直接sum就行。2.2 调用模型并拿到结构化结果有了 schema调用逻辑就变得很直白。下面这段是核心解析函数注意tool_choice这个参数——它强制模型必须调用这个工具而不是跟你闲聊。# parser.py import json from openai import OpenAI from tools import RECORD_TOOL client OpenAI() # 从环境变量读取 API Key SYSTEM_PROMPT 你是一个记账助手。用户会用口语描述一笔收支 你需要调用 record_transaction 工具提取字段。 规则 1. 支出金额为负数收入为正数 2. 无法判断账户时默认填「微信」 3. 相对时间昨天、上周五请换算成具体日期 4. 不要向用户追问直接给出最合理的判断。 def parse_text(user_text: str) - dict: resp client.chat.completions.create( modelgpt-4o-mini, # 解析任务用轻量模型足够 messages[ {role: system, content: SYSTEM_PROMPT}, {role: user, content: user_text}, ], tools[RECORD_TOOL], tool_choice{type: function, function: {name: record_transaction}}, temperature0, # 解析任务要确定性温度归零 ) call resp.choices[0].message.tool_calls[0] return json.loads(call.function.arguments)逻辑说明tool_choice指定具体函数名模型没有「不调用」的选项这比在 prompt 里写「请务必输出 JSON」可靠得多。temperature0是为了让同样的输入尽量得到同样的输出记账这种场景不需要创造性。gpt-4o-mini这类小模型处理字段抽取完全够用成本比大模型低一个数量级批量清洗 CSV 时这个差距会被放大到很可观。参数说明model换成更强的模型不会让字段准确率显著提升反而拖慢速度除非你的输入里夹杂大量方言和隐喻。temperature不要设成 0.7 以上否则同一句话两次解析可能给出不同分类。2.3 落库SQLite 起步别一上来就上 PostgreSQL个人记账的数据量一年撑死几千条SQLite 完全够用而且零运维。表结构建议把原始文本也存下来方便日后回溯模型判断对不对。# storage.py import sqlite3 from datetime import datetime def init_db(pathledger.db): conn sqlite3.connect(path) conn.execute( CREATE TABLE IF NOT EXISTS transactions ( id INTEGER PRIMARY KEY AUTOINCREMENT, amount REAL NOT NULL, category TEXT NOT NULL, account TEXT NOT NULL, occurred_at TEXT NOT NULL, note TEXT, raw_text TEXT, -- 保留原始输入便于复盘 created_at TEXT DEFAULT CURRENT_TIMESTAMP ) ) conn.commit() return conn def insert_tx(conn, tx: dict, raw: str): conn.execute( INSERT INTO transactions (amount, category, account, occurred_at, note, raw_text) VALUES (?, ?, ?, ?, ?, ?), (tx[amount], tx[category], tx[account], tx.get(occurred_at) or datetime.now().isoformat(), tx.get(note, ), raw) ) conn.commit()raw_text这一列是我强烈建议加的。模型分类偶尔会翻车比如把「买药」归到「购物」事后你只看到一条错误分类根本想不起来当时说了什么。留着原文排查时一眼就能定位是模型的问题还是你表述的问题。3. 批量清洗历史账单CSV 导入的完整流程3.1 先看清你的 CSV 长什么样从支付平台导出的账单格式五花八门。常见的有「交易时间 / 交易类型 / 交易对方 / 商品说明 / 收/支 / 金额」这种列结构也有把金额和收支方向揉在一列的。动手写代码前先花五分钟用 pandas 看一眼import pandas as pd df pd.read_csv(bill.csv, encodingutf-8) print(df.columns.tolist()) print(df.head(3)) print(df[收/支].value_counts())这一步能帮你确认三件事编码是不是 UTF-8很多导出是 GBK读进来会乱码、金额列有没有带「¥」符号、收支方向是单独一列还是靠正负号。我踩过的坑是直接假设金额是数字结果pd.read_csv把它读成字符串后面求和全是拼接。3.2 逐行喂给模型但要控制并发和成本批量场景下逐行调用模型是最直观的做法但几千行串行跑会等到天荒地老。用线程池并发同时给个上限避免触发速率限制。from concurrent.futures import ThreadPoolExecutor from parser import parse_text from storage import init_db, insert_tx import pandas as pd def clean_amount(v): # 去掉货币符号和千分位逗号转成 float return float(str(v).replace(¥, ).replace(,, ).strip()) def process_row(row): text f{row[交易时间]} {row[交易对方]} {row[商品说明]} {row[金额]} try: tx parse_text(text) return tx, text except Exception as e: return None, text # 失败的行先跳过后面单独处理 def batch_import(csv_path, max_workers5): df pd.read_csv(csv_path, encodingutf-8) conn init_db() with ThreadPoolExecutor(max_workersmax_workers) as pool: for tx, raw in pool.map(process_row, df.to_dict(records)): if tx: insert_tx(conn, tx, raw) conn.close()max_workers5是个保守值实测再往上加容易撞到每分钟请求数限制反而因为重试拖慢整体。失败的行不要直接丢收集起来最后统一重跑往往是因为某一行文本特别长或者含特殊字符。3.3 用规则兜底别让模型处理它不擅长的模型擅长理解「星巴克」是餐饮但不擅长做精确的算术和格式转换。金额清洗、日期标准化这类确定性工作用代码做比用模型做又快又准。我的分工原则是语义判断交给模型格式规整交给代码。比如日期模型返回的可能是「2024-03-15」也可能是「3月15日」落库前统一用dateutil解析一遍from dateutil import parser as date_parser def normalize_date(s): try: return date_parser.parse(s).isoformat() except (ValueError, TypeError): return None # 解析不了就交给上层用当前时间兜底4. 避坑与排查那些让账本对不上的细节4.1 金额正负号反了月底统计全错现象月底一看支出是负数收入也是负数总支出算出来是个正数。原因多半是模型把「支出」也返回了正数而你的统计逻辑假设支出为负。解决在落库前加一道校验如果category属于支出类且amount 0强制取反反之收入类强制取正。别指望 prompt 里写一句「支出为负」模型就永远遵守加代码校验才是后悔药。4.2 同一笔账被记了两次现象批量导入时发现某些行重复。原因通常是并发重试——某次请求超时了你的代码重试但第一次其实已经成功落库。解决给transactions表加一个基于raw_text occurred_at的唯一索引插入时用INSERT OR IGNORE。这样即使重试也不会产生重复行。4.3 分类总是往「其他」里塞现象大量账目分类是「其他」模型像是偷懒。原因有两个一是你的enum里分类太粗模型找不到合适的就往兜底项放二是 prompt 里没给分类的定义。解决在 system prompt 里给每个分类配一句说明比如「餐饮指正餐、外卖、咖啡奶茶购物指实物商品」。实测加了定义之后「其他」的占比能从三成降到一成以内。4.4 中文编码把 CSV 读成乱码现象pd.read_csv之后列名是æäº¤æ这种。原因文件是 GBK 编码你用 UTF-8 读。解决先试encodingutf-8报UnicodeDecodeError就换encodinggbk。更稳的做法是用chardet探测编码但个人项目里手动试两次就够了。4.5 模型偶尔返回空参数现象json.loads报错或者amount字段是None。原因输入文本太短或太模糊比如「花了点钱」模型无法确定金额。解决在parse_text里加一层校验amount缺失时抛出明确异常让上层决定是丢弃还是标记为待补充。不要让它静默写入一条金额为 0 的记录那比报错更危险。5. 让账本自己会说话从流水到月度洞察账目落库只是起点真正有价值的是让模型基于结构化数据做二次分析。这一步和前面的解析是反过来的——前面是「文本转结构」这里是「结构转洞察」。我一般会写一个查询函数把某个月的流水拉出来拼成一段紧凑的文本再让模型生成一段人话总结。def monthly_insight(conn, year_month2024-03): rows conn.execute( SELECT category, SUM(amount) AS total, COUNT(*) AS cnt FROM transactions WHERE occurred_at LIKE ? GROUP BY category ORDER BY total ASC, (f{year_month}%,) ).fetchall() summary \n.join( f{r[0]}: 合计 {r[1]:.2f} 元, 共 {r[2]} 笔 for r in rows ) prompt f以下是我 {year_month} 的支出分类汇总\n{summary}\n \ 请用三句话指出最值得注意的一点并给一条可执行的省钱建议。 resp client.chat.completions.create( modelgpt-4o-mini, messages[{role: user, content: prompt}], temperature0.5, ) return resp.choices[0].message.content这里temperature可以稍微调高到 0.5让总结读起来不那么机械。注意查询里ORDER BY total ASC因为支出是负数升序排列意味着花得最多的排在最前面模型一眼就能看到大头在哪。进阶一点的做法是把连续几个月的分类汇总都喂进去让模型找趋势。比如「餐饮支出连续三个月上升」这种结论靠人翻账本很难发现但模型对比几组数字就能指出来。参数上要注意的是喂给模型的汇总文本别太长超过几百行反而会稀释重点按月聚合到分类级别就够了。还有一个我常用的技巧让模型给每笔大额支出打一个「必要性」标签分「必要 / 可选 / 冲动」三档存回数据库。月底一统计冲动消费占比一目了然。这个标签同样用 tool calling 实现schema 里加一个enum字段即可和前面的记账工具是同一套模式。我自己用这套东西跑了小半年最大的教训是别追求一次把分类体系设计完美。一开始我列了二十多个分类结果模型经常在相近分类之间摇摆后来砍到九个准确率反而上去了。分类是给人看的够用就行剩下的细节交给note字段。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

金融数据服务平台架构实践:实时指标计算与规则引擎设计

金融数据服务平台架构实践:实时指标计算与规则引擎设计

前阵子一直在忙一个代号叫 financial-services 内部项目的重构,这个项目说白了就是一套面向金融服务场景的通用数字化服务平台。不是那种给人一种高深莫测感觉的量化交易系统,也不涉及任何复杂金融模型,它更像是一套“运营中台”——负责把分…

2026/10/18 18:48:07 阅读更多 →
行车记录仪方案实战:主控选型、硬件设计与产测避坑指南

行车记录仪方案实战:主控选型、硬件设计与产测避坑指南

简介:汽车电子领域这份PDF完整梳理了行车记录仪主流方案与主控芯片的对比资料,面向正在选购记录仪的车主、汽车电子爱好者及相关行业从业者,针对性解答“什么是好方案”“主控芯片差别在哪”等高频疑问。内容包括安霸A7系列、联咏NT96650/NT9…

2026/10/18 17:45:59 阅读更多 →
Teamcenter 2007安装全攻略:Oracle 10g数据库环境搭建与避坑指南

Teamcenter 2007安装全攻略:Oracle 10g数据库环境搭建与避坑指南

简介:面向需要部署PLM环境的实施、运维与技术支持人员,这份文档作为一份doc格式的安装记录,详细梳理了Teamcenter 2007安装中数据库层的完整流程,重点围绕Oracle 10.0展开。包内仅一份doc文档,压缩包大小约3MB&#xf…

2026/10/18 18:48:06 阅读更多 →

最新新闻

AI可复现性挑战:固定随机种子、锁定环境,让训练结果稳定复现

AI可复现性挑战:固定随机种子、锁定环境,让训练结果稳定复现

1. 从一次“幽灵复现失败”说起:到底什么是AI的可复现性挑战前阵子我把一台GPU服务器上的旧训练任务重新跑了一遍,用的确实是当时的代码、当时的数据文件、当时的依赖环境,结果AUC从0.713变成了0.704。当时我的第一反应不是“代码写错了”&am…

2026/10/19 3:13:03 阅读更多 →
从零构建计算机网络知识体系:分层、封装与核心概念详解

从零构建计算机网络知识体系:分层、封装与核心概念详解

1. 为什么我要从零写一个计算机网络专栏做开发这些年,被问得最多的问题里,“怎么学计算机网络”绝对排得进前三。每次有新人问我该看什么书、该刷什么题,我都想起自己当年抱着那本厚得能当枕头的经典教材,从物理层啃到应用层&…

2026/10/19 3:13:03 阅读更多 →
Vue3——第 2 篇:开发环境搭建与第一个 Vue 应用

Vue3——第 2 篇:开发环境搭建与第一个 Vue 应用

本篇定位:在理解 Vue 的基本概念之后,我们需要真正"把代码跑起来"。本篇将带你从零开始,一步步搭建 Vue 3 开发环境,创建并运行你的第一个 Vue 应用,并深入理解项目目录结构。读完本篇,你将拥有一…

2026/10/19 3:13:03 阅读更多 →
Vue3——第 3 篇:模板语法——从插值到指令的全面掌握

Vue3——第 3 篇:模板语法——从插值到指令的全面掌握

本篇定位:模板语法是 Vue 的"视图层语言",是你每天编写 Vue 代码时打交道最多的部分。本篇将系统讲解 Vue 模板中的所有语法规则——从简单的文本插值到强大的指令系统,让你彻底掌握"如何把数据变成用户看到的界面"。读完…

2026/10/19 3:13:03 阅读更多 →
用siege做高并发压测:从入门到实战的完整指南

用siege做高并发压测:从入门到实战的完整指南

做Web开发的人,迟早要面对一个问题:系统到底能扛住多少并发?网上把并发数喊到一万两万的不少,可真到上线那一秒,接口被打穿、数据库连接池占满、页面直接白屏,这种事我见过太多次了。说到底,很多…

2026/10/19 3:13:03 阅读更多 →
企业级提示词工程系统化实践:从资产管理到自动化闭环

企业级提示词工程系统化实践:从资产管理到自动化闭环

1. 从零散提示词到企业级提示词资产聊到提示词工程,很多团队还停留在“某个人的编辑器里存着一堆好用的 prompt,谁要用找他要”的阶段。我在多个项目里见过这种状态,最开始效率不算低,一个人能写出质量不错的提示词,自…

2026/10/19 3:12:02 阅读更多 →

日新闻

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

频率跟踪型PWM电源设计:臭氧发生器谐振点漂移与软开关实现

简介:本资源为电源技术方向的学术研究文档,聚焦介质阻挡放电型臭氧发生器电源的频率跟踪型PWM控制策略,面向电力电子、高压电源及臭氧应用领域的科研人员与工程技术人员。针对负载呈容性、等效电容随外加电压升高而增大、谐振频率随之漂移的难…

2026/10/19 0:00:47 阅读更多 →
Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

Vibe Coding 遇上 TaoToken:把开发者思维圣殿的灵感流接入统一 Key 通道

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 0:00:47 阅读更多 →
HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

HundredCV-Chat话题建模实战教程:利用4,871个内置话题标签构建主题分析系统

【免费下载链接】HundredCV-Chat 项目地址: https://ai.gitcode.com/hf_mirrors/Jax-dan/HundredCV-Chat 点击查看 免费下载 HundredCV-Chat 是一个由 100 位青年简历数据开发而来的中文多轮对话数据集,共包含 24,750 组日常闲聊对话和 4,871 个话题标签…

2026/10/19 0:00:47 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型(GMM)的语音识别系统,很多人第一反应就是一堆数学公式,觉得门槛很高。但如果你真的在MATLAB里把它落地跑通,会发现核心其实就是三件事:把声音变成特征向量,用GMM给每个类别…

2026/10/18 0:00:55 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人,但落到具体操作上,无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程:对图像做反白、改调色板、彩色转灰度,最后把像素数据导出成txt文件。单看每一…

2026/10/18 0:00:55 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词,你会发现一个很有意思的现象:每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型,有人指的是运动控制系统,还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/19 1:30:52 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/19 2:54:50 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/18 12:16:16 阅读更多 →