我自己的记账系统,MIT 许可开源。账单从邮箱进来,读成流水,和已记的账对上,AI 预填,我只确认。网页给人用,CLI 和 API 把同一套能力交给 AI Agent。一句话的目标:每月花半天以内,每个账户的余额和银行 App 对得上。
- 2,124行真实流水回放,与旧流程逐行一致
- 5种账单格式,两家银行、两个支付平台
- 70条能力,其中 10 条只能由人确认
- 359个测试全部通过(9 月 28 日)
为什么做
国内的账单分散在支付平台和几家银行。同一笔钱常常出现两次,银行一次、支付平台一次;提现和退款的方向容易记反;余额对不上时,只能直接查数据库找原因。
这些平台都不开放接口,只能靠邮件。只有一个来源每天自动发账单,另外四个每月要去 App 里申请,前后大约 20 步、4 个解压密码。
从一开始就定下一条:发给模型的内容里,不能有卡号、订单号、密码或验证码。
主循环
邮件或上传,经过读取器,变成统一格式的流水行,再经过渠道语义、匹配、提案、收件箱确认、入账,最后由阿贝回答关于账的问题。每一环只有一个入口、一个落库的状态、一个数。
代码是 pnpm monorepo,六个包:contracts、db、server、cli、web、admin。一个服务进程同时跑 API、任务队列和内置 AI 阿贝。
几个关键决定
读取器只报事实,含义交给一张表
旧的解析器自己决定正负号,某个支付平台「不计收支」的行被记成了收入。现在读取器只报账单上写了什么:金额,收、支还是不计,以及原始的类型和状态。含义(转账、退款、不计收支)交给一张渠道语义表,规则一律是锚定的正则,按优先级取第一条命中;每个渠道最后都有一条按符号兜底并交给人看的规则,新出现的类型不会被瞎猜。在全部真实数据上,没有一行落空,还修正了 24 行被记成收入的错。规则可以在后台编辑,保存前能预览哪些行会变。
一种账单一个读取器,用样本回放
以前改一个解析要过五层配置。现在每种账单是一个代码读取器,输出同一种行结构,每个都带脱敏样本和期望结果:改了就回放,一行都不能变。带密码的 zip、GBK 编码探测、CSV、XLSX、PDF、邮件这些通用处理集中在一处。拿 49 份文档、2124 行真实数据只读回放,和旧流程逐行一致;其中一家银行的 PDF 改成按列位置读取后,修好了旧版约 27% 的行对方户名错位。
一个数,落在库里
「需要人处理」以前在四个地方各算一遍,同一页上会同时出现 20 和 25。现在行状态落库:8 种状态,每次跃迁写一条事件(谁、何时、依据、能否撤销)。旧的状态列改成只读的生成列,写不进去;约束规定已入账的行必须挂交易,被忽略的行必须写理由。
一份能力目录,派生四端
CLI 会推荐不存在的命令,试运行能过、真写却失败,Agent 还有五处绕开 API。现在每条能力只用 zod 定义一次,HTTP 路由、CLI 命令、阿贝的工具和 OpenAPI 都从它生成。试运行在真实事务里跑同一个函数,最后回滚,所以「试运行过了、真写失败」在结构上不会发生。共 70 条能力:读 27 条、写 33 条,另有 10 条只能由人确认。
AI 碰钱的边界
- 外部 Agent 通过 CLI 接入,不走 MCP,因为命令行对模型来说更省 token。没有直接发 HTTP 的命令,模型只能看到建过模的能力。
- 需要确认的能力,模型和 Agent 令牌都拿不到。在 CLI 上要加 --confirmed-by-human,AI 不许自己加。
- 发给模型之前先脱敏:去掉黑名单字段(账号、交易号、订单号、原始列、密钥),所有连续 4 位以上的数字打星。已知的缺口也记下了:这只覆盖网页助手,终端那条路拿到的还是原始数据。
- 我故意用较弱的模型来试 CLI,逼着帮助文字和报错写得足够清楚。
重建
第一版是 Rust 后端加 Firefly III 的 PHP 分叉:Rust 3.3 万行,PHP 13.7 万行。单用户、以 I/O 为主的应用用不上 Rust 的长处,代价却天天在付:没有 ORM,一张表长到 43 列;AI SDK 都在 TypeScript 那边;Agent 写 Rust 又慢。
42 个问题里没有一个是 Rust 造成的,但 Rust 让修每一个都更贵。
我在旧代码旁边重建:Node 22、Hono、Drizzle、pg-boss、zod、Vitest。大约四天后,读取器、语义、匹配和回放基线都跑起来了;随后删掉了 127 个 Rust 文件和 1506 个 Firefly 文件。
- 集成测试只连名字以 abei_test 开头的库,迁移命令拒绝指向开发库。
- 回放拿三个数和一份 886 行人工裁决的基线比对(自动处理 75.7%,打断 199 次,漏错 1.8%),有任何偏离就失败。
- 28 条架构决策记录,每条写明决定了什么、推荐、代价和能否撤回。
学到的
- 测试全绿不等于能用。端到端验收时修的 10 个问题里,有 4 个是单测全绿、主链路却走不通的阻塞。一次脚本试走记了 33 步「走通」,对照截图人工核对,真走通的是 21 步。
- 它是按平台的规格造的,实际是一个人每月用一次:43 张表、70 条能力,够用的版本大约是 27 张表、50 条。下一步是用,而不是再加:用新版把九月的账整理完,每个余额都对上。
时间线
- 2026.07最早的 Rust 后端封存
- 2026.08.07基于 Firefly 的版本:React 前端、命令行和 Agent
- 2026.08.31真实整理一个月,记下 43 个问题
- 2026.09.01新架构定稿:自建单式账本
- 2026.09.03在旧代码旁边用 TypeScript 重建后端
- 2026.09.04五个读取器完成;2124 行真实回放一致
- 2026.09.17开源