AI Agent 可观测性:如何知道它到底在哪一步出错
Agent 的一次回答可能经过多次模型调用、检索、工具执行和重试。本文从日志、指标与 Trace 的分工讲起,介绍 OpenTelemetry 的 GenAI 语义约定、失败排查方法、敏感内容采集边界,以及如何把 AI 运行变成可解释的执行链路。

当 Agent 答错时,先别急着换模型
一个 Agent 花了 45 秒才回答一个简单问题,原因可能完全不同:模型本身慢、检索服务慢、工具重试了三次、上下文被塞得太长,或者多个步骤串行执行导致整体延迟被放大。
如果系统只有一条“请求失败”日志,你无法知道问题发生在哪里。AI 应用的可观测性,不能只记录最终答案,而要记录一次 Agent 运行中发生过的模型调用、工具调用、检索、重试和输出。

OpenTelemetry(简称 OTel)正在为 GenAI 场景补充语义约定(Semantic Conventions),把“模型名称”“输入输出 Token”“工具调用”和“Agent 工作流”等信息用统一字段记录。OpenTelemetry 的官方实践文章展示了如何把一次 LLM 调用放进普通服务的 Trace 中。
日志、指标和 Trace 各自回答什么
三种信号不是互相替代的:
- 日志回答“某一刻发生了什么”,适合记录错误详情和业务事件。
- 指标回答“整体趋势怎样”,适合看延迟、Token 消耗、错误率和调用量。
- Trace回答“一次请求经过了哪些步骤”,适合定位 Agent 的链路瓶颈。
对传统 Web 请求来说,一条 Trace 可能是“网关 → API → 数据库”。对 Agent 来说,它更像:
用户请求
└─ Agent 工作流
├─ LLM:判断是否需要检索
├─ Retriever:查询知识库
├─ LLM:生成工具参数
├─ Tool:调用订单 API
├─ LLM:整理结果
└─ 最终回答
没有这条树状链路,工程师只能靠猜。
GenAI 语义约定记录了什么
具体字段仍处于演进中,但常见信息包括:
- 请求使用的模型与服务商。
- 输入 Token、输出 Token 和调用持续时间。
- 模型停止原因,例如正常结束或发起工具调用。
- Agent、Workflow、Session 和工具的标识。
- 检索、工具执行和模型调用之间的父子关系。
例如,一次慢请求可以被拆成:模型调用 1.2 秒,向量检索 0.4 秒,订单 API 8 秒,模型总结 1.1 秒。你不必猜“是不是模型变慢了”,因为 Trace 会直接显示大部分时间花在订单 API 上。
一次失败应该怎样排查
假设用户投诉:“客服 Agent 这次答非所问。”可以按下面顺序看 Trace:
先看输入是否正确
检查系统指令、用户问题、历史摘要和检索片段是否真的进入了模型上下文。很多所谓“模型幻觉”,根源是检索为空、字段被截断,或者把旧版本政策混进了当前请求。
再看工具是否返回正确
工具调用成功不代表业务结果正确。HTTP 状态码是 200,不等于订单查询返回了正确用户的数据。Trace 里应记录工具名称、版本、参数摘要、耗时和错误类型;对于敏感值,只记录哈希、字段名或脱敏后的摘要。
最后看模型是否正确使用上下文
如果上下文里有正确资料,模型仍然选错工具或忽略约束,才更像提示设计、模型能力或路由策略的问题。此时可以把同一个 Trace 送进离线评测,比较不同模型、提示模板和工具描述。
一条 Agent Trace 应该长什么样
不要把所有信息都塞到一个巨大的 Span 里。更容易排查的结构,是为一次用户任务建立根 Span,再按执行层级嵌套:
agent.run
├── retrieval.query
├── gen_ai.chat
│ └── tool.call
├── tool.execute
└── gen_ai.chat
每个 Span 记录“这个步骤做了什么”和“花了多少时间”,而不是默认保存全部内容。模型 Span 可以记录模型名、响应状态、输入输出 Token 和结束原因;检索 Span 可以记录索引名、Top-K、过滤条件摘要和命中文档 ID;工具 Span 可以记录工具版本、参数校验结果、外部响应码和重试次数。
把字段分成低基数和高基数也很重要。模型名、操作类型和错误类别适合做指标标签;完整用户问题、订单号和工具参数不适合直接作为指标标签,否则时间序列数量会爆炸。高基数信息应该放在受控的日志或事件里,并设置访问权限。
从代码到 Trace:先包住边界,再追求完整
第一版 instrumentation 不需要覆盖整个 Agent 框架。可以先包住三个边界:
with tracer.start_as_current_span("agent.run") as run:
result = call_model(messages)
record_model_usage(run, result.usage)
with tracer.start_as_current_span("tool.execute") as tool_span:
tool_span.set_attribute("tool.name", tool_name)
tool_result = execute_tool(args)
final = call_model(messages + [tool_result])
关键不是这段代码本身,而是让每次模型调用和工具调用都继承同一个 Trace 上下文。否则你会得到一堆互相孤立的请求记录,仍然无法回答“这次回答经过了哪个工具”。
接下来再补齐重试、检索和人工接管。每加一类信号,都应该配一个排查问题:它能不能帮助定位慢、错、贵或不安全?如果不能,就不要为了“字段齐全”增加采集复杂度。
内容采集是双刃剑
记录完整 Prompt 和模型输出,对调试非常有帮助;但这些内容也可能包含个人信息、商业机密、访问令牌和用户输入的恶意指令。
OpenTelemetry 的 GenAI 指南特别强调,默认可以只记录模型名、Token 和耗时,只有明确开启内容采集时才保存完整消息、工具参数和工具结果。生产环境建议分层:
- 默认记录元数据,不记录原文。
- 调试租户或抽样请求才采集内容。
- 对邮箱、手机号、订单号和密钥做脱敏。
- 限制 Trace 的保存时间和访问角色。
- 严禁把完整 Prompt 直接打进普通应用日志。
可观测性本身也必须经过威胁建模,否则为了排查 AI 问题,反而建立了一个更大的数据泄露面。
一种实用的内容策略是“默认摘要、按需取原文”:Trace 默认只保存消息长度、哈希、敏感字段数量和版本号;当用户授权调试时,再从加密的短期存储中关联原文。这样既能判断“上下文是否变长、是否发生了重试”,也不会让每个监控面板都暴露完整对话。
如果确实需要记录工具结果,应优先保存经过裁剪的结构化摘要。例如只保存 HTTP 状态、返回字段集合和结果条数,不保存完整客户资料。对于安全事件,还可以保存触发规则和脱敏后的攻击片段,让安全团队能复盘,不让普通业务角色看到原始隐私数据。
指标应该如何设计
至少需要四类指标:
- 延迟:首 Token 延迟、完整响应延迟、工具调用延迟。
- 消耗:输入输出 Token、缓存命中、模型调用次数。
- 可靠性:超时、解析失败、工具失败、重试次数和人工接管率。
- 质量代理指标:引用覆盖率、结构化输出校验率、拒答率和离线评测分数。
不要把“Token 越少越好”当成唯一目标。压缩上下文可能降低成本,却也可能删掉回答所需的证据。正确的做法是同时看质量、延迟和成本,按用户任务分组,而不是只看全局平均数。
这些指标还需要和“请求类型”绑定。客服问答、代码生成、文档摘要和事务执行的正常范围不同,混在一起看会把异常平均掉。建议至少按 Agent、任务类型、模型版本和租户分组,并同时看 P50 与 P95。平均延迟正常,不代表最慢的那 5% 用户没有一直卡住。
成本归因也不要只用一个总金额。可以把一次任务的成本拆成模型成本、检索成本、工具调用成本和重试成本。这样当账单上升时,你才能判断应该缩短上下文、调整模型路由、修复工具超时,还是限制某一类 Agent 的最大步数。
与传统 OTel 的关系
GenAI 语义约定不是一套新的监控后端,也不是要求你换掉现有的 Jaeger、Prometheus 或 OTLP Collector。它更像是一组让不同厂商“说同一种字段语言”的约定。
因此,落地可以从现有链路开始:给每次 Agent 运行创建一个根 Span,把模型调用和工具调用作为子 Span,再把 Token、模型版本和错误原因写入标准属性。这样未来更换可观测性后端时,数据仍然能迁移。
不过要注意,语义约定仍在快速发展,字段的稳定级别可能变化。建议把属性名集中封装在自己的 instrumentation 层,不要在几十个业务文件里散落字符串。
从观测到自动修复
可观测性最终不只是给人看,还可以成为控制回路:
- 工具连续超时,自动降低该工具的并发并切换备用路径。
- 输入 Token 接近预算,先压缩历史,再决定是否升级模型。
- 结构化输出连续校验失败,暂停自动执行,转人工处理。
- 某个模型版本的错误率显著上升,按流量比例回滚到上一版本。
但自动修复必须有边界。不要让 Agent 根据自己记录的 Trace 无限调整权限、提示词或工具列表。观测数据应该进入经过审核的策略层,由明确的阈值、审批和回滚机制控制变更。
结语:从“答案错了”走向“哪一步错了”
AI Agent 的可观测性不是给日志加几个 Token 字段,而是把一次非确定性运行还原成可解释的执行链路。工程团队真正需要的不是知道“模型很慢”,而是知道“哪一个模型调用、哪一次检索、哪一个工具、哪一次重试造成了这次慢”。
建议先选一个高价值流程,建立最小 Trace:请求 ID、Agent ID、模型名、输入输出 Token、工具名、耗时和错误类型。等链路稳定后,再逐步加入内容采集、评测结果和成本归因。
落地清单:
- 是否能看到一次 Agent 运行的完整步骤树?
- 是否能区分模型慢、工具慢和重试造成的慢?
- 是否记录了模型版本、提示版本和工具版本?
- 是否默认关闭敏感内容采集?
- 是否把 Trace 与离线评测样本关联起来?



