Agent 出错时怎么定位?Trace、日志与指标设计
内容摘要:排查 Agent 问题、算成本、做评测,全都依赖可观测性。本章讲 Agent 需要记录的完整信号(模型输入输出、工具调用与结果、重试、Token、延迟、错误),以及结构化日志与脱敏,并给出一个最小 Trace 记录器。 难度:进阶 |前置知识:06 工具调用、08 Agent Loop
常见误区
记录用户问题和最终答案就足够排查故障,模型的中间调用与工具参数既复杂又没有必要保存。
先给结论
Agent 故障跨越模型、编排和工具多层,没有 Trace(追踪)就无法复盘。每次运行应关联模型与 Prompt 版本、状态转换、工具请求与结果、重试、Token、延迟、费用和终止原因,并对敏感数据脱敏。
带着这三个问题阅读:
- Log、Metric、Trace 与 Agent 轨迹分别回答什么问题?
- 一次运行中哪些 Span 和字段必须可关联?
- 怎样在可诊断性与隐私、存储成本之间取舍?
一、为什么 Agent 更需要可观测性
Agent 是"模型 + 多次工具调用 + 循环 + 非确定性",出问题时若没有完整轨迹,几乎无法定位根因(22)。三支柱 + Agent 专属信号:
二、一次 Agent 运行该记录什么
| 信号 | 内容 | 用途 |
|---|---|---|
| 模型输入/输出 | messages、decision(脱敏) | 复现、评测 |
| 工具轨迹 | 工具名、参数、结果摘要、耗时、成功/失败 | 排障、成功率 |
| 重试与错误 | 错误类型、重试次数 | 可靠性 |
| Token | 输入/输出 Token、按步累计 | 成本(18) |
| 延迟 | 每 Span、端到端 P50/P95 | 性能 |
| 停止原因 | ok/max_steps/circuit_open/... | 质量诊断 |
三、最小 Trace 记录器(可运行)
"""trace.py —— 最小 Trace/Span 记录器(可离线运行)。"""
from __future__ import annotations
import time, json, re
from contextlib import contextmanager
from dataclasses import dataclass, field
SECRET = re.compile(r"sk-[A-Za-z0-9]{6,}")
def _redact(v):
return SECRET.sub("[REDACTED]", v) if isinstance(v, str) else v
@dataclass
class Trace:
trace_id: str
spans: list[dict] = field(default_factory=list)
tokens_in: int = 0
tokens_out: int = 0
@contextmanager
def span(self, name: str, **attrs):
start = time.monotonic()
rec = {"name": name, "attrs": {k: _redact(v) for k, v in attrs.items()}}
try:
yield rec
rec["status"] = "ok"
except Exception as e:
rec["status"] = "error"; rec["error"] = str(e)
raise
finally:
rec["elapsed_ms"] = round((time.monotonic() - start) * 1000, 1)
self.spans.append(rec)
def add_usage(self, tin: int, tout: int):
self.tokens_in += tin; self.tokens_out += tout
def summary(self) -> dict:
return {"trace_id": self.trace_id, "spans": len(self.spans),
"tokens_in": self.tokens_in, "tokens_out": self.tokens_out,
"total_ms": round(sum(s["elapsed_ms"] for s in self.spans), 1)}
if __name__ == "__main__":
tr = Trace("run-1")
with tr.span("model.decide", prompt="key sk-abcdef123"): # 密钥会被脱敏
tr.add_usage(120, 40)
with tr.span("tool.get_slow_queries", instance_id="mysql-test-01"):
pass
print(json.dumps(tr.summary(), ensure_ascii=False))
print(tr.spans[0]["attrs"]["prompt"]) # 预期:key [REDACTED]预期输出:summary 含 spans: 2、Token 计数;首个 span 的 prompt 中密钥被替换为 [REDACTED]。
四、结构化日志与脱敏
- 结构化:日志用 JSON,带
trace_id、step、tool、status,便于检索聚合。 - 脱敏:密钥、Token、PII、内部地址一律脱敏(14)。Trace 是常见的密钥泄露点。
- 关联:同一
trace_id串起模型、工具、重试,便于端到端排障。
五、指标与告警
关键指标:任务完成率、工具成功率、平均步数、P95 延迟、每任务 Token/成本、错误率、注入拦截数。设告警阈值(成本突增、成功率下跌、步数打满率上升)。
六、失败路径
- 只记最终答案:出错无法定位是模型、工具还是校验的问题。
- Trace 记明文密钥:安全事故。
- 无 trace_id 关联:多次工具调用无法拼成链路。
七、测试与验证
- 断言异常时 span 记录了
status=error与错误;断言密钥被脱敏(见上例可直接测)。 - 断言 Token/耗时被正确累计。
八、安全与权限
- Trace/日志的访问要鉴权;含敏感字段的 Trace 加密与保留期管理。
九、性能与成本
- 采样:全量 Trace 成本高,可对正常流量采样、对错误全量。Token 统计是成本核算基础(18)。
十、框架差异
- OpenTelemetry 有 GenAI 语义约定(
gen_ai.*);LangSmith 等提供托管 Trace。原则一致:结构化、可关联、脱敏。
十一、关联章节
面试复述
Agent 可观测性要能回答“哪个版本在什么状态下,基于什么证据调用了什么工具,结果如何,为什么停止”。日志、指标与 Trace 必须统一 trace_id,并默认脱敏。
复述时先给结论,再补机制、适用边界和一个可验证指标;不要只罗列框架名称或产品功能。
迁移练习
为一次包含两次模型调用和一次工具调用的任务设计 Trace 树,列出每个 Span 的必填字段、敏感字段和聚合指标。
检索关键词
Agent 可观测性 Trace Span Token 消耗统计 工具轨迹 结构化日志 脱敏 OpenTelemetry GenAI
官方来源
- OpenTelemetry GenAI 语义约定;LangSmith/各观测平台文档(
verified_at: 2026-09-07)。
