AI AgentTechnical Deep Dive

Agent 调试:先复现坏轨迹,再讨论优化

发布时间2026/01/31
分类AI Agent
预计阅读10 分钟
作者吴长龙
*

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

例如:

python snippetpython
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,这样更适合精确复现问题。

例如:

python snippetpython
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 问题,最后都会回到很具体的工程修复上。

12.参考资料