abei 是我业余在做的一个开源记账工具:账单从邮箱收进来,解析成统一的流水,AI 预填分类,最后由人确认入账。上一篇写的是导入时怎么去重,这一篇写 AI 在里面能做什么、不能做什么。
记账是一件容错很低的事。分类错一笔,月底的统计就歪一点;把一笔转账当成消费,这个月就凭空多出一笔开销。AI 能帮上很多忙,但不能想改就改。所以在写任何 AI 功能之前,我先写了一份「协作契约」:AI 能碰什么、怎么碰、碰错了怎么办。
第一原则:AI 只写提案
AI 的一切写入都只经过提案表。 落到数据层,就是事实表和提案表物理分离:账目、流水这些事实表,AI 一张都写不了,它唯一能写的是 proposals。
提案要么被人批准,要么命中人事先授权的规则,才会变成事实。
这里有一个容易误会的地方:abei 有一条「直通」车道,符合条件的提案会自动入账。但直通不是 AI 自己决定入账,而是人通过信任档位预先授权的一条车道。每一笔自动入账都标着 source = ai 和那一次运行的 ai_run_id,进周报,可以整批撤销。
所以现在的承诺是:每一笔都查得到是谁定的,也都撤得回来。
两个生产者,一张提案表,一套车道策略
提案有两个来源:
- 内置阿贝:abei 里常驻的 agent,跑一条预填管线;
- 外部 agent:比如 Claude Code,通过命令行
abei proposals create --from-file提交。
两边的提案进同一张表,走同一套车道策略,分到三条车道里:
| 车道 | 什么情况 | 结果 |
|---|---|---|
| 直通 | 校准后的置信度 ≥ 0.95,并且是小额 | 自动入账,标成 auto,可以撤销 |
| 批量确认 | 置信度在 0.6 到 0.95 之间 | 进确认队列,默认勾选,人扫一眼再批 |
| 必问 | 置信度 < 0.6 | 阻塞式提问,附上最可能的两个答案 |
外部 agent 的提案单独校准置信度,而且初期一律进批量确认,不直通。
直通车道本身不做判断,它是内置阿贝那条预填管线的出口策略。
分类引擎:四层瀑布
内置阿贝给一笔流水预填分类,是一层一层往下漏的:
| 层 | 做什么 | 目标覆盖 |
|---|---|---|
| L0 商户归一化 | 剥掉渠道前缀、括号里的主体、卡号尾号;规则和命名实体识别两路并行 | 全部 |
| L1 确定性规则 | 把我写的《个人记账规则》编译成结构化规则 | 约 90% |
| L2 历史近邻 | 归一化的商户加金额分桶,找历史上最像的几笔来投票 | 剩下的大半 |
| L3 LLM | 只处理剩下的 5% 到 15%,批量调用,输出分类、理由和引用的规则 | 残差 |
| L4 人 | 必问车道 | 5% 以内 |
大模型排在最后。额度用完时,abei 退回 L1 和 L2,照样能用。
模型自报的置信度不能信
LLM 说自己有 95% 的把握,不代表它真的有 95% 是对的。所以置信度要过一层校准:按「谁提的、提的哪一类」分组,用这一组最近 500 条已经被人确认过的提案做保序回归(isotonic regression),把模型自报的分数换算成它历史上真实的准确率。
样本还不够的时候(冷启动),先打个八折:校准后 = 自报 × 0.8。
有些情况不看置信度
置信度再高,下面几种情况也至少要进批量确认:
- 金额在大额线以上;
- 匹配引擎认为它可能是一笔转账的其中一半;
- 它可能是另一笔的退款;
- 这个商户以前出现不到 3 次;
- 规则和历史近邻给出的分类互相矛盾。
这些是写死的判断条件,命中就降级,没有商量。
信任档位:直通多少,由人决定
在 abei 的「阿贝」页面上,人可以在三个档位里选一个:
| 档位 | 行为 |
|---|---|
| 全部确认 | 没有直通,每一笔都要人点头 |
| 小额直通(默认) | 置信度够高、金额在小额线以下的,自动入账 |
| 只问大额 | 除了大额和上面那几种情况,其他都直通 |
小额线和大额线默认是 200 元和 1000 元,还在调。
提案表的几条硬约束
提案表的每一行只指向一个目标(一行账单或者一笔交易),内容是一个 patch,描述它想改什么。几条硬约束:
patch里不能有金额字段,数据库的 schema 层直接拒绝。唯一的例外是拆单(split):拆出来的几部分加起来必须等于原来那一笔,由数据库触发器强制。- 数字由确定性查询算出来,AI 只负责措辞。 周报里「某一类比上个月多了多少」这种句子,数字来自 SQL,不来自模型。
- dry-run 和真正执行是同一段代码,
apply = false时只返回 diff。 - 同一个提案不会重复堆积:内容的哈希在没被否决的提案之间唯一。被否决过的可以再提,因为人可能改主意。
先影子运行,再放权
新的自动化不直接上线,先跑 shadow mode:提案照写,标成 shadow_applied,但永远不执行;周报把影子给出的结论和人实际的裁决逐条对照。
毕业只看一个数:漏网错误率,也就是自动应用之后又被人改回来的比例。它要低于 2%,而且样本不少于 200 条。
纠正一次,就学会
人改了一笔的分类,abei 会问一句「以后都这样?」。点了是,就生成一条规则,进待审队列;要回溯应用到旧账上,先 dry-run,把会改到的列出来让人勾选。
规则都写在《个人记账规则》这一份文档里,控制在 200 行以内。每一条都对应一次真实的纠错,写成可以验证的祈使句。agent 想改这份文档,也得走提案。
有的商户什么都卖,比如超市,分类没法学。这种商户标成不学习,AI 不再对它出提案。
审计和隐私
- 所有 agent 的动作都写进一张只能追加的审计日志:运行 id、模型版本、prompt 的哈希、输入摘要、输出、批准人。
- 每一笔交易都有修订记录,回答「这一笔是谁定的」。
- 模型看不到卡号、订单号和密码;写回之前按白名单校验字段;标了
x-abei-human-only的参数,模型永远拿不到。
最后
这份契约现在还是草案,几个阈值要等 shadow mode 跑出真实数据再调。但第一原则不会变:AI 只写提案,人决定哪些提案可以自动通过,而且每一笔都撤得回来。
「模型决定 AI 能做什么,平台决定人是否敢让它去做。」这句话我在另一篇文章里写过,abei 是我自己动手把它落下来的一次尝试。