你不能改你看不见的东西。Agent 工程里这句话字面成立
写过 Agent 的人都知道一个痛:Agent 跑歪了,你打开日志,看到几十轮 messages、几十次 tool_call,不知道到底是哪一步开始偏的。一次失败的 Agent 调试,可能花你一下午。
这不是调试能力问题,是可观测性缺失问题。Agent 比传统程序难调试得多——输出非确定、路径不固定、单次运行的 trace 很长、失败原因可能在 10 步之前埋的。靠 print 和 log 文件已经不够。这一篇讲清楚 Agent 工程里的可观测性栈:trace、replay、eval。
Trace:把 Agent 的每一步变成可检索的数据
trace 是 Agent 调试的基础设施。它把"这一次 Agent 跑"的每一步——每次 LLM 调用、每次工具执行、每次状态更新——都记录成结构化数据,可以按时间、按任务、按失败类型检索。
一个最简 trace 长这样:
import time, uuid, json
class Trace:
def __init__(self, task_id: str):
self.task_id = task_id
self.spans = []
def start_span(self, name: str, inputs: dict = None):
span = {
"id": str(uuid.uuid4()),
"name": name,
"inputs": inputs,
"start_time": time.time(),
}
self.spans.append(span)
return span
def end_span(self, span, outputs: dict = None, error: str = None):
span["end_time"] = time.time()
span["outputs"] = outputs
span["error"] = error
def save(self):
with open(f"traces/{self.task_id}.json", "w") as f:
json.dump({"task_id": self.task_id, "spans": self.spans}, f, indent=2, default=str)
在 Agent 的关键路径上埋 span:
async def run(task: str):
trace = Trace(task_id=str(uuid.uuid4()))
root = trace.start_span("agent_run", {"task": task})
messages = [...]
for step in range(10):
# LLM 调用 span
llm_span = trace.start_span(f"llm_call_step_{step}", {"messages": messages})
try:
resp = await client.chat.completions.create(...)
trace.end_span(llm_span, outputs={"message": resp.choices[0].message.model_dump()})
except Exception as e:
trace.end_span(llm_span, error=str(e))
raise
# 每个 tool call 一个 span
for tc in resp.choices[0].message.tool_calls or []:
tool_span = trace.start_span(f"tool_{tc.function.name}",
inputs=json.loads(tc.function.arguments))
try:
result = await run_tool(tc)
trace.end_span(tool_span, outputs={"result": result})
except Exception as e:
trace.end_span(tool_span, error=str(e))
result = f"Error: {e}"
trace.end_span(root)
trace.save()
保存的 JSON 长这样:
{
"task_id": "abc123",
"spans": [
{"name": "agent_run", "inputs": {"task": "..."}, "start_time": ...},
{"name": "llm_call_step_0", "inputs": {...}, "outputs": {"message": {...}}},
{"name": "tool_get_weather", "inputs": {"city": "北京"}, "outputs": {"result": "15 度"}},
{"name": "llm_call_step_1", ...},
...
]
}
有了结构化 trace 后,调试 Agent 就变成了翻 trace。找失败的 span、看那之前的 LLM 输出、找出哪里开始偏。一次调试从几小时压缩到几分钟。
生产的 Trace 工具
自己写 trace 能跑但不够。生产 Agent 通常用现成工具,几个主流的:
Langfuse——开源、自托管或云托管。对 OpenAI / Anthropic / LangChain / LlamaIndex 都有集成。可视化做得非常好——时间线视图、嵌套 span、token/成本统计、按 session 聚合。如果你要自己搭 Agent 平台,Langfuse 是目前的默认选项。
LangSmith (LangChain 出品)——商业化 SaaS,和 LangChain / LangGraph 集成最深。做 Prompt 管理、eval、标注都很强。适合已经用 LangChain 栈的团队。
OpenTelemetry + OpenLLMetry——把 LLM 调用变成 OpenTelemetry 的 standard span。适合已经有 Datadog / Grafana / Honeycomb 之类 APM 基础设施的团队,把 Agent trace 融入现有观测体系。
Helicone——专注 LLM 代理和成本可视化。轻量,接入简单。
实务上经验是中小团队用 Langfuse,已经有 APM 栈的用 OpenLLMetry,重度用 LangChain 的用 LangSmith。
埋点的关键信号
除了 LLM 调用和 tool call,有几个信号值得专门记录,让 trace 更有价值:
token 使用——每次 LLM 调用的 prompt tokens 和 completion tokens。是成本分析和上下文膨胀诊断的基础。
工具失败类型——tool 异常要打分类标签:timeout / invalid_args / permission_denied / external_error。按标签聚合能快速发现"哪类工具失败率高"。
Agent 决策的"理由"——如果你用 ReAct 式 Thought,把每一步的 Thought 单独记录,不只是塞在 messages 里。调试时按"Thought 包含 XX"做检索很有用。
上下文大小演化——每一步 messages 的总 token 数。上下文膨胀是 Agent 常见失败的前兆。
任务元数据——task_id、user_id、session_id、Agent 版本。按用户、按会话聚合 trace 时用。
Replay:从 trace 重放
Trace 的高级用法是Replay——从一个已记录的 trace 重新跑 Agent,验证"如果我改了 prompt、换了模型,在同样的输入下结果会怎样"。
实现思路:
async def replay(trace_id: str, modifications: dict = None):
trace = load_trace(trace_id)
initial_messages = trace.spans[0].inputs["messages"]
# 应用修改(换模型、改 system prompt 等)
if modifications:
initial_messages = apply_mods(initial_messages, modifications)
# 重新跑 Agent,但 tool_call 不真的执行——从 trace 里取记录的结果
recorded_tool_results = {
s["inputs"]["tool_call_id"]: s["outputs"]["result"]
for s in trace.spans if s["name"].startswith("tool_")
}
new_trace = await run_agent(initial_messages, tool_executor=MockExecutor(recorded_tool_results))
return diff(trace, new_trace)
为什么这有用?
调 Prompt——改了 system prompt,不用真跑 10 个线上任务验证,replay 10 条历史 trace 就能看新 prompt 下哪些任务会 regression。
换模型——从 gpt-4o 换到 gpt-5 或 claude-opus,用同一批历史任务跑 replay,对比新旧结果的质量。
复现 bug——用户说某任务跑错了,给 trace,你本地 replay,能精准重现那次失败。
Replay 的关键是tool 不真执行,用 trace 记录的结果。这样同一次 replay 能复现,不依赖外部服务的实时状态(数据库可能变了、网页可能改了、时间可能不同了)。
Eval:给 Agent 打分
Trace 告诉你"发生了什么",Eval 告诉你"这次跑得好不好"。Agent 的 eval 比一次性 LLM 的 eval 复杂得多,因为要评估的是整个多步交互的最终效果。
几种 eval 策略:
任务完成率——针对一批 golden 任务,人工标注"成功标准",Agent 跑完后自动检查。例如:"订机票任务"的成功标准是"最终确认页出现"。这种精确但难覆盖复杂任务。
LLM-as-judge——跑完后让另一个 LLM(通常更强的模型)判断"这个 Agent 的最终输出达成了用户目标吗?",给分 1-5。便宜、可扩展,但有 bias。
async def judge(task: str, agent_output: str, trace: dict) -> dict:
resp = await openai.chat.completions.create(
model="o4",
messages=[{"role": "system", "content": """你是严格的评审。
判断 Agent 的最终输出是否达成了用户的任务目标。
按以下维度打分(1-5):
- task_completion: 目标达成度
- correctness: 信息正确度
- efficiency: 步骤合理性(步骤是否过多或过少)
- format: 格式符合度
返回 JSON"""},
{"role": "user", "content": f"任务: {task}\n输出: {agent_output}\ntrace 摘要: {summarize_trace(trace)}"}],
response_format={"type": "json_object"},
)
return json.loads(resp.choices[0].message.content)
轨迹 eval——不只评最终输出,评中间过程。"Agent 调用了合适的工具吗?""有没有走不必要的弯路?""Thought 的推理清晰吗?" 更细但也更复杂。
对比 eval——两个 Agent 版本跑同一批任务,让 judge 判断"哪个输出更好"。pairwise 比 absolute 打分稳定得多。Chatbot Arena 就是这个原理。
回归测试集:Agent 的 CI
要做一个持续在生产跑的 Agent,必须有回归测试集——一批 golden 任务,每次改 prompt / 模型 / 工具前都跑一遍,对比新旧结果。
构建经验:
从真实失败案例挖。线上跑失败的任务、用户差评的任务、客服处理过的 Agent 问题,都是测试集素材。每周从线上捞 5~10 个补充进测试集。
分层。简单任务(检查基线能力)、中等任务(检查主要场景)、难任务(检查极限)。新版本在简单任务上不应回退,在难任务上可以接受偶尔回退但要有合理解释。
边界和对抗。故意加刁钻任务:工具故障、用户输入含 prompt injection、需要拒答的敏感请求。
测试集一开始可以小(30~50 个任务)。每条带 golden output 或 judge prompt,跑一次看通过率。新版本发布前跑全量,通过率回退超过阈值(比如 5%)就不允许发。
这套流程和传统软件的 CI 几乎一致,只是用例更难写、判断更模糊。有了它,Agent 从"手感调"变成"工程化迭代"。
可观测性驱动的改进循环
把前面所有加起来,一个成熟 Agent 团队的工作流是:
线上 Agent → Langfuse trace → 每周分析失败 trace →
补进测试集 → 改 prompt / 工具 → 本地 replay 验证 →
测试集回归 → 部署 → 循环
每个环节的工具都有相对成熟的选项,但真正的挑战在于把这个循环跑起来、跑顺。大多数团队卡在"捞失败 trace 太麻烦"或"每次都手动判断新版本好不好"这种操作细节。
如果这一篇你只带走一条,我希望是这条:先埋 trace,再优化 Agent。没 trace 时改 Agent 是蒙着眼睛开车;有 trace 后每一次改动都是数据支撑的决策。
小结
Agent 工程和传统软件工程最大的差别就在可观测性——非确定性让每一次调试都更复杂,传统的 log + breakpoint 远远不够。Trace、Replay、Eval 构成 Agent 工程的三个支柱。用 Langfuse 之类的工具把它们搭起来,然后专注在内容上。
下一篇是这个系列的工程篇高潮——生产化。上下文压缩、成本优化、延迟优化、失败恢复、安全边界,把前面所有篇讲过的东西收拢成"Agent 上生产要做的工程"。
相关阅读
- Langfuse Docs — 可观测性首选
- LangSmith Agent Eval
- OpenLLMetry (Traceloop) — OpenTelemetry 风格
- Agent Benchmarking (Princeton NLP) — 学术 eval 方法
版权声明: 如无特别声明,本文版权归 sshipanoo 所有,转载请注明本文链接。
(采用 CC BY-NC-SA 4.0 许可协议进行授权)
本文标题:16. 可观测性与评测:Agent 的 trace、replay 与回归测试
本文链接:https://www.sshipanoo.com/blog/ai/ai-agent/16-可观测性与评测/
本文最后一次更新为 天前,文章中的某些内容可能已过时!