欢迎光临
我们一直在努力

Agent可观测性自建方案:零成本搭建智能体全链路追踪系统(Python 3.10+ / OpenTelemetry)

Agent可观测性自建方案:零成本搭建智能体全链路追踪系统(Python 3.10+ / OpenTelemetry)

⚠️ 版本说明:本文基于 Python 3.10+、OpenTelemetry SDK(opentelemetry-api==1.27.0、opentelemetry-sdk==1.27.0)。代码可降级至 Python 3.8+(需调整 match 语法)。自建方案零额外成本,生产环境建议配合 Prometheus+Grafana。

导读:你的Agent在生产环境出问题了,你怎么知道"它到底做了什么"?当用户投诉"Agent给了错误答案",你能在5分钟内定位是LLM幻觉、工具调用参数错误、还是检索到了错误的记忆吗?2026年,Agent从Demo走向生产,可观测性(Observability)不是"可选项"而是"必选项"——没有它,你就是在黑盒里运行Agent。但CSDN上关于Agent可观测的文章,全是LangSmith的入门教程或Langfuse的部署指南,没有人告诉你"怎么从零自建一套Agent可观测体系"。本文用OpenTelemetry + Python,用零额外成本搭建Agent的日志、指标、追踪三支柱,附与LangSmith的对比决策表和GB/Z 185审计合规的映射方案。如果你已经读过我的《GB/Z 185合规的Agent Loop设计》,这篇文章就是7.1可审计性条款的"工程实现"。

文章目录

  • Agent可观测性自建方案:零成本搭建智能体全链路追踪系统(Python 3.10+ / OpenTelemetry)
    • 一、为什么Agent可观测性不是"可选",是"必需"
      • 1.1 Agent的可观测性比传统应用更复杂
      • 1.2 生产环境的真实痛点
      • 1.3 与GB/Z 185的关联:可观测性=合规要求
    • 二、Agent可观测性的三支柱模型
      • 2.1 Logs:记录"发生了什么"
      • 2.2 Metrics:量化"有多少/多快/多贵"
      • 2.3 Traces:追踪"请求走了哪条路"
    • 三、Python实现:OpenTelemetry + 自定义Agent追踪器
      • 3.1 环境准备
      • 3.2 核心实现:AgentOpenTelemetryTracer
    • 四、与GB/Z 185的映射:7.1可审计性的工程实现
    • 五、自建 vs 使用SaaS(LangSmith/Langfuse等):选型决策表
    • 六、生产级Dashboard:用Prometheus + Grafana可视化
      • 6.1 核心Dashboard设计
      • 6.2 告警规则(Alertmanager)
    • 七、总结
    • 八、适用边界与限制条件

一、为什么Agent可观测性不是"可选",是"必需"

1.1 Agent的可观测性比传统应用更复杂

传统Web应用的可观测性关注:请求入口→业务逻辑→数据库→响应。但Agent的运行轨迹完全不同:

