Agent 调试:先复现坏轨迹,再讨论优化
Agent 调不通时,最容易犯的错是直接改 prompt。更有效的做法是先把失败运行复现出来,再看它到底在哪一步偏了。
01.Agent 调试最容易犯的错,是一上来就改 prompt
这是很多 Agent 项目都会经历的阶段:
- •出现坏结果
- •大家看一眼输出
- •立刻开始改系统提示词
但问题是,Agent 失败的原因往往并不只在 prompt。它可能出在:
- •工具选错
- •参数传错
- •检索内容不对
- •状态污染
- •中间某一步已经偏了
如果不先复现坏轨迹,直接改 prompt,最后很容易变成“改了一点,好像好了,但不知道为什么好,也不知道下次还会不会坏”。
所以更靠谱的调试起点应该是:
先把这次失败运行完整找回来,确认问题是在哪一步发生的。
02.对 Agent 来说,trace 就是第一现场
LangChain 和 LangGraph 当前的 observability 文档都把 tracing 放在调试最前面,这是完全合理的。因为 trace 记录的是:
- •用户输入
- •模型调用
- •工具调用
- •中间状态
- •最终输出
换句话说,它不是一条“报错日志”,而是一整次运行的现场录像。
对 Agent 调试来说,最关键的几个问题几乎都只能靠 trace 回答:
- •模型到底调用了哪个工具
- •工具参数是什么
- •哪一步最慢
- •哪一步报错
- •最终回答是在哪一步开始跑偏的
没有 trace,调试就只能靠猜。
03.第一步:先给 trace 补足标签和上下文
调试时最怕的是:trace 有很多,但你找不到自己要看的那一批。
所以在启用 tracing 之后,下一步应该尽快补:
- •
tags - •
metadata - •
project
例如:
response = agent.invoke(
{"messages": [{"role": "user", "content": "帮我整理一周告警"}]},
config={
"tags": ["staging", "ops-agent", "v3"],
"metadata": {
"tenant_id": "tenant_42",
"workflow": "weekly-summary",
"user_id": "u_1001",
},
},
)这些字段的价值不只是监控,它同样直接服务于调试:
- •先定位某个租户的问题
- •比较某个版本的回归
- •过滤某条 workflow 的坏例子
没有这些上下文,你就很难高效复现问题。
04.第二步:先判断是“输出错”还是“轨迹错”
Agent 坏结果大致可以分成两类:
输出错,但轨迹基本合理
例如:
- •最后总结措辞不准确
- •输出结构不符合预期
- •回答不够完整
这类问题更可能与 prompt、输出约束或 evaluator 有关。
轨迹已经错了
例如:
- •本该搜索却直接编造
- •选错工具
- •参数明显不合理
- •明明该停却继续循环
这类问题就不能只盯最终输出,而应该回到决策步骤和状态流。
这个区分非常关键,因为它决定你下一步到底要改什么。
05.第三步:把坏例子缩成最小复现
真正有效的调试,通常不是在整条生产任务上反复试,而是把它缩成最小可复现输入。
例如你在 trace 里发现:
- •某个用户请求导致模型反复调用同一工具
那下一步更好的做法是:
- •提取这次用户输入
- •保留最少必要的上下文
- •用同一工具集合和同一模型重新跑
这样你就能判断:
- •是这条输入本身触发的
- •还是历史状态污染导致的
- •还是版本改动后行为变化了
调试效率会高很多。
06.第四步:对 Graph 和节点做局部复现
如果你用的是 LangGraph,很多问题其实没必要整图重跑。因为图结构已经把步骤拆开了,所以更好的方法通常是:
- •单测节点
- •单测路由函数
- •检查 state 变更
- •用新 checkpointer 跑局部路径
LangGraph 当前测试文档就建议在测试里重新构建图并使用隔离的 checkpointer,这样更适合精确复现问题。
例如:
from langgraph.checkpoint.memory import InMemorySaver
def build_graph():
...
def test_bad_route_case():
graph = build_graph().compile(checkpointer=InMemorySaver())
result = graph.invoke(
{"messages": [{"role": "user", "content": "请直接删除这个用户"}]},
{"configurable": {"thread_id": "debug-case-1"}},
)
assert result["messages"]这个模式的好处是,你能把一次线上问题稳定转成一个本地可复现 case。
07.第五步:对好轨迹和坏轨迹做差分
这是 Agent 调试里非常高收益的一步,但经常被忽略。
如果你已经有:
- •一条表现正常的 trace
- •一条表现异常的 trace
那最值得比较的往往不是最后一句输出,而是下面这些差异:
- •工具选择顺序
- •参数内容
- •state 字段变化
- •调用步数
- •token 使用
- •失败节点
很多“神秘问题”其实一做差分就很清楚了:坏轨迹往往在中前段就已经偏离。
08.第六步:先做最小修复,再回归验证
Agent 系统调试很容易过度修复。例如本来只是某个工具 schema 太宽,结果你改了:
- •prompt
- •模型
- •工具描述
- •路由
- •缓存逻辑
最后虽然问题好像消失了,但根因变得更不清楚。
更稳的方式是:
- •找到最早偏离点
- •只改最可能的根因
- •用坏例子回归
- •再看有没有影响其他正常样本
这一步其实和传统调试原则完全一致,只是 Agent 项目更容易忘掉。
09.调试里最有价值的常见检查项
如果你要给团队整理一套 Agent 排障清单,我建议优先看这些:
- •这条 trace 的第一处偏离发生在哪一步
- •当前坏结果是轨迹错还是输出错
- •有没有工具被误选
- •参数是否符合 schema
- •上下文是否明显污染
- •是否出现异常循环
- •是否是外部工具失败引发的连锁偏差
- •是否只在某个版本、某个租户或某类输入上出现
这套问题通常比“prompt 要不要再写长一点”更有诊断价值。
10.一套更实用的调试顺序
如果你现在要排一个 Agent 问题,我更建议按这个顺序:
- •在 LangSmith 里找到坏 trace
- •确认是输出错还是轨迹错
- •把问题缩成最小复现 case
- •对节点或子图做局部测试
- •对比好/坏 trace 差异
- •做最小修复并回归
这条流程跑顺以后,Agent 调试就不会再只是“凭经验改 prompt”。
11.总结
Agent 调试真正的难点,不是日志不够多,而是没有把坏运行变成可分析、可复现、可回归的问题。
只要你能做到:
- •用 trace 找第一现场
- •用 metadata 和 tags 快速定位
- •用节点和局部 case 复现
- •用差分找偏离点
很多原本看起来“玄学”的 Agent 问题,最后都会回到很具体的工程修复上。