1. 引言:为什么 AI Agent 需要可观测性

过去两年,基于大语言模型(LLM)的应用开发经历了从“单轮问答”到“Agent”的范式跃迁。一个典型的 AI Agent 不再是简单的“输入—输出”映射,而是在一次用户请求中串联起理解意图、制定计划、调用工具、观察结果、反思纠错、形成最终答复等多个步骤。每一步都依赖模型推理,每一步都可能因为模型幻觉、工具异常、上下文污染或规划偏差而走向错误的方向。

这种多步推理(Multi-step Reasoning)能力赋予了 Agent 前所未有的灵活性,但也把它变成了一个运行时的“黑盒”。传统软件系统的可观测性建立在确定性的控制流之上:一次数据库查询要么成功要么失败,一次 RPC 调用有明确的入口与出口,错误堆栈能告诉我们问题发生在哪一行代码。而 AI Agent 的执行路径是非确定性的——同一个问题,模型可能选择完全不同的工具组合;同一个工具调用,不同的上下文可能产生截然不同的结果;一个看似正常的最终答案,可能隐藏着中间步骤的逻辑跳跃或事实错误。

黑盒带来的四大挑战:

  • 错误难以归因:当一个 Agent 输出错误答案时,是模型推理错了、Prompt 设计不当、工具返回了脏数据、还是记忆系统污染了上下文?没有过程数据,调试只能靠猜。
  • 性能瓶颈隐匿:一次 Agent 请求可能触发数十次 LLM 调用和工具调用,Token 消耗、首字延迟、端到端耗时都分散在各环节。不观测就无从优化。
  • 成本失控风险:Agent 的 Token 消耗量通常是单轮问答的数倍甚至数十倍。没有细粒度的成本归因,团队很难定位哪条 Prompt 或哪个工具调用在“烧钱”。
  • 安全与合规盲区:Agent 会访问数据库、调用外部 API、读取用户隐私数据,这些操作如果是透明的,将无法满足审计与合规要求。

可观测性(Observability)正是破解这一黑盒的关键工程手段。它不是简单的日志堆积,而是一套系统性的方法论,帮助团队在生产环境中观察、理解、诊断和优化 Agent 的行为。本文将从理论到实战,系统性地介绍 AI Agent 可观测性的数据模型、技术架构、落地方案与最佳实践,并以 LangChain/LangGraph 为例给出可运行的工程方案。

可观测性系统

一次 Agent 请求的执行过程

推理事件

工具调用

反思事件

用户输入

意图理解

计划生成

工具调用 1

观察结果

反思与重规划

工具调用 2

最终答复

事件采集

Span/Trace 建模

日志聚合

指标计算

可视化大盘

告警与审计

本文的组织思路:第 2 节剖析多步推理黑盒的问题本质;第 3 节介绍可观测性三大支柱;第 4 节深入统一数据模型;第 5 节定义 Agent 的核心观测数据;第 6 节给出生产级观测系统的技术架构;第 7~8 节通过 OpenTelemetry、LangChain、LangGraph 展示完整落地代码;第 9~13 节分别讨论查询分析、可视化告警、性能成本优化、质量评估、安全合规与多 Agent 观测;第 15~17 节总结工程化最佳实践与未来趋势。

2. AI Agent 的多步推理黑盒:问题本质

2.1 从“单轮问答”到“自主决策”的跨越

单轮问答系统(如早期的 ChatGPT 封装应用)的执行链路非常简单:用户提问 → LLM 推理 → 返回答案。整条链路中唯一的“不确定环节”是模型推理,而推理过程可以部分通过 Prompt、模型参数与输出日志来复盘。此时的可观测性需求相对有限,关注点主要是:

  • 请求延迟与吞吐量;
  • Token 消耗与成本;
  • 回答质量与用户反馈。

但当系统升级为 Agent 后,情况发生了质变。一个 ReAct 风格的 Agent 会在一次会话中循环执行“思考—行动—观察”三个步骤:

用户问题
  └─> 思考(模型推理下一步做什么)
        └─> 行动(调用某个工具或检索知识库)
              └─> 观察(获取工具返回结果)
                    └─> 思考(基于结果决定继续还是结束)
                          └─> ... 循环直到输出最终答案

这个循环的数量不确定、路径不确定、每一步的结果也不确定。更复杂的是,现代 Agent 框架还引入了状态管理(state)、记忆系统(memory)、子图/多 Agent 编排(subgraphs/multi-agent)、人工介入(human-in-the-loop)等机制,让执行路径变得更加复杂。

2.2 黑盒的三个层次

Agent 的“黑盒”问题实际上可以拆解为三个层次,每个层次对应不同的观测需求:

第一层:模型黑盒(Model Black Box)。LLM 本身是一个高维参数空间中的函数映射,我们无法像调试普通代码那样“下断点”查看模型内部状态。但我们可以观测模型的输入(Prompt)与输出(Completion),记录模型推理的显式思考过程(如 Chain-of-Thought 的输出),并在一定程度上去交叉验证其正确性。

第二层:编排黑盒(Orchestration Black Box)。Agent 框架在模型之上构建了调度层:何时调用哪个工具、循环何时终止、状态如何流转。这个编排逻辑部分来自代码,部分来自模型决策,两者的边界往往很模糊。如果不记录编排事件,开发者很难区分“模型做错了决策”与“框架把正确的决策执行错了”。

第三层:环境黑盒(Environment Black Box)。Agent 与外部世界的交互(数据库查询、API 调用、文件读写、代码执行)构成了执行环境。环境返回的结果可能包含异常、脏数据、超时或不完整信息,这些又会反过来影响模型后续的推理。环境交互的不可观测会让错误在链路上传播并被放大。

2.3 一个典型的排障困境

设想一个生产环境中的客服 Agent,用户询问“我的订单为什么还没有发货”。Agent 执行了以下步骤:

  1. 从用户输入中提取订单号 #123456
  2. 调用订单查询工具 get_order_status
  3. 工具返回该订单状态为“已发货”,但没有物流跟踪号;
  4. 模型看到结果后,判断“信息不足”,转而调用物流查询工具 get_shipping_info
  5. 物流工具返回“订单号不存在”;
  6. 模型最终回复用户:“您的订单可能尚未进入物流系统,请稍后再查询。”

最终用户收到的是一个模棱两可的答案,但问题究竟出在哪?可能的原因包括:

  • 步骤 2 的工具参数错误:模型提取订单号时多带了一个空格,导致订单查询失败却返回了默认状态;
  • 步骤 4 的工具选择错误:模型本应调用 get_shipping_info_by_phone 而不是 get_shipping_info
  • 步骤 5 的工具接口异常:物流系统临时超时返回了非标准错误信息;
  • 步骤 6 的推理问题:模型本可以基于已有信息推断“已发货但无跟踪号”属于配送商透传延迟,却选择了保守表述。

如果没有可观测性,开发者只能看到最终的问答对,无法定位链条上的断点。有了完整的 Trace 数据,团队可以在秒级内定位到问题出在步骤 5 的工具调用,并进一步发现是物流接口的异常返回格式导致了模型的错误判断。