#mermaid-svg-dDMKMwWa7QEQ2aTu{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-dDMKMwWa7QEQ2aTu .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-dDMKMwWa7QEQ2aTu .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-dDMKMwWa7QEQ2aTu .error-icon{fill:#552222;}#mermaid-svg-dDMKMwWa7QEQ2aTu .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-dDMKMwWa7QEQ2aTu .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-dDMKMwWa7QEQ2aTu .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-dDMKMwWa7QEQ2aTu .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-dDMKMwWa7QEQ2aTu .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-dDMKMwWa7QEQ2aTu .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-dDMKMwWa7QEQ2aTu .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-dDMKMwWa7QEQ2aTu .marker{fill:#333333;stroke:#333333;}#mermaid-svg-dDMKMwWa7QEQ2aTu .marker.cross{stroke:#333333;}#mermaid-svg-dDMKMwWa7QEQ2aTu svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-dDMKMwWa7QEQ2aTu p{margin:0;}#mermaid-svg-dDMKMwWa7QEQ2aTu .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-dDMKMwWa7QEQ2aTu .cluster-label text{fill:#333;}#mermaid-svg-dDMKMwWa7QEQ2aTu .cluster-label span{color:#333;}#mermaid-svg-dDMKMwWa7QEQ2aTu .cluster-label span p{background-color:transparent;}#mermaid-svg-dDMKMwWa7QEQ2aTu .label text,#mermaid-svg-dDMKMwWa7QEQ2aTu span{fill:#333;color:#333;}#mermaid-svg-dDMKMwWa7QEQ2aTu .node rect,#mermaid-svg-dDMKMwWa7QEQ2aTu .node circle,#mermaid-svg-dDMKMwWa7QEQ2aTu .node ellipse,#mermaid-svg-dDMKMwWa7QEQ2aTu .node polygon,#mermaid-svg-dDMKMwWa7QEQ2aTu .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-dDMKMwWa7QEQ2aTu .rough-node .label text,#mermaid-svg-dDMKMwWa7QEQ2aTu .node .label text,#mermaid-svg-dDMKMwWa7QEQ2aTu .image-shape .label,#mermaid-svg-dDMKMwWa7QEQ2aTu .icon-shape .label{text-anchor:middle;}#mermaid-svg-dDMKMwWa7QEQ2aTu .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-dDMKMwWa7QEQ2aTu .rough-node .label,#mermaid-svg-dDMKMwWa7QEQ2aTu .node .label,#mermaid-svg-dDMKMwWa7QEQ2aTu .image-shape .label,#mermaid-svg-dDMKMwWa7QEQ2aTu .icon-shape .label{text-align:center;}#mermaid-svg-dDMKMwWa7QEQ2aTu .node.clickable{cursor:pointer;}#mermaid-svg-dDMKMwWa7QEQ2aTu .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-dDMKMwWa7QEQ2aTu .arrowheadPath{fill:#333333;}#mermaid-svg-dDMKMwWa7QEQ2aTu .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-dDMKMwWa7QEQ2aTu .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-dDMKMwWa7QEQ2aTu .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dDMKMwWa7QEQ2aTu .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-dDMKMwWa7QEQ2aTu .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dDMKMwWa7QEQ2aTu .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-dDMKMwWa7QEQ2aTu .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-dDMKMwWa7QEQ2aTu .cluster text{fill:#333;}#mermaid-svg-dDMKMwWa7QEQ2aTu .cluster span{color:#333;}#mermaid-svg-dDMKMwWa7QEQ2aTu div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-dDMKMwWa7QEQ2aTu .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-dDMKMwWa7QEQ2aTu rect.text{fill:none;stroke-width:0;}#mermaid-svg-dDMKMwWa7QEQ2aTu .icon-shape,#mermaid-svg-dDMKMwWa7QEQ2aTu .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dDMKMwWa7QEQ2aTu .icon-shape p,#mermaid-svg-dDMKMwWa7QEQ2aTu .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-dDMKMwWa7QEQ2aTu .icon-shape .label rect,#mermaid-svg-dDMKMwWa7QEQ2aTu .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dDMKMwWa7QEQ2aTu .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-dDMKMwWa7QEQ2aTu .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-dDMKMwWa7QEQ2aTu :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

用户输入

LLM意图识别

工具调用?

调用MCP Server

工具返回结果

LLM再思考

最终输出

记忆检索

向量数据库

这个循环中,任何一个环节都可能出问题:

  • LLM意图识别:用户问"查一下数据",LLM理解为"查天气"→后面的工具调用全错
  • 工具调用参数:参数幻觉,传了{"city": "北京"}给数据库查询工具→报错
  • 记忆检索:检索到了去年的旧数据,当作当前数据使用→决策偏差
  • 循环发散:Agent在"搜索→没找到→再搜索"里循环了10轮→token烧光

没有可观测性,你只知道"输出错了",但不知道"错在哪一步"。

1.2 生产环境的真实痛点

场景用户反馈没有可观测性时有可观测性时
响应慢 “Agent回复太慢了” 不知道慢在哪 追踪显示:工具调用耗时3s,LLM推理耗时1s→优化工具端
结果错 “Agent给的数据是错的” 不知道错在哪 追踪显示:检索到了旧记忆,向量数据库中数据已过时→更新记忆
费用高 “这个Agent怎么这么贵” 不知道token花在哪 指标显示:平均每次调用消耗5000 tokens,循环迭代4次→优化Prompt缩短循环
不稳定 “有时对有时错” 无法复现问题 追踪显示:工具A有50%概率超时→增加重试机制或替换工具
安全事件 “Agent泄露了敏感信息” 无法追溯 日志显示:Agent在循环第3轮通过read_file读取了/etc/passwd→加固权限

