目录
- [1. AI Agent 为什么这么难调试?](#1. AI Agent 为什么这么难调试?)
- [2. 排障方法论:从 Prompt 到执行链的系统化视图](#2. 排障方法论:从 Prompt 到执行链的系统化视图)
- [3. Prompt 追踪:捕获每一次"大脑活动"](#3. Prompt 追踪:捕获每一次"大脑活动")
- [4. 执行回放:把 Agent 的"行为录像"复现出来](#4. 执行回放:把 Agent 的"行为录像"复现出来)
- [5. 工具篇:LangSmith / Langfuse / Phoenix 实战对比](#5. 工具篇:LangSmith / Langfuse / Phoenix 实战对比)
- [6. 实战案例:一次多步推理失败的回溯定位](#6. 实战案例:一次多步推理失败的回溯定位)
- [7. 构建你自己的调试工作流](#7. 构建你自己的调试工作流)
- [8. 总结与展望](#8. 总结与展望)
1. AI Agent 为什么这么难调试?
如果你用过 LangChain、AutoGPT 或任何多步推理的 AI Agent,一定遇到过这样的场景:
明明单独调用 LLM 时回答不错,一到 Agent 循环里就反复跳坑、死循环或者突然"降智"输出无关内容。传统的 print 调试在这里几乎失效------因为问题可能出在任何一步的 Prompt 拼装、工具调用的参数传递、甚至是多轮上下文累积出的幻觉。
与普通 Web 应用不同,AI Agent 的"业务逻辑"不是写死的 if/else,而是由大模型实时生成的推理链。这使得排障变成了一项"追溯思维过程"的工程,既需要工具支持,也需要一套系统化的方法。
本文将从Prompt 追踪 和执行回放两个核心手段切入,带你构建一套可落地的 Agent 排障工作流,让你不再面对"玄学"报错干瞪眼。
2. 排障方法论:从 Prompt 到执行链的系统化视图
在深入工具之前,我们先建立一个统一的观察模型。
任何一次 Agent 运行都可以拆解为三层信息:
- Layer 1 -- 输入 Prompt:发送给 LLM 的完整提示词,包括系统指令、历史对话、工具定义、当前任务等。
- Layer 2 -- 模型输出 :LLM 返回的原始文本,可能包含
Thought、Action、Final Answer等结构。 - Layer 3 -- 工具执行 & 环境反馈:实际调用了哪个函数、传了什么参数、返回了什么结果,以及是否出现异常。
传统调试只能看到 Layer 3(比如一个报错堆栈),却丢掉了最关键的前两层。而系统化排障的核心思想就是:对每一次运行,记录上述三层数据的完整快照,以便随时回放和比对。
记住两个关键词:
- 追踪(Tracing):实时或事后采集调用链数据。
- 回放(Replay):在离线环境中用相同输入重放某一环节,隔离变量进行深度分析。
3. Prompt 追踪:捕获每一次"大脑活动"
Agent 的决策质量直接取决于它"看到"了什么样的 Prompt。如果某次运行突然输出 Action: None 或者调用了错误的工具,第一步就是检查发送到 LLM 的完整消息列表。
3.1 常见追踪维度
| 维度 | 说明 |
|---|---|
| 系统消息 | 角色设定、输出格式要求、约束条件等 |
| 历史对话 | 本轮之前的所有 user/assistant/function 消息 |
| 工具定义 | 全部可用函数的名称、描述、参数 schema(通常拼在系统消息里) |
| 当前查询 | 用户最新输入,可能被模板包裹后发送 |
大部分框架(LangChain、LlamaIndex、AutoGen 等)都提供了回调(Callback)机制,你可以在回调中拦截这些消息并打印或存储。
3.2 动手实现一个最小追踪器
以 LangChain 为例,我们可以通过自定义 CallbackHandler 记录所有 Prompt:
python
from langchain.callbacks.base import BaseCallbackHandler
import json
class PromptTracer(BaseCallbackHandler):
def on_llm_start(self, serialized, prompts, **kwargs):
for idx, prompt in enumerate(prompts):
print(f"======== Prompt {idx} 开始 ========")
if isinstance(prompt, str):
print(prompt[:500]) # 太长只打印开头
else:
for msg in prompt:
print(json.dumps(msg.dict(), ensure_ascii=False, indent=2))
print(f"======== Prompt {idx} 结束 ========")
agent = initialize_agent(tools, llm, agent="zero-shot-react-description",
callbacks=[PromptTracer()])
运行 Agent 后,你将在控制台看到每次 LLM 调用前的完整消息结构。如果发现工具描述缺失、历史消息过长导致关键信息被截断,就可以针对性优化 Prompt 模板或调整聊天窗口大小。
当然,控制台输出只是入门。更推荐的做法是将追踪数据持久化到 JSON 或专门的平台,以便后续检索和对比。
4. 执行回放:把 Agent 的"行为录像"复现出来
Prompt 追踪让你知道 Agent"看到了什么",而执行回放让你知道 Agent"干了什么"以及"为什么这么干"。
很多诡异的 bug 只有在特定上下文下才会出现,线上遇到的问题很难靠猜修复。如果能在本地用完全相同的 LLM 响应序列重放一次运行,就能快速定位错误根因。
4.1 记录完整执行轨迹
我们可以在每次 on_llm_end、on_tool_start、on_tool_end 等回调中记录事件,并带有时间戳和顺序号:
python
from langchain.callbacks import BaseCallbackHandler
class ExecutionRecorder(BaseCallbackHandler):
def __init__(self):
self.events = []
def on_llm_start(self, serialized, prompts, **kwargs):
self.events.append({
"type": "llm_start",
"ts": time.time(),
"prompts": [p if isinstance(p,str) else [m.dict() for m in p] for p in prompts]
})
def on_llm_end(self, response, **kwargs):
self.events.append({
"type": "llm_end",
"ts": time.time(),
"generations": [[gen.dict() for gen in g] for g in response.generations]
})
def on_tool_start(self, serialized, input_str, **kwargs):
self.events.append({
"type": "tool_start",
"ts": time.time(),
"tool": serialized["name"],
"input": input_str
})
def on_tool_end(self, output, **kwargs):
self.events.append({
"type": "tool_end",
"ts": time.time(),
"output": output
})
运行结束后,将 self.events 保存为 JSON 文件,这就是一次完整的"执行录像"。
4.2 回放原理:Mock LLM 与真实工具
回放实现的关键在于:用录制好的模型响应替换真实 LLM 调用,但工具调用仍走真实函数。这样可以在不依赖外部模型(避免非确定性和花费)的情况下,重放 Agent 的每一步决策。
具体做法是创建一个 Mock LLM 类,根据事件队列中的 llm_end 数据按顺序返回预置的响应:
python
from langchain.llms.base import LLM
class ReplayLLM(LLM):
events: list
idx: int = 0
def _call(self, prompt, stop=None, **kwargs):
while self.idx < len(self.events):
ev = self.events[self.idx]
self.idx += 1
if ev["type"] == "llm_end":
# 从录制的 generations 中取出第一个候选文本
return ev["generations"][0][0]["text"]
raise StopIteration("No more recorded LLM responses")
@property
def _llm_type(self):
return "replay"
然后用这个 ReplayLLM 替换原来的 LLM,重新构建 Agent 并喂入相同的用户输入,就能逐步骤复现执行过程。此时你可以在关键节点设置断点,检查变量状态,轻松定位到哪一步的输出不符合预期。
5. 工具篇:LangSmith / Langfuse / Phoenix 实战对比
自己写追踪和回放虽然灵活,但生产环境中更推荐使用成熟的可观测性平台。下表对比了目前主流的三个工具:
| 特性 | LangSmith | Langfuse | Arize Phoenix |
|---|---|---|---|
| 与 LangChain 集成 | 原生深度集成 | 良好 | 良好 |
| 开源 | 否(有免费版) | 是(自托管/云) | 是 |
| Prompt 版本管理 | ✅ | ✅ | ❌ |
| 数据集与评估 | ✅ | ✅ | ✅ |
| 执行回放 | 通过数据集重放 | 通过 trace 重放 | 有限支持 |
| 学习曲线 | 中等 | 中等 | 低 |
选型建议:
- 如果你是重度 LangChain 用户,且预算允许,LangSmith 开箱即用,调试体验最顺滑。
- 如果你需要私有化部署或成本敏感,Langfuse 的自托管方案非常合适,且功能迭代快。
- 如果你的团队已经在用 Arize 做 LLM 评估,Phoenix 的追踪能力也能满足基本需求。
这三种工具都支持通过简单的回调或环境变量开启追踪,极大降低了"先有数据再排障"的门槛。强烈建议在项目初期就接入,别等到 bug 一堆再补课。
6. 实战案例:一次多步推理失败的回溯定位
下面我们通过一个真实场景,走一遍"追踪 → 回放 → 定位 → 修复"的完整流程。
场景: 一个用于查询天气并给出穿衣建议的 Agent,偶尔会返回"由于某些原因,无法提供建议",但单独调用 get_weather 工具时一切正常。
6.1 追踪异常运行
在 Agent 外层加上 PromptTracer 和 ExecutionRecorder,触发一次失败运行后得到事件日志。查看 LLM Prompt,发现关键片段:
Human: 今天北京的天气怎么样?我应该穿什么?
AI Thought: 需要使用 get_weather 工具查询天气
Action: get_weather
Action Input: {"city": "北京"}
Observation: {"temperature": "12°C", "condition": "小雨", "humidity": "85%"}
# 第二轮 Prompt
Human: ...
AI Thought: 天气信息是 12°C 小雨,湿度较大,需要考虑保暖和防潮......
Final Answer: 由于某些原因,无法提供建议。
看起来模型拿到了天气数据,但最终输出却是拒绝回答。为什么?
6.2 执行回放定位根本原因
我们用录制的 LLM 响应对 Agent 进行回放,并在第一轮工具返回后插入断点,检查即将发送到 LLM 的消息列表。结果发现:工具返回的 Observation 中包含了一个换行符异常,导致后续消息中的 "humidity": "85%"} 被"切断",后面的系统约束未被正确拼接。 LLM 收到的上下文残缺,因而选择了"保守"的 Fallback 回答。
进一步检查发现,这是天气 API 在特定情况下返回的 JSON 字符串末尾带了一个 Windows 风格的回车符 \r,LangChain 的解析器没有正确处理。
6.3 修复与验证
修复方案很简单:在自定义工具函数中对输出做一次 .strip() 即可:
python
def get_weather(city: str) -> str:
raw = weather_api.call(city)
return raw.strip()
修复后,重新用同一份录制数据回放,Agent 能够正常给出穿衣建议。线上部署后同类问题不再出现。
这个案例清楚地展示了**"记录一切 → 隔离重放 → 精确修复"**三部曲的有效性。
7. 构建你自己的调试工作流
将上述方法组合起来,你可以形成一套标准化的调试管线:
- 接入可观测性平台:在项目启动初期就集成 LangSmith/Langfuse,自动记录每次 trace。
- 建立异常检测机制:设置 alert,当 Final Answer 包含特定拒绝词(如"无法提供"、"抱歉")或工具调用序列出现异常模式时自动标记。
- 导出录制数据集:将异常运行导出为 JSON(包含完整 LLM 响应和工具输出),存入可重放的数据集。
- 本地回放与迭代 :在开发环境用
ReplayLLM和真实工具重建 Agent 实例,快速迭代修复方案,并在同一数据集上验证效果。 - 回归测试:将修复后的 Agent 在异常数据集上批量运行,确保不再复现,同时跑通已有的正常用例。
整个流程可画成一张流程图(此处省略 Mermaid,读者可自行拓展)。
8. 总结与展望
AI Agent 的调试不应再停留在"天灵灵地灵灵再加个 print"的层次。通过系统化地追踪 Prompt 和执行链 ,并借助回放技术将非确定性调用变成可复现的排障场景,你将获得前所未有的控制力。
未来,随着 Agent 能力的增强和链路的复杂化,调试工具也会朝着以下方向发展:
- 自动根因分析:平台自动对比失败 trace 与成功 trace 的差异,给出最可能的故障原因。
- 在线回放沙箱:直接在云端重放历史运行,无需本地搭建环境。
- Prompt 版本与 A/B 对比:精细化对比不同 Prompt 版本对最终行为的影响,真正做到数据驱动优化。
无论工具如何演进,掌握"记录 → 回放 → 分析"的核心方法论,都是每一位 Agent 开发者绕不开的基本功。希望本文能为你铺平这条路。