Trace 先行:用可观测性驱动 Agent 调试
调 Agent 的 bug,最大的陷阱是「凭感觉改 prompt」。Agent 没返回期望的结果,直觉告诉你是指令不够清楚,于是你加了一段描述,重新跑,结果变了但依然不对,于是再加……这个循环可以持续很长时间,每一轮都在盲目地做参数搜索,而你始终不知道 Agent 在哪一步出了问题。
真正的解法和调传统程序一样:先把执行过程看清楚,再下结论。
Agent 的「printf 调试」局限
传统程序出 bug,第一步是看日志,或者加断点——你能看到每一条语句的执行状态。Agent 的麻烦在于,它的「执行语句」是 LLM 的推理过程和工具调用序列,这些在默认情况下是不透明的。你只能看到最终输出,看不到中间的决策路径。
所以 Agent 工程的可观测性问题比传统程序更严重,不是更轻。每一次 LLM 调用、每一次工具调用、每一次内存读写,都应该被记录成结构化的 trace,供事后分析。
LangSmith 做的就是这件事:把 LangChain/LangGraph Agent 的完整执行链路持久化,让你能在事后逐帧回放。
四类诊断场景的思路
用 trace 调试,大致覆盖四类场景,每类的切入角度不同:
执行沉默——Agent 没有反应,或者输出为空。这时先确认 trace 是否存在。如果连 trace 都没有,说明调用根本没发生,问题在 Agent 运行环境的配置层(环境变量、网络、触发逻辑),而不是 Agent 逻辑本身。有 trace 但输出为空,才往工具调用和 LLM 响应里找。
工具选错——Agent 调用了错误的工具,或者调用顺序不对。这类问题在 trace 里很好定位:展开工具调用序列,看 Agent 在每一步的 reasoning,通常能直接看出它为什么选了这个工具。根因往往是工具描述歧义,或者可用工具集里有两个语义相近的工具在互相干扰。
记忆失效——Agent「忘记」了之前的上下文。在 trace 里搜索记忆操作(写入、检索),确认写入是否成功、检索是否返回了结果、返回的结果是否真的被注入到了后续的 prompt 里。这三步任何一步断掉,都会表现为「记忆不工作」,但修法完全不同。
性能劣化——整体变慢。用 trace 里的时间戳和 token 用量做瀑布图分析:哪个工具调用占用了最长时间,context 在哪一步开始急剧膨胀,是否存在不必要的循环调用。
养成习惯:改了 Agent 逻辑之后,在本地跑几个典型场景,拉最近几分钟的 trace 快速过一遍,确认工具调用序列和预期一致,再提交。这比事后在生产环境排查要省力得多。
结构化推断 vs. 盲目猜测
这套思路背后有一个更通用的原则:诊断要基于证据,而不是假设。
「Agent 没记住上次的内容,可能是 prompt 没写清楚」是假设。「trace 里 store_memory 工具在 14:38 被调用,返回成功,但 recall 在下一轮调用中返回了空列表」是证据。两者导向的修复动作完全不同。
凭假设改 prompt 之所以容易形成死循环,是因为假设可以无限细化——你永远可以想出更多「可能的原因」,但如果没有 trace 数据来验证,每一次修改都只是在扩大搜索空间,而不是在收敛。
有了 trace,调试就变成了一个有终止条件的过程:找到执行链路中第一个与预期不符的节点,那就是真正的故障点,其他的全部是次生影响。
## 一句话心法
调 Agent 时,改 prompt 之前先看 trace——你需要的不是更好的猜测,而是更清楚的事实。