1.3 与GB/Z 185的关联:可观测性=合规要求

GB/Z 185 7.1条款明确要求:

“全链路操作可追溯、可回放。”

这不是"建议",是强制要求。如果你的Agent在受监管的行业(金融、政务、医疗)使用,没有可观测性就是不合规。


二、Agent可观测性的三支柱模型

可观测性不是"打个日志"那么简单,而是三个支柱的协同:

#mermaid-svg-xjys12PLCr29OEqd{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-xjys12PLCr29OEqd .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-xjys12PLCr29OEqd .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-xjys12PLCr29OEqd .error-icon{fill:#552222;}#mermaid-svg-xjys12PLCr29OEqd .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-xjys12PLCr29OEqd .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-xjys12PLCr29OEqd .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-xjys12PLCr29OEqd .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-xjys12PLCr29OEqd .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-xjys12PLCr29OEqd .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-xjys12PLCr29OEqd .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-xjys12PLCr29OEqd .marker{fill:#333333;stroke:#333333;}#mermaid-svg-xjys12PLCr29OEqd .marker.cross{stroke:#333333;}#mermaid-svg-xjys12PLCr29OEqd svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-xjys12PLCr29OEqd p{margin:0;}#mermaid-svg-xjys12PLCr29OEqd .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-xjys12PLCr29OEqd .cluster-label text{fill:#333;}#mermaid-svg-xjys12PLCr29OEqd .cluster-label span{color:#333;}#mermaid-svg-xjys12PLCr29OEqd .cluster-label span p{background-color:transparent;}#mermaid-svg-xjys12PLCr29OEqd .label text,#mermaid-svg-xjys12PLCr29OEqd span{fill:#333;color:#333;}#mermaid-svg-xjys12PLCr29OEqd .node rect,#mermaid-svg-xjys12PLCr29OEqd .node circle,#mermaid-svg-xjys12PLCr29OEqd .node ellipse,#mermaid-svg-xjys12PLCr29OEqd .node polygon,#mermaid-svg-xjys12PLCr29OEqd .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-xjys12PLCr29OEqd .rough-node .label text,#mermaid-svg-xjys12PLCr29OEqd .node .label text,#mermaid-svg-xjys12PLCr29OEqd .image-shape .label,#mermaid-svg-xjys12PLCr29OEqd .icon-shape .label{text-anchor:middle;}#mermaid-svg-xjys12PLCr29OEqd .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-xjys12PLCr29OEqd .rough-node .label,#mermaid-svg-xjys12PLCr29OEqd .node .label,#mermaid-svg-xjys12PLCr29OEqd .image-shape .label,#mermaid-svg-xjys12PLCr29OEqd .icon-shape .label{text-align:center;}#mermaid-svg-xjys12PLCr29OEqd .node.clickable{cursor:pointer;}#mermaid-svg-xjys12PLCr29OEqd .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-xjys12PLCr29OEqd .arrowheadPath{fill:#333333;}#mermaid-svg-xjys12PLCr29OEqd .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-xjys12PLCr29OEqd .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-xjys12PLCr29OEqd .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xjys12PLCr29OEqd .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-xjys12PLCr29OEqd .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xjys12PLCr29OEqd .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-xjys12PLCr29OEqd .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-xjys12PLCr29OEqd .cluster text{fill:#333;}#mermaid-svg-xjys12PLCr29OEqd .cluster span{color:#333;}#mermaid-svg-xjys12PLCr29OEqd div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-xjys12PLCr29OEqd .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-xjys12PLCr29OEqd rect.text{fill:none;stroke-width:0;}#mermaid-svg-xjys12PLCr29OEqd .icon-shape,#mermaid-svg-xjys12PLCr29OEqd .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-xjys12PLCr29OEqd .icon-shape p,#mermaid-svg-xjys12PLCr29OEqd .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-xjys12PLCr29OEqd .icon-shape .label rect,#mermaid-svg-xjys12PLCr29OEqd .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-xjys12PLCr29OEqd .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-xjys12PLCr29OEqd .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-xjys12PLCr29OEqd :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

