Agent 可观测性(Observability):分布式追踪、链路诊断与 Token 成本精细化核算

作 者:吴佳浩(Alben)
微某信公众某号:全栈架构师笔记
系列专栏:《企业级 Agent 质量保障体系:Evaluation 与安全防线》· 第 02 篇
导读
传统的 APM 监控只看 HTTP 状态码与响应延迟,Agent 的可观测性必须深入到 Prompt 变迁、Tool 耗时分布、LLM 思考时间与 Token 成本归因。
线上 Agent 变慢了,到底是模型推理卡住、工具外部接口超时、还是检索召回把垃圾塞满了 Prompt?
没有可观测性的 Agent 系统是一个黑盒,基于 OpenTelemetry 构建全链路分布式追踪是精细化运营与降本增效的唯一底座。
在企业落地 Agent 后,运维团队和业务主管最常问研发架构师三个灵魂拷问:
| 1. 性能黑盒排查难 | 一次任务执行耗时 20 秒,无法定位 | 缺乏统一的 Span 追踪树, |
| (Performance Blind) | 是模型 Prefill、工具还是网关慢 | 模型生成与工具执行时间混为一谈 |
| 2. 成本无法精细分摊 | 月度 API 账单暴涨数十万,无法按 | 缺乏基于 Tenant / User / Task |
| (Cost Attribution) | 部门、项目进行多维 Token 核算 | 的细粒度 Token 成本标记与归因 |
| 3. 现场难以复现 | 线上用户反馈一次执行错误,因为 | 缺乏脱敏后的完整历史 Trace 回放 |
| (Trace Reproduce) | 丢失了当时的环境上下文无法复原 | (Replay) 机制 |
为了让 Agent 系统从“黑盒运转”走向“全透明治理”,必须基于 OpenTelemetry(OTel) 标准构建专属的 Agent Observability 体系。
一、Agent 可观测性的核心维度:Trace 树状拓扑模型
在传统的微服务调用中,链路通常是扁平的请求-响应。而在 Agent 场景下,一次用户请求会演化为一棵深度嵌套的 树状执行轨迹(Execution Trace Tree):
Trace: user_request_123 (Total: 4.8s / 4200 Tokens / $0.032)
├── Span: Memory Recall (0.2s / 500 Tokens Injected)
├── Span: LLM Reasoning Turn 1 (1.8s / TTFT: 0.3s / 1200 Tokens)
│ └── Call Tool: search_files (0.1s)
├── Span: LLM Reasoning Turn 2 (1.5s / TTFT: 0.25s / 1800 Tokens)
│ └── Call Tool: run_bash (pytest) (0.9s / exit_code: 0)
└── Span: Final Synthesis Turn 3 (0.3s / 700 Tokens)
#mermaid-svg-TmZhVQjqato7SY0C{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-TmZhVQjqato7SY0C .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-TmZhVQjqato7SY0C .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-TmZhVQjqato7SY0C .error-icon{fill:#552222;}#mermaid-svg-TmZhVQjqato7SY0C .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-TmZhVQjqato7SY0C .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-TmZhVQjqato7SY0C .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-TmZhVQjqato7SY0C .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-TmZhVQjqato7SY0C .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-TmZhVQjqato7SY0C .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-TmZhVQjqato7SY0C .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-TmZhVQjqato7SY0C .marker{fill:#333333;stroke:#333333;}#mermaid-svg-TmZhVQjqato7SY0C .marker.cross{stroke:#333333;}#mermaid-svg-TmZhVQjqato7SY0C svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-TmZhVQjqato7SY0C p{margin:0;}#mermaid-svg-TmZhVQjqato7SY0C .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-TmZhVQjqato7SY0C .cluster-label text{fill:#333;}#mermaid-svg-TmZhVQjqato7SY0C .cluster-label span{color:#333;}#mermaid-svg-TmZhVQjqato7SY0C .cluster-label span p{background-color:transparent;}#mermaid-svg-TmZhVQjqato7SY0C .label text,#mermaid-svg-TmZhVQjqato7SY0C span{fill:#333;color:#333;}#mermaid-svg-TmZhVQjqato7SY0C .node rect,#mermaid-svg-TmZhVQjqato7SY0C .node circle,#mermaid-svg-TmZhVQjqato7SY0C .node ellipse,#mermaid-svg-TmZhVQjqato7SY0C .node polygon,#mermaid-svg-TmZhVQjqato7SY0C .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-TmZhVQjqato7SY0C .rough-node .label text,#mermaid-svg-TmZhVQjqato7SY0C .node .label text,#mermaid-svg-TmZhVQjqato7SY0C .image-shape .label,#mermaid-svg-TmZhVQjqato7SY0C .icon-shape .label{text-anchor:middle;}#mermaid-svg-TmZhVQjqato7SY0C .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-TmZhVQjqato7SY0C .rough-node .label,#mermaid-svg-TmZhVQjqato7SY0C .node .label,#mermaid-svg-TmZhVQjqato7SY0C .image-shape .label,#mermaid-svg-TmZhVQjqato7SY0C .icon-shape .label{text-align:center;}#mermaid-svg-TmZhVQjqato7SY0C .node.clickable{cursor:pointer;}#mermaid-svg-TmZhVQjqato7SY0C .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-TmZhVQjqato7SY0C .arrowheadPath{fill:#333333;}#mermaid-svg-TmZhVQjqato7SY0C .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-TmZhVQjqato7SY0C .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-TmZhVQjqato7SY0C .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TmZhVQjqato7SY0C .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-TmZhVQjqato7SY0C .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TmZhVQjqato7SY0C .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-TmZhVQjqato7SY0C .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-TmZhVQjqato7SY0C .cluster text{fill:#333;}#mermaid-svg-TmZhVQjqato7SY0C .cluster span{color:#333;}#mermaid-svg-TmZhVQjqato7SY0C 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-TmZhVQjqato7SY0C .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-TmZhVQjqato7SY0C rect.text{fill:none;stroke-width:0;}#mermaid-svg-TmZhVQjqato7SY0C .icon-shape,#mermaid-svg-TmZhVQjqato7SY0C .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TmZhVQjqato7SY0C .icon-shape p,#mermaid-svg-TmZhVQjqato7SY0C .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-TmZhVQjqato7SY0C .icon-shape .label rect,#mermaid-svg-TmZhVQjqato7SY0C .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TmZhVQjqato7SY0C .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-TmZhVQjqato7SY0C .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-TmZhVQjqato7SY0C :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
Root Trace: Agent Session Task (4.8s)
Span 3: ReAct Turn 2 (2.4s)
LLM Generation (1.5s / 1800 Tokens)
Tool Execution: run_bash (pytest) (900ms)
Span 1: Memory & Context Assembly (200ms)
Span 2: ReAct Turn 1 (1.8s)
LLM Generation (1.5s / 1200 Tokens)
Tool Execution: search_files (300ms)
- 🔸 耗时拆解(Latency Breakdown):将总耗时精确拆解为 Memory 检索耗时、模型首字延迟(TTFT)、模型 Token 生成耗时、工具外部 I/O 耗时;
- 🔸 Token 与成本归因(Token & Cost Attribution):记录每一轮的 Input Tokens、Output Tokens 以及 Cache Hit Tokens,按模型单价实时换算美元/人民币成本;
- 🔸 状态与断言归档(State Snapshotting):在每个 Span 上记录工具调用的入参、回包截断快照与真实退出码。
一句话总结这一章的核心观点:
树状 Span 结构是看清 Agent 推理与执行全生命周期的唯一透视镜。
二、生产级代码实战:基于 OpenTelemetry 标准的 Agent 追踪器
以下为基于 Python 3.11+ 构建的轻量级 Agent OTel 追踪器实现,支持嵌套 Span 计时、Token 消耗统计与成本精细核算:
"""
agent_observability_tracer.py – 生产级 Agent 链路追踪与成本核算引擎
基于 OpenTelemetry 标准模型设计,支持树状 Span、Token 统计与链路复盘
"""
import time
import uuid
from datetime import datetime
from typing import Any, Dict, List, Optional
from pydantic import BaseModel, Field
class SpanRecord(BaseModel):
span_id: str = Field(default_factory=lambda: str(uuid.uuid4())[:8])
parent_id: Optional[str] = None
name: str
start_time: float
end_time: Optional[float] = None
duration_ms: Optional[float] = None
input_tokens: int = 0
output_tokens: int = 0
estimated_cost_usd: float = 0.0
attributes: Dict[str, Any] = Field(default_factory=dict)
class AgentTraceContext:
"""单个 Agent 执行链路追踪上下文"""
def __init__(self, trace_id: str, tenant_id: str, user_id: str):
self.trace_id = trace_id
self.tenant_id = tenant_id
self.user_id = user_id
self.spans: List[SpanRecord] = []
self._active_span_stack: List[SpanRecord] = []
def start_span(self, name: str, attributes: Optional[Dict[str, Any]] = None) –> SpanRecord:
parent_id = self._active_span_stack[–1].span_id if self._active_span_stack else None
span = SpanRecord(
parent_id=parent_id,
name=name,
start_time=time.time(),
attributes=attributes or {}
)
self.spans.append(span)
self._active_span_stack.append(span)
return span
def end_span(
self,
input_tokens: int = 0,
output_tokens: int = 0,
input_price_per_1m: float = 2.5,
output_price_per_1m: float = 10.0,
extra_attrs: Optional[Dict[str, Any]] = None
) –> None:
if not self._active_span_stack:
return
span = self._active_span_stack.pop()
span.end_time = time.time()
span.duration_ms = round((span.end_time – span.start_time) * 1000.0, 2)
span.input_tokens = input_tokens
span.output_tokens = output_tokens
# 成本核算
cost = (input_tokens / 1_000_000 * input_price_per_1m) + (output_tokens / 1_000_000 * output_price_per_1m)
span.estimated_cost_usd = round(cost, 6)
if extra_attrs:
span.attributes.update(extra_attrs)
def export_summary(self) –> Dict[str, Any]:
total_duration = sum(s.duration_ms or 0 for s in self.spans if s.parent_id is None)
total_tokens = sum(s.input_tokens + s.output_tokens for s in self.spans)
total_cost = sum(s.estimated_cost_usd for s in self.spans)
return {
"trace_id": self.trace_id,
"tenant_id": self.tenant_id,
"user_id": self.user_id,
"total_spans": len(self.spans),
"total_tokens": total_tokens,
"total_cost_usd": round(total_cost, 4),
"spans_detail": [s.model_dump() for s in self.spans]
}
本篇总结
- 🔸 传统的 APM 测不出 Agent 的病因:必须将监控细化到模型推理、工具耗时与 Token 消耗;
- 🔸 树状 Trace 模型是标准:将每一次 ReAct 循环分解为独立的子 Span;
- 🔸 Token 成本必须按租户与任务实时核算,杜绝企业账单黑天鹅;
- 🔸 建立全链路 Trace 回放与可观测性面板,是线上稳定运行的护航舰。
筒子们本篇为《企业级 Agent 实战指南》· 第四章的第 2 篇,后续续会更新完整的agent的开发的全部过程,如果你对Agent开发感兴趣不妨关注一下本合集。
在下一篇中,我们将深入安全深水区:《Agent 安全红线:越狱防御、间接注入与数据防泄漏实战》!





