深入解析 AI 应用可观测性:OpenTelemetry GenAI 规范下的调用链追踪与 Token 成本治理
当你的 Agent 花了 45 秒才回答一个简单问题,问题出在哪?是模型太慢、工具调用卡顿,还是陷入了重试循环?在 LLM 应用里,没有可观测性,你只能靠猜。
一、为什么传统 APM 在 LLM 应用面前失效
传统 APM(应用性能监控)建立在一个隐含假设之上:系统是确定性的------相同输入产生相同输出,延迟由 CPU、I/O、网络决定,成本以请求数计费。LLM 应用把这四条假设全部打破了:
| 维度 | 传统应用 | LLM 应用 |
|---|---|---|
| 输出 | 确定性 | 非确定性,需记录模型版本 + 温度才能复现 |
| 延迟主因 | CPU、I/O、网络 | Token 数量、首 Token 时间(TTFT) |
| 成本单位 | QPS、请求数 | Token(输入/输出/缓存分档计价) |
| 故障形态 | 异常、超时 | 静默劣化:幻觉、截断、死循环 |
| 调试线索 | 堆栈追踪 | 完整 Prompt + 上下文快照 |
| 数据敏感性 | URL、用户 ID | Prompt 可能含 PII 和业务机密 |
其中最致命的是静默劣化 。一个回答被 max_tokens 截断的请求,在所有标准监控面板上都显示为"成功"------API 返回 200,没有异常,没有超时,但用户拿到的是半截答案。这类故障无法靠异常率发现,必须靠专门的信号设计。
另一个被低估的问题是成本结构。一次携带长文档上下文的请求,成本可能是普通请求的 30 倍,而响应长度完全相同。请求速率监控对这种差异完全失明------这就是为什么"昨天还好好的,今天账单翻倍"在 LLM 应用里反复上演。
二、可观测性三大信号在 LLM 场景的重定义
可观测性的经典三大信号------Traces、Metrics、Logs------在 LLM 场景下都需要重新定义:
- Traces :一次用户请求对应一条完整 Trace,必须覆盖:组装后的 Prompt、模型 ID 与 Prompt 模板版本、检索到的文档 Chunk、每次工具调用及其结果、停止原因(finish_reason)。对 Agent 系统而言,每一轮推理循环都要有独立 Span,否则无法定位"在哪一轮跑偏了"。
- Metrics:传统 QPS 之外,需要新增 Token 用量直方图、TTFT、操作时长直方图、缓存命中率、工具调用错误率、循环上限触发次数。
- Logs:从"文本日志"升级为结构化事件------每条记录必须携带 request_id、模型、Token 数、成本,才能支撑后续的聚合分析。
三、OpenTelemetry GenAI 语义规范:调用链的事实标准
过去每家可观测性厂商都为"哪次调用用了什么模型、花了多少 Token"发明自己的字段名,切换后端意味着重新插桩。2026 年,OpenTelemetry GenAI SIG 的语义规范已成为事实上的厂商中立标准------LangGraph、CrewAI、AutoGen 等框架,以及 Datadog、Grafana、Langfuse、Phoenix 等后端均相继原生支持。它的核心价值是:一次插桩,任意后端可读。
3.1 关键属性
一次 LLM 调用的 Span 主要携带:
| 属性 | 含义 |
|---|---|
gen_ai.operation.name |
操作类型:chat / embeddings / execute_tool / invoke_agent |
gen_ai.provider.name |
提供方标识:openai / anthropic / aws.bedrock |
gen_ai.request.model / gen_ai.response.model |
请求与实际响应的模型 |
gen_ai.request.temperature / top_p / max_tokens |
采样参数 |
gen_ai.usage.input_tokens / output_tokens |
Token 用量 |
gen_ai.response.finish_reasons |
停止原因:stop / length / tool_calls |
gen_ai.tool.name / gen_ai.tool.call.arguments / gen_ai.tool.call.result |
工具调用详情 |
gen_ai.agent.name |
Agent 标识 |
3.2 Span 层级设计:Agent 是一等公民
与传统分布式追踪"入口 Span → 子 Span"的模型相比,GenAI 规范新增了两个关键原语:Agent Span (聚合一个完整推理循环)和 Tool Span(与 LLM Span 平级)。一条多步 Agent 请求的 Trace 结构如下:
ini
Trace: "帮我分析这份财报的风险点"
│
├─ invoke_agent "financial-analyst" ← Agent Span
│ │
│ ├─ chat [gpt-4o] in=1,842 out=312 ← 规划:拆解任务
│ ├─ execute_tool "query_db" 850ms ← 查数据库
│ ├─ chat [gpt-4o] in=2,105 out=156 ← 中间推理
│ ├─ execute_tool "web_search" 1,200ms ← 补充检索
│ └─ chat [gpt-4o] in=2,890 out=640 ← 最终生成
│
└─ Trace 汇总: 3 次 LLM 调用 / 2 次工具 / in=6,837 out=1,108
这个层级带来的直接能力:每条 Trace 的 Token 用量可以在 Span 树上逐层聚合 ,从单次 LLM 调用 → 单个 Agent 循环 → 整条用户请求,成本归因水到渠成。对于跨服务多智能体系统(Router Agent 把任务委派给 Research Agent),通过 W3C traceparent 头传播上下文,整条链路依然能串成一条 Trace。规范还为 MCP 协议提供了工具发现与执行的结构化插桩------这与之前我们讨论的 MCP 无状态架构恰好形成"协议层 + 可观测层"的互补。
3.3 隐私设计:内容采集必须 opt-in
Prompt 和 Completion 往往包含用户 PII 与业务机密。GenAI 规范在设计上遵循一个铁律:默认只采集元数据(模型名、Token 数、时长),内容采集必须显式 opt-in (gen_ai.system_instructions、gen_ai.input.messages、gen_ai.output.messages 等属性)。规范在演进过程中,内容记录从 Span Events 迁移到了结构化 Span Attributes,但无论哪种形式,工程上的正确姿势是:
- 内容记录单独开关,只在受控环境开启;
- 在采集层统一做截断与脱敏,不依赖业务开发者自觉;
- 永远不要把大段 Completion 塞进会被索引、有大小限制、长期存储的普通属性里。
四、生产级分层架构
一个可落地的 LLM 可观测性系统分为四层:
css
┌──────────────────────────────────────────────────────┐
│ 应用层 自动插桩: opentelemetry-instrumentation- │
│ {openai,anthropic,langchain,llamaindex} │
│ 手动插桩: 业务步骤 Span + 归因元数据注入 │
├──────────────────────────────────────────────────────┤
│ 采集层 OTel SDK: BatchSpanProcessor + 采样策略 │
│ head-based 10%~30% / tail-based 错误全采 │
├──────────────────────────────────────────────────────┤
│ 处理层 OTel Collector: PII 脱敏 / 长度截断 / │
│ 元数据丰富化(user_id, feature, env) │
├──────────────────────────────────────────────────────┤
│ 展示层 Langfuse(Prompt 级调试) + Grafana(SLO 级监控) │
│ 同一份 gen_ai.* 属性,双后端扇出 │
└──────────────────────────────────────────────────────┘
三个架构要点:
- 根 Span 必须在请求生命周期最开始创建。BatchSpanProcessor 会缓冲子 Span 直到父 Span 到达,根 Span 创建太晚会静默导致聚合失败。
- 采样分级:开发/预发环境 100% 采样;生产环境 head-based 采 10%~30% 控制成本,tail-based 对错误与高延迟请求全量保留------出问题的 Trace 恰恰是最值得保留的。
- 双后端扇出 :LLM 原生平台擅长"对话级调试",通用平台擅长"告警与 SLO"。OTel Collector 把同一份数据发给两者,两边基于相同的
gen_ai.*属性,成本报表一条查询通用。
五、代码示例:从自动插桩到手动埋点
自动插桩最快路径------三行代码让所有 API 调用被追踪:
python
from opentelemetry.instrumentation.openai import OpenAIInstrumentor
from opentelemetry.instrumentation.langchain import LangChainInstrumentor
OpenAIInstrumentor().instrument() # 之后的每次 OpenAI 调用自动生成 Span
LangChainInstrumentor().instrument()
生产系统还需要手动插桩补足自动插桩覆盖不到的部分:自定义检索步骤、业务归因、评估打分:
python
from opentelemetry import trace
tracer = trace.get_tracer("ai-service")
def call_llm(prompt: str, model: str = "gpt-4o", feature: str = "qa") -> str:
with tracer.start_as_current_span("llm.chat") as span:
span.set_attributes({
"gen_ai.operation.name": "chat",
"gen_ai.request.model": model,
"app.feature": feature, # 业务归因:这次调用属于哪个功能
})
resp = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": prompt}],
)
span.set_attributes({
"gen_ai.usage.input_tokens": resp.usage.prompt_tokens,
"gen_ai.usage.output_tokens": resp.usage.completion_tokens,
"gen_ai.response.finish_reasons": resp.choices[0].finish_reason,
})
return resp.choices[0].message.content
注意 app.feature 这个自定义属性------它就是下文成本归因的关键。
六、四个"隐形故障"监控信号
结构化指标里,最容易被忽略但价值最高的四个:
1. finish_reason 分布 。length 占比超过 5% 就应告警------这意味着模型在完成输出前被截断,用户拿到的是残次品,但所有错误率面板都显示正常。修复手段通常是调大 max_tokens 或改写 Prompt 让输出更精炼。
2. TTFT(首 Token 时间)。流式场景下,用户感知的"快慢"主要是首 Token 延迟而非总时长。总延迟 5 秒但首 Token 300ms 的体验,远好于总延迟 3 秒但白屏 2.8 秒。
3. 按 Workflow 的 Token 预算。为每个工作流定义 p95 Token 基线,超出 20% 告警。这是发现"上下文膨胀"和"Prompt 改动引入回归"的最灵敏信号。
4. 缓存命中率。Prompt 前缀缓存是大厂提供的主要折扣:Anthropic 缓存读约 90% 折扣,OpenAI 约 50%。命中率上不去,等于每分钟都在原价烧钱。将命中率纳入监控,能直接指导 Prompt 结构优化(把稳定前缀放前面、动态内容放后面)。
七、成本归因:架构问题,不是报表问题
"本月花了 $14,300" 是一个无用数字 ------它回答不了任何可行动的问题。有效的成本归因必须能对每一批 Token 回答三个问题:谁触发的 (用户/服务账号)、哪个功能发起的 、流水线的哪个环节消耗的。
难点是结构性的:LLM 调用往往发生在没有业务上下文的深层函数里,摘要函数不知道自己是被"文档搜索"还是"邮件助手"调用的。归因必须在代码架构层面解决------通过 Span Attribute 或 OTel Baggage 将业务元数据沿调用栈显式传播,而不是指望事后从日志里反推。
算一笔账就能理解为什么值得:给每次请求加 500 词背景上下文 ≈ 667 input tokens,按 2.50/M计价每次多0.00167------10 万次/天的调用就是 167/天,∗∗每月约5,000,源于一个 Prompt 设计决策**。没有 Token 级归因,这类"合理性检查"根本无从谈起。
八、质量监控:两级评估体系
可观测性不只盯性能,还要盯"对不对"。生产级实践是两级评估:
- Tier 1(全量、零 LLM 成本):规则检查------JSON Schema 校验、输出长度 sanity check(异常短 → 截断或拒答;异常长 → Prompt 泄漏或失控生成)、轻量安全分类器。零成本,覆盖大部分格式类失败。
- Tier 2(抽样、5%~10% 流量) :用更便宜的模型给输出打分(准确性/完整性/格式/语气),分数存回原始 Trace。关键不是单次分数,而是趋势线------稳定均值突然下跌,几乎必然对应模型更新、Prompt 回归或输入分布漂移。
不要对每个请求跑模型评估------那会让推理成本翻倍。
九、选型对比
| 方案 | 定位 | 优势 | 适合 |
|---|---|---|---|
| Langfuse | LLM 原生调试平台 | 开源可自托管、Prompt 管理、评估闭环 | Prompt 级调试与质量分析 |
| LangSmith | LangChain 官方 | 框架深度集成 | LangChain 生态重度用户 |
| Grafana / Datadog + OTel | 通用可观测平台 | 与微服务监控统一、告警与 SLO 强 | 已有成熟 SRE 体系的团队 |
| Phoenix (Arize) | 评估导向 | OpenInference 生态、RAG 评估突出 | 评估密集型场景 |
推荐组合:OTel Collector 扇出到 Langfuse + Grafana 双后端。前者回答"这次对话为什么错了",后者回答"系统整体健康吗"。同一份 gen_ai.* 属性两边通用,切换或新增后端零成本。
十、常见陷阱清单
- 升级插桩库导致面板失联:规范仍在快速演进,属性命名经历过多次破坏性变更(provider 属性、Token 用量键都改过名)。锁定插桩库版本、升级前读 changelog。
- 全局开启内容采集:PII 直送共享后端,合规事故预备役。内容开关按环境隔离。
- 用普通 Span Attribute 存大段 Completion:会被索引、有大小上限,改用专门的内容记录机制。
- 混用 OpenInference 与 OTel GenAI 两套属性命名:多数后端两者都收,但混着写会让查询变得噩梦。
- 以为规范覆盖评估分数:它标准化的是"调用",不是"判断"。评估体系要自己建。
结语
LLM 应用的故障大多是静默的:截断、幻觉、成本失控、质量漂移------它们不会抛异常,只会慢慢侵蚀用户体验和预算。可观测性是 AI 应用从 Demo 走向生产力的分水岭,而 OpenTelemetry GenAI 语义规范把"自研监控"变成了"标准插桩"。
落地路径可以很简单:先上自动插桩拿到 Token 与延迟数据,再补 finish_reason 与 Token 预算告警,最后建两级评估闭环。一周时间,你的 LLM 应用就从"盲飞"进入"仪表飞行"。
参考资料:OpenTelemetry GenAI Semantic Conventions 官方规范与博客《Inside the LLM Call: GenAI Observability with OpenTelemetry》、MLflow《Setting Up LLM Observability Pipelines in 2026》、DevOps.com 生产可观测性实践指南。