自由职业者每到季度末都有一场固定仪式:翻出一堆皱巴巴的收据照片、六种货币的PDF发票,然后面对会计那句“哪些含增值税”。有人干脆把整个文件夹交给一个跑在自己笔记本上的小工具,几分钟后拿到一张干净的支出看板。
这个工具叫 Receipt Box,核心是一件事:所有文件都不离开本机。照片和PDF交给本地运行的开放权重视觉模型 Gemma 3(4B),识别出卖家、日期、总额、增值税、货币,以及软件、差旅、餐饮这类分类。整个应用唯一的网络请求,指向 localhost。
它到底吐出什么
识别完不是给一段文字,而是直接生成可用的账目结构:
- 看板:总支出、可退增值税、按类别和按月的支出分布
- 按季度汇总的增值税表——这是自由职业者真正要交出去的那个数
- 可编辑表格:4B模型有时会错,所以每个单元格都能改
- 重复检测:同一张发票传两次会被标出来,而不是算两遍
- 多币种并列查看,金额从不做换算
- 导出 CSV 或 Excel 工作簿,直接发给会计
表格的可编辑性不只是修错。改一次某个卖家的分类,应用会记住这条规则,存进 rules.json,之后这个卖家自动按同样方式归类。作者的说法很直白:模型被允许出错,提示词要求它只用纸面上印着的信息、不许猜,剩下的交给人工修正表和学到的卖家规则。
结构化输出省掉了整条重试链
每张图都带着一个 JSON schema 送进模型,字段固定为卖家、日期、总额、增值税、货币、分类,其中分类被约束在一个固定列表里。结果是回复永远能解析,不需要正则,也不需要重试循环。
PDF走了两条路:带文本层的直接抽文字给模型,更快;扫描件用 PyMuPDF 渲染成图片,走视觉通道。处理是排队进行的,页面上显示“正在读取 5 个中的第 2 个:name.png”,而不是让界面卡死。
速度上作者没有粉饰:在 CPU 上,一张收据大约要 15-20 秒。逐条明细提取更慢,所以默认关闭。想换更大的 Gemma 或者上 GPU,改一行环境变量 RECEIPT_MODEL 就行。
为什么非要本地跑
收据大概是一个自由职业者最敏感的“无聊数据”:你给谁干活、去了哪、花了什么钱。走闭源 API 就意味着在便利和隐私之间二选一,而这恰恰是最在意隐私的那批客户流失的地方。
开放权重模型把这个取舍抹掉了。文件不出笔记本,就没有东西可泄露、可留存、可拿去训练;没有按张计费的 API 账单,产品定价不绑在别人的价目表上;没网也能用,火车上、客户办公室里都行;将来有更好的开放模型,改一个环境变量就能换。
作者对买家的判断也很具体:想要记账帮助、但不能也不该把客户发票、行程单据和私人收据传到云端的自由职业者和小型工作室,以及服务他们的会计——桌上堆着几十个客户的单据时,“什么都不会离开这台机器”是能对客户做出的承诺。他设想的收费方式是一次性授权,或者按会计席位收小额月费。
部署上还有一条路径:同一套应用可以通过 Blueprint 部署到 Render,一个把 Gemma 3 烤进 Docker 镜像的私有 Ollama 服务,Web 应用通过 Render 的私有网络与它通信,模型从不暴露在公网。Gemma 3 4B 大约需要 4-5 GB 内存,所以那个服务跑在大规格实例上。在自己的笔记本上,这些都不需要。
数据层——重复检测、季度划分、规则、导出——用 pytest 覆盖了测试。仓库里还放了三个虚构的示例文档,一张PDF发票和两张不同货币的收据图片,拖进去就能看着看板被逐份填满。
热门跟贴