一个 AI Agent 的工具调用链路,真正进生产以后,最怕的不是某一次调用失败,而是失败之后没有证据可查——问题看起来像“偶发”,排障却只能靠猜。
Demo 里工具调用几乎总是成功:问题清楚、数据能取到、接口返回正常。但真实流程里,用户表达会变形、权限各不相同、接口会超时、工具返回会冲突,某些动作还会影响订单、资金或设备状态。只要其中一环没处理好,Agent 就会从“看起来很聪明”变成“出了事没人能解释”。
如果你正在把 Agent 接进真实业务流程,下面按三层来拆:先留证据、再谈幂等与回滚、最后是失败分级。
一、先把证据留下
排障的前提是能还原“它当时基于什么做了这个动作”。一次工具调用至少要落这几类字段,并关联到同一次运行的 run id:
- 工具名与版本
- 入参(结构化,不是拼进一句日志字符串)
- 出参或错误
- trace id / run id
- 错误码与错误分类
- 重试次数
- 权限上下文(谁、什么角色、被授权做什么)
把它封装成统一入口,而不是让每个工具各写各的日志:
def call_tool(name: str, args: dict, ctx: RunContext):
span = tracer.start_span(name, attributes={
"tool.name": name,
"run.id": ctx.run_id,
"actor.role": ctx.role,
})
try:
result = dispatch(name, args)
span.set_attribute("tool.status", "ok")
return result
except ToolError as e:
span.set_attribute("tool.status", "error")
span.set_attribute("tool.error_code", e.code)
raise
finally:
audit.write(ctx.run_id, name, args, span) # 逐步落库,不只记最终输出
关键是逐步记录,而不是只记最终回答。只记最终输出,等于放弃了中间决策的可追溯性。
二、幂等与回滚:副作用动作的底线
会写入或产生副作用的动作(创建工单、发消息、扣费、改状态)必须满足两点:幂等键 + 执行前检查。否则一旦步骤重试,就会重复执行——两张工单、两次扣费、两条发给客户的消息。
设计上分两类动作区别对待:
- 只读查询:可自由重试,无需幂等。
- 写操作:带幂等键,执行前先查“这个 key 是否已执行过”,已执行则返回上次结果而不是再做一次。
- 高风险写操作(改订单状态、对外发送、涉及资金):额外加一道人工确认闸,不靠模型自觉。
回滚路径要在设计期就想清楚:哪些动作可补偿(撤销/冲正),哪些不可逆(已发出的消息)。不可逆动作应尽量前置人审。
三、失败分级:不是所有失败都一样
把失败分成三类,Agent 的处理策略完全不同:
- 可重试:超时、限流、瞬时网络错误 → 带退避重试,记录重试次数。
- 需降级:某工具不可用但有替代路径 → 切换或返回部分结果,并显式标注“数据不完整”。
- 必须人工:权限不足、数据冲突、涉及不可逆动作 → 停下来转人工,不要硬猜。
最贵的错误不是明显报错,而是数据缺失时 Agent 编了一个“看起来合理”的值继续往下走。缺数据时的正确行为是说明不可用并给出原因,而不是补一个假值。
常见问答
Q:工具偶发返回错误状态,我该先调 prompt 吗? A:先看审计日志定位问题在哪一层(是工具本身、权限、还是入参构造),再决定。先动 prompt 往往是在没有证据的情况下猜。
Q:幂等键应该放在哪一层实现? A:放在产生副作用的动作入口,执行前做去重检查。放在模型或 prompt 层不可靠。
Q:所有动作都加人审会不会太慢? A:只对有副作用、高风险的动作加人审,只读查询直接放行。分级之后,绝大多数调用不需要人工介入。
把证据链、幂等回滚和失败分级这三层补齐,偶发失败就能从“猜”变成“可复盘、可收敛的问题”。这也正是我们做 AI 业务系统生产就绪审查时,会针对 Agent 层逐项检查的内容:数据、权限、审计、成本与人工接管点,先列成 P0/P1/P2,再决定上线。小结一下,证据链、幂等回滚、失败分级三层缺一不可;想先照着这三层自查现有 Agent,可以做一次生产就绪自检。
