AI Agent 可观测性:如何知道它到底在哪一步出错

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

AI Agent 可观测性:如何知道它到底在哪一步出错

当 Agent 答错时,先别急着换模型

一个 Agent 花了 45 秒才回答一个简单问题,原因可能完全不同:模型本身慢、检索服务慢、工具重试了三次、上下文被塞得太长,或者多个步骤串行执行导致整体延迟被放大。

如果系统只有一条“请求失败”日志,你无法知道问题发生在哪里。AI 应用的可观测性,不能只记录最终答案,而要记录一次 Agent 运行中发生过的模型调用、工具调用、检索、重试和输出。

OpenTelemetry 标志

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 状态、返回字段集合和结果条数,不保存完整客户资料。对于安全事件,还可以保存触发规则和脱敏后的攻击片段,让安全团队能复盘,不让普通业务角色看到原始隐私数据。

指标应该如何设计

至少需要四类指标:

  1. 延迟:首 Token 延迟、完整响应延迟、工具调用延迟。
  2. 消耗:输入输出 Token、缓存命中、模型调用次数。
  3. 可靠性:超时、解析失败、工具失败、重试次数和人工接管率。
  4. 质量代理指标:引用覆盖率、结构化输出校验率、拒答率和离线评测分数。

不要把“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 与离线评测样本关联起来?

一手资料

KEEP READING