可观测性系统 物流查询工具 订单查询工具 LLM Agent 主循环 用户 可观测性系统 物流查询工具 订单查询工具 LLM Agent 主循环 用户 为什么订单还没发货? 1 规划步骤 2 调用 get_order_status( 3 记录 span[plan] 4 get_order_status( 5 {"status":"shipped"} 6 记录 span[tool_call_1] 7 基于结果反思 8 调用 get_shipping_info( 9 记录 span[reflect] 10 get_shipping_info( 11 {"error":"order not found"} 12 记录 span[tool_call_2] 13 生成最终答复 14 模棱两可的答案 15 请稍后再查询 16 记录 span[final_answer] 17

2.4 可观测性要解决的核心问题

综合以上分析,AI Agent 可观测性需要回答四个核心问题:

问题 观测手段 典型场景
发生了什么? 事件日志、Trace 用户请求后,Agent 具体执行了哪些步骤
为什么发生? 思维链记录、工具输入输出 模型为什么选择了某个工具,某个步骤为什么失败
效率如何? 耗时分布、Token 统计、成本归因 哪些步骤最慢、哪些工具最烧钱
表现如何? 成功率、准确率、用户反馈 某个版本的 Agent 相比上个版本是否更稳定

只有同时回答了这四个问题,团队才算真正“看见”了 Agent 的内部运行情况。

3. 可观测性三大支柱:日志、指标与链路追踪

经典的软件可观测性建立在三个支柱之上:日志(Logging)、指标(Metrics)和链路追踪(Tracing)。AI Agent 的可观测性同样需要这三大支柱,但每根支柱的内涵都因 LLM 与 Agent 的特殊性而有所扩展。

3.1 日志:完整记录 Agent 的推理细节

日志是最原始、最直接的可观测性手段。对于 Agent 系统,日志需要覆盖三个层次:

应用层日志:Agent 服务的启动配置、请求接入、异常堆栈、框架版本、依赖资源状态等。这类日志帮助团队快速定位环境级问题,与普通后端服务的日志没有本质区别。

Agent 运行层日志:记录一次 Agent 执行中的关键事件,包括:

  • 用户原始输入(注意脱敏);
  • 每一步的 Prompt 模板与渲染后的完整 Prompt;
  • LLM 的原始输出(包括思考链、工具调用指令、最终文本);
  • 每个工具的名称、输入参数(脱敏后)、返回结果、耗时、状态码;
  • 状态更新、记忆读写、上下文裁剪的触发与内容变化;
  • 循环终止条件、最大迭代次数触发、异常重试。

模型交互层日志:记录与具体模型提供方的 API 交互,包括模型名称、版本、采样参数(temperature、top_p、max_tokens 等)、流式请求的起止、速率限制重试等。这层日志对成本核算与供应商对比非常重要。

一个值得强调的实践是结构化日志。Agent 的日志不应是一串非结构化文本,而应是带有固定字段的 JSON 对象,这样才方便后续检索、聚合与关联。以下是一条结构化日志的示例:

{
  "timestamp": "2026-08-16T00:30:00.123Z",
  "trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
  "span_id": "0f4f9be1acbb4d2a",
  "event_type": "tool_call",
  "service": "order-agent",
  "session_id": "sess_987654",
  "tool": {
    "name": "get_order_status",
    "args": {"order_id": "123456"},
    "status": "success",
    "latency_ms": 235,
    "response_size": 128
  },
  "llm": {
    "model": "gpt-4.1",
    "prompt_tokens": 1842,
    "completion_tokens": 96
  },
  "level": "INFO"
}

3.2 指标:量化 Agent 的健康度与效率

指标是对日志数据的聚合与抽象,回答“系统整体状态如何”的问题。对于 Agent 系统,指标可以分为四类:

服务质量指标(Golden Signals)

  • 请求量:每分钟/每小时的 Agent 请求数;
  • 成功率:Agent 最终给出有效答复的比例(需要定义“有效”的标准,如无异常终止、用户未点踩);
  • 端到端延迟:P50/P95/P99 的请求完成时间;
  • 首字延迟(Time to First Token, TTFT):用户从发起请求到收到第一个输出 token 的时间;
  • 会话时长与迭代步数:每个任务平均执行多少步、多少轮迭代。

模型调用指标

  • Token 消耗:输入 Token、输出 Token、总 Token,按模型、按工具、按用户维度聚合;
  • 单次调用成本:结合各模型单价计算得出;
  • 模型调用次数分布:一次 Agent 请求触发了多少次 LLM 调用;
  • 模型错误率:限流、超时、内容过滤等错误的比例。

工具调用指标

  • 工具调用成功率:某个工具被调用成功的比例;
  • 工具平均耗时与超时率;
  • 工具调用链路的依赖关系与失败传播路径;
  • 工具输入输出的数据量统计。

业务指标

  • 任务完成率:Agent 是否真正解决了用户的问题(需要通过显式信号或后置评估判断);
  • 人工接手率(Escalation Rate):多少会话需要转接人工;
  • 用户满意度评分(CSAT):收集用户反馈并进行关联分析;
  • 幻觉率:通过事实核查或人工抽检统计 Agent 输出中事实性错误的比例。

指标的存储通常采用时序数据库,常用技术栈包括 Prometheus + Grafana、InfluxDB、Datadog、VictoriaMetrics 等。设计指标时要注意高基数问题:不要为每个 trace_id 或 session_id 都生成一张独立的指标序列,否则时序数据库会迅速膨胀。

3.3 链路追踪:还原单次会话的完整推理轨迹

链路追踪回答“这一次请求具体是怎么走的”。在传统微服务架构中,Trace 由一组 Span 组成,每个 Span 代表一次 RPC 调用或内部操作,Span 之间通过父子关系构成一棵调用树。AI Agent 的 Trace 在概念上类似,但 Span 的语义更加丰富:

  • Root Span:一次用户请求的完整生命周期;
  • LLM Span:一次 LLM 推理调用,记录模型、Prompt、输出、Token 统计;
  • Tool Span:一次工具调用,记录工具名、参数、结果、耗时;
  • Retriever Span:一次向量检索,记录查询文本、召回文档、相似度分数;
  • Agent Step Span:一个 ReAct 循环中的“思考/行动/观察”步骤;
  • State Span:状态读写或记忆操作。
Root Span: /agent/execute (2.8s)
├── LLM Span: chat_completion (1.2s)   # 意图理解
│     ├── attributes: model=gpt-4.1, prompt_tokens=1200, completion_tokens=80
├── Tool Span: database.query (0.4s)   # 订单查询
│     ├── attributes: stmt_type=SELECT, rows=1
├── LLM Span: chat_completion (0.9s)   # 反思与重规划
├── Tool Span: http.get (0.2s)         # 物流接口
└── LLM Span: chat_completion (0.1s)   # 最终答复

链路追踪的价值在于关联。它把一次会话中的所有日志、指标事件串联起来,使得开发者可以沿着 Trace 树从根节点一路下钻到问题发生的具体 Span,查看该 Span 关联的日志与属性。同时,Trace 数据也是后续构建可视化瀑布图、依赖拓扑图的基础。

3.4 三大支柱的协同关系

三大支柱不是互相替代的关系,而是相互补充、层层递进的关系:

维度 日志 指标 链路追踪
粒度 最细(单事件) 聚合(时间序列) 单次请求的完整调用链
视角 事后复盘 实时健康度 端到端因果关系
存储量级 小(聚合后)
典型问题 “这一步模型输出了什么?” “成功率为什么下降了?” “这次请求的瓶颈在哪?”
存储技术 Elasticsearch / Loki Prometheus / InfluxDB Jaeger / Tempo / LangSmith

实践中,团队应以 Trace 为骨架、日志为血肉、指标为体检报告:Trace 提供结构化的事件关联,日志记录最原始的细节,指标反映系统的宏观趋势。三者通过 trace_idspan_id 贯穿,形成从宏观到微观的全链路下钻能力。

4. 统一可观测性数据模型:Span、Event 与 Attribute

有了三大支柱的概念,还需要一个统一的数据模型来承载这些观测数据。否则日志、指标、追踪数据各自为政,最终会变成“数据孤岛”,无法关联分析。

4.1 业界标准:OpenTelemetry 的数据模型

OpenTelemetry(简称 OTel)已经成为云原生可观测性的实际标准,其数据模型经过精心设计,具备良好的统一性与扩展性。OTel 将观测数据统一为三种类型:

  • Trace:由一组 Span 构成的调用树,描述一次分布式操作的全过程;
  • Metric:带有属性的数值型时间序列;
  • Log:带时间戳的结构化事件记录。

在 OTel 中,Span 是核心概念。一个 Span 代表一个带起始时间与结束时间的操作片段,可以携带任意数量的属性(Attribute)与事件(Event)。属性是键值对,用于描述 Span 的静态上下文(如模型名称、工具名);事件带时间戳,用于记录 Span 生命周期中的动态节点(如“开始生成第一个 token”)。

这样的设计恰好契合 AI Agent 的可观测性需求:

  • Span 对应一次 LLM 调用、一次工具调用或一个 Agent 步骤;
  • Attribute 对应模型名、工具参数、Token 数量等上下文信息;
  • Event 对应流式输出的关键节点、重试、降级等动态事件。

4.2 面向 LLM 的语义约定:GenAI Semantic Conventions

OpenTelemetry 社区已经意识到 LLM 应用可观测性的需求,发布了 GenAI Semantic Conventions(生成式 AI 语义约定),为 GenAI 相关的 Span 定义了统一的属性命名规范。截至目前,该规范已经覆盖:

  • LLM Request Attributesgen_ai.operation.namegen_ai.system(如 openaianthropic)、gen_ai.request.modelgen_ai.request.temperaturegen_ai.request.max_tokens 等;
  • LLM Response Attributesgen_ai.response.modelgen_ai.usage.input_tokensgen_ai.usage.output_tokensgen_ai.response.finish_reason 等;
  • Agent/框架相关属性:正在讨论中的 gen_ai.agent.namegen_ai.agent.stepgen_ai.tool.namegen_ai.tool.call.args 等。

遵循这套语义约定有两大好处:一是团队不需要自己发明零散的属性命名,降低了跨团队协作的成本;二是可以无缝接入 LangSmith、Langfuse、Arize Phoenix、Datadog LLM Observability 等已经支持 GenAI 语义的第三方平台。

以下是一个符合 GenAI 语义约定的 LLM Span 属性示例:

{
  "name": "chat gpt-4.1",
  "kind": "CLIENT",
  "attributes": {
    "gen_ai.operation.name": "chat",
    "gen_ai.system": "openai",
    "gen_ai.request.model": "gpt-4.1",
    "gen_ai.request.temperature": 0.2,
    "gen_ai.request.max_tokens": 4096,
    "gen_ai.response.finish_reasons": ["stop"],
    "gen_ai.usage.input_tokens": 1842,
    "gen_ai.usage.output_tokens": 96
  }
}

4.3 扩展 Schema:定义 Agent 专属字段

GenAI 语义约定还在演进中,对于 Agent 特有的概念(如工具调用循环、状态更新、内存读写、反思步骤),团队需要在标准之上做适度扩展。建议遵循以下原则:

  • 前缀命名:使用 agent.ai_agent. 前缀标记 Agent 专属属性,如 agent.step_typeagent.iterationagent.state_key
  • 枚举化:对 step_type、tool_status 等字段使用受控的枚举值,避免自由文本导致的检索困难;
  • 版本化:Schema 作为代码管理,具有版本号,保证新旧版本数据可兼容解析;
  • 最小必要原则:记录足够支持诊断与改进的信息,但不要无节制地采集敏感数据。

以下是一个包含工具调用与反思步骤的 Span 树示例:

Root Span [name="agent_execute", type="AGENT"]
  ├─ attributes:
  │    agent.name="customer_service_agent"
  │    agent.version="v1.3.2"
  │    agent.framework="langgraph"
  │    session.id="sess_987654"
  │    user.id="u_1024"(脱敏)
  │
  ├── LLM Span [name="llm.plan"]
  │     ├─ attributes: agent.step_type="plan", gen_ai.operation.name="chat", ...
  │
  ├── Tool Span [name="tool.get_order_status"]
  │     ├─ attributes: agent.step_type="act", agent.tool.name="get_order_status"
  │     ├─ events:
  │     │    {name="tool.call.start", tool.args={"order_id":"123456"}}
  │     │    {name="tool.call.end", tool.status="success", tool.latency_ms=235}
  │
  ├── LLM Span [name="llm.reflect"]
  │     ├─ attributes: agent.step_type="reflect"
  │
  └── LLM Span [name="llm.answer"]
        ├─ attributes: agent.step_type="answer"

4.4 事件驱动:面向 Agent 流程的观测模型

除了 Span 树模型,许多 Agent 框架天然是事件驱动的。例如 LangGraph 在执行过程中会发出 on_chain_starton_tool_starton_llm_end 等事件,LangChain 的 Callback 机制本质上也是一个事件流。因此,在设计观测数据模型时,可以将 Agent 运行期间的关键事件定义为第一等公民:

class AgentEvent(BaseModel):
    event_type: Literal[
        "agent_start", "agent_end",
        "llm_start", "llm_end", "llm_stream",
        "tool_start", "tool_end", "tool_error",
        "retriever_start", "retriever_end",
        "state_update", "memory_read", "memory_write",
        "reflection_start", "reflection_end",
        "human_approval", "checkpoint", "error"
    ]
    trace_id: str
    span_id: str
    timestamp: datetime
    payload: dict  # 事件具体的结构化数据

事件模型与 Span 模型并不矛盾:Span 提供了操作的边界(起止时间与嵌套关系),Event 提供了操作内部的动态细节。在实现上,可以把事件以 Log 形式存储,并通过 span_id 关联到对应 Span 上;也可以在 Span 内部直接挂载 events 数组。OTel 的 Span API 本身就支持 add_event(name, attributes) 方法,天然契合这个思路。

5. 核心观测数据:捕获 Agent 的“思维脉搏”

了解了数据模型之后,本节能具体回答一个问题:在 Agent 的运行过程中,我们究竟应该记录哪些数据? 按照数据来源与用途,可以将核心观测数据分为六类。

5.1 思维链与内部状态

思维链(Chain-of-Thought)是 Agent 推理过程最直接的体现。尽管让模型“把思考过程写出来”会带来一定的隐私与安全考量,但在诊断与审计场景中,思维链的价值不可替代。

需要记录的数据:

  • Prompt 模板与渲染后 Prompt:模板是静态的、可版本化的,渲染后 Prompt 则反映了当次请求的实际上下文。两者都要记录,模板用于审计与 A/B 对比,渲染结果用于排障;
  • 模型原始输出:包括文本内容、工具调用参数(Function Calling 的 JSON)、finish_reasonsystem_fingerprint 等;
  • 中间推理步骤:如果使用 ReAct、CoT 等显式推理范式,每个“Thought/Action/Observation”都应作为独立事件记录;
  • 自我反思与修正结果:Agent 是否否定了之前的计划、修正了什么内容、基于什么信号修正。

一个容易忽略的细节是流式输出的记录。在使用 streaming 模式时,模型输出是逐步到达的。观测系统应记录流式事件的时序(如首字时间、每个 token 的间隔),这对优化用户感知延迟至关重要。

5.2 工具调用与函数执行

工具调用是 Agent 与外部世界交互的窗口,也是错误的高发区。每个工具调用至少要记录:

字段 说明 示例
tool.name 工具唯一名称 get_order_status
tool.args 实际传入参数(脱敏) {"order_id": "123456"}
tool.result 返回结果(截断与脱敏) {"status": "shipped"}
tool.status 成功 / 失败 / 超时 success
tool.error 错误类型与消息 TimeoutError
tool.latency_ms 工具执行耗时 235
tool.retry_count 重试次数 1
tool.source 工具来自框架内置 / 自定义 / 第三方 custom

对于访问数据库、缓存、文件系统的工具,还应记录对应的语句类型(SELECT/INSERT)、目标表/索引、读写行数等,便于 DBA 协同排查。

5.3 检索增强生成(RAG)的观测数据

RAG 是 Agent 系统中最常见的子流程之一,其质量直接影响最终输出的准确率。RAG 的可观测性需要覆盖检索与生成两个环节:

检索环节

  • 查询文本与重写后的查询;
  • 向量检索的 top_k、过滤条件;
  • 召回文档的 ID、来源、元数据、相似度分数;
  • 召回文档与查询的相关性(可通过 reranker 或 LLM 后置评估);
  • 缓存命中率、索引版本。

生成环节

  • 模型实际引用了哪些召回文档(citation 链接);
  • 生成文本与召回文档的事实一致性;
  • 是否出现幻觉(引用了召回文档中不存在的数字/事实)。

以下是一个检索 Span 的待配置属性示例:

{
  "retrieval.query": "用户订单 #123456 的物流状态",
  "retrieval.index": "order_index_v3",
  "retrieval.top_k": 5,
  "retrieval.hits": [
    {"doc_id": "d_8012", "score": 0.92, "source": "crm"},
    {"doc_id": "d_1033", "score": 0.84, "source": "order_db"}
  ],
  "retrieval.cache_hit": False,
  "retrieval.latency_ms": 42
}

5.4 状态管理、记忆存储与上下文窗口

现代 Agent 框架(如 LangGraph)引入了显式的状态(State)与检查点(Checkpoint)机制,让 Agent 可以在多轮执行中保持上下文。这些机制的读写行为同样是观测重点:

  • State 更新:哪个节点更新了状态、更新的键值对、更新前后差异;
  • Checkpoint 保存与恢复:什么时机保存检查点、恢复时用的哪个版本、是否发生状态回滚;
  • 记忆读写:长期记忆(向量库/数据库)与短期记忆(上下文窗口)的写入、检索、裁剪;
  • 上下文窗口使用率:当前上下文占用了多少 token、距离上限还有多少、是否触发了摘要压缩;
  • 历史对话摘要:摘要生成触发时机、摘要前后的 token 变化、摘要质量评估。

上下文窗口的管理是一个容易被忽视但影响巨大的环节。当上下文接近上限时,Agent 可能会触发自动摘要或裁剪,而这些操作可能丢失关键信息,导致后续回答质量下降。通过观测上下文使用率的变化曲线,团队可以发现并预防这类问题。

5.5 模型服务与外部依赖

Agent 的稳定运行依赖多个外部服务:LLM 提供商、向量数据库、知识库、传统 API 等。观测数据应覆盖:

  • 调用的模型名称、版本、以及是否存在供应商端的模型版本漂移;
  • API 请求的延迟、超时、重试、限流(HTTP 429)事件;
  • 供应商的有状态指纹(如 system_fingerprint),用于识别模型服务的隐性变更;
  • 外部服务的可用性指标:错误率、熔断/降级触发次数。

5.6 用户反馈与服务上下文

可观测性不只是看“机器如何运行”,还要把机器行为与人类反馈关联起来:

  • 显式反馈:点赞/点踩、评分、用户补充留言;
  • 隐式反馈:用户是否中断了生成(说明答案可能跑偏或过长)、用户是否重新提交了修改后的问题、是否转人工;
  • 服务上下文:用户等级、租户、会话来源、设备、时区等,以便做分段分析。

所有的用户相关数据都涉及隐私,采集时必须遵守最小必要原则并落实脱敏和访问控制。反馈数据与 Trace 的关联是后续构建“质量评估—回归测试”闭环的基础,这一点在第 12 节会详细展开。

6. 技术架构:构建生产级 Agent 可观测性系统

有了清晰的数据模型,下一步是搭建承载这些数据的技术架构。一个生产级的 Agent 可观测性系统通常包括五个层次:插桩采集层、数据管道层、存储层、计算与查询层、展示与动作层

展示与动作层

计算与查询层

存储层

数据管道层

插桩采集层

Agent 运行时

OpenTelemetry SDK

框架 Callback/EventHandler

日志库 (structlog)

OTel Collector

消息队列 (Kafka/Pulsar)

数据处理 (脱敏/采样/聚合)

时序库: Prometheus/VictoriaMetrics

Trace 库: Tempo/Jaeger

日志库: Elasticsearch/Loki

对象存储: S3 (原始数据归档)

Streaming: Flink/ksql

Batch: Spark/DuckDB

Trace Query: TraceQL

指标查询: PromQL

Grafana 大盘

告警: Alertmanager

审计与报表

评估与回归平台

6.1 插桩采集层:无侵入与低开销

插桩是观测系统与 Agent 应用之间的桥梁。好的插桩方案应具备无侵入性(不改变业务代码)、低开销(对主流程延迟影响小)和高保真(捕获的数据完整准确)三个特点。

实现插桩有三种主流方式:

第一种:框架原生 Hook 与 Callback。LangChain/LangGraph 提供了丰富的 Callback 机制,开发者可以在 on_llm_starton_tool_end 等事件中嵌入观测逻辑。这种方式开发成本最低,但侵入性略高——需要业务代码显式注册回调。

第二种:OpenTelemetry 自动插桩(Auto-instrumentation)。利用 OTel 的 monkey-patching 或字节码注入能力,在模型 SDK(如 OpenAI 官方库)的网络请求层自动创建 Span。opentelemetry-instrumentation-openaiopentelemetry-instrumentation-requests 等库可以让团队在几乎不改代码的情况下获得 LLM API 调用的追踪。这是目前最值得推荐的低成本方案。

第三种:中间件/网关层拦截。在 LLM API 调用前统一走一个代理网关(如 LiteLLM Proxy、自建网关),由网关统一记录请求与响应。这种方式对多语言、多框架的团队特别有效,但要求所有 LLM 调用都经过网关。

三种方式并不互斥,生产环境通常组合使用:OTel 自动插桩兜底网络层,框架 Callback 捕获编排层,网关拦截做统一治理与控制

6.2 数据管道层:采集、处理与分发

观测数据从应用侧产生后,通常先进入 OpenTelemetry Collector 或消息队列,再做进一步处理。数据管道层需要处理四类任务:

  • 数据缓冲与削峰填谷:Agent 请求可能带来突发流量,中间的 Kafka/Pulsar 队列可以缓冲数据写入的尖峰;
  • 脱敏与合规处理:在数据进入存储之前,对用户 PII、敏感字段进行脱敏或哈希(如手机号、身份证号、信用卡号)。脱敏应在数据源头附近完成,避免敏感数据流入下游;
  • 采样策略:全量采集 Trace 在超大规模生产环境下成本高昂。常见的做法是头部采样(按优先级保留错误/慢请求/抽检)结合尾部采样(基于 Trace 完整特征决定保留,如“包含错误”的 Trace 全部保留)。对 LLM 应用,建议至少保留所有失败请求和随机抽样的正常请求;
  • 聚合与预计算:对高频指标进行流式聚合,降低存储压力。例如将单次 LLM 调用的 token 消耗即时聚合成每分钟的时序数据,而不是存储每个请求的明细。

在大规模场景下,采样策略直接影响可观测性的完整性。如果采样后丢失了关键会话,排查问题时就会“断链”。一个有效的折中方案是:错误 Trace 全量保留,成功 Trace 按 5%~20% 比例抽样,同时维护一份“用户可主动触发的强制保留名单”(如 VIP 用户或特定测试账号的请求全部保留)。

6.3 存储层:三驾马车各司其职

按照数据类型的不同,存储层通常分为三条线:

指标存储:采用时序数据库。Prometheus 是云原生标配,VictoriaMetrics 兼容 Prometheus 协议且具有更好的压缩率和横向扩展能力。指标数据可以做自动降采样(Retention Policy),例如 15 秒精度的数据保留 30 天,5 分钟精度的聚合数据保留 1 年。

Trace 存储:采用兼容 OTel 的 Trace 后端。开源方案有 Grafana Tempo(基于对象存储,成本低)、Jaeger(经典方案,适合中小规模);商业方案有 Datadog、Dynatrace、阿里云 ARMS 等。Trace 数据体量通常最大,建议结合采样与保留期限控制成本。

日志存储:Elasticsearch + Kibana 是常见组合,但成本与运维复杂度较高;Grafana Loki 面向日志的轻量存储是更经济的替代方案。Agent 的关键日志建议与 Trace 关联存储(通过 trace_id 关联),方便下钻。

此外,对象存储用于长期归档原始 Trace、完整 Prompt/Completion、评估样本等。对于合规要求高的行业,原始交互记录可能需要保存相当长的时间。

6.4 计算与查询层:让数据可被理解

数据存下来只是第一步,真正产生价值的是计算与查询能力:

  • Trace 查询:Grafana Tempo 提供 TraceQL,支持按 Span 名称、属性、耗时等条件查询 Trace,例如 {resource.service.name = "order-agent" && span.gen_ai.usage.output_tokens > 500}
  • 指标查询:PromQL 是事实标准,可对时序数据做聚合、计算、告警,例如 rate(agent_llm_requests_total{status="error"}[5m])
  • 批处理分析:对于跨会话的深度分析(如“过去一周幻觉率趋势”“不同 Prompt 版本的成本对比”),需要 Spark、DuckDB、Pandas 等批处理引擎,从对象存储或日志存储中读取原始数据做离线计算。

6.5 展示与动作层:从仪表盘到自动化

最终,观测数据要落到两个动作上:

  • :通过 Grafana 等平台构建多层级仪表盘——全局健康大盘、模型成本大盘、工具依赖拓扑、单会话 Trace 查看器;
  • :通过 Alertmanager 等告警系统在指标异常时通知团队;更进一步,可以形成自动化闭环:当告警触发时,自动拉取相关 Trace 做初步诊断,或触发 Agent 的降级策略(如切换到备用模型、简化工具链)。

在架构演化后期,展示层还可以发展出自动化评估与回归测试面板:自动对线上 Agent 输出做批量化质量评估,将结果反馈给研发与产品团队,形成持续改进的飞轮。

7. 实战:用 OpenTelemetry 实现 LLM 与 Agent 通用插桩

本节进入实战环节。我们首先使用 OpenTelemetry 构建一套框架无关的插桩方案,它适用于任何基于 Python 的 LLM/Agent 应用,不依赖 LangChain 等特定框架。

7.1 环境准备与依赖安装

创建虚拟环境并安装核心依赖:

python -m venv .venv
source .venv/bin/activate

pip install opentelemetry-api opentelemetry-sdk
pip install opentelemetry-exporter-otlp
pip install opentelemetry-instrumentation-openai
pip install opentelemetry-instrumentation-requests
pip install structlog

说明:

  • opentelemetry-sdk 提供 Tracer、Meter 的具体实现;
  • opentelemetry-exporter-otlp 将数据通过 OTLP 协议发送到 Collector 或后端;
  • opentelemetry-instrumentation-openai 自动为 OpenAI SDK 的调用创建 Span;
  • opentelemetry-instrumentation-requests 自动为 requests 库的 HTTP 调用创建 Span;
  • structlog 提供结构化日志能力。

7.2 配置 TracerProvider 与 Exporters

创建一个统一的可观测性初始化模块:

# observability/setup.py
import os
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import (
    BatchSpanProcessor,
    ConsoleSpanExporter,
)
from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
    OTLPSpanExporter,
)
from opentelemetry.sdk.resources import Resource
from opentelemetry.semconv.resource import ResourceAttributes

def init_observability(service_name: str = "agent-service"):
    # 资源信息:服务名、命名空间、部署环境
    resource = Resource.create({
        ResourceAttributes.SERVICE_NAME: service_name,
        ResourceAttributes.SERVICE_NAMESPACE: "production",
        ResourceAttributes.DEPLOYMENT_ENVIRONMENT: os.getenv("ENV", "dev"),
    })

    provider = TracerProvider(resource=resource)

    # 控制台导出器(开发调试用)
    console_exporter = ConsoleSpanExporter()
    provider.add_span_processor(BatchSpanProcessor(console_exporter))

    # OTLP 导出器(生产环境发送到 Collector)
    otlp_endpoint = os.getenv("OTEL_EXPORTER_OTLP_ENDPOINT")
    if otlp_endpoint:
        otlp_exporter = OTLPSpanExporter(endpoint=otlp_endpoint)
        provider.add_span_processor(BatchSpanProcessor(otlp_exporter))

    trace.set_tracer_provider(provider)
    return trace.get_tracer(service_name)

在应用入口处调用一次 init_observability(),之后所有通过 opentelemetry.trace.get_tracer() 获取的 Tracer 都会使用统一的 Provider。

7.3 编写装饰器:为工具调用自动创建 Span

我们可以编写一个通用装饰器,让任何函数在调用时自动创建 Span,并记录参数、结果与耗时:

# observability/decorators.py
import functools
import time
import json
from opentelemetry import trace

_tracer = trace.get_tracer(__name__)


def traced_tool(name: str | None = None, max_result_chars: int = 2000):
    """
    为工具函数创建 Span 的装饰器。

    :param name: Span 名称;默认使用函数名
    :param max_result_chars: 结果最大记录字符数,避免过大的返回值撑爆 Trace 存储
    """
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            span_name = name or func.__name__
            with _tracer.start_as_current_span(
                span_name,
                kind=trace.SpanKind.INTERNAL,
            ) as span:
                span.set_attribute("agent.tool.name", func.__name__)
                span.set_attribute("agent.step_type", "act")

                # 安全序列化入参(注意生产环境必须做脱敏)
                try:
                    args_payload = {
                        "args": str(args),
                        "kwargs": json.dumps(kwargs, ensure_ascii=False, default=str),
                    }
                    span.set_attribute(
                        "agent.tool.args",
                        json.dumps(args_payload, ensure_ascii=False)[:max_result_chars],
                    )
                except Exception:
                    span.set_attribute("agent.tool.args", "<serialization_error>")

                start = time.perf_counter()
                try:
                    result = func(*args, **kwargs)
                    span.set_status(trace.Status(trace.StatusCode.OK))
                    result_str = (
                        json.dumps(result, ensure_ascii=False, default=str)
                        if not isinstance(result, str)
                        else result
                    )
                    span.set_attribute(
                        "agent.tool.result", result_str[:max_result_chars]
                    )
                    return result
                except Exception as exc:
                    span.set_status(trace.Status(trace.StatusCode.ERROR, str(exc)))
                    span.record_exception(exc)
                    span.set_attribute("agent.tool.error", type(exc).__name__)
                    raise
                finally:
                    duration_ms = (time.perf_counter() - start) * 1000
                    span.set_attribute("agent.tool.latency_ms", round(duration_ms, 2))

        return wrapper
    return decorator

使用方式非常简单:

@traced_tool(max_result_chars=500)
def get_order_status(order_id: str) -> dict:
    # 模拟数据库查询
    return {"order_id": order_id, "status": "shipped"}

7.4 手动插桩:为 Agent 主流程创建 Span 树

在 Agent 的编排主循环中,我们需要手动创建 Span 来组织整棵调用树:

# agent/executor.py
import asyncio
from opentelemetry import trace
from observability.decorators import traced_tool

_tracer = trace.get_tracer(__name__)


@traced_tool()
def call_llm(prompt: str, model: str = "gpt-4.1") -> str:
    # 调用 LLM 的底层函数;如果使用了 OpenAI SDK,
    # OTel 自动插桩会为我们创建 L3 层的模型调用 Span。
    # 这里用伪代码示意。
    return "模型输出内容"


async def run_agent(user_query: str, session_id: str) -> str:
    tracer = trace.get_tracer(__name__)

    with tracer.start_as_current_span(
        "agent_execute",
        kind=trace.SpanKind.SERVER,
    ) as root_span:
        root_span.set_attribute("agent.name", "customer_service_agent")
        root_span.set_attribute("agent.version", "v1.3.2")
        root_span.set_attribute("session.id", session_id)

        # 第一步:意图理解与规划
        with tracer.start_as_current_span("agent.plan") as plan_span:
            plan_span.set_attribute("agent.step_type", "plan")
            plan = await call_llm(f"用户问题:{user_query}\n请制定执行计划。")

        # 第二步:依次执行工具(这里示例仅一个工具)
        with tracer.start_as_current_span("agent.act") as act_span:
            act_span.set_attribute("agent.step_type", "act")
            order_info = get_order_status("123456")
            act_span.set_attribute(
                "agent.tool.result", str(order_info)
            )

        # 第三步:反思并生成最终答复
        with tracer.start_as_current_span("agent.answer") as answer_span:
            answer_span.set_attribute("agent.step_type", "answer")
            final_answer = await call_llm(
                f"计划:{plan}\n工具结果:{order_info}\n请回答用户。"
            )

        root_span.set_attribute("agent.output_chars", len(final_answer))
        return final_answer

为了简化示例,这里没有加入循环与异常重试。在实际的 ReAct 实现中,每一步都应创建独立的 Span,并通过循环边界保持 Span 父子关系清晰。

7.5 接入 OpenTelemetry Collector

在推理开发阶段,可以只使用 ConsoleExporter 查看 Span 输出;但生产环境需要将数据发送到 Collector 做统一处理。以下是一个最小化的 Collector 配置:

# otel-collector-config.yaml
receivers:
  otlp:
    protocols:
      http:
        endpoint: 0.0.0.0:4318
      grpc:
        endpoint: 0.0.0.0:4317

processors:
  batch:
    timeout: 5s
    send_batch_size: 512
  memory_limiter:
    check_interval: 1s
    limit_mib: 512
  attributes:
    actions:
      - key: session.id
        action: hash   # 对 session_id 做哈希脱敏

exporters:
  otlp/tempo:
    endpoint: tempo:4317
    tls:
      insecure: true
  prometheus:
    endpoint: "0.0.0.0:8889"
  logging:
    loglevel: debug

service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [memory_limiter, attributes, batch]
      exporters: [otlp/tempo, logging]
    metrics:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [prometheus]

需要注意在处理器中配置脱敏与采样规则,确保敏感数据不流入下游。

8. 实战:LangChain 与 LangGraph 的可观测性

第 7 节的方案是框架无关的,但如果你使用 LangChain 或 LangGraph 构建 Agent,框架本身的可观测性机制值得充分利用。它们提供了更丰富的内部事件与更便捷的集成方式。

8.1 LangChain 的 Callback 机制

LangChain 的 Callback Manager 允许开发者注册多个 Callback Handler,在 LLM 调用、工具调用、链执行等节点触发回调。通过自定义 Callback Handler,我们可以把这些事件转换成 OTelt Span 或结构化日志。

# langchain_observability/callbacks.py
import logging
import time
from typing import Any

from langchain.callbacks.base import BaseCallbackHandler
from opentelemetry import trace

logger = logging.getLogger(__name__)
_tracer = trace.get_tracer(__name__)


class OtelCallbackHandler(BaseCallbackHandler):
    """将 LangChain 事件转换为 OpenTelemetry Span 的回调处理器。"""

    def on_llm_start(
        self,
        serialized: dict[str, Any],
        prompts: list[str],
        **kwargs: Any,
    ) -> None:
        span = _tracer.start_span(
            name="langchain.llm",
            kind=trace.SpanKind.CLIENT,
        )
        span.set_attribute("agent.step_type", "llm")
        span.set_attribute("agent.event", "llm_start")
        # 获取模型名称(不同 LangChain 版本字段位置略有差异)
        model = serialized.get("kwargs", {}).get("model_name", "unknown")
        span.set_attribute("gen_ai.request.model", model)
        span.set_attribute("agent.prompt_count", len(prompts))
        span.set_attribute(
            "agent.prompt_preview",
            prompts[0][:1000] if prompts else "",
        )
        # 将 Span 挂到当前上下文上,供后续回调关闭使用
        getattr(self, "_active_spans", {}).setdefault(
            kwargs.get("run_id"), []
        ).append(span)
        span_ctx = trace.set_span_in_context(span)
        # 注意:这里为了简洁,直接用成员变量追踪 Span 生命周期;
        # 生产代码建议用 run_id 到 span 的映射 + contextvar 结合的方式。
        self._current_spans = getattr(self, "_current_spans", [])
        self._current_spans.append(span)

    def on_llm_end(self, response, **kwargs: Any) -> None:
        spans = getattr(self, "_current_spans", [])
        if spans:
            span = spans.pop()
            span.set_attribute("agent.event", "llm_end")
            # 从 response 中提取 token 用量
            llm_output = response.llm_output or {}
            usage = llm_output.get("token_usage", {})
            span.set_attribute(
                "gen_ai.usage.input_tokens",
                usage.get("prompt_tokens", 0),
            )
            span.set_attribute(
                "gen_ai.usage.output_tokens",
                usage.get("completion_tokens", 0),
            )
            span.set_attribute(
                "gen_ai.usage.total_tokens", usage.get("total_tokens", 0)
            )
            span.end()

    def on_tool_start(
        self,
        serialized: dict[str, Any],
        input_str: str,
        **kwargs: Any,
    ) -> None:
        span = _tracer.start_span(name="langchain.tool")
        span.set_attribute("agent.step_type", "act")
        span.set_attribute("agent.tool.name", serialized.get("name", "unknown"))
        span.set_attribute("agent.tool.args", input_str[:1000])
        spans = getattr(self, "_tool_spans", [])
        spans.append(span)

    def on_tool_end(self, output: Any, **kwargs: Any) -> None:
        spans = getattr(self, "_tool_spans", [])
        if spans:
            span = spans.pop()
            span.set_attribute("agent.event", "tool_end")
            span.set_attribute(
                "agent.tool.result", str(output)[:2000]
            )
            span.end()

使用这个 Handler 时,把它传给 Agent 的 callbacks 参数即可:

from langchain.agents import AgentExecutor, create_react_agent
from langchain_openai import ChatOpenAI
from langchain_observability.callbacks import OtelCallbackHandler

llm = ChatOpenAI(model="gpt-4.1", temperature=0)
agent = create_react_agent(llm=llm, tools=[...])
executor = AgentExecutor(agent=agent, tools=[...])

handler = OtelCallbackHandler()
result = executor.invoke(
    {"input": "帮我查一下订单状态"},
    config={"callbacks": [handler]},
)

8.2 LangGraph:面向状态机的可观测性

LangGraph 将 Agent 建模为一张有向状态图,节点之间通过边传递状态。这种模型天然适合可观测性,因为每个节点、每条边都可以成为观测点。

与 LangChain 不同,LangGraph 推荐使用事件流 API。LangGraph 的 .stream().astream() 会产出详细的事件列表,每个事件包含节点名、触发时机与数据负载。我们可以订阅这些事件并分流到观测后端:

# langgraph_observability/stream_handler.py
import json
import logging
from typing import Any

from opentelemetry import trace

logger = logging.getLogger(__name__)
_tracer = trace.get_tracer(__name__)


def stream_agent_with_observability(graph: Any, user_input: str):
    """以可观测方式流式运行 LangGraph Agent。"""

    with _tracer.start_as_current_span("langgraph.agent_execute") as root_span:
        root_span.set_attribute("agent.framework", "langgraph")
        root_span.set_attribute("agent.input", user_input[:1000])

        for event in graph.stream(
            {"messages": [("user", user_input)]},
            stream_mode="updates",
        ):
            event_type = list(event.keys())[0]
            payload = event[event_type]

            # 为每个节点事件创建子 Span
            with _tracer.start_as_current_span(
                f"node.{event_type}"
            ) as node_span:
                node_span.set_attribute("agent.node.name", event_type)
                node_span.set_attribute(
                    "agent.node.payload",
                    json.dumps(payload, ensure_ascii=False, default=str)[:2000],
                )

            logger.info(
                "langgraph node event",
                extra={
                    "node": event_type,
                    "event_type": "node_update",
                },
            )
        # 构造最终答案(示例:从 messages 状态中取最后一条 AI 消息)
        # 实际实现取决于你的图定义。

在 LangGraph 典型的多节点图中(意图识别 → 工具调用 → 反思 → 输出),上述代码会为每个节点的执行创建一个 Span,从而构建出带有 langgraph.agent_execute 根 Span 的完整 Trace 树。结合每个节点内部 LLM 调用的自动插桩(第 7 节的 OpenAI instrumentation),整条链路将非常清晰。

8.3 全链路数据在不同流中的协同

LangGraph 的事件流与 OTel 的 Span 流在时间上是并行的。当它们都接入同一个观测后端时,开发者就可以看到一个从“图节点”到“LLM 调用”再到“具体 Prompt”的完整下钻路径。LangGraph 也提供了 astream_events 方法来获取更细粒度的内部事件,包括 LLM 子调用的开始与结束事件:

async for event in graph.astream_events(input_data, version="v2"):
    kind = event["event"]
    if kind == "on_chat_model_start":
        # LLM 子调用开始
        print(event["data"]["input"])
    elif kind == "on_chat_model_end":
        # LLM 子调用结束,包含 token 用量与输出
        print(event["data"]["output"])

.astream_events(version="v2") 的事件中,每个事件都自带 run_idparent_ids 等关联信息,可以方便地映射到 OTel Span 结构。对于想要完全掌控观测数据流向的团队,这是一条非常灵活的接入路径。

8.4 使用托管平台:LangSmith 与 Langfuse

如果不想自建全部基础设施,托管可观测性平台是个不错的选择:

  • LangSmith:LangChain 官方平台,对 LangChain/LangGraph 的支持最完善。可以自动捕获 Prompt、响应、Token 用量、工具调用、评估结果,并提供在线调试器与反馈采集接口。适合已深度采用 LangChain 生态的团队;
  • Langfuse:开源协议友好的 LLM 可观测平台,支持 OpenTelemetry 与多种框架 SDK。可以对 LLM 调用做成本追踪、Prompt 版本管理、评估追踪,并有自托管选项。适合有自主研发需求、关注数据主权的团队;
  • Datadog / New Relic:传统 APM 厂商的 LLM 观测能力,适合已有这些平台的大型企业,可以与基础设施监控打通。

平台选择没有绝对优劣,取决于团队规模、技术栈、预算与数据合规要求。

9. 查询与分析:用 TraceQL 与 PromQL 下钻数据

在这一节,我们介绍如何查询和利用已采集的观测数据。只要前几节的数据采集与建模合理,查询分析就会变得事半功倍。

9.1 TraceQL:在 Trace 中寻找线索

TraceQL 是 Grafana Tempo 提供的 Trace 查询语言,可以基于 Span 的属性、资源、耗时等维度筛选 Trace。以下是一些实用示例。

查询某个服务最近 15 分钟内所有出错或超时的根 Span:

{ resource.service.name = "order-agent" && status = error }

查询输出 token 数大于 1000 的 LLM Span:

{ span.gen_ai.operation.name = "chat" && span.gen_ai.usage.output_tokens > 1000 }

查询某个会话 ID 的完整调用链:

{ resource.service.name = "order-agent" && span.session.id = "sess_987654" }

查询调用工具失败率偏高的 Trace,并进一步聚合:

{ span.agent.tool.name = "get_order_status" && span.agent.tool.error != "" }

还可以使用 by() 与聚合函数做统计,例如:

{ resource.service.name = "order-agent" } |
  rate() by (span.agent.tool.name)

这些查询可以直接在 Grafana 的 Trace 查询面板中使用,也可以接入自动化脚本中做巡检。

9.2 PromQL:监控指标的实时脉搏

对于预聚合的指标,PromQL 是表达监控需求的标准语言。这里给出与 Agent 相关的核心查询示例:

过去 5 分钟的 Agent 请求量:

sum(rate(agent_requests_total[5m]))

过去 15 分钟 LLM 调用的错误率:

sum(rate(agent_llm_requests_total{status="error"}[15m]))
/
sum(rate(agent_llm_requests_total[15m]))

模型 Token 消耗速率(按模型聚合):

sum by (model) (rate(agent_llm_tokens_total[5m]))

P99 端到端延迟:

histogram_quantile(0.99, rate(agent_e2e_latency_bucket[5m]))

工具调用失败率:

sum by (tool) (rate(agent_tool_calls_total{status="error"}[10m]))
/
sum by (tool) (rate(agent_tool_calls_total[10m]))

使用 PromQL 构建 Grafana 面板与告警规则,可以让团队实时掌握 Agent 的运行态势。

9.3 多维分析:把观测数据用于决策

查询的价值最终体现在决策上。以下是一些由观测数据驱动的典型分析场景:

场景 1:定位“成本泄漏”源头。按 agent.tool.namegen_ai.request.model 两个维度聚合 Token 消耗,找出每天消耗最多预算的工具与模型。很多时候,成本超支不是整体流量上涨,而是某个工具触发了频繁的重试或超大 Prompt。

场景 2:找出最慢的环节。统计每一个 agent.step_type 的平均耗时与耗时分位数,定位瓶颈是模型推理、检索还是工具网络调用。如果发现 retriever 的 P95 延迟过高,可以决定更换检索后端或优化索引。

场景 3:版本对比。对比两个 Agent 版本在相同时间窗口内的成功率、延迟与成本,判断新版本是否稳定可用。这比只看代码 diff 更贴近线上真实表现。

场景 4:用户会话回溯。当收到用户投诉时,通过会话 ID 检索完整 Trace,还原整个执行过程,迅速定位问题。

10. 可视化与告警体系设计

有了数据和查询能力,下一步是把它们转化为人类可理解的仪表盘与机器可执行的告警规则。

10.1 分层仪表盘体系

设计可视化时,建议按角色与诉求构建分层仪表盘,而不是一张“全家福”大屏塞下所有指标。

第一层:SRE 全局健康大盘。面向值班与运维,关注系统级信号:

  • Agent 请求量、P50/P95 延迟、错误率;
  • LLM API 限流/超时/5xx 错误趋势;
  • 工具服务依赖的可用性(数据库、RAG 服务、第三方 API);
  • 队列积压、Worker 扩容事件等基础设施指标。

第二层:研发调试 Trace 视图。面向算法工程师与应用开发,聚焦单次执行的内部细节:

  • 瀑布图展示每个 Span 的耗时与依赖;
  • 下钻查看每一步的 Prompt、输出、工具参数;
  • 关键步骤的字段级 diff(如状态变更前后对比)。

第三层:业务与成本看板。面向产品与管理者,聚焦业务效果与预算:

  • 会话完成率、用户满意度、人工接手率;
  • Token 消耗趋势、单位会话成本、按模型/工具的成本分布;
  • 版本迭代的业务效果对比。

10.2 告警体系设计

好的告警不是“有什么指标就告什么”,而是精准地在业务受损之前或之初发出信号。对于 AI Agent,以下场景值得设置告警:

告警项 触发条件示例 严重级别
LLM 错误率过高 5 分钟内 LLM 错误率 > 3% P1
Agent 端到端延迟异常 P99 延迟连续 10 分钟超阈值 P2
Token 消耗突增 分钟级 Token 速率环比突增 200% P2
工具调用失败率 某工具错误率 > 5% 持续 5 分钟 P1
幻觉风险高 事实一致率 < 90%(基于评估管线) P2
上下文窗口耗尽 上下文使用率 > 90% 且伴随 retry P3
成本超预算 当日成本 > 预算的 80% P3
数据质量异常 Trace 采样丢失率 > 10% P1

注意:告警必须附带可操作的链接。例如在告警通知中直接附上 Grafana Trace 查询链接,让接警人一键进入相关出错 Trace,而不是只给一个百分比的数字。如果告警无法引导行动,它的价值就会大打折扣。

10.3 告警的降噪与自愈

Agent 系统由于模型不确定性的存在,会比传统系统产生更多噪声。以下降噪策略值得采用:

  • 聚合与去重:同一根因产生的多条告警应归并成一条;
  • 冷却窗口:同类告警在静默窗口内不重复发送;
  • 分级路由:P1 走电话/即时通讯,P3 仅记录到工单系统;
  • 自动缓释:对已知抖动(如供应商限流)可以配置自动降级与告警抑制。

更进一步,告警可以与自动动作联动:当检测到某模型可用率下降时,自动把流量切换到备用模型;当工具错误率升高时,自动对该工具实施熔断。这已经迈进第 17 节讨论的“自动化监控与自愈”范畴。

11. 性能诊断与成本优化实战

可观测性数据最直接的工程价值,是在生产环境中持续优化 Agent 的性能与成本。本节介绍系统化的诊断与优化方法。

11.1 诊断性能问题

Agent 的性能问题通常表现在三个层面:延迟吞吐量稳定性

延迟优化路径

  1. 用 Trace 瀑布图找到最粗的 Span。可能是 LLM 推理(依赖模型的速度与输出长度)、工具调用(网络/数据库)、或检索步骤;
  2. 对 LLM 调用,检查输出长度与 max_tokens。过于冗长的 Prompt 或 Answer 会直接放大延迟。可以考虑压缩 Prompt、设置合理的 max_tokens、使用更快的模型处理简单步骤;
  3. 考虑并行化。在 Agent 的规划步骤中,如果多个工具调用之间没有依赖(例如同时查询订单状态与用户信息),可以让它们并行执行以降低端到端延迟;
  4. 缓存常用检索结果与工具结果。基于 agent.tool.name + args哈希 做缓存,命中后跳过实际调用。

吞吐量优化路径

  • 使用异步 I/O 提升工具调用的并发度,减少等待外部 API 的空闲时间;
  • 引入连接池与 HTTP keep-alive 降低每次调用的建连开销;
  • 在语义明确时使用批处理(Batch API)或跨请求的 Prompt 前缀缓存。

稳定性优化路径

  • 用指数退避与抖动处理 LLM API 的限流与瞬时错误;
  • 为关键工具调用设置超时与熔断;
  • 对易失败的依赖提供服务降级方案(例如检索不可用时,退化到基于模型自身知识的回复并明确告知用户)。

11.2 定位成本热点

Agent 的 Token 消耗通常呈现明显的长尾分布:少部分路径贡献了大部分成本。通过按 agent.step_typeagent.tool.namegen_ai.request.model 等维度做成本聚合,可以准确找到这些热点。常见的成本问题与对策包括:

现象 根因 对策
单次请求 LLM 调用次数过多 规划/反思步骤过多,循环未收敛 限制最大迭代次数;优化终止条件
Prompt 过长 上下文管理不当,把无关历史都塞进 Prompt 摘要压缩、检索式上下文注入
高价模型使用过度 所有步骤都调用旗舰大模型 简单任务路由到小模型
重复计算 相同的检索/LLM 调用没有缓存 引入 Prompt 缓存与结果缓存
重试风暴 暂时性错误被大量重试 指数退避 + 熔断

11.3 模型路由与分级策略

观测数据支持的另一项重要优化是模型分级路由。不同步骤的难度差异很大:意图分类、SQL 生成、总结答复的复杂度和对模型能力的要求各不相同。通过观测历史数据,团队可以判断哪些任务真需要强模型,哪些任务用小模型就能完成。例如:

难度感知路由(基于观测数据训练)
├── 低难度:情感分类、关键信息提取 → 小模型(成本低、延迟低)
├── 中难度:SQL 生成、工具选择 → 中等模型
└── 高难度:复杂推理、多步规划 → 旗舰模型

这类方案可以将整体成本降低 30%~70%,而质量损失几乎可以忽略。路由策略本身也可以作为配置项纳入可观测性中,方便对比不同策略的效果。

12. 质量评估:幻觉、偏见与回归测试

可观测性不应只停留在“系统跑没跑通”,还要回答“Agent 做没做对”。在 LLM 应用的非确定性之下,质量评估需要一套工程化的方法。

12.1 构建自动评估管线

自动评估的核心思路是:在可观测性数据的基础上,额外运行一组评判器(Judge)来为每次 Agent 输出打分。评判器可以是指标规则、LLM-as-a-Judge、或人工抽样。评估结果回写为 Trace 的指标或独立评估记录,形成闭环。

一个实用的评估管线包括以下步骤:

  1. 采样:从线上会话中按策略抽取样本(随机、出错优先、低分优先);
  2. 脱敏与入库:将样本以评测数据集格式存入对象存储或评测平台;
  3. 标注与评判:运行评估器(规则/模型/人工)对答案质量打分;
  4. 结果回写:将评分关联回原始 Trace 与会话,生成质量趋势指标;
  5. 回归与改进:在新版本发布前运行同套评估,对比质量变化。

12.2 关键评估维度

维度 说明 自动化方法示例
事实准确率 回答中的事实是否与工具返回或知识库一致 检查生成内容与工具输出的实体/数值是否匹配
任务完成度 是否真正解决了用户问题 LLM-as-a-Judge 或完成任务流验证
忠实性 回答是否忠于检索到的文档,有无幻觉 抽取陈述—证据对,用 NLI 模型判断蕴含关系
安全性 是否遵守安全约束、避免泄露 PII 规则扫描 + 安全模型评判
鲁棒性 对提示词扰动、上下文噪声的抵抗能力 对抗样本测试、上下文注入测试
效率 是否用合理的步骤数与成本完成任务 统计迭代步数、Token 消耗

12.3 实施 LLM-as-a-Judge 的加固建议

用 LLM 当评委非常方便,但也存在自身的偏差问题。实践中有几点值得重视:

  • 使用成对比较:不仅对单个输出打分,还需要与基线输出比较,给出排名;
  • 统一评测 Prompt 与温度:把 Judge Prompt 纳入版本管理,并将温度设为 0 以提高可复现性;
  • 交叉验证:关键指标同时配置规则评判(如正则、精确匹配)与 LLM 评判,二者不一致时触发人工抽检;
  • 数据隔离:评测样本独立存放,避免 Judge 模型在训练/微调时“偷看”答案。

12.4 防幻觉与防越狱

在观测中,幻觉与安全事件应当有专门的识别机制。两类信号值得重点监控:

  • 工具输出与最终答案的不一致:例如工具返回“库存 0 件”,最终答案却宣称“库存充足”。这类信号可以通过规则或模型对比自动检测;
  • 上下文注入与越狱信号:用户输入中包含“忽略之前的指令”“现在扮演……”等模式时,应记录告警并启用额外防护。

这些检测结果应回流到观测系统,作为质量指标的一部分,驱动安全与算法团队持续迭代防护策略。

13. 安全、隐私与合规审计

Agent 可观测性在带来透明度的同时,也可能因为记录了敏感数据而带来新的风险。本节讨论如何在可观测性与隐私安全之间取得平衡。

13.1 观测数据中的隐私风险

由于 Agent 会处理真实用户数据,观测系统作为一个数据的“复制源”,会集中记录大量敏感信息。值得特别关注的三类风险:

  • 个人身份信息(PII):姓名、手机号、身份证号、地址、银行卡号等。这些数据绝不能进入日志或 Trace;
  • 业务机密与用户内容:用户的提问内容可能包含医疗、法律、财务等敏感信息,或者企业的内部业务数据;
  • 提示词与内部指令:完整的系统 Prompt 可能透露产品逻辑、边界条件与安全策略。这些内容被泄露可能招致 prompt injection 攻击与商业机密泄露。

13.2 工程化脱敏方案

脱敏应该贯穿数据生命周期:从采集、传输、存储到查询展示,每一层都应有保护措施。

采集端脱敏——在数据源头(应用侧)第一时间处理:

import hashlib
import re

PII_PATTERNS = [
    (r"\b1[3-9]\d{9}\b", "<PHONE>"),          # 中国手机号
    (r"\b\d{18}\b", "<ID_CARD>"),            # 18 位身份证号(简化示意)
    (r"\b\d{16,19}\b", "<CARD_NUMBER>"),     # 银行卡号(简化示意)
    (r"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}", "<EMAIL>"),
]

def redact_pii(text: str) -> str:
    for pattern, replacement in PII_PATTERNS:
        text = re.sub(pattern, replacement, text)
    return text

def hash_token(value: str, salt: str = "observability-salt") -> str:
    return hashlib.sha256(f"{salt}:{value}".encode()).hexdigest()[:16]

在写入 Span 或日志之前,对 Prompt 与输出统一执行 redact_pii;对需要关联分析但无需明文展示的标识(如用户 ID、会话 ID),使用确定性哈希保留可关联性。

传输与存储端保护:内部网络传输使用 TLS;存储层启用字段级加密与访问控制;原始数据归档时做二次脱敏或加密。

查询展示端保护:为不同角色配置数据访问范围。例如,一线客服查 Trace 时只能看到脱敏后的工具参数,而高级工程师在获得授权后可申请查看会话脱敏前的关键字段。访问日志本身也应被审计。

13.3 合规审计与数据生命周期

在 GDPR、PIPL 等数据保护法规下,可观测性系统自身也要遵循数据生命周期管理:

  • 数据保留期限:明确各类观测数据的保留时长,按策略自动过期删除;
  • 最小采集原则:只采集完成可观测性目标所必需的字段,避免“先采为敬”;
  • 导出与删除:支持数据主体的查询与删除请求。这要求构建从用户 ID 到观测数据的索引能力,并能按索引删除;
  • 审计追踪:对观测系统自身的访问行为进行审计记录,确保“谁在什么时候看了什么数据”可追溯。

许多团队会在观测管道层设计数据治理策略引擎,基于规则动态执行脱敏、过滤、采样与丢弃。例如当检测到某个 Span 包含信用卡号时,立即将对应字段替换为脱敏符号并产生一条合规告警。

13.4 关键指标与合规报表

为满足审计与安全运营需要,建议在观测系统中常备以下合规类指标:

  • 脱敏字段命中率(检测到多少数据包含 PII);
  • 观测数据时效性(数据从产生到可查询的延迟);
  • 数据保留策略执行情况(超期数据清理任务成功率);
  • 观测系统自身可用性(以防“监控系统宕机”);
  • 敏感字段的访问审计事件数量。

14. 多 Agent 系统的可观测性

当系统从单个 Agent 演进到多个 Agent 协作时,可观测性的复杂度会成倍增加。多 Agent 系统中的挑战不只是“观测更多组件”,而是理解 Agent 之间的交互协议与时序逻辑

14.1 多 Agent 系统的典型架构

多 Agent 系统常见三种拓扑:

拓扑 说明 例子
主从式 一个主 Agent 调度多个子 Agent,子 Agent 完成特定子任务 旅行规划 Agent 调度机票 Agent 与酒店 Agent
对等式 多个 Agent 可以相互通信、协商} 软件研发团队中,分析师、编码 Agent、测试 Agent 相互协作
层级式 Agent 组成层级树,父节点合并子节点的结果并向上汇报 企业知识管理中的部门 Agent → 领域 Agent → 顶层 Agent

无论哪种拓扑,可观测性都要求把一个跨 Agent 的任务关联成一条端到端的 Trace。这需要请求上下文的传播(Context Propagation),确保每个子 Agent 产生的 Span 都能挂到同一个根 Trace 上。

14.2 上下文传播的关键挑战

上下文传播在单服务内部比较容易实现(共享内存中传递 context),跨服务、跨 Agent 时则需要显式的协议支持。常见的做法是引入 traceparent 头或类似机制。在 Agent 协作中,上下文传播还要处理以下新问题:

  • 异步与长时任务:一个子 Agent 可能需要几分钟甚至几小时才能完成任务。这段时间内,父级 Agent 可能已经结束了自己的 Span。观测系统必须支持异步 Span 或把子 Agent 的结果关联回父级,而不是让 Trace 断开;
  • Agent 重启与状态恢复:如果子 Agent 因故障重启并从检查点恢复,恢复后的执行应该继续沿用原 Trace,或通过 resume_from 关联建立新的 Span 链接;
  • 模型生成的协商消息:多 Agent 之间的通信可能本身就是 LLM 输出,这些消息如同“Agent 间的对话”,需要记录为特殊的事件并纳入 Trace 关联。

14.3 多 Agent 系统的观测重点

在单 Agent 观测的基础上,多 Agent 系统还要额外关注以下方面:

  • Agent 间通信数量与频率:过度通信可能意味着规划低效;
  • 任务分配与负载均衡:某个子 Agent 是否成为瓶颈或承担了不合理的任务量;
  • 跨 Agent 错误传播:一个子 Agent 的失败如何在协作链中被放大或抑制;
  • 结果融合质量:父 Agent 合并子结果时是否发生信息丢失或矛盾;
  • 协商一致性:多 Agent 达成一致的路径是否可追溯(谁主导、谁妥协、经过几轮)。

子 Agent 2:售后建议

子 Agent 1:订单查询

主 Agent

任务分解

协调与合并

查询订单

分析物流

读取售后政策

生成建议

最终答复

上图展示了主从式多 Agent 的调用关系。在实际观测中,对应每个子 Agent 都应是 Root Span 下的子树,节点之间的消息传递则通过 Span 链接或事件关联记录。

14.4 借助框架的支持

LangGraph 的 subgraph 机制天然支持多 Agent 编排。子图与被调度关系的 Span 需要额外的观测语义。LangGraph 的高版本事件包含 run_idparent_ids,可以帮助关联子图与父图。使用第 8 节的 LangGraph 观测方案时,将父图与子图视为独立可嵌套的跨度层级,可以较为自然地构建跨 Agent 的调用树。

对于非 LangGraph 的自研多 Agent 系统,团队需要自行设计上下文传播协议与观测约定。可以参考 OpenTelemetry 的跨服务传播标准,把 trace context 附着在 Agent 间通信的消息元数据中。

15. 工程化最佳实践与反模式

前文从方法论与工具层面展开了可观测性体系。本节回到工程落地本身,从团队视角总结最实用的最佳实践与常见反模式。

15.1 建立可观测性数字底座的四条准则

准则一:先定义“成功”,再开始测量。 建立可观测性之前,团队必须明确:Agent 的成功率和延迟到什么程度是可以接受的?哪些模型与工具可以调用、哪些禁止?只有当“正常”与“异常”有清晰定义,指标才有意义。否则数据很多,却全是噪声。

准则二:将观测能力视为产品特性,而非事后补丁。 最差的做法是等线上出现事故了,再紧急给 Agent 添加日志。可观测性应该成为 Agent 代码评审的一部分:任何新增的 Agent 节点、工具调用,都必须同步声明观测字段与测试用例。

准则三:投入产出比是首要设计约束。 观测数据本身有存储、计算与传输成本。不要试图“把所有东西都记下来”。优先覆盖关键链路、关键工具与高价值用户,再逐步扩展。当数据量增长到一定规模后,通过采样与聚合降低成本。一个没有成本约束的观测系统本身就是不可观测的。

准则四:让观测数据面向所有角色开放。 可观测数据不应该被运维团队独占。产品经理需要会话完成率,算法工程师需要 Prompt 效果,财务需要 Token 成本,安全团队需要 PII 泄露事件。建设统一的数据访问层并以自助式查询为目标是成熟观测体系的标志。

15.2 可观测性实施的反模式

以下反模式来自于大量失败案例,值得警惕:

反模式 表现 后果
日志“碎成渣” 大量非结构化文本,无 trace_id 关联 无法检索,无法关联,排查效率低下
指标“高基数爆炸” 用 trace_id 或用户原始输入作为指标标签 时序库内存暴涨,查询拖慢
采样“一视同仁” 错误与正常流量同等采样 排障时关键错误被采样掉
Trace 与指标割裂 日志、指标、Trace 分属不同平台且无关联 数据孤岛,下钻链路断裂
采集“冷启动缺失” 只在生产环境开启观测,开发环境没有 问题到了线上才暴露,修复代价高
告警“狼来了” 阈值不合理,告警泛滥且无人处理 团队对告警麻木,错过真故障
脱敏缺失或过度 要么直接记录明文 PII,要么把一切字段都哈希导致无法分析 要么合规风险,要么数据失去价值

15.3 可观测性落地的阶段路线图

一个务实的落地路线通常分四个阶段:

  1. 阶段一:最小可行观测(1~2 周)。为 Agent 请求接入基础结构化日志与根 Span,标记 trace_id、session_id,接入基础日志聚合与看板。目标:出问题时能基本还原一次请求的主干路径;
  2. 阶段二:全链路追踪与核心指标(2~4 周)。扩展插桩到 LLM 调用、工具调用、检索环节;定义并上报核心指标;建立 Grafana 大盘与基础告警;
  3. 阶段三:分析能力与质量闭环(1~2 个月)。建设 TraceQL/PromQL 查询能力、搭建评估管线与人工反馈采集;引入 Trace 采样策略与数据生命周期管理;
  4. 阶段四:自动化与平台化(持续)。告警联动自动动作、多 Agent 上下文传播、成本优化自动化、评估反哺模型/Agent 迭代,形成“监测—洞察—行动”的持续飞轮。

16. 可观测性即代码与 CI/CD 集成

现代软件的运维理想是“一切皆代码”:基础设施即代码、配置即代码、管道即代码。可观测性也应遵循同样的原则,将观测配置与 Agent 业务代码版本化联动,让每次变更都有对应的观测保障。

16.1 把观测配置纳入版本管理

观测配置包括:Dashboard JSON(Grafana)、告警规则(Prometheus YAML/Alertmanager)、Collector 配置(OTel Collector YAML)、评估集与评估脚本、采样与脱敏策略。这些配置应与 Agent 代码放在同一个仓库(或独立配置仓库)中,遵循同一套版本发布流程。

好处很直接:当 Agent 中新增了一个工具时,其新的指标与告警配置可以在 PR 里一并提交;当 Agent 修改了 Prompt 模板或模型路由规则时,相关评估集与阈值同步更新。配置变更可以走代码评审,避免手改生产面板造成的配置漂移。

示例仓库目录:

agent-repo/
├── src/
│   └── agent/                  # Agent 业务代码
│       └── executor.py
├── observations/
│   ├── dashboards/
│   │   └── agent-overview.json
│   ├── alerts/
│   │   └── agent-alerts.yaml
│   ├── collector/
│   │   └── otel-collector.yaml
│   ├── evals/
│   │   ├── dataset.jsonl
│   │   └── judge_prompts.py
│   └── policies/
│       └── redaction_rules.yaml
└── infra/
    └── docker-compose.yml

16.2 CI 阶段的可观测性检查

在 CI 阶段,可观测性不应只是“静态存在”,而应成为质量门禁的一部分。可以为 CI 增加以下检查:

  • Schema 校验:校验观测字段命名是否符合团队 Schema 约定(如所有工具 Span 必须包含 agent.tool.name);
  • 配置 lint:对 Grafana Dashboard JSON、Prometheus YAML 做语法与结构校验;
  • 采样与脱敏策略验证:运行自动化测试,确认观测代码在给定输入下不会泄漏 PII、不会生成高基数标签;
  • 评估集回归:在 CI 中运行一组小规模评估集,发现新代码带来的质量或成本劣化。

以下是一个基于 pytest 的观测 Schema 校验测试示意:

# tests/test_observability_schema.py
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider, export
from opentelemetry import trace as trace_api


class CollectingExporter(export.SpanExporter):
    def __init__(self):
        self.spans = []

    def export(self, spans):
        self.spans.extend(spans)

    def shutdown(self):
        pass


def test_tool_span_contains_required_attributes():
    provider = TracerProvider()
    exporter = CollectingExporter()
    provider.add_span_processor(export.SimpleSpanProcessor(exporter))
    trace.set_tracer_provider(provider)
    tracer = trace.get_tracer(__name__)

    with tracer.start_as_current_span("tool.get_order_status") as span:
        span.set_attribute("agent.tool.name", "get_order_status")
        span.set_attribute("agent.step_type", "act")

    # 触发 span 导出
    provider.force_flush()

    exported_spans = exporter.spans
    assert len(exported_spans) == 1
    attrs = dict(exported_spans[0].attributes)
    assert attrs["agent.tool.name"] == "get_order_status"
    assert attrs["agent.step_type"] == "act"

16.3 CD 阶段的一键发布与回滚

将观测配置纳入 CD 管道,可以实现:

  • 蓝绿回滚:Agent 新版发布后,基于可观测指标自动决策是否回滚(例如错误率超过阈值自动切回旧版);
  • 灰度发布:新版本先承担 5% 流量,观测良好后逐步放量;
  • 观测审计:每次发布后,自动生成“发布会诊报告”,包含新旧版本在成功率、延迟、Token 成本等维度的对比。

这种自动化程度较高的发布体系,是“观测驱动开发/运维”的最终形态,使团队对线上 Agent 拥有像传统软件一样的发布信心。

17. 未来展望:自动化监控、自愈与 AI 辅助可观测性

Agent 技术的快速演进也在反向塑造可观测性本身。未来几年,以下几个方向的进展值得关注。

17.1 自动化监控与自愈闭环

当下的观测体系仍以“人工主体”的监控为主:人看大盘、人分析 Trace、人做修复。未来的方向是让监控系统**自动完成“发现问题—诊断根因—执行修复”**的闭环。

比如,当某个工具的失败率升高时,系统可以自动:

  • 查询历史 Trace,识别异常模式与可能根因;
  • 触发预案:对该工具启用熔断,将相关请求路由到备用工具;
  • 记录告警处理结果,供后续学习。

在 Agent 系统中,LLM 本身也可以参与诊断。例如,出现错误时,自动把 Trace 摘要、相关日志、历史相似案例喂给一个诊断 Agent,由它生成根因假设与修复建议,供工程师或自动化系统采纳。这类“用 AI 运维 AI”的方式,将显著降低人工运维成本。

17.2 强化学习与可观测性数据的互相促进

可观测性采集的“思维链—工具调用—结果反馈”数据,本身就是训练和微调 Agent 的宝贵语料。尤其是在使用强化学习(RLHF/GRPO 等)优化 Agent 时,可观测性数据可以帮助构建奖励信号:

  • 成功完成的交互可作为正向样本;
  • 被用户中断、点踩、转人工的会话可作为负向样本;
  • Trace 中工具调用的效率可作为成本约束的惩罚信号。

反过来,经过优化的 Agent 会更稳定地运行,从而产生更高质量的观测数据。可观测性将从“被动的事后分析”逐步走向“主动的训练数据供给”。

17.3 标准化与生态融合

当前 LLM 与 Agent 可观测性领域处于快速标准化阶段。OpenTelemetry 的 GenAI 语义约定正在蓬勃发展,各主流平台也在积极对接。未来,随着标准成熟,Agent 应用将能够以与语言无关、与框架无关的方式输出统一格式的观测数据,各平台之间也更容易互操作。

同时,可观测性将与 Agent 平台本身深度融合。我们可以期待以下能力:

  • 原生 Trace 支持:主流 Agent 框架默认集成 OTel 与核心语义约定;
  • 统一成本追踪:跨模型、跨供应商的 Token 成本被自动折算并归因;
  • 端隐私计算:在不暴露原始用户内容的前提下,对观测数据做隐私保护分析(如联邦分析、差分隐私);
  • AI 辅助的大盘生成:根据观测数据自动发现新的关键指标,并生成推荐的面板与告警建议。

17.4 可观测性作为 Agent 生产就绪的第一道门槛

行业正逐渐形成一个共识:一个没有可观测性的 Agent,不具备真正意义上的生产就绪能力。可观测性不再是“好东西有最好”,而是生产级 Agent 系统的必备能力。就像我们不会接受一个没有日志和监控的 Web 服务一样,未来我们同样不会接受一个无法解释自身行为、无法追踪推理路径的 Agent。

这一趋势也会反映在招聘、平台选型与架构评审中:是否具备可观测性设计与实践能力,将成为 AI 应用工程师的重要能力标尺。

18. 总结:让 Agent 从黑盒走向透明

本文从问题本质、数据模型、技术架构、工程实战到未来趋势,系统性地梳理了 AI Agent 可观测性的完整图景。

回顾全文,有几个核心认知值得再次强调:

第一,可观测性是生产级 AI Agent 的基石,而不是可选功能。 多步推理、工具调用、记忆与反思构成的复杂性,使 Agent 天然比传统软件更难调试与掌控。只有构建起日志、指标、链路追踪三位一体的观测体系,团队才能真正理解 Agent 的运行状态,在故障发生时有据可查、有迹可循。

第二,统一的数据模型与语义约定是高效观测的前提。 基于 OpenTelemetry 的 Span/Event/Attribute 模型,结合 GenAI 语义约定与团队自定义的 Agent 扩展字段,可以实现日志、指标、Trace 的互联互通。数据只有被结构化、被关联,才能产生分析价值。

第三,观测的价值最终体现在行动上。 无论是定位一次线上事故、优化 Token 成本、发现幻觉模式、还是驱动 Agent 版本迭代,观测数据都必须转化为人在看板上看得懂的信息、告警系统可以执行的动作、评估系统可以比较的分数。数据本身不是目的,洞察与改进才是。

第四,安全与合规是观测的底线。 观测系统天然汇聚了大量敏感数据,脱敏、访问控制、生命周期管理与审计能力必须内建于架构之中。可观测性追求透明,但透明不等于无边界地暴露。

第五,实践是唯一的检验标准。 本文提供的 OpenTelemetry、LangChain/LangGraph 示例可以作为起点,但真正的挑战在于根据自身业务形态选择观测粒度、采样策略与评估方式。建议团队按“最小可行观测 → 全链路追踪 → 质量闭环 → 自动化运维”的路线逐步演进,不追求一步到位。

当 AI Agent 从实验室走进生产环境,可观测性就是那道连接“模型能力”与“工程信任”的桥梁。一个可观测的 Agent,不仅能让团队在问题出现时快速定位,更能让团队在迭代时充满信心——因为每一次变化,都看得见、测得准、可回退。

让黑盒变透明,不是一句口号,而是一套可以逐步落地的工程实践。愿本文能成为你构建 Agent 可观测性体系的一张可靠的施工图。

Logo

小龙虾开发者社区是 CSDN 旗下专注 OpenClaw 生态的官方阵地,聚焦技能开发、插件实践与部署教程,为开发者提供可直接落地的方案、工具与交流平台,助力高效构建与落地 AI 应用

更多推荐