AI Agent 可观测性:破解多步推理黑盒
1. 引言:当 Agent 开始「独立思考」
2024 年以来,大语言模型的能力边界被不断打破。最初,我们把 LLM 封装在一个简单的请求-响应循环里:用户输入一句话,模型返回一段文本,系统把它渲染到界面上。这个阶段的工程问题非常简单——HTTP 状态码是否 200、首字延迟多少毫秒、token 消耗是否超预算。可观测性几乎是「锦上添花」的事情,一组 Prometheus 指标加几条日志就能覆盖绝大多数故障场景。
然而,当 LLM 从「被动回答问题」进化到「主动调用工具、规划步骤、修改环境、自我纠错」时,一切都变了。Agent 不再是一个函数,而是一个拥有隐式状态机的复杂系统。它在一次任务中可能调用 3 个不同的外部 API、读写 2 张数据库表、生成并执行 1 段临时代码,然后在第 7 步根据前 6 步的累积上下文做出一个「看起来合理但完全错误」的决定。
更麻烦的是,Agent 的行为具备三重不确定性:
第一,模型输出的非确定性。即使 temperature 设为 0,不同版本的模型、不同的上下文组织方式,甚至同一模型在不同硬件上的浮点运算差异,都可能导致推理路径分叉。传统软件里「同样的输入必然产生同样的输出」这一调试前提,在 Agent 场景下彻底崩塌。
第二,工具调用的开放性。传统微服务中,一个服务调用另一个服务的契约是静态的、可枚举的。而 Agent 调用工具是「模型决策的结果」——它可以选择调用哪个工具、传什么参数、如何解释返回值、是否重试。这意味着调用图不是代码写死的,而是运行时动态生成的。
第三,多步依赖的脆弱性。Agent 的第 N 步决策依赖第 1 到第 N-1 步的全部上下文。任何一步的微小偏差——一次工具返回的格式变化、一次上下文截断、一次误导性的检索结果——都可能沿着推理链放大,最终在第 10 步表现为一个「离奇」的错误。传统调试中「从报错点向上追溯」的方法在这里效率极低,因为根因往往不在报错点附近,而在五次工具调用之前。
让我们看一个真实的、每天都在发生的故障场景:
一个客服 Agent 收到用户请求:「帮我查一下订单 8848 的物流状态,如果已经签收就自动确认收货。」
Agent 的推理链是这样的:
- 识别意图:查询物流 + 条件触发确认收货
- 调用
order_service.get_order(8848),获得订单信息- 调用
logistics_service.track(8848),获得物流轨迹- 解析物流 JSON,判断当前状态是否为「已签收」
- 判断成立,调用
order_service.confirm_receipt(8848)- 返回用户:「您的订单已确认收货」
表面上看,这一串操作天衣无缝。但第 4 步可能出问题:物流服务返回的 JSON 结构变了,status 字段从 "signed" 变成了嵌套的 "delivery": {"state": "SIGNED"}。模型的「推理能力」让它非常擅长处理这种结构变化——它读懂了新的 JSON,正确判断出包裹已签收。但紧接着,它注意到 delivery 对象里还有一个 "recipient": "李明" 字段。而订单信息里收货人是「王芳」。模型「聪明地」推断:包裹可能被错误签收了,于是没有调用 confirm_receipt,而是调用了一个它认为更合理的工具 order_service.create_complaint(),替用户发起了一条投诉工单。
用户第二天收到投诉处理电话,一脸茫然:「我只是想确认收货啊,为什么要投诉我?」
这个例子听起来荒诞,但它揭示了一个深刻的工程问题:Agent 的每一步推理都可能是对的,但合起来的结果却可能是错的。如果我们只能看到最终的「创建了投诉工单」这个事实,以及系统日志里记录的「调用了 create_complaint 工具」,我们永远无法理解这个错误是怎么发生的。我们必须看到第 4 步的完整上下文、模型看到的数据结构、它生成推理文本、它为什么放弃了 confirm_receipt 而选择了 create_complaint——也就是说,我们必须「黑盒变灰」。
这正是本文要解决的问题。我们将系统性地回答以下问题:
- 为什么传统的 Metrics / Logs / Traces 三支柱在 Agent 场景下全面失效?
- 一套面向 AI Agent 的可观测性体系应该记录什么、如何组织、如何存储?
- 多步推理的黑盒到底「黑」在哪几层?我们如何不打开模型内部,仅凭外部信号重构因果链?
- LangSmith、Langfuse、OpenTelemetry 等主流方案各自的边界在哪里?自建方案应该如何设计?
- 如何设计真正有用的指标,而不是堆砌数字?
- 如何把可观测性从「事后排查工具」升级为「研发流程的核心基础设施」和「运行时安全护栏」?
本文面向 AI 应用工程师、平台工程师、SRE 以及正在把 Agent 从 Demo 推向生产环境的团队。我们会提供大量可以直接落地的数据结构、架构图和代码示例,但更重要的是梳理清楚背后的设计原则——因为工具会变,原则不会。
2. 为什么传统可观测性不够用了
2.1 传统监控三支柱的诞生逻辑
现代可观测性体系建立在三个支柱之上,它们各自的诞生都对应着一类明确的工程问题:
- Metrics(指标):回答「系统是否健康」。通过聚合时间序列数据,我们可以快速判断 QPS、错误率、P99 延迟是否在正常区间。指标的特点是压缩率高、查询快、适合告警,但丢失了单次请求的细节。
- Logs(日志):回答「某一次发生了什么」。日志保留了单次操作的上下文,是人类排查问题最直观的入口。但日志的缺点是格式混乱、检索困难、缺乏关联性——你很难从十万行日志里自动找出「导致这次故障的那一行」。
- Traces(链路追踪):回答「跨服务调用的时间花在哪里」。在微服务架构中,一次用户请求会穿过十几个服务,Trace 通过 span 的父子关系把调用链串起来,让我们能看到每一跳的耗时和状态。
这三支柱的底层假设是:系统的行为是确定性的、结构化的、代码可枚举的。一个 RPC 调用链在代码写好的那一刻就确定了;一个函数的输入输出结构由接口定义固定;一次故障要么是资源耗尽,要么是代码 bug,要么是依赖服务异常——三者都可以通过既有的观测数据定位。
2.2 Agent 引入的四类新变量
Agent 的出现,从根本上动摇了三支柱的假设。我们来看看 Agent 引入了哪些传统体系无法表达的新变量:
变量一:非确定性输出。 传统服务的输出由代码决定:传入 order_id=8848,查询数据库,返回订单对象。逻辑是确定的。而 LLM 的输出是一个概率分布上的采样——同样的 prompt,两次调用可能给出不同的措辞、不同的推理路径、甚至不同的结论。这意味着「复现」在传统意义上是不可行的:你不能像调试一个 NPE 那样,把请求参数原样重放就稳定地触发同一个 bug。
变量二:动态工具调用。 传统服务的调用依赖关系写在代码里,用静态分析工具(如调用图)就能可视化。而 Agent 的工具调用是由模型实时「选择」的。一个 Agent 拥有 20 个工具,它这次调用了其中的 3 个,下次可能调用完全不同的 5 个。调用序列不是代码决定的,而是模型在运行时「思考」的结果。这导致:
- 调用图无法预先建模
- 工具的输入参数是模型生成的,可能不符合预期格式
- 工具的返回结果会被模型「解读」,解读过程可能引入新的错误
变量三:长链路规划与上下文累积。 传统微服务的一次链路追踪通常在毫秒到秒级完成,span 数量在几十以内。而一个复杂 Agent 任务可能持续几分钟、跨越 50+ 步、累计上下文达到几十万 token。每一步的上下文都包含之前所有步骤的结果,任何一步的信息丢失或误解都会向后传播。这导致 Trace 不再是「一棵浅树」,而可能是一棵「深且分叉的决策树」,其中还包含循环(重试、纠错、反思)。
变量四:模型内部状态。 这是最深的一层。模型在生成每一步的输出之前,内部经历了一个我们无法直接观测的推理过程。即使像 OpenAI o1/o3 这样的推理模型会输出「思考摘要」,那也只是模型对自己的推理所做的「事后解释」,不一定忠实于真实的内部计算。这意味着我们能观测到的最深粒度,就是模型的输入和输出——中间那块永远是黑的。
2.3 传统监控在 Agent 场景下的四大失效点
把这些新变量叠加起来,就能清楚地看到传统三支柱为什么失效:
失效一:只看入口出口,看不到中间推理。 传统 Trace 记录的是一次调用的起止时间、状态码、耗时。对于「Agent 完成任务」这件事,我们顶多能看到「Agent 入口调用耗时 45 秒,成功返回」。但这 45 秒里发生了什么?模型在哪一步犹豫了?哪个工具返回了异常数据?哪一次上下文压缩导致了信息丢失?全是一个黑盒。Trace 给了我们一条线,但线上全是空洞。
失效二:结构化日志难以表达自然语言决策过程。 传统日志是结构化的键值对:{"level": "error", "service": "order_api", "code": 500}。Agent 的关键决策过程却是一段自然语言:「我认为物流服务返回的时间戳字段格式不正确,决定先调用 parse_timestamp 工具进行规范化处理。」这段文本无法被拆成结构化的键值对,也无法被传统的日志检索系统高效利用。如果我们强行只记结构化字段而丢弃自然语言,就丢失了最关键的决策信息;如果只记自然语言而不做结构化,就无法做聚合分析和告警。
失效三:指标只统计「调用了几次」,无法回答「为什么这么调」。 Metrics 的优势是聚合,但也是它的局限。你可以统计「工具调用的失败率」「平均每任务的步数」「token 消耗分布」,这些指标能告诉你「有问题」,但几乎无法告诉你「问题在哪、为什么发生」。当你看到「工具选择准确率」从 95% 跌到 87% 时,下一步怎么办?你还是得去翻 Trace 和日志。更糟糕的是,行为层面的「错误」往往不像系统错误那样有明确的信号——一个 HTTP 500 是一个清晰的错误事件,而「模型错误地解读了数据字段」则是一个无法被 Metrics 直接捕捉的语义错误。
失效四:故障定位依赖人工复现,而复现不可靠。 传统故障排查的打法是「复现问题 → 打断点 → 逐步调试」。但在 Agent 场景下,复现问题本身就极具挑战:非确定性意味着即使你原样重放输入,Agent 也可能走一条不同的推理路径,问题很可能不会再次出现。就算问题复现了,你面对的是一个跨多个 LLM 调用、工具调用、上下文压缩操作的复杂时间线,单靠打断点式的调试已经无法建立全局因果视图。
2.4 我们需要什么:从「三支柱」到「四维度」
综合以上分析,Agent 可观测性需要在传统三支柱的基础上,引入一个新的核心对象——一次 Agent 任务的完整决策轨迹,以及它衍生出的四个观测维度:
- Trace(决策轨迹):以一次任务为单位的完整执行树,记录每一步的输入、输出、推理、工具调用和状态变化。
- Log(推理留痕):将自然语言的思考过程作为一等公民进行记录和索引。
- Metrics(行为指标):从系统健康指标延伸到任务质量指标、行为正确性指标和成本效率指标。
- Artifacts(中间产物与状态快照):记录 Agent 每一步的输入输出数据、工具调用参数、文件变更、状态快照,支持任意步骤的确定性回放。
下面这张表总结了传统可观测性与 Agent 可观测性的关键差异:
| 维度 | 传统微服务 | AI Agent |
|---|---|---|
| 核心观测对象 | 请求-响应链路 | 一次任务的决策树 |
| 调用图 | 代码静态决定 | 模型动态生成 |
| 确定性 | 输入确定输出确定 | 输入确定输出不确定 |
| 关键决策载体 | 分支判断、异常处理 | 自然语言推理过程 |
| 失败模式 | 错误码、异常、超时 | 语义误解、错误工具选择、上下文缺失 |
| 复现方式 | 重放请求 | 重放状态快照(需冻结模型与工具) |
| Trace 深度 | 几十个 span | 几十步 × 每步多个子 span |
| 日志核心内容 | 结构化键值对 | 结构化元数据 + 自然语言推理 |
| 指标核心 | 资源、延迟、错误率 | 任务成功率、行为正确性、成本效率 |
| 可观测性作用 | 事后排查 + 容量规划 | 事后排查 + 研发反哺 + 运行时护栏 |
接下来,我们逐一深入这四个维度的技术设计。
3. AI Agent 可观测性的核心维度
3.1 Trace:以一次任务为单位的完整决策树
3.1.1 从线性调用链到决策树
传统分布式追踪(如 OpenTelemetry)的 Trace 模型是一棵有向无环树:根 span 代表入口请求,子 span 代表下游调用,叶子 span 代表最底层的数据库查询或缓存操作。父子关系由代码的调用栈决定,天然形成树状结构。
Agent 的 Trace 模型则更复杂。一个 Agent 任务的执行过程包含三种结构:
第一种:LLM 调用(LLM Call)。 模型的一次推理请求。关键记录内容:system prompt、user prompt、完整上下文、模型输出、推理文本(如果有)、模型名与版本、token 消耗(输入/输出/推理)、延迟、temperature 等采样参数。
第二种:工具调用(Tool Call)。 Agent 调用外部工具的一次操作。关键记录内容:工具名、调用参数、返回结果(或错误)、耗时、副作用声明(如果工具支持)。
第三种:控制流节点(Control Flow)。 循环的开始/结束、条件分支的选择、并行任务的 fork/join、重试与纠错。这些节点帮助我们理解 Agent「为什么走到了这里」。
更重要的是,Agent 的执行不是一个严格的树——它是一棵决策树与状态机的混合体。同一层可能有多个候选方案(模型生成三个候选工具调用,选择一个执行),执行过程中可能有循环(重试、复审、自我纠错),还可能有「回溯」(模型发现第 3 步出错,回到第 2 步重新执行)。因此,Agent Trace 的数据模型需要支持:
- 多父节点:一个节点的形成可能受多个前置节点影响(如第 5 步的决策受第 2 步和第 4 步共同影响)
- 候选分支:记录被考虑但未执行的路径,而不只是最终执行的路径
- 循环标记:区分「新的执行」和「对旧步骤的重新执行」
- 因果边:显式声明「第 N 步的输出被第 M 步引用」的依赖关系
下面这张图展示了 Agent 任务执行树的结构,以及它和传统 RPC 调用链的区别:
3.1.2 Trace 的事件数据模型
为了让上面的决策树可以被机器处理,我们需要设计一套足够灵活的事件数据模型。下面是一个经过实践验证的、基于 JSON 的 Agent Trace 事件结构:
{
"trace_id": "trace_01J5F8K2M9P4Q7R3",
"task_id": "task_8848_confirmation",
"session_id": "user_12345_2026-08-18",
"agent_name": "customer_service_agent",
"agent_version": "v2.3.1",
"started_at": "2026-08-18T13:54:09.123Z",
"ended_at": "2026-08-18T13:56:42.891Z",
"status": "completed_with_anomaly",
"span_tree": {
"span_id": "span_root",
"span_type": "agent.run",
"parent_span_id": null,
"started_at": "2026-08-18T13:54:09.123Z",
"ended_at": "2026-08-18T13:56:42.891Z",
"input": {
"user_message": "帮我查一下订单 8848 的物流状态,如果已经签收就自动确认收货"
},
"output": {
"final_answer": "您的订单已确认收货",
"anomaly_flags": ["unexpected_tool_choice"]
},
"children": [
{
"span_id": "span_llm_001",
"span_type": "llm.call",
"parent_span_id": "span_root",
"model": "gpt-4.1",
"model_version": "2026-07-01",
"temperature": 0.2,
"prompt_template_id": "planner_v3",
"input_tokens": 1250,
"output_tokens": 380,
"reasoning_tokens": 0,
"latency_ms": 2410,
"input": {
"system_prompt": "你是客服 Agent 的规划器……",
"user_prompt": "帮我查一下订单 8848 的物流状态……"
},
"output": {
"reasoning_text": "用户有两个意图:查询物流状态和条件触发确认收货。需要先调用 order_service.get_order 获取订单信息,再调用 logistics_service.track 查询物流。",
"tool_choice": {
"selected": "order_service.get_order",
"candidates": ["order_service.get_order", "logistics_service.track"],
"rejected_reasons": {
"logistics_service.track": "需要先获得订单号对应的物流单号,故先查订单"
}
}
}
},
{
"span_id": "span_tool_001",
"span_type": "tool.call",
"parent_span_id": "span_llm_001",
"tool_name": "order_service.get_order",
"tool_version": "v1.5.0",
"input": {"order_id": 8848},
"output": {
"order_id": 8848,
"status": "shipped",
"consignee": "王芳",
"logistics_id": "SF1234567890"
},
"latency_ms": 120,
"error": null,
"side_effects": []
},
{
"span_id": "span_llm_002",
"span_type": "llm.call",
"parent_span_id": "span_root",
"model": "gpt-4.1",
"input_tokens": 2150,
"output_tokens": 520,
"latency_ms": 3100,
"output": {
"reasoning_text": "物流结果显示已签收,但收货人姓名不匹配。订单收货人是王芳,物流签收人是李明。这可能是包裹被错误签收,决定不执行自动确认收货,转而发起投诉工单。",
"tool_choice": {
"selected": "order_service.create_complaint",
"candidates": [
"order_service.confirm_receipt",
"order_service.create_complaint",
"customer_service.notify_agent"
],
"rejected_reasons": {
"order_service.confirm_receipt": "收货人姓名不匹配,不应确认",
"customer_service.notify_agent": "系统具备自动处理能力,先发起投诉"
}
}
}
}
]
},
"causal_edges": [
{
"from_span_id": "span_tool_001",
"to_span_id": "span_llm_002",
"relation": "provides_context",
"fields_used": ["output.consignee", "output.logistics_id"]
}
],
"metadata": {
"environment": "production",
"region": "cn-east-1",
"deployment_id": "deploy_20260818_001"
}
}
这个结构的几个关键设计点值得展开说明:
causal_edges(因果边):这是 Agent Trace 区别于传统 Trace 的核心概念。传统 Trace 的父子关系由调用栈天然决定,但 Agent 的认知依赖关系并不等同于调用关系。例如,第 5 步的决策可能引用了第 2 步的某个输出字段——通过在 span 图中显式记录这种「数据依赖边」,我们就能在排查时快速回答「这个决策受到了哪些历史信息的影响」。
candidates(候选分支):只记录「最终选了什么」是不够的,我们还需要记录「当时还有什么选项、为什么没选」。这些被否定的路径往往比实际执行的路径更有诊断价值——如果 Agent 每次出错时都「考虑了正确的工具但最终否决了它」,那说明问题出在模型的选择机制;如果 Agent 的候选列表里根本没有正确选项,那说明问题出在 Tool 描述或规划 Prompt 的设计上。
reasoning_text(推理文本):当模型支持输出推理过程时(如 o1 的 reasoning tokens、Claude 的 thinking blocks),必须原封不动地保存。这是「黑盒变灰」的最关键数据——即使它不 100% 忠实于内部计算,也远好于什么都没有。
side_effects(副作用声明):工具调用可能产生不可逆的副作用(写库、发消息、删除文件)。在 Trace 中显式声明副作用,是后续支持「安全回放」和「事故审计」的基础。
3.1.3 Trace 的采样策略
全量记录所有 Agent Trace 在成本上是不现实的。一个高频使用的 Agent 可能每天产生数百万次任务,每次任务的完整 Trace 可能包含数 MB 的结构化数据。我们需要设计合理的采样策略:
| 采样策略 | 触发条件 | 适用场景 |
|---|---|---|
| 全量记录(头部) | 所有任务的最外层元数据 | 基本计数、成本核算 |
| 错误全采 | status = failed 或 anomaly_flags 非空 |
所有异常路径 |
| 高质量采样 | 任务成功但符合「高价值」条件 | 用户反馈良好、金额高、操作敏感 |
| 随机采样 | 其余任务按固定比例(如 5%) | 基线分析、趋势观察 |
| 按需采样 | 人工或系统触发 | 疑难问题深挖 |
一个工程上的实用建议是:「头部全量、身体采样、异常必采」。即所有任务都至少记录一层轻量元数据(task_id、agent 版本、状态、耗时、成本、用户反馈),但只有异常任务和高价值任务才记录完整的 nested span 树。这样既控制了存储成本,又保证了「只要出了事,一定查得到」。
3.2 Log:面向自然语言的推理留痕
3.2.1 推理日志为什么不能只用传统日志
传统日志系统(ELK、Loki 等)的检索模型是建立在关键词匹配和结构化字段过滤之上的。你可以发出这样的查询:service=order_api AND level=error AND timestamp > 2026-08-18T00:00:00Z。这种查询效率极高,前提是你的日志内容是结构化的键值对。
但 Agent 的关键决策信息往往是这样的自然语言:
「我注意到物流记录中的收货人姓名『李明』与订单收货人『王芳』不一致。考虑到可能存在冒领风险,我决定不执行自动确认收货操作,转而调用投诉工具生成工单。如果用户后续反馈订单正常,该工单可被人工关闭。」
这段文本包含了意图、观察、推理、决策和备选方案的信息。传统日志系统能做的是把它当作一个字符串存储,然后让你用全文检索去搜「李明」或「王芳」。但你不能查询「所有因为收货人姓名不匹配而改变决策的 Agent 日志」,因为「姓名不匹配」是一种语义,不是关键词。
这就是推理日志与传统日志的本质区别:推理日志需要语义检索能力。
3.2.2 推理日志的双层结构
在实践中,我们推荐「结构化元数据 + 自然语言推理」的双层日志结构。结构化层负责过滤和聚合,自然语言层负责理解和诊断。
{
"log_id": "log_01J5F8K3N2P5Q8R4",
"trace_id": "trace_01J5F8K2M9P4Q7R3",
"span_id": "span_llm_002",
"timestamp": "2026-08-18T13:56:38.512Z",
"level": "info",
"event_type": "agent.decision",
"agent_name": "customer_service_agent",
"agent_version": "v2.3.1",
"structured": {
"step_index": 5,
"decision_type": "tool_choice_override",
"tool_selected": "order_service.create_complaint",
"tool_originally_planned": "order_service.confirm_receipt",
"confidence": 0.82,
"context_tokens_used": 2150,
"retrieved_docs_count": 0,
"mismatch_field": "consignee",
"expected_value": "王芳",
"actual_value": "李明"
},
"natural_language": {
"intent": "根据物流信息中的收货人姓名与订单信息不匹配,判断包裹可能被错误签收",
"observations": [
"物流记录显示签收人为李明",
"订单记录的收货人为王芳",
"两个姓名不一致"
],
"reasoning": "收货人姓名不匹配可能意味着包裹被冒领或错误签收。在这种情况下自动确认收货会带来交易纠纷风险。",
"decision": "不执行确认收货,转而创建投诉工单",
"rejected_alternatives": [
{
"alternative": "继续执行确认收货",
"reason": "姓名不匹配,风险过高"
},
{
"alternative": "转接人工客服",
"reason": "投诉工具可自动化处理,先试自动方案"
}
],
"confidence_self_assessment": "中等偏高"
},
"tags": ["decision_override", "data_inconsistency", "high_risk_operation"]
}
几个设计要点:
event_type 是首公民字段。 推理日志不只记录错误,更要记录关键决策点。典型的事件类型包括:agent.decision(关键决策)、agent.plan(规划生成)、agent.tool_selection(工具选择)、agent.retry(重试)、agent.self_correction(自我纠错)、agent.context_compression(上下文压缩)、agent.guardrail_trigger(护栏触发)、agent.human_escalation(人工升级)。
structured 层用「能不能回答关键问题」来驱动设计。 不要试图把自然语言的所有信息都结构化——那是不可能的。反过来思考:如果我要统计「因数据不一致而改变决策的频率」,我需要哪些字段?如果要有 mismatch_field、expected_value、actual_value。这个思维模式可以帮你设计出「少而精」的结构化字段,而不是堆砌几百个无用字段。
natural_language 层是给语义检索和 LLM 辅助分析用的。 有了这一层,你可以用向量数据库做语义检索:「找出所有因为数据不一致而放弃原计划的日志」,或者把大量日志喂给另一个 LLM 做批量分析:「总结过去 24 小时 Agent 决策变更的三大原因」。
3.2.3 关键事件的定义与埋点
推理日志的质量取决于埋点的位置。以下是 Agent 执行循环中必须埋点的关键位置:
| 埋点位置 | 事件类型 | 必须记录的内容 |
|---|---|---|
| 每步规划生成后 | agent.plan |
生成的计划文本、计划步骤数、是否包含工具调用 |
| 每次工具选择时 | agent.tool_selection |
候选工具列表、选择结果、拒绝理由 |
| 每次工具调用前后 | tool.call_start / tool.call_end |
工具名、参数、返回结果、耗时、错误 |
| 模型判断「需要重试」时 | agent.retry |
重试原因、重试次数、重试前后的差异 |
| 模型自我纠错时 | agent.self_correction |
发现了什么错误、如何修正、修正是否成功 |
| 上下文被压缩/截断时 | agent.context_compression |
压缩前 token 数、压缩后 token 数、被丢弃的内容摘要 |
| 护栏被触发时 | agent.guardrail_trigger |
触发的规则、触发的内容、处置动作 |
| 任务结束时 | agent.completion |
最终状态、总步数、总耗时、总成本、用户反馈 |
3.3 Metrics:从系统指标到「行为指标」
3.3.1 指标分层框架
传统 APM 的指标模型是围绕系统资源构建的:CPU、内存、磁盘、网络、以及应用层面的 QPS、错误率、延迟分布。这些指标在 Agent 场景下依然有必要——LLM API 调用的延迟和错误率直接影响用户体验——但它们远远不够。
Agent 的指标需要覆盖四个层次,每一层回答一类问题:
健康度层(System Health)——传统指标的延伸:
| 指标 | 定义 | 告警参考 |
|---|---|---|
| LLM 调用成功率 | 成功的 LLM 调用 / 总调用数 | < 99% 告警 |
| LLM P99 延迟 | 模型响应时间的 99 分位数 | 因模型而异 |
| 工具调用成功率 | 工具返回正常 / 总调用 | < 95% 告警 |
| 上下文窗口利用率 | 输入 token / 模型上下文窗口大小 | > 90% 警告 |
| 限流比例 | 被 rate limit 的请求占比 | > 1% 告警 |
| 空输出率 | 模型返回空文本的比例 | > 0.5% 告警 |
质量层(Task Quality)——Agent 特有的核心指标:
| 指标 | 定义 | 挑战 |
|---|---|---|
| 任务成功率 | 用户/系统确认成功的任务 / 总任务数 | 需要明确「成功」的判据 |
| 工具选择准确率 | 选择了正确工具的任务 / 总任务数 | 需要人工标注或自动判定 |
| 无效循环率 | 重试超过 3 次的任务占比 | 过高的循环消耗成本且用户体验差 |
| 纠正率 | Agent 自我纠错且最终成功的任务占比 | 衡量 Agent 的自愈能力 |
| 幻觉率 | 输出中包含虚构信息的任务占比 | 判定依赖抽查或引用验证 |
| 用户满意度 | 用户反馈为正向的任务占比 | 依赖于反馈机制的设计 |
效率层(Cost Efficiency)——把「试错成本」显性化:
| 指标 | 定义 | 意义 |
|---|---|---|
| 每任务平均 token | 所有任务的总 token / 任务数 | 最直接的金钱成本 |
| 每任务平均步数 | 所有任务的总步数 / 任务数 | 步数越多,延迟和成本越高 |
| 有效步占比 | 对最终结果有贡献的步骤 / 总步骤 | 反映规划的精确性 |
| 无效工具调用率 | 调用了但结果未被使用的工具调用占比 | 反映工具选择的浪费 |
| 每任务平均成本(元) | 按各模型定价折算 | 管理层关心的金指标 |
| 重试成本占比 | 重试消耗的 token / 总 token | 反映「试错税」 |
安全层(Behavior Safety)——Agent 特有的风险指标:
| 指标 | 定义 | 意义 |
|---|---|---|
| 护栏触发率 | 触发了安全规则的会话占比 | 高危行为的频率 |
| 敏感操作频率 | 执行了删除/写入/支付类操作的任务占比 | 评估暴露面 |
| 越权尝试率 | 尝试调用权限外工具的任务占比 | 安全风控的核心信号 |
| 提示注入拦截率 | 检测并拦截了提示注入的会话占比 | Agent 安全成熟度 |
| 数据泄露事件数 | 输出中包含敏感数据的次数 | 合规风险 |
3.3.2 指标设计的三条原则
原则一:先定义「好」,再设计指标。 不要因为某样东西「可以量化」就去量化它。在引入任何指标之前,先回答:「这个指标上升或下降,意味着什么是好、什么是坏?」如果答不上来,这个指标就不该存在。「任务成功率」是一个好例子——但你需要明确什么算「成功」:是 Agent 返回了答案就算成功,还是用户点击了「满意」按钮才算?不同定义的指标数值差异巨大,没有明确定义的指标只会制造噪音。
原则二:区分「观测指标」和「告警指标」。 观测指标可以多,用于事后分析和趋势观察;告警指标必须少而精,每条告警都要有清晰的处置手册。典型的问题是「告警疲劳」:团队把 20 个指标都设了阈值告警,最终所有告警都被忽略。一个实用的标准:每条告警被触发时,接收者应该能回答「我现在要做什么」。从告警到行动的映射,是设计告警的唯一标准。
原则三:在线指标与离线评测互补。 在线指标(如用户满意度、任务成功率)反映真实表现,但数据稀疏、反馈滞后、受外界因素干扰。离线评测(在标注数据集上运行 Agent)数据密集、反馈即时、可控性强,但与真实场景有分布差距。两者必须结合:离线评测用于日常回归和快速迭代,在线指标用于真实效果验证和长尾问题发现。
3.4 Artifacts:中间产物与状态快照
3.4.1 为什么需要记录 Artifacts
Agent 任务的每一步都会产生中间数据:工具返回的 JSON、生成的临时代码、计算的中间结果、修改后的文件内容、查询到的知识片段。这些中间产物在传统 Trace 中通常被忽略或被截断存储,但在 Agent 场景下,它们是理解行为和复现问题的关键证据。
更重要的是,「状态快照」使得确定性回放成为可能。想象一个场景:Agent 在生产环境执行了 20 步后出了问题。你想要回放这个问题,但缺乏第 7 步工具返回的完整 JSON、第 12 步生成的一段代码、第 15 步压缩后保留的上下文摘要。你只能重新运行整个任务,但由于模型非确定性和外部状态变化,Agent 可能走一条完全不同的路径。有了完整的 Artifacts 记录,你就可以从第 7 步开始重放——冻结之前的全部状态,用当时的工具返回结果(而非重新调用工具)喂给模型,观察后续推理是否复现。
3.4.2 Artifacts 的类型
| 类型 | 内容 | 存储方式 | 用途 |
|---|---|---|---|
| 工具输入/输出 | 每个 tool call 的参数和返回 | 对象存储(可截断) | 回放、错误分析 |
| 模型输入/输出 | 完整的 prompt 和 response | 对象存储 | 版本比较、Prompt 优化 |
| 生成代码 | Agent 生成并执行的代码 | 代码版本库 | 安全审计、bug 追踪 |
| 检索片段 | RAG 系统返回的知识片段 | 向量库 + 对象存储 | 分析幻觉来源 |
| 上下文快照 | 每步模型实际看到的上下文 | 对象存储(可截断) | 理解决策、重构上下文 |
| 状态变更 | 数据库写操作的前后值 | 变更日志 | 回滚、副作用审计 |
| 工具 Metadata | 工具版本、环境信息 | 元数据库 | 归因、版本对比 |
3.4.3 状态快照与「Agent 行为的 Git」
「Agent 行为的 Git」这个类比值得展开。Git 让软件开发的每一步变更都可追踪、可回滚、可对比。Agent 可观测性的 Artifacts 层要实现类似能力:
- Every change is recorded:Agent 对环境的每一次修改(写库、改文件、调 API)都要记录修改前后的快照
- Any state can be restored:给定一个任务 ID 和一个步骤号,可以恢复出该步骤时刻的完整环境状态
- Diff is possible:两次运行或两个版本的 Agent 在同一任务上的行为差异可以被自动对比
这是理想状态,实际工程中需要根据数据类型权衡存储成本。一个折中方案是 「分级快照」:
- L0:只记录元数据(工具名、时间戳、哈希),用于快速浏览
- L1: 记录完整数据,但设置 TTL(如 30 天),过期后只保留 L0
- L2: 对敏感数据做脱敏后存储,长期保留
4. 多步推理黑盒的技术拆解
4.1 黑盒的四层结构
当我们说「Agent 是黑盒」时,实际上是在说四个不同层次的不透明叠加在一起。把四个层次拆开,才能找到各自的破解方法。
第一层:模型内部推理不可见
这一层位于最深处:模型在生成输出之前,内部经历的计算过程是我们无法直接观测的。对于普通 LLM,我们只能看到输入和输出;对于 reasoning 模型,我们可以看到模型生成的「思考文本」,但如前所述,这不可完全等同于真实的内部计算。
但重要的是:我们不需要打开这一层。工程上的破解思路是「行为等价替代」——用外部信号(输入、输出、工具调用、耗时、token 分布)来间接推断模型的决策逻辑。这就像我们无法直接观测一个人的大脑活动,但可以通过他的言行来理解他的思考。心理学不打开大脑,同样能建立有用的行为模型;Agent 可观测性不需要打开模型,只需要足够丰富的外部信号。
第二层:工具调用的副作用难以追踪
这一层的黑盒在于:Agent 调用了什么工具、传了什么参数、工具对系统产生了什么影响,这些信息分散在不同的服务日志中,缺乏统一的关联视图。
破解方法相对成熟:在工具调用层做统一拦截。无论是通过 SDK 包装、代理网关还是框架中间件,我们可以在 Agent 框架与工具之间插入一个「观测层」,统一记录每次调用的完整信息。这是当前所有可观测性方案(LangSmith、Langfuse、自建 SDK)都在做的事情,也是技术可行性最高的一层。
第三层:多步之间的依赖关系与状态传递
这一层的黑盒在于:Agent 的第 N 步决策依赖第 1 到 N-1 步的哪些信息?这些信息的传递过程中是否发生了丢失、变形或误解?即使每一步的输入输出都被记录了,我们仍然难以自动回答「这个错误决策是受到了哪个早期步骤的影响」。
破解方法是构建因果图。这里不追求严格的因果推断(那需要反事实干预),而是构建「数据依赖图」:通过记录每一步实际引用了哪些前序步骤的输出字段,我们可以把决策的「影响面」可视化。更进一步,可以用 LLM 辅助分析——把某一步的完整上下文和前序步骤的摘要喂给模型,让它判断「这个决策可能受到了哪些历史信息的影响」。
第四层:失败步骤的「传染效应」
这是最隐蔽的一层。Agent 的错误往往不是孤立发生的,而是一个「小错误被逐步放大」的过程。一个典型的传染链:
破解传染效应的方法是在全链路设置「校验点」。在关键节点(工具返回后、重要决策前、最终输出前)插入自动校验逻辑——可以是一段代码(如 JSON schema 校验),也可以是一次额外的 LLM 调用(如「请检查当前结论是否与历史信息一致」)。这些校验点不仅拦截错误传播,还产生额外的观测数据,为后续分析提供切面。
4.2 不打开模型,如何重构因果链
综合以上四层,Agent 可观测性的方法论可以总结为一句话:不追求打开黑盒,而是用「外部可观测信号」编织一张足够密的网,让决策过程以间接的方式浮现出来。
这张网由四类信号构成:
- 输入输出信号:模型的 prompt 和 response、工具的参数和返回值
- 推理过程信号:模型的 reasoning text、chain-of-thought、tool selection 理由
- 行为信号:调用了什么工具、调用顺序、重试行为、循环模式、任务结果
- 时间与资源信号:每步耗时、token 消耗、成本
当这四类信号被完整记录并关联起来时,「为什么 Agent 这样做」这个问题就从「猜测」变成了「有证据的推理」。你不一定总能得到确定性的答案,但你总能收窄可能性空间——而这正是可观测性的本质:从「不知道发生了什么」到「知道在哪里找答案」。
下面这张图总结了四层黑盒与四类外部信号的映射关系:
| 黑盒层次 | 不可见内容 | 破解信号 | 技术手段 |
|---|---|---|---|
| 模型内部推理 | 隐层计算、注意力分布 | reasoning text、输入输出对比 | 推理模型、prompt 要求逐步解释 |
| 工具副作用 | 跨服务状态变更 | 工具调用拦截、前置/后置快照 | SDK 埋点、代理网关 |
| 步骤间依赖 | 信息引用关系 | 数据依赖边、上下文快照 | causal_edges、上下文 diff |
| 错误传染 | 错误放大路径 | 校验点记录、异常模式 | guardrails、自动校验层 |
5. 主流技术方案与工具选型
5.1 专用 Agent 可观测平台
专用平台的最大优势是开箱即用——它们深度集成了主流 Agent 框架(LangChain、LlamaIndex、AutoGen、CrewAI 等),几行代码就能开始记录 Trace。缺点是生态锁定和扩展性限制,以及数据主权方面的考虑。
LangSmith(LangChain 生态)
LangSmith 是 LangChain 团队打造的端到端 LLM 应用开发与观测平台。它的核心功能围绕「Trace + Eval + Monitoring」三角:
- Trace:自动记录 LangChain/LangGraph 应用的所有中间步骤,包括 LLM 调用、工具调用、retriever 调用。支持 Trace 回放——你可以用历史 Trace 的输入重新运行 Agent 并对比输出差异。
- Eval:内置多种评估器(LLM-as-judge、字符串匹配、结构化对比等),支持在数据集上批量评估 Agent 表现。
- Monitoring:提供在线监控看板、成本分析、延迟追踪,支持按 agent 版本、用户、时间段等多维度切分。
LangSmith 适合已经深度使用 LangChain 生态的团队。它的集成体验最好,但如果你使用非 LangChain 框架,适配成本会显著增加。另外 LangSmith 是 SaaS 服务(有私有化部署选项),数据需要存储在第三方平台,这一点在数据合规要求严格的企业需要仔细评估。
Langfuse
Langfuse 是目前最流行的开源 Agent 可观测平台。它的定位是「面向 LLM 应用的开源可观测性与分析平台」,核心能力包括:
- Trace 与 Span:支持任意 Agent 框架的无侵入式接入,通过 SDK 手动埋点或 OpenTelemetry 自动接入
- 成本追踪:根据模型定价自动计算每次调用的成本,支持成本看板和预算告警
- 评测与 Prompt 管理:内置评测功能,支持 prompt 版本管理和 A/B 对比
- 自托管:完整开源,可以部署在自己的基础设施上,数据完全自主可控
Langfuse 的开源自托管能力使其在企业级场景中非常有吸引力,尤其对于数据不能离开内网的团队。
W&B Weave
Weights & Biases 的 Weave 定位不同——它更偏「ML 实验追踪」而非「生产可观测性」。Weave 的优势在于对模型评估、实验对比、数据集版本管理的一流体验,适合在 Agent 的研发阶段使用:追踪不同 prompt 版本、不同工具配置、不同模型的表现差异。但它在生产环境的实时监控和告警能力相对较弱。
Phoenix(Arize)
Phoenix 是 Arize AI 开源的 LLM 可观测与评估平台。它的特色是强大的语义分析能力:自动聚类 Trace 中的异常模式、用 embedding 检索相似历史 Trace、用 LLM 自动标注 Trace 的质量。如果你需要「从海量 Trace 中自动发现异常模式」的能力,Phoenix 是一个很好的选择。
专用平台的横向对比
| 维度 | LangSmith | Langfuse | W&B Weave | Phoenix |
|---|---|---|---|---|
| 开源 | 否 | 是 | 部分 | 是 |
| 自托管 | 企业版 | 是 | 有限 | 是 |
| Agent 框架集成 | LangChain 深度 | 通用 SDK/OTel | 有限 | 通用 |
| 实时监控告警 | 强 | 强 | 弱 | 中 |
| 评测能力 | 强 | 中 | 强 | 强 |
| 语义分析 | 中 | 中 | 弱 | 强 |
| 成本分析 | 强 | 强 | 中 | 中 |
| 学习曲线 | 低 | 中 | 中 | 中上 |
| 适用阶段 | 全周期 | 全周期 | 研发期 | 分析期 |
选型决策
如果团队规模小、技术栈单一、需要快速上线,选 LangSmith 或 Langfuse 的 SaaS 版本。如果数据合规要求高、需要自托管,选 Langfuse 或 Phoenix。如果团队的核心诉求是「研发阶段的快速迭代和实验对比」,W&B Weave 更合适。如果团队已经有较强的平台工程能力,且需要深度的语义分析和自定义分析,Phoenix 值得认真评估。
5.2 基于 OpenTelemetry 的通用方案
GenAI 语义约定
OpenTelemetry 社区于 2024 年开始推进 GenAI Semantic Conventions 的标准化工作,旨在为 LLM 应用的 Trace 定义统一的属性命名和行为规范。核心约定包括:
gen_ai.system:模型提供方(如openai、anthropic、azure)gen_ai.request.model:请求的模型名称gen_ai.usage.input_tokens/gen_ai.usage.output_tokens:token 使用量gen_ai.response.finish_reason:生成结束原因gen_ai.operation.name:操作类型(chat、text_completion、embeddings 等)
这些约定的意义在于:一旦 LLM 提供商和 Agent 框架纷纷遵循统一约定,企业就可以用同一套 OTel 管道收集、处理、存储 Agent Trace,而无需为每个框架单独开发适配器。
通用方案与专用方案的边界
通用方案的优势:
- 统一管道:Agent Trace 和微服务 Trace 进入同一个存储和查询体系,站在全局视角看「一个请求穿过了微服务和 Agent 的全路径」
- 避免厂商锁定:符合 OTel 标准的产品可以互换
- 团队技能复用:SRE 团队不需要学习新工具
通用方案的劣势:
- 语义维度不足:OTel span 模型是为 RPC 设计的,表达 Agent 的「候选分支」「推理文本」「因果边」等概念比较别扭。虽然可以通过自定义 attribute 变通,但失去了标准化带来的互操作性
- 查询体验落后:通用 APM 平台(如 Grafana、Datadog)的查询语言和 UI 都不是为自然语言推理内容设计的,无法做语义检索
- 发展滞后:GenAI 语义约定仍在演进中,很多关键概念(如 Agent span、推理 token)尚未稳定
实用边界建议:
| 场景 | 推荐方案 |
|---|---|
| Agent 数量少、结构简单(< 5 步/任务) | 直接接入 OTel,在通用平台上观察 |
| Agent 与微服务深度耦合,需要全局视角 | 双轨:OTel 记录基础链路,专用工具记录推理细节 |
| Agent 是核心产品,需要深度分析 | 专用方案为主,OTel 做基础设施层监控 |
| 多框架混合使用 | 优先考虑支持 OTel 的通用方案 |
5.3 自建方案的架构要点
当团队有足够的平台工程能力,且对数据主权、定制化有强需求时,自建是一个值得考虑的路径。自建 Agent 可观测系统的经典架构如下:
埋点层设计
埋点层是自建方案中最关键的部分。它需要在 Agent 框架和 LLM/工具体之间插入「观测切面」。根据你使用的框架不同,有以下几种实现方式:
- 装饰器模式(LangChain/LlamaIndex):在工具函数上添加
@observe装饰器,自动记录调用的输入输出。这是最简洁的方式。 - 回调系统(LangChain 的
BaseCallbackHandler):注册全局回调,拦截所有 LLM 调用和工具调用。侵入性最低。 - 中间件层(自研 Agent 框架):在 Agent 执行循环中嵌入观测逻辑。最灵活,但需要自己设计。
- 代理网关(适用于多框架或外部 Agent):在 LLM API 和工具体之前部署反向代理,统一拦截所有流量。最通用,但需要处理协议兼容性。
下面是一个基于装饰器模式的埋点示例:
from functools import wraps
import uuid
import time
import json
class AgentObserver:
def __init__(self, queue_client):
self.queue = queue_client
self._active_spans = {}
def observe_tool(self, tool_name: str, tool_version: str):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
span_id = f"span_{uuid.uuid4().hex[:16]}"
parent_id = self._current_parent()
span = {
"span_id": span_id,
"parent_span_id": parent_id,
"span_type": "tool.call",
"tool_name": tool_name,
"tool_version": tool_version,
"input": {"args": args, "kwargs": kwargs},
"started_at": time.time(),
}
self._push_span(span_id)
try:
result = func(*args, **kwargs)
span["output"] = result
span["status"] = "success"
return result
except Exception as e:
span["status"] = "error"
span["error"] = {"type": type(e).__name__, "message": str(e)}
raise
finally:
span["ended_at"] = time.time()
span["latency_ms"] = (span["ended_at"] - span["started_at"]) * 1000
self._pop_span()
self._emit(span)
return wrapper
return decorator
def observe_llm(self):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
span_id = f"span_{uuid.uuid4().hex[:16]}"
parent_id = self._current_parent()
span = {
"span_id": span_id,
"parent_span_id": parent_id,
"span_type": "llm.call",
"model": kwargs.get("model", "unknown"),
"input": {"prompt": args[0] if args else kwargs.get("prompt")},
"started_at": time.time(),
}
self._push_span(span_id)
try:
result = func(*args, **kwargs)
span["output"] = result
span["token_usage"] = result.get("usage", {}) if isinstance(result, dict) else {}
span["status"] = "success"
return result
except Exception as e:
span["status"] = "error"
span["error"] = str(e)
raise
finally:
span["ended_at"] = time.time()
span["latency_ms"] = (span["ended_at"] - span["started_at"]) * 1000
self._pop_span()
self._emit(span)
return wrapper
return decorator
def _push_span(self, span_id):
from contextvars import ContextVar
self._active_spans.set(span_id)
def _current_parent(self):
return self._active_spans.get(None)
存储层设计
自建方案的存储层通常需要三类存储协作:
时序库(ClickHouse / InfluxDB):存储聚合指标和事件流。每条 Trace 的每个 span 都是一条带时间戳的记录,适合按时间范围做聚合查询。ClickHouse 在大量 append-only 写入和高基数列上的表现优异,是大多数自建方案的首选。
向量库(pgvector / Milvus / Qdrant):存储 reasoning text 和自然语言日志的 embedding,支持语义检索。选择向量库时需要考虑:嵌入维度、索引性能、是否支持混合查询(向量 + 结构化过滤)、与现有数据栈的集成。
对象存储(S3 / MinIO):存储大体积的 Artifacts(工具返回的完整 JSON、生成代码、上下文快照)。在 Trace 记录中只保存对象存储的 URL 或引用 ID,避免大对象压垮时序库。
查询层设计
查询层需要支持三类查询模式:
- 结构化查询:按 task_id、agent 版本、工具名、时间范围、状态码等字段过滤
- 语义查询:用自然语言描述「我想要的 Trace 特征」,检索相似记录
- 图查询:沿着因果边和父子关系遍历决策树,回答「谁影响了这个决策」
# 结构化查询示例:找出所有包含异常工具选择的任务
SELECT trace_id, task_id, span_id, structured->>'tool_selected' AS tool
FROM agent_logs
WHERE event_type = 'agent.decision'
AND structured->>'decision_type' = 'tool_choice_override'
AND timestamp BETWEEN '2026-08-18T00:00:00Z' AND '2026-08-19T00:00:00Z'
ORDER BY timestamp DESC
LIMIT 100;
6. 关键指标的工程化设计
6.1 从问题出发,而非从数据出发
可观测性领域最常犯的错误是「指标堆积症」:团队把能想到的全部指标都接入监控系统,创建了上百个看板和几十条告警规则,最终没有人看任何一块面板。指标的价值不在于数量,而在于它能回答的问题。
在设计指标体系之前,先让团队写下「我们最需要回答的 10 个问题」。以下是一个实际团队的问题清单示例:
- 我们的 Agent 任务成功率是多少?在下降吗?
- 失败任务的最常见原因是什么?
- 哪个工具调用最容易出问题?
- 不同模型的成本差异有多大?值得切换到更贵的模型吗?
- 哪些任务的步数异常多?是不是陷入了无效循环?
- Agent 有没有执行过超出预期的敏感操作?
- 用户对 Agent 输出的满意度趋势如何?
- 提示注入攻击被成功拦截了吗?
- 上下文截断对任务成功率的影响有多大?
- 上一个版本和新版本相比,表现更好还是更差?
然后,为每个问题映射到指标和查询。最后,只在「问题需要持续关注」的指标上设置告警。
6.2 评估集的构建与版本化
指标的计算依赖于「什么是对的」。对于系统指标(如延迟、错误率),正确性是客观的。但对于行为指标(如工具选择准确率、任务成功率),「对错」需要标注。这就是评估集(Eval Set)的价值。
构建评估集的几个关键原则:
从真实流量中采样,而非人工构造。 人工编写的测试用例虽然可控,但分布往往与真实用户需求偏离。最有效的评估集来源于真实的生产任务——从线上 Trace 中采样,经过人工标注后固化为回归用例。
标注要「可判定」。 一个任务的「成功」应该有明确的判定标准:可以是用户反馈、可以是结构化校验、可以是人工审核。模糊的判定标准会让评估结果失去意义。
评估集是版本控制的资产。 随着 Agent 能力演进,旧的评估集可能过时。评估集需要版本化,支持「在某版本评估集上运行某版本 Agent」的矩阵式对比。
6.3 成本指标的显性化
Agent 成本是许多团队忽视的维度。单次 LLM 调用的成本看起来微不足道(几美分),但乘以每天百万级的调用量,乘以无效重试和长链路带来的浪费,成本会变得不可忽视。
成本指标的设计关键是归因:
- 哪些 Agent 消耗了最多的 token?
- 哪些任务类型的平均成本最高?
- 无效步(调用后未使用的工具、重试失败的循环)消耗了多少成本?
- 不同模型的性价比如何?
一个实用的成本分析查询是计算「每成功任务的成本」而不是「每任务的成本」。假设 Agent A 的任务成功率是 60%,每任务平均成本 0.1 元,那么每成功任务的成本是 0.1 / 0.6 ≈ 0.167 元。Agent B 的成功率是 80%,每任务平均成本 0.12 元,则每成功任务的成本是 0.15 元。Agent B 虽然单次更贵,但每成功交付的成本反而更低。这种分析帮助团队做出正确的技术选型。
6.4 指标可信度:离线评测与线上表现的分野
一个常见的陷阱是:Agent 在离线评测集中表现良好,上线后却频繁出问题。原因在于分布偏移——评估集的分布与真实流量的分布不一致。
应对策略是线上采样回放:定期(如每天)从生产 Trace 中采样一定比例的任务,在隔离环境中回放,并与当时的实际行为进行对比。这样既能持续更新评估集,又能检测「线上表现漂移」。
另一个策略是影子模式:新版本 Agent 与旧版本并行运行,新版本的结果不直接输出给用户,而是对比两者的行为差异。当差异在可接受范围内时,再逐步切换流量。
7. 实战案例:一次任务失败的完整排查
7.1 案例背景
某电商平台部署了一个「数据分析 Agent」,供运营团队使用。用户用自然语言提问(如「过去 30 天各地区的销售额分布」),Agent 自动选择合适的数据分析工具(SQL 查询、图表生成、趋势分析),执行分析并返回结论。
上线三个月后,运营团队反馈:「Agent 偶尔会返回错误的结论,而且错误的方式很诡异——不是计算错误,而是把数据正常但逻辑偏了,比如把环比增长说成同比下降。」
7.2 第一轮排查:从 Metrics 入手的初步观察
值班工程师打开了监控面板,首先查看系统健康度指标。结果令人困惑:
| 指标 | 当前值 | 历史均值 | 状态 |
|---|---|---|---|
| LLM 调用成功率 | 99.2% | 99.1% | 正常 |
| 工具调用失败率 | 1.8% | 2.1% | 正常 |
| 平均延迟 | 32s | 35s | 正常 |
| 任务成功率(按用户反馈) | 87% | 92% | 偏低 |
工具调用失败率正常、系统健康指标正常,但任务成功率下降。这说明问题不在系统层面,而在行为层面。工程师转向行为指标:
| 指标 | 当前值 | 历史均值 | 状态 |
|---|---|---|---|
| 工具选择准确率(抽样标注) | 85% | 90% | 偏低 |
| 无效循环率 | 4.2% | 2.8% | 偏高 |
| 幻幻觉率(抽查) | 3.5% | 1.2% | 显著偏高 |
幻觉率显著偏高,指向 Agent 可能在输出中虚构了数据。
7.3 第二轮排查:下钻 Trace 定位问题
工程师筛选了用户反馈为负面的任务,查看它们的 Trace。其中一个任务的过程如下:
Trace 显示:步骤 4 的工具返回了 150 行数据,而步骤 7 的上下文压缩组件将数据截断到了前 20 行。步骤 9 的推理文本显示,模型明确知道「数据已被截断」,但仍然基于前 20 行推断出了整体趋势——而前 20 行的趋势恰好与整体趋势相反。
7.4 第三轮排查:复盘根因
进一步检查 Trace 中的 Artifacts 发现:
- 工具
sql_query返回了 150 行数据,总大小约 58KB - Agent 的上下文压缩策略规定:当工具返回超过 45KB 时,保留前 30% 并截断其余
- 截断操作没有在返回数据中标记「已截断」和「截断策略」
- 模型在步骤 9 的推理文本中写道:「数据可能不完整,但前 20 行的分布看起来足够代表整体」
- 模型错误地假设了「前 20 行是随机采样」,而实际上 SQL 查询按销售额降序排列,前 20 行全部是销售额最高的地区
根因:上下文压缩截断了数据但没有保留「截断元数据」,导致模型无法判断被截断数据的代表性和排序特征,做出了错误的统计推断。深层次原因是:压缩策略的设计没有考虑到「截断可能引入系统性偏差」,而模型的「自信幻觉」让它把不完整的证据当作了充分的证据。
7.5 修复与验证
团队实施了三层修复:
修复一:截断策略升级。 不再简单保留前 N 行,而是在截断时增加元数据标记:
{
"truncated": true,
"original_row_count": 150,
"retained_row_count": 20,
"truncation_strategy": "sorted_by_sales_desc",
"retained_rows_bias": "top_performers_only",
"warning": "当前数据按销售额降序排列,保留行只覆盖头部区域,不可用于整体趋势推断"
}
修复二:增加中间校验步骤。 在「数据解读」步骤之后新增一个「统计检验」步骤,由独立的 LLM 调用执行,检查推理文本中是否有基于不完整数据做出全量推断的情况。
修复三:历史 Trace 回放验证。 利用平台的回放能力,将出现问题的历史任务重新运行,验证修复后的 Agent 能否正确识别截断偏差并调整输出。
修复后,任务成功率恢复至 94%,幻觉率降至 0.8%。
7.6 沉淀经验
这次排查的经验被固化为三条新的观测规则:
- 规则 1:当
truncated=true且后续 span 使用了被截断数据时,自动标记anomaly_flags=["truncated_data_inference"] - 规则 2:当推理文本中出现「数据不完整」「可能」「推断」等词且后续输出为确定性结论时,标记
anomaly_flags=["uncertainty_to_certainty"] - 规则 3:新增评估集条目:所有涉及「截断数据解读」的历史任务,标注为高优先级回归用例
这个案例的完整链路展示了一个关键事实:行为层面的错误只能通过行为层面的可观测性来发现和诊断。系统指标一切正常,传统告警不会触发,但用户已经感受到了伤害。没有完整的 Trace 记录,这个问题的根因可能永远无法被定位。
8. 可观测性对 Agent 研发流程的重塑
8.1 从「调 Prompt → 看结果」到「基于 Trace 数据分析 Prompt」
传统 LLM 应用开发的迭代循环是:写 Prompt → 跑几个测试 → 不满意 → 修改 Prompt → 再跑。这个过程高度依赖开发者的直觉和经验。有了完整的 Trace 数据,Prompt 优化可以从「直觉驱动」转变为「数据驱动」。
具体方法:
找出高频失败模式。 用 LLM 辅助分析或人工抽样,对失败 Trace 做聚类,提炼出最常见的失败原因。比如「工具返回格式变化导致误读」「上下文过长导致信息丢失」「相似工具混淆」。
定位失败的 Prompt 环节。 把失败归因到 Prompt 的具体部分:是 planner 部分的工具描述不清?是指令中的约束冲突?还是 few-shot 示例与真实场景偏差过大?
用真实 Trace 替换手工示例。 从成功和失败的 Trace 中提炼 few-shot 示例,把它们加入 Prompt。一个「曾经失败的案例 + 正确的处理方式」的 few-shot,胜过开发者绞尽脑汁构造的十条规则。
A/B 对比量化 Prompt 修改效果。 在同一评估集上运行 Prompt 版本 A 和 B,用指标(准确率、步数、成本)量化差异,而不是靠「感觉更好了」。
8.2 可观测性作为运行时护栏
随着 Agent 能力的增强,仅靠事前约束(system prompt 中的规则)已经不够。Agent 可能在任何时候生成不安全的操作——删除重要数据、执行未授权的 API 调用、输出敏感信息。可观测性基础设施可以进化为运行时护栏:
规则型护栏:在关键路径上设置确定性校验。例如「任何写操作必须经过审批」「任何金额超过 1000 元的操作必须转人工」「输出中包含身份证号时自动脱敏」。
LLM 型护栏:用另一个 LLM 调用(或同一个 LLM 的独立调用)审查 Agent 的计划或输出。例如:「请判断以下操作计划是否超出了用户的请求范围,如果是,请列出原因。」
护栏与可观测性的关系:护栏的每次触发都是一条高价值观测数据。通过分析护栏触发率和触发原因,团队可以持续调整 Agent 的安全策略和工具权限设计。
8.3 协作模式的改变
在传统微服务团队中,可观测性数据的消费者主要是 SRE 和后端工程师。在 Agent 团队中,可观测性数据的消费者拓展到了:
- Prompt 工程师:需要 Trace 来理解模型行为、提炼 few-shot 示例
- 产品经理:需要看「行为指标」来评估 Agent 的任务成功率、用户满意度
- 安全团队:需要审计 Agent 的敏感操作和越权尝试
- 管理层:需要成本指标和 ROI 分析
- 数据标注团队:需要 Trace 来为评估集挑选和标注样本
这意味着可观测性平台不再是运维团队的专属工具,而是整个 Agent 团队的协作平台。一个好的 Agent 可观测平台应该能服务这些不同角色的差异化需求。
8.4 安全与合规
Agent 执行的操作可能触发安全与合规问题:处理用户敏感信息、执行财务操作、访问受限数据。可观测性体系在合规方面的作用是:
完整审计日志:谁(哪个用户)触发了 Agent、Agent 做了什么、访问了什么数据、产生了什么结果、被谁审批过。这条链路需要完整记录,满足审计要求。
敏感操作的追踪:删除、写入、支付、导出等敏感操作必须有独立的审计记录,包括操作前的上下文、审批链条、操作结果。
责任归属:当 Agent 的行为造成损失时,「谁负责」成为一个法律和管理问题。可观测性数据是厘清责任的基础——它明确记录了模型决策、工具执行、人工审批各自的角色和时间线。
9. 落地路线图与最佳实践
9.1 四阶段路线图
可观测性体系的建设不宜追求一步到位,而应分阶段推进:
阶段一:基础 Trace 覆盖(2-4 周)
目标:确保每个 Agent 任务的完整执行路径可被记录和查看。
关键动作:
- 选择入地方案(LangSmith / Langfuse / 自建 SDK)
- 在 LLM 调用和工具调用处埋点
- 确保 Trace 包含:输入、输出、模型版本、token 消耗、耗时、状态
- 建立「Trace ID 与任务 ID 的映射」方便用户反馈关联
产出:团队能回答「这个任务发生了什么」的基本问题。
阶段二:指标看板与基础告警(2-3 周)
目标:从「事后翻 Trace」升级为「主动发现问题」。
关键动作:
- 建立核心健康度指标面板(LLM 调用成功率、延迟、工具调用成功率)
- 建立成本指标面板(每任务 token、每任务成本)
- 设置少量高质量告警(如任务成功率显著下降、异常的错误率飙升)
- 建立「用户反馈 → Trace 关联」的闭环
产出:问题能在用户大规模感知之前被团队发现。
阶段三:评测回放与失败模式沉淀(4-8 周)
目标:从「发现问题」升级为「系统化地理解问题和防止问题复发」。
关键动作:
- 从线上流量采样构建评估集
- 建立标注流程(人工标注 + LLM 辅助标注)
- 引入 Trace 回放能力
- 对失败 Trace 做模式聚类,沉淀「失败模式库」
- 为每种失败模式建立回归用例
产出:每一次修复都有回归保障,历史故障模式可被系统性识别。
阶段四:自动化与智能诊断(持续)
目标:从「人看 Trace」升级为「系统看 Trace」。
关键动作:
- 自动异常检测(基于历史基线的行为偏差检测)
- LLM 辅助的 Trace 自动分析(自动总结失败原因、生成修复建议)
- 护栏策略自动化(根据护栏触发统计自动调整阈值和规则)
- 成本自动优化(自动识别低效模式、推荐 prompt 或工具调整)
产出:可观测性从被动工具变为主动系统。
9.2 常见误区与避坑指南
误区一:只记录最终结果,不记录中间过程。 这是最常见的错误。有些团队为了简单,只记录了「Agent 收到什么、输出什么」,中间推理一概不记。结果是出了问题后,只能重新猜测 Agent 的内部过程。中间过程的 Trace 是 Agent 可观测性的核心价值,省掉它就是省掉了整个体系的意义。
误区二:指标过多导致告警疲劳。 可观测性不是指标越多越好。每一个指标都要经过「它回答什么问题」「它触发的告警对应什么行动」的灵魂拷问。如果一个指标的告警触发后你不知道该做什么,这个指标就不该有告警。
误区三:把可观测性当成上线前的「一次性甩尾工作」。 可观测性不是「上线前装个监控就完事」的任务。Agent 在持续进化,新的失败模式不断出现,评估集需要持续更新,护栏规则需要动态调整。可观测性体系的建设是一个与 Agent 产品同步演进的持续过程。
误区四:忽略成本维度的观测。 在 Demo 阶段,Agent 的 token 消耗看起来不值一提。但当 Agent 跑上成千上万次任务、每次任务几十步、每步上千 token 时,成本会迅速累积。没有成本观测,团队无法做出「用哪个模型更划算」「prompt 怎么优化省钱」的明智决策。
误区五:把推理日志当作普通日志处理。 把 Agent 的自然语言推理直接塞进传统日志系统,然后用关键词检索——这是把可观测性的核心价值(语义理解能力)给丢掉了。推理日志需要配套语义检索能力(向量检索或 LLM 辅助分析),否则它只是一堆「能存不能查」的文本。
9.3 从今天开始的最小行动清单
如果你现在就有一个 Agent 在生产环境中运行,且完全没有任何可观测性建设,以下是最小行动清单,按优先级排列:
- 今天:在每次 LLM 调用前后记录
{model, input, output, tokens, latency, timestamp}到结构化日志 - 本周:为每个工具调用添加记录
{tool_name, input, output, error, latency} - 下周:设计并实现一个「任务级」的 trace_id 贯穿整个任务生命周期
- 两周内:在监控系统(Grafana/Datadog/自建)中建立首个面板:LLM 调用成功率、平均延迟、token 消耗趋势
- 一个月内:从真实流量中采样 100 条任务,人工标注成功/失败,计算首个真实的任务成功率
10. 总结与展望
10.1 核心观点重申
本文从「Agent 黑盒」问题出发,系统性地拆解了 AI Agent 可观测性的技术全景。核心观点可以浓缩为三句话:
第一,Agent 的可靠性建立在「看得见」的基础上。 多步推理、工具调用、上下文累积的三重复杂性叠加,使得 Agent 的行为无法通过传统的监控手段理解。没有完整的 Trace 记录,Agent 的故障排查只能依靠猜测和运气。
第二,可观测性的价值远超「事后排查」。 它同时服务于事前的 Prompt 优化和评测、事中的运行时护栏和安全拦截、事后的故障定位和根因分析。可观测性不是附属工具,而是 Agent 产品研发和运维的核心基础设施。
第三,建设可观测性体系有明确的方法论。 四个观测维度(Trace / Log / Metrics / Artifacts)、四层指标框架(健康度 / 质量 / 效率 / 安全)、四阶段落地路线(覆盖 / 看板 / 评测 / 自动化)——这些框架提供了从零到一的清晰路径,团队不需要重复造轮子。
10.2 三大趋势展望
趋势一:Agent 可观测性与模型评测的边界越来越模糊。 过去,可观测性(observability)和评测(evaluation)是两个独立的领域。前者关注「系统表现如何」,后者关注「模型能力如何」。但在 Agent 场景下,两者正在融合:评测需要基于真实 Trace 数据来构建评估集,可观测性需要评测能力来自动判断 Trace 的质量。未来的 Agent 平台将模糊这两者的边界,提供「观测-评测-优化」的一体化体验。
趋势二:从被动诊断走向主动干预。 可观测性系统正在从「记录和展示」进化为「理解和行动」。自动异常检测、LLM 辅助的故障分析、实时护栏触发、自动回滚——这些能力让可观测性从「事后验尸」进化为「事前预防」和「事中干预」。可观测性正在成为 Agent 运行时的一部分,而不仅仅是观察它的外部工具。
趋势三:面向多 Agent 协作的可观测性将升级为「全局行为审计」。 当多个 Agent 相互协作、互相调用时,单个 Agent 的 Trace 已经不足以理解系统行为。我们需要追踪「Agent 之间如何协商、如何传递任务、如何共享状态、如何解决冲突」。这需要新的数据模型和分析方法,可观测性将演化为对 Agent 社会的「全局行为审计」能力。
10.3 最后的行动建议
无论你的 Agent 项目处于哪个阶段——Demo、内测、灰度还是大规模生产——今天就开始记录第一条完整的 Agent Trace。不需要完美的方案,不需要复杂的架构,只需要在 LLM 调用和工具调用处加上最基本的结构化记录。
因为 Agent 的每一次「独立思考」,都可能在未来的某一天变成一个需要被理解的谜题。而谜题的答案,从来不在黑盒里面,而在你记录的数据中。
看得见,才管得住;管得住,才信得过。
更多推荐


所有评论(0)