前言

春节前 OpenClaw 火起来的时候,我基本是第一时间就开始折腾了。

那段时间挺兴奋的。白天正常工作,晚上回家继续配各种东西:Telegram、Gmail、Calendar、browser、skills、cron、各种本地脚本。经常一弄就到夜里一两点。那种感觉有点像很多年前第一次买 VPS,明明没有特别明确的目的,但就是觉得“这里面一定有东西”。

等基础设施都配得差不多以后,问题反而来了:

到底让它帮我做什么?

如果只是问答、总结网页、写几段代码,那当然也能用,但总觉得不够“个人助理”。真正的助理不应该只是在我打开聊天框的时候回答问题,而应该长期知道我在做什么、帮我处理那些反复发生又很烦的杂事。

我最后选中的第一个真实场景,是差旅报销。

这两年我出差比较多,每次最麻烦的不是出差本身,而是回来报销。各种票据散落在不同地方:

每次报销时,我都要重新翻邮箱、翻聊天记录、翻相册、对日期、改文件名、核金额。每一件事单独看都不难,但连起来非常消耗精力。

于是我开始想:

能不能让 OpenClaw 帮我从“出差发生”开始,就把报销这件事一路接住?

这听起来像是“让 AI 识别票据并生成表格”,但真正做起来之后我才发现,它完全不是一个简单任务。

因为一个真正可用的报销 agent,至少要回答三个问题:

  1. 这张票属于哪次差旅? 只看票据本身不够,它必须知道我的 trip map。
  2. 这张票据合不合格? 国内电子发票、酒店专票+水单、海外 receipt、打车行程单,各有不同规则。
  3. 怎么避免大模型幻觉? 报销不是写作文,金额、日期、城市、发票号错一个都可能出问题。

这篇文章想记录的,就是我怎么从一个“让 OpenClaw 帮我整理票据”的想法,一步步把它做成一条差旅报销工作流。

整体架构:不是一个功能,而是一条链路

最终我把这件事拆成了四个核心模块:

  1. Trip Planner:先知道我有哪些差旅,哪天在哪个城市;
  2. Claim Intake:从 Telegram、Gmail、文件里收集和解析票据;
  3. Export / Audit:按某次 trip 导出 claim.csv 和证据包,并检查缺失和错误;
  4. Submit:把整理好的材料录入内部报销系统。

这四个模块里面,最重要的其实是第一个:Trip Planner。

很多人一开始会把报销自动化理解成“票据识别”。但票据识别只解决了“这是什么”——比如这是一张 2026-03-24 的餐饮小票,金额 HKD 248。真正难的是“它应该去哪”——它属于哪次出差?是否在差旅日期内?城市是否匹配?这次出差是否允许这类 expense?

所以整个系统的基础不是 OCR,而是一张可靠的 trip map。

另一个关键设计是:用 skill 约束模型的行为,用 script 固化确定性逻辑。

我不希望每次都让 LLM 自由发挥:“请帮我看看这张票怎么处理”。这种方式 demo 起来很快,但稳定性很差。后来基本形成了一个原则:

截至写这篇文章,相关项目已经有 200 多个 commit、180 多个文件、4 万多行内容。前期我还会认真看 AI 每个 commit 改了什么,后面迭代速度实在太快,已经变成“看测试、看审计结果、看最终产物”。这也是我对 agent 编程感受变化很大的地方:人不再逐行控制所有实现,而是在更高层控制目标、边界和验证方式。

第一关:先让它知道我哪天在出差

Trip Planner 最开始看起来很简单:读日历,找出差。

比如日历里有一个多日 event,location 在香港,那就认为这几天是一趟香港出差。或者某一天有外地会议,就认为这天是一日往返。

但真实日历很快教我做人。

挑战一:日历事件并不标准

公司里不同的人创建会议的习惯完全不一样:

如果让 LLM 去猜,短期看起来很聪明,长期一定会翻车。比如它把一个 tentative meeting 当成真实出差,后面的票据就可能匹配到一趟根本不存在的 trip。

后来我给 Trip Planner 加了很多硬规则:只信任结构化 location;过滤 cancelled/tentative;必要时重新拉完整 ICS;对 organizer 做去重;对过去 trip 做保留,不能因为一次同步没扫到就删除。

但即便如此,真实世界总有例外。最后我接受了一个很朴素的方案:dummy event + override

也就是说,如果某次出差日历本身不够标准,我就手动创建一个结构清晰的 dummy event,或者在配置里加 override。它不优雅,但可靠。对于个人助理系统来说,可靠比自动猜测更重要。

挑战二:JSON 文件不可靠

早期 trip map 是 JSON 文件。做 demo 很方便,但后面问题越来越多:

后来我把它迁移成了 SQLite-backed trip store,并且通过 script 提供稳定查询接口。LLM 不直接“凭感觉读文件”,而是调用固定脚本查询:某天有哪些 trip、某个 trip 的城市和日期是什么、是否需要 backfill。

这个变化非常关键。因为从这里开始,Trip Planner 不再是一个临时中间文件,而变成了整个报销系统的事实来源。

第二关:Claim Intake,真正的坑都在票据里

有了 trip map 以后,下一步就是收集票据。

我设计了几种入口:

这部分花的时间最多,因为票据世界非常混乱。

12306:最规范,但也有坑

12306 算是最规范的。邮件、发票、行程信息相对稳定,parser 可以写得比较确定。

但它也不是完全没有坑。比如退票、红字发票、手续费等场景都要排除或特殊处理。曾经有一次 parser 看到“红字”就直接把内容判断成退款,结果误伤了正常发票。后来我专门重写了 12306 parser,并加了回归测试。

这个案例让我意识到:越是看起来规则明确的东西,越要把边界条件写进测试。

国内电子发票:XML 是救命稻草

国内很多电子发票邮件里会带 XML、PDF、OFD 附件。PDF 给人看,XML 给机器读。

早期如果只让 LLM/OCR 读 PDF,稳定性其实一般;后来我把重点转到 XML 解析上,发票代码、号码、金额、购买方、销售方、日期这些核心字段基本都能稳定拿到。

所以这里的经验很明确:只要有结构化数据,就不要迷信多模态。多模态是 fallback,不是第一选择。

Telegram 小票:不是“收到图片”这么简单

海外 meal 很多只有纸质 receipt。我的理想流程是:吃完饭,拍照,发到 Telegram 的某个报销 topic,OpenClaw 自动记录。

一开始这个流程很不稳定。有时候我发一张小票,它会问:“这张图片是干嘛的?”

这其实很合理。站在一个通用聊天机器人的角度,它并不知道这个 topic 的语境。后来我发现可以在 openclaw.json 里给特定 Telegram topic 配 system prompt,把这个话题定义成 expense intake channel。之后它就稳定很多:收到图片就按票据处理,而不是把它当成普通聊天图片。

这也是 skill/system prompt 的价值:它不是让模型“更聪明”,而是让模型在正确的场景里做正确的事。

酒店 folio:最难处理的一类

酒店材料是我花时间最多的地方之一。

因为酒店报销往往不只是一个发票。可能需要:

而且酒店 folio 格式极其不统一。英文里可能叫 check-in date / arrival / stay period,中文里可能是入住日期/离店日期,香港或台湾酒店还可能出现繁体中文。很多 PDF 文本层也不干净,普通 parser 抽不到关键字段。

最后我用了 Gemini 多模态来解析这类材料,把它作为酒店 folio 的 fallback。确定性 parser 能处理的先处理,处理不了的再交给多模态。这样既保留稳定性,也能覆盖真实世界里那些乱七八糟的扫描件。

Baiwang 发票:一次很典型的 reverse engineering

最让我印象深的是 Baiwang 的发票。

它不是直接在邮件里给一个附件,而是给一个链接。点进去以后是一个 Vue 页面,要等前端渲染完,再点击下载 PDF。

如果用 browser automation,当然也能做:打开网页、等待、点击下载。但这太脆弱了。页面慢一点、按钮变一下、登录态失效,都可能失败。

后来我让 Claude Code 去做 reverse engineering。它直接读前端 JS 的逻辑,找到下载链接是怎么生成的,最后把这个过程固化成 script 放进 skill 里。这样就不需要真的打开浏览器点页面了,而是直接根据链接生成下载地址,拿到 PDF/XML。

这个例子很能代表 agent 的新工作方式:不是“让 AI 代替人点网页”,而是让它理解网页背后的机制,把一次浏览器操作升级成一个稳定脚本。

去重:一张票不一定只有一个来源

去重也是后面才意识到的大坑。

同一笔费用可能有多个来源:

如果简单用文件 hash 去重,很快就不够了。因为不同附件可能代表同一笔费用;反过来,同一封邮件也可能包含多笔费用。

后来 evidence DB 里区分了两个 identity:

source identity 用来避免重复处理同一个输入,expense identity 用来合并现实世界里的同一笔费用。对于 key 不够强的情况,先进入 pending 状态,后续 parser 或人工确认后再升级。

这一步让系统从“文件整理器”变成了“证据系统”。claim.csv 不再是事实来源,而只是最终导出的一个视图。

第三关:Export,不只是打包,而是审计

Export 相比 intake 看起来简单:用户说“帮我导出某次 trip”,系统生成 claim.csv,把相关 evidence 打包成 zip,再发回 Telegram。

但这里也踩过很多坑。

早期依赖 JSON trip plan 时,导出经常遇到一些奇怪问题:字段缺失、历史 trip 查不到、future trip 和 historical trip 混淆、某天的一日往返被多日 trip 覆盖。后来通过 SQLite trip store + query script,导出逻辑稳定了很多。

现在的流程大概是:

  1. 用户在 Telegram 里说要导出某个日期或某次出差;
  2. OpenClaw 调用 trip 查询脚本解析目标 trip;
  3. 从 evidence DB 找到匹配的证据;
  4. 生成 claim.csv
  5. 把发票、行程单、folio、receipt 等 evidence 放进 zip;
  6. 运行 audit,检查缺失、弱 key、日期不匹配、城市不匹配;
  7. 把 zip 发回 Telegram。

后来还加了汇率字段。因为不同国家/地区的报销最终要折算成人民币,不能随便让模型编一个汇率。我接入了付费 API,按指定 expense date 拉汇率,写进导出结果里。

这里的关键不是“自动生成 CSV”,而是生成一个可审计的交付物。如果某条证据只有 weak key,audit 会标出来;如果 hotel folio 和 invoice 没配齐,也会标出来。这样我可以信任它的结果,而不是拿到一个漂亮但不知道对不对的表格。

第四关:Submit,最后一公里永远最脏

最后一步是提交到内部报销系统。

这部分因为涉及公司内部系统,就不展开具体细节了。大概思路是:OpenClaw capture request,让大模型分析浏览器里的请求结构,再把可自动化的步骤写成 skill。

这里我用了 Qoder work / browser automation 一类方式去探索内部系统。很多录入和校验其实可以直接调用页面背后的 API 完成,比 UI 自动化稳定得多。

但文件上传没有完全搞定。模型一直判断是被内部安全系统拦截,直接 API 上传很难稳定复现浏览器里的完整状态。最后还是需要回到 UI automation:打开页面、选择文件、上传、检查状态。

这也很真实。agent 做企业系统自动化,不是“有 API 就万事大吉”。它必须能在 API、browser、文件系统、人工确认之间切换。能 API 就 API,不能 API 就像人一样操作浏览器。

我真正学到的东西

做完这一轮,我对 agent 的理解变化挺大。

1. 未来系统一定会 API 化和 CLI 化

这次最明显的感受是:凡是有 API、有 CLI、有结构化数据的地方,agent 就能稳定工作;凡是只有复杂 UI、没有稳定接口的地方,就很痛苦。

这个趋势我觉得不可逆。未来如果一个系统完全不考虑 agent 访问方式,它就会越来越难被自动化,也越来越难进入新的工作流。

当然 API 不一定是免费的。相反,我觉得高质量 API 会成为新的内容和能力变现方式。比如这次 trip planning、地点解析、汇率查询,我都愿意调用付费 API,因为它们提供的是稳定、准确、可验证的能力。

2. Skill 的本质是 SOP,不是 prompt 魔法

这次也让我更确信:skill 不是“写一段很长的 prompt”。

真正可靠的 skill,背后一定有成熟 SOP:收到什么输入,先检查什么,调用哪个脚本,状态写到哪里,失败怎么标记,什么时候需要人工确认。

如果人类自己都没有稳定流程,直接把一堆混乱经验塞给模型,只会得到一个看起来很聪明但不可靠的系统。

不过 skill 的测试依然是难题。代码可以写 unit test,parser 可以做 fixture,但一个完整 skill 涉及模型行为、工具调用、外部系统、聊天上下文,怎么系统性测试还没有特别成熟的答案。

3. 代码没有消失,但人和代码的关系变了

这个项目里已经有 200 多个 commit、4 万多行内容。前期我还会认真看每个 diff,后来实在看不过来了。

但这不代表代码不重要。恰恰相反,代码更重要了:确定性逻辑必须靠代码固化,状态必须靠数据库保存,审计必须靠脚本检查。

变化在于,人不再需要逐行控制所有代码。我的注意力更多放在:

这有点像从“亲手拧每颗螺丝”变成“设计流水线和质检标准”。

4. 个人助理最有价值的不是聪明,而是可靠

报销这个场景一点也不炫酷。它没有科幻感,也不会让人惊呼 AGI。

但它很真实。

它需要 agent 长期记住我的日历、理解我的出差、接住我随手发的票据、从邮箱里找附件、知道哪些证据合格、导出时能审计、出错时能留下线索。

这才是我心目中个人助理应该做的事。

不是每次都完美回答一个问题,而是长期维护一条工作流。

小结

一开始我以为这是一个“AI 帮我报销”的小项目。后来发现,它其实是一个很完整的 agent workflow 实验:

中间踩了很多坑:日历事件不标准、JSON 状态不可靠、Gmail query 污染、12306 退票误判、酒店 folio 多语言解析、Baiwang Vue 页面下载、Telegram topic 语境不稳定、dedupe key 设计、历史 trip 导出、汇率查询、内部系统上传。

但正是这些坑,让它从一个 demo 变成了一个真实可用的系统。

我现在越来越觉得,agent 真正改变的不是“写代码更快”或者“回答问题更聪明”,而是它让个人也可以拥有一套会持续演进的自动化工作流。

以前这种东西需要一个团队做系统集成。现在,一个人、一台机器、一些 skills、一堆 scripts,再加上足够多的迭代,就能把生活和工作里那些麻烦的小流程一点点交给 agent。

这可能才是我最兴奋的地方。