Agent可观测性三支柱

Logs 日志记录发生了什么

Metrics 指标有多少/多快/多贵

Traces 追踪请求经过的路径

LLM调用日志

工具调用日志

记忆操作日志

安全事件日志

请求QPS

Token消耗/次

循环迭代次数

错误率

请求链路图

循环迭代路径

工具调用依赖

跨Agent协作路径

2.1 Logs:记录"发生了什么"

Agent的日志不是传统的"INFO/ERROR",而是结构化的事件日志:

事件类型记录内容示例
llm_decision LLM的输入、输出、模型版本、耗时 “gpt-4o, prompt=1024tokens, output=256tokens, 1450ms”
tool_call 工具名称、参数、返回结果、耗时 “get_weather, params={city:‘北京’}, result=晴, 320ms”
memory_retrieval 查询query、返回结果、相似度分数 “query=‘用户偏好’, top3=[{id:1, score:0.92}]”
loop_iteration 循环轮次、当前状态、是否终止 “iteration=3, state=working, is_complete=false”
security_event 事件类型、风险等级、触发条件 “path_traversal_detected, level=high, path=‘/etc/passwd’”
error 异常类型、堆栈、恢复策略 “ToolTimeout, retry=2, fallback=cache”

2.2 Metrics:量化"有多少/多快/多贵"

Agent特有的指标:

指标名类型含义告警阈值
agent_request_qps Counter 每秒请求数 >100
agent_request_latency Histogram 请求响应时间(P50/P90/P99) P99>5s
agent_token_usage Counter 单次请求消耗的token数 >8000
agent_loop_iterations Histogram 循环迭代次数 >10
agent_tool_call_errors Counter 工具调用失败次数 错误率>5%
agent_memory_retrieval_relevance Gauge 检索结果平均相关度 <0.7
agent_llm_cost Counter 单次请求的LLM成本($) >$0.5

2.3 Traces:追踪"请求走了哪条路"

Trace是Agent可观测性的核心——它记录了一个请求从入口到出口的完整路径,每个环节的时间戳和上下文。

Trace: request_abc123
├── Span: user_input (0ms)
│ └── 标签: intent="sales_query"
├── Span: llm_decision_1 (50ms – 1200ms)
│ ├── 标签: model="gpt-4o", prompt_tokens=512
│ └── 事件: tool_call_decided="get_sales_data"
├── Span: tool_call_get_sales (1200ms – 3200ms)
│ ├── 标签: tool="get_sales_data", params="{quarter:'Q2'}"
│ └── 事件: result_size=1024
├── Span: memory_retrieval (3200ms – 3400ms)
│ ├── 标签: query="用户偏好", top_k=3
│ └── 事件: results=[{id:1, score:0.92}, …]
├── Span: llm_decision_2 (3400ms – 4200ms)
│ └── 标签: is_complete=true
└── Span: final_output (4200ms – 4250ms)
└── 标签: output_tokens=256, total_cost=$0.023


三、Python实现:OpenTelemetry + 自定义Agent追踪器

3.1 环境准备

⚠️ 以下依赖锁定版本号,确保可复现。

# 安装核心依赖(锁定版本)
pip install opentelemetry-api==1.27.0 opentelemetry-sdk==1.27.0
pip install opentelemetry-exporter-otlp==1.27.0

# 验证安装
python -c "import opentelemetry; print(opentelemetry.__version__)"
# 预期输出:1.27.0

3.2 核心实现:AgentOpenTelemetryTracer

from opentelemetry import trace, metrics
from opentelemetry.sdk.trace import TracerProvider, SpanProcessor
from opentelemetry.sdk.trace.export import BatchSpanProcessor, ConsoleSpanExporter
from opentelemetry.sdk.metrics import MeterProvider
from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader, ConsoleMetricExporter
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.exporter.prometheus import PrometheusMetricReader
from contextlib import contextmanager
from typing import Dict, Any, Optional, List
from datetime import datetime
import json
import time
import uuid

class AgentOpenTelemetryTracer:
"""
Agent可观测性追踪器:基于OpenTelemetry的Agent全链路追踪。

功能:
– Trace:请求链路追踪(Span嵌套)
– Metrics:Agent特有指标采集
– Logs:结构化事件日志
"""

def __init__(self, service_name: str = "agent_service",
export_to_console: bool = True,
export_to_otlp: bool = False,
otlp_endpoint: str = "http://localhost:4317"):
self.service_name = service_name

# 1. 初始化Trace Provider
self.trace_provider = TracerProvider()

if export_to_console:
self.trace_provider.add_span_processor(
BatchSpanProcessor(ConsoleSpanExporter())
)

if export_to_otlp:
otlp_exporter = OTLPSpanExporter(endpoint=otlp_endpoint)
self.trace_provider.add_span_processor(
BatchSpanProcessor(otlp_exporter)
)

trace.set_tracer_provider(self.trace_provider)
self.tracer = trace.get_tracer(service_name)

# 2. 初始化Metrics Provider
metric_readers = []
if export_to_console:
metric_readers.append(PeriodicExportingMetricReader(ConsoleMetricExporter()))

self.metrics_provider = MeterProvider(metric_readers=metric_readers)
metrics.set_meter_provider(self.metrics_provider)
self.meter = metrics.get_meter(service_name)

# 3. 定义Agent特有指标
self._init_metrics()

def _init_metrics(self):
"""初始化Agent特有指标。"""
# 请求计数器
self.request_counter = self.meter.create_counter(
"agent.request.count",
description="Agent请求总数"
)

# Token使用量
self.token_counter = self.meter.create_counter(
"agent.token.usage",
description="Agent Token使用量",
unit="token"
)

# 请求耗时直方图
self.request_latency = self.meter.create_histogram(
"agent.request.latency",
description="Agent请求耗时",
unit="ms"
)

# 循环迭代次数
self.loop_iterations = self.meter.create_histogram(
"agent.loop.iterations",
description="Agent循环迭代次数"
)

# 工具调用错误率
self.tool_error_counter = self.meter.create_counter(
"agent.tool.error",
description="Agent工具调用失败次数"
)

# 检索相关度
self.retrieval_relevance = self.meter.create_histogram(
"agent.memory.retrieval_relevance",
description="记忆检索结果平均相关度"
)

@contextmanager
def trace_request(self, user_input: str, request_id: Optional[str] = None):
"""
追踪一次完整的Agent请求(Trace根Span)。

使用方式:
with tracer.trace_request("查北京天气") as ctx:
# Agent执行逻辑…
pass
"""
req_id = request_id or str(uuid.uuid4())[:8]

with self.tracer.start_as_current_span(
name="agent_request",
attributes={
"agent.request_id": req_id,
"agent.user_input": user_input[:100],
"agent.input_length": len(user_input),
}
) as span:
start_time = time.time()

try:
yield {
"request_id": req_id,
"span": span,
"start_time": start_time
}

# 请求成功
span.set_attribute("agent.status", "success")
self.request_counter.add(1, {"status": "success"})

except Exception as e:
# 请求失败
span.set_attribute("agent.status", "error")
span.set_attribute("agent.error", str(e)[:200])
span.record_exception(e)
self.request_counter.add(1, {"status": "error"})
raise

finally:
duration = (time.time() start_time) * 1000
self.request_latency.record(duration)
span.set_attribute("agent.duration_ms", duration)

@contextmanager
def trace_llm_call(self, model: str, prompt_length: int):
"""追踪一次LLM调用(子Span)。"""
with self.tracer.start_as_current_span(
name="llm_call",
attributes={
"llm.model": model,
"llm.prompt_length": prompt_length,
}
) as span:
start_time = time.time()

try:
yield span

finally:
duration = (time.time() start_time) * 1000
span.set_attribute("llm.duration_ms", duration)

def record_llm_output(self, span, output_tokens: int, output_text: str):
"""记录LLM输出结果。"""
span.set_attribute("llm.output_tokens", output_tokens)
span.set_attribute("llm.output_length", len(output_text))
self.token_counter.add(output_tokens, {"component": "llm_output"})

@contextmanager
def trace_tool_call(self, tool_name: str, params: Dict):
"""追踪一次工具调用(子Span)。"""
with self.tracer.start_as_current_span(
name="tool_call",
attributes={
"tool.name": tool_name,
"tool.params": json.dumps(params, ensure_ascii=False)[:500],
}
) as span:
start_time = time.time()

try:
yield span
span.set_attribute("tool.status", "success")

except Exception as e:
span.set_attribute("tool.status", "error")
span.set_attribute("tool.error", str(e)[:200])
self.tool_error_counter.add(1, {"tool": tool_name})
raise

finally:
duration = (time.time() start_time) * 1000
span.set_attribute("tool.duration_ms", duration)

def record_tool_result(self, span, result: Any, result_size: int = 0):
"""记录工具返回结果。"""
result_preview = str(result)[:200] if result else "None"
span.set_attribute("tool.result_preview", result_preview)
span.set_attribute("tool.result_size", result_size)

def record_memory_retrieval(self, query: str, results: List[Dict]):
"""记录记忆检索操作。"""
with self.tracer.start_as_current_span("memory_retrieval") as span:
span.set_attribute("memory.query", query[:100])
span.set_attribute("memory.result_count", len(results))

if results:
avg_score = sum(r.get("score", 0) for r in results) / len(results)
span.set_attribute("memory.avg_relevance", avg_score)
self.retrieval_relevance.record(avg_score)

def record_loop_iteration(self, iteration: int, state: str, is_complete: bool):
"""记录循环迭代状态。"""
with self.tracer.start_as_current_span("loop_iteration") as span:
span.set_attribute("loop.iteration", iteration)
span.set_attribute("loop.state", state)
span.set_attribute("loop.is_complete", is_complete)

if is_complete:
self.loop_iterations.record(iteration)

def record_security_event(self, event_type: str, risk_level: str, details: str):
"""记录安全事件。"""
with self.tracer.start_as_current_span("security_event") as span:
span.set_attribute("security.event_type", event_type)
span.set_attribute("security.risk_level", risk_level)
span.set_attribute("security.details", details[:200])
span.set_status(trace.Status(trace.StatusCode.ERROR, f"Security: {event_type}"))

def get_trace_context(self) > Dict[str, str]:
"""获取当前Trace上下文(用于跨Agent传递)。"""
current_span = trace.get_current_span()
trace_id = current_span.get_span_context().trace_id
span_id = current_span.get_span_context().span_id
return {
"trace_id": format(trace_id, '032x'),
"span_id": format(span_id, '016x'),
}

# ========== 使用示例:与Agent Loop整合 ==========
class ObservableAgent:
"""集成了可观测性的Agent Loop。"""

def __init__(self, llm, tools, tracer: AgentOpenTelemetryTracer):
self.llm = llm
self.tools = tools
self.tracer = tracer

def run(self, user_input: str) > str:
"""运行Agent,全链路被追踪。"""
with self.tracer.trace_request(user_input) as ctx:
request_id = ctx["request_id"]
print(f"[Request {request_id}] 开始处理: {user_input[:50]}…")

# 1. LLM决策
with self.tracer.trace_llm_call("gpt-4o", len(user_input)) as llm_span:
# 模拟LLM调用
time.sleep(0.1)
decision = "调用get_weather工具"
self.tracer.record_llm_output(llm_span, output_tokens=50, output_text=decision)

# 2. 工具调用
with self.tracer.trace_tool_call("get_weather", {"city": "北京"}) as tool_span:
time.sleep(0.2)
result = {"temperature": 28, "condition": "晴"}
self.tracer.record_tool_result(tool_span, result, result_size=256)

# 3. 记忆检索(可选)
self.tracer.record_memory_retrieval(
query="用户偏好",
results=[{"id": 1, "score": 0.92, "content": "喜欢深色模式"}]
)

# 4. 循环迭代记录
self.tracer.record_loop_iteration(
iteration=1, state="completed", is_complete=True
)

final_output = "北京今天晴,气温28°C。"
print(f"[Request {request_id}] 完成: {final_output}")
return final_output

# ========== 演示 ==========
if __name__ == "__main__":
# 创建追踪器(输出到控制台,便于演示)
tracer = AgentOpenTelemetryTracer(
service_name="weather_agent",
export_to_console=True,
export_to_otlp=False
)

# 创建Agent
agent = ObservableAgent(llm=None, tools=[], tracer=tracer)

# 运行请求
result = agent.run("帮我查一下北京天气")
print(f"\\n最终输出: {result}")


四、与GB/Z 185的映射:7.1可审计性的工程实现

GB/Z 185 7.1条款要求"全链路操作可追溯"。本文的可观测性体系直接对应:

GB/Z 185 7.1要求可观测性支柱本文实现合规价值
操作可追溯 Logs record_security_event + trace_tool_call 每条操作都有时间戳、操作者、结果
操作可回放 Traces trace_request的完整Span链 给定request_id,可以回放完整执行路径
异常可定位 Metrics + Traces tool_error_counter + span.record_exception 错误率和错误类型实时可见
责任可认定 Logs trace_context传递Agent ID 跨Agent协作时,可以追溯每个环节的责任方
数据可审计 Logs record_memory_retrieval + 敏感操作日志 记忆检索、工具调用、LLM决策都有记录

五、自建 vs 使用SaaS(LangSmith/Langfuse等):选型决策表

维度自建方案(本文)LangSmith(SaaS)Langfuse(开源)
成本 零额外成本(OpenTelemetry免费) 按量付费($0.005/trace起) 自托管免费,云服务按量
数据隐私 数据完全在本地 数据上传到第三方服务器 自托管数据在本地
定制化 完全可定制(Agent特有指标) 有限定制(用他们定义的指标) 中等定制
集成复杂度 中(需自己写代码) 低(SDK集成) 低-中(SDK集成)
Dashboard 需自己搭建(Prometheus+Grafana) 开箱即用 开箱即用
告警能力 强(Prometheus Alertmanager) 中(内置告警) 中(内置告警)
学习曲线 陡峭(需理解OpenTelemetry) 平缓 平缓
适用场景 数据敏感、深度定制、成本敏感 快速启动、不想运维 数据敏感但不想自建全栈

建议:

  • 快速验证:先用LangSmith SDK跑通,验证可观测性的价值
  • 生产落地:数据敏感或成本敏感时,迁移到本文的自建方案
  • 混合方案:LangSmith做快速排查,自建方案做深度分析和合规审计

六、生产级Dashboard:用Prometheus + Grafana可视化

6.1 核心Dashboard设计

# 如果使用Prometheus导出,可以这样定义Dashboard面板
AGENT_DASHBOARD_PANELS = [
{
"title": "Agent请求QPS",
"query": "rate(agent_request_count[1m])",
"type": "graph",
"alert": "QPS > 100"
},
{
"title": "请求延迟P99",
"query": "histogram_quantile(0.99, rate(agent_request_latency_bucket[5m]))",
"type": "graph",
"alert": "P99 > 5000ms"
},
{
"title": "Token消耗/请求",
"query": "rate(agent_token_usage[5m]) / rate(agent_request_count[5m])",
"type": "stat",
"alert": "> 8000"
},
{
"title": "工具调用错误率",
"query": "rate(agent_tool_error[5m]) / rate(agent_tool_call_count[5m])",
"type": "graph",
"alert": "> 0.05"
},
{
"title": "记忆检索相关度",
"query": "avg(agent_memory_retrieval_relevance)",
"type": "gauge",
"alert": "< 0.7"
},
{
"title": "循环迭代次数",
"query": "avg(agent_loop_iterations)",
"type": "graph",
"alert": "> 10"
}
]

6.2 告警规则(Alertmanager)

# agent_alerts.yml
groups:
name: agent_alerts
rules:
alert: AgentHighLatency
expr: histogram_quantile(0.99, rate(agent_request_latency_bucket[5m])) > 5000
for: 5m
labels:
severity: warning
annotations:
summary: "Agent请求延迟过高"
description: "P99延迟超过5秒,当前值: {{ $value }}ms"

alert: AgentHighErrorRate
expr: rate(agent_tool_error[5m]) / rate(agent_tool_call_count[5m]) > 0.05
for: 5m
labels:
severity: critical
annotations:
summary: "Agent工具调用错误率过高"
description: "错误率超过5%,当前值: {{ $value }}"

alert: AgentHighTokenUsage
expr: rate(agent_token_usage[5m]) / rate(agent_request_count[5m]) > 8000
for: 10m
labels:
severity: warning
annotations:
summary: "Agent Token消耗过高"
description: "平均每次请求消耗超过8000 tokens,当前值: {{ $value }}"


七、总结

本文是一篇从零自建Agent可观测性的实战文章,核心交付:

交付物工程价值
AgentOpenTelemetryTracer 基于OpenTelemetry的Agent追踪器,三支柱全覆盖
trace_request 请求级Trace(根Span)
trace_llm_call LLM调用追踪
trace_tool_call 工具调用追踪
record_memory_retrieval 记忆检索追踪
record_loop_iteration 循环迭代追踪
record_security_event 安全事件追踪
Dashboard面板定义 6个核心面板(QPS/延迟/Token/错误率/相关度/迭代次数)
告警规则 3个核心告警(延迟/错误率/Token消耗)

核心结论:

  • Agent可观测性不是"锦上添花",是"生产必需":没有它,你无法定位问题、优化性能、通过合规审计
  • OpenTelemetry是自建方案的最佳选择:开源、标准、零成本、可扩展
  • Trace是Agent可观测性的核心:Logs和Metrics告诉你"什么"和"多少",Trace告诉你"为什么"和"怎么走的"
  • 与GB/Z 185 7.1直接对应:自建可观测性体系就是合规要求的工程实现
  • 八、适用边界与限制条件

    自建可观测性方案并非适合所有场景:

    场景自建方案的局限建议
    单Agent调试 完整的Tracing过于重量级 只使用 trace_request + trace_llm_call,跳过Metrics和Dashboard
    无运维能力团队 Prometheus+Grafana维护成本高 先用LangSmith快速验证,再迁移自建方案
    需要实时告警 自建方案告警规则需手动配置 使用Prometheus Alertmanager + 飞书/钉钉Webhook
    跨Agent追踪 需要手动传递Trace Context 参考 get_trace_context() 方法在A2A协议中传递trace_id
    已有APM系统(Datadog等) 不需要再自建一套 将OpenTelemetry Exporter指向已有APM系统

    ⚠️ OpenTelemetry版本兼容性:opentelemetry-api==1.27.0 与 opentelemetry-sdk==1.27.0 版本必须匹配。不同大版本之间API不兼容,升级时注意同时更新所有子包。


    相关阅读:

    • GB/Z 185合规的Agent Loop设计(标准合规——7.1可审计性的条款解读)
    • Loop Engineering四层架构(循环控制——循环迭代次数是可观测性指标之一)
    • MCP Server安全加固(安全层——安全事件是可观测性的重要组成部分)
    • Agent评估体系自建指南(评估体系——可观测性数据是评估的输入来源)

    你的Agent现在有可观测性吗? 是打日志、用LangSmith、还是完全没管?评论区说说你的现状——如果还在"裸奔",打算怎么开始?我针对高频场景整理一份"Agent可观测性5分钟快速启动脚本",复制粘贴就能跑。

    收藏这篇Agent可观测性自建指南,上线前把可观测性补上,出了问题才能"有据可查"。觉得有用的话点赞+收藏,让更多开发者看到这篇Agent生产化的基础设施指南。


    📅 更新日志:

    日期更新内容
    2026-07 初始发布(基于Python 3.10+ / OpenTelemetry 1.27.0)

    ⚠️ 版本变更提示:本文基于OpenTelemetry 1.27.0。如果SDK大版本升级(如2.0.0),TracerProvider和MeterProvider的初始化方式可能变化。OpenTelemetry的三大子包(api/sdk/exporter)版本必须保持一致。Prometheus+Grafana集成需分别安装。

    赞(0)
    未经允许不得转载:171主机测评 » Agent可观测性自建方案:零成本搭建智能体全链路追踪系统(Python 3.10+ / OpenTelemetry)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址