内容提要
文章以发票报销工具的三次演进为例,分析AI Agent调用外部系统时的分布式系统问题:第一阶段LLM仅作OCR纯函数,分类靠人工;第二阶段引入Agent自动分类并保留人工确认;第三阶段通过Skill与MCP让Agent直接调用API,随之出现超时、状态污染、重复创建等问题,需用幂等键、状态持久化和先查后做等工程手段约束。
延伸解读
从纯函数到分布式系统:Agent 的边界扩展
文章通过报销工具的三次进化,清晰展示了 AI Agent 能力扩展带来的系统性风险。第一阶段 LLM 仅作 OCR,错误封闭在输出内;第二阶段 Agent 获得前端写操作,但受浏览器 Session 和人工确认双重约束;第三阶段 Agent 通过 MCP 直接调用外部 API,成为真正的分布式系统,错误可能直接影响生产环境。这一演进表明,Agent 每获得一项新能力,都会引入新的故障模式,开发者必须同步升级工程约束。
超时与重复创建:分布式系统的经典陷阱
第三阶段中,Agent 调用 add_expense 超时后盲目重试,导致费用条目重复创建。文章指出,超时不代表失败,而是未知状态。解决方案结合了工具层幂等键(如发票号码)和流程层先查后做(get_expense_report),确保重复执行安全。这提醒我们,当 Agent 跨越网络边界调用外部服务时,必须假设网络不可靠,并通过幂等性和状态查询来应对不确定性。
状态污染与幻觉:上下文不是可靠的数据源
Agent 在长对话中可能从上下文“回忆”出错误的 reportId,导致后续操作静默失败。文章强调,上下文即状态,而状态会过期和污染。修复方法是将关键状态持久化到文件系统,并强制所有工具参数从源文件 json.load() 派生,禁止 Agent 现场拼接字符串。这体现了分布式系统中状态管理的核心原则:记忆应视为可失效的缓存,必须带有来源信息。
确定性消退后的工程约束升级
从第一阶段 100% 确定性,到第二阶段半确定性(Agent + HITL),再到第三阶段概率性加分布式,文章指出当用户不在场时,不能依赖人工确认兜底。必须用幂等键、状态持久化、先查后做和数据来源约束等工程手段来限制 Agent 的错误影响范围。这呼应了 Salman 的观点:构建 Agent 时,最该问的是当它犯错时,系统允许它做到哪个程度。
Q&A
报销工具从第一阶段到第三阶段,LLM 的角色发生了什么变化?
第一阶段 LLM 是纯函数,只做 OCR(PDF 转 JSON),不执行任何外部操作;第二阶段 LLM 变成 Agent Loop Driver,能自主分类、匹配并调用前端 Tool 创建 Expense;第三阶段 LLM 成为通用 Agent Driver,通过 Skill 和 MCP 直接调用外部 API,不再依赖浏览器 Session。
为什么说 AI Agent 调用外部系统时变成了分布式系统问题?
因为 Agent 调用外部服务时会遇到网络超时、状态不一致、重复请求、级联故障等分布式系统经典问题。例如超时后无法确定操作是否成功、上下文中的状态可能污染后续行动、重复调用可能创建重复条目。这些问题不能靠模型能力解决,必须用分布式系统的工程手段来约束。
第三阶段中,Agent 直接调用 API 后遇到了哪些具体问题?
主要遇到四类问题:1)幻觉 reportId——Agent 从对话上下文回忆了错误的 ID,导致后续操作静默失败;2)沙箱超时后重复创建——超时被误判为失败,Agent 重试导致重复创建 Expense;3)两张火车票同一 PDF 被误判为重复——Agent 根据记忆推测文件名,丢失了唯一性信息;4)Expense ID 漂移——Concur 内部重新分配 ID,导致收据上传到错误条目。
针对 Agent 调用外部 API 时的超时和重复创建问题,文章提出了哪些工程手段?
文章提出双保险:工具层的幂等键(如中国发票的 invoiceNo)和流程层的先查后做(调用 get_expense_report 查看已有费用)。具体做法是:超时后不盲目重试,先查询当前报告里已有的费用,按 (amount, date, typeId, vendor) 匹配,已存在则记录并跳过,不存在才安全重试(最多 1 次);连续 3 次超时则停止批处理,保存状态并通知用户。
如何防止 Agent 因上下文记忆导致的状态污染和幻觉?
文章的做法是禁止 Agent 从对话上下文取数据,所有关键状态必须从文件系统的持久化存储(如 expense_state.json)或权威数据源(API)读取。具体约束包括:创建报告后立即将 reportId 写入 state 文件,后续只从文件读取;所有 MCP 工具参数必须来自源文件 json.load() 派生链;文件路径必须来自 groups[].primary_pdf 等字段,禁止按日期/商户/金额推测;每个关键步骤开头强制刷新状态,不信任本地缓存。
三个阶段中,用户角色和错误影响范围分别有什么不同?
第一阶段:用户是分类器和操作员,错误影响封闭(错在 OCR 输出里);第二阶段:用户是审核者,错误影响受控(用户浏览器和人工确认兜底);第三阶段:用户是观察者甚至不在场,错误影响开放(可能直接影响 Concur/Emburse 生产系统)。
第三阶段中,Skill 和 MCP 分别解决了什么问题?
Skill 是可复用的工作流描述,让使用者能定制个性化流程(如按差旅拆分票据、设置话费报销比例);MCP 是标准化的工具接口,让 Agent 能直接调用外部系统 API。两者结合使 Agent 不再需要单独部署,也无需上传发票,并且 Agent 助手自带记忆功能,提供更个性化的服务。
文章最后强调的工程约束原则有哪些?
文章总结出四条核心工程约束:1)幂等键——让重复执行是安全的;2)状态持久化——让中断是可恢复的;3)先查后做——让超时是可处理的;4)数据来源约束——让幻觉是不可能的。这些约束共同构成了当 Agent 全自动执行且用户不在场时的 Harness 机制。