AI 应用的可观测性设计:从 OpenTelemetry GenAI 语义约定到生产级落地架构

AI 应用的可观测性设计:从 OpenTelemetry GenAI 语义约定到生产级落地架构

引言:为什么传统 APM 在 LLM 场景会"失明"

把一个大模型调用放进生产环境的第一周,你大概率会收到三种 P0 告警:账单爆表、某个 prompt 让所有用户拿到 5 秒空白回复、模型在某次静默升级后开始胡说八道。Datadog 的 HTTP 平均延迟告诉你"系统很健康",但你已经亏了一天的预算并且流失了用户。这不是 APM 工具不行,而是它们的语义模型从诞生起就没假设过"请求的成本由 token 决定、行为由概率决定、错误不是异常而是分布漂移"。LLM 应用必须有一张独立于传统监控的可观测性图,而这张图的骨架,就是 OpenTelemetry GenAI 语义约定。

一、LLM 系统的五大可观测性陷阱

在进入工具与代码之前,先把"为什么不能复用 Prometheus + Grafana"讲清楚。LLM 系统有五个维度打破了传统监控的假设:

  1. 非确定性。同一个 prompt 在 temperature=0.7 下会产生不同输出。没有捕获请求时刻的 prompt 全文、模型 ID、温度参数和随机种子,问题就无法复现。传统 APM 给你的是 stack trace,LLM 给你的应该是一条"决策快照"。
  2. Token 计价的成本模型。一个 128k 长上下文请求的账单可能是普通请求的 100 倍。监控"每秒请求数"完全看不到成本结构,需要的是"每秒 token"和"每次请求 token 分布"。
  3. 多步 Agent 链。一次用户请求背后可能是 5 次 LLM 调用、3 次工具调用、2 次向量检索。任何一个步骤失败、变慢或变贵都会污染最终体验。传统的 service map 会把这堆调用画成一团乱麻。
  4. Prompt 敏感性。"helpful assistant"和"You are a helpful assistant"之间可能隔着一次安全策略翻转。Prompt 既是配置又是输入,需要像 Git 一样版本化地追踪。
  5. 内容合规风险。Prompt 里大概率有 PII、商业机密、医疗信息。直接把它们打到日志后端就是合规事故。可观测性必须从设计上就把"捕获 vs 留存 vs 脱敏"当成一等公民。

二、OpenTelemetry GenAI 语义约定:唯一的"通用语言"

2026 年 6 月,OpenTelemetry 核心仓库 v1.42 把 GenAI 相关约定移到了独立仓库 open-telemetry/semantic-conventions-genai,并把状态标记为 Development。这意味着两件事:约定已经事实上成为跨厂商的事实标准,但字段名还会变,工程上必须做版本快照。

核心属性命名空间统一是 gen_ai.*,下面是生产代码里一定要打的几个:

属性 类型 含义
gen_ai.system / gen_ai.provider.name string openai / anthropic / aws.bedrock 等
gen_ai.operation.name string chat / embeddings / execute_tool / invoke_agent
gen_ai.request.model string 你请求的模型名
gen_ai.response.model string 实际响应的模型名(别名/路由/降级时可能不同)
gen_ai.request.temperature / top_p / max_tokens float / int 采样参数
gen_ai.usage.input_tokens / output_tokens int token 用量
gen_ai.response.finish_reasons string\[\] stop / length / tool_calls / content_filter

需要特别强调的两条工程纪律:

  • Prompt 与 completion 内容走 span events,不走 attributes。Attributes 会被所有 backend 索引,超大且含敏感数据。Events 可以在 OTel Collector 里被过滤或丢弃,应用代码完全无感。
  • gen_ai.request.modelgen_ai.response.model 的差值本身就是告警维度。当 OpenAI 把 gpt-4o 后台升级成 gpt-4o-2025-08-06,你的行为可能变化,但部署没动。这个 gap 是监测模型漂移的最便宜信号。

约定还为 Agent 系统定义了 create_agent / invoke_agent / execute_tool 三类 span,分别承载"代理被创建/调用/工具执行"的语义,加上 gen_ai.agent.namegen_ai.tool.name 两个属性,就能在 trace UI 里把一棵决策树完全展开。

三、三大信号支柱:怎么把语义约定变成可查询的数据

OpenTelemetry 用统一的三大信号承载 GenAI 遥测。理解它们各自的角色是设计埋点的前提。

Spans 是 LLM 操作的最小工作单元。一个 chat completion 是一个 span,一个工具调用是一个嵌套 span,一个 Agent 循环是一棵 span tree。Span 是定位"哪一步出问题"的唯一单位。

Metrics 是可聚合的数字。GenAI 的官方 metric 目前都是 Development 状态,包括:

  • gen_ai.client.token.usage(Histogram,单位 token)
  • gen_ai.client.operation.duration(Histogram,单位 s)
  • gen_ai.client.operation.time_to_first_chunk(Histogram,单位 s,TTFT)
  • gen_ai.client.operation.time_per_output_chunk(Histogram,单位 s)

TTFT 和"每个 chunk 的间隔"是流式场景下拆解延迟的标准切片。一个 latency 退化如果发生在 TTFT,那是 prefill/排队问题(批大小、调度器);如果发生在 chunk 间隔,那是 decode 问题(KV Cache、投机解码)。这两类问题的优化路径完全不同,混在一起看就只能瞎调。

Events/Logs 是高基数内容。Prompt、completion、检索到的 chunk、工具参数和返回值,都应该进 span events 而非 metrics。可以用 gen_ai.system.messagegen_ai.user.messagegen_ai.assistant.messagegen_ai.tool.message 四个事件名规范内容捕获。

四、Agent 系统的层级追踪模型

LLM 应用从"单次 prompt"演化成"Agent 循环"后,可观测性的复杂度跳跃了一个量级。一个生产 Agent 至少有三层 span:

css 复制代码
agent.run(根 span,invoke_agent)
├─ llm.think(gen_ai.chat span)
│  └─ tool.search(execute_tool span)
│     └─ vector.query(retrieval span)
├─ llm.think(gen_ai.chat span)
│  └─ tool.search(execute_tool span)
└─ llm.answer(gen_ai.chat span,最终输出)

这三层每一层都有自己的成本、延迟和失败模式。把它们画在一棵 trace tree 里,你就能直接回答业务方最常问的几个问题:

  • "为什么这次回答这么贵?" → 看哪一步 LLM 调用消耗了 80% 的 token
  • "为什么这次回答这么慢?" → 看 TTFT 还是 tool 调用拉垮
  • "为什么这个用户没拿到正确答案?" → 看哪一步 retrieve 错了、哪一步工具返回了空

更进一步,给每个 tool span 加上 gen_ai.tool.name 后缀,你就能用一句 PromQL 查询出"上周工具 X 的失败率上升了 30%",而不需要解析日志。

五、主流平台横评:Langfuse / LangSmith / Helicone / Phoenix / Portkey

工具选型本质是问三个问题:你愿意被锁定吗?你愿意为便利付多少钱?你团队有没有平台工程能力?

平台 定位 开源 自托管 上手时间 核心优势 关键短板
Langfuse 开源生产可观测性 + 评估 MIT ~30min 完整 span tree、prompt 版本化、LLM-as-judge、社区最大 需要 SDK 集成,非代理模式
LangSmith LangChain 深度集成 仅企业版 ~30min LangChain/LangGraph 零配置 trace、dataset 与 eval 成熟 离开 LangChain 价值大减
Helicone 代理模式 + 成本监控 ~2min 改一行 base URL 即可,100+ 模型成本仪表盘 请求级而非 span 级,2026-03 被 Mintlify 收购进入维护模式
Arize Phoenix OTel 原生 + ML 评测 MIT ~20min embedding drift、RAG 指标、OTel 一等公民 部署复杂,2-4 周才能跑稳生产
Portkey AI 网关 + 可观测性 部分 ~15min 网关级路由、guardrail、语义缓存 可观测性是网关附赠,深度有限

按 2026 年中最新状态做选型决策:

  • 如果你要数据主权、要自托管、要长期不被锁定:Langfuse。2026 年 1 月 ClickHouse 收购 Langfuse 后,MIT 许可覆盖整个核心产品,反而打消了"开源项目被收购就闭源"的担忧。
  • 如果你已经全栈 LangChain/LangGraph:LangSmith。零配置 trace 的便利远超其定价溢价。
  • 如果你只是想要成本可见性、几小时内就要上线:Helicone。代理模式仍然是最快路径,但要接受"在维护模式"的产品风险。
  • 如果你的运维团队已经在用 Datadog/Grafana:用 OTel 直接打到你现有的栈,再叠加一个轻量 LLM 评估工具。不要为 LLM 单独买一套后端。

六、生产级架构:从 SDK 到 Collector 到多后端

一段能跑的 OTel GenAI 埋点代码大致如下:

python 复制代码
from opentelemetry import trace
from opentelemetry.trace import Status, StatusCode
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.openai import OpenAIInstrumentor
import openai

# 应用启动时埋点一次
OpenAIInstrumentor().instrument()
exporter = OTLPSpanExporter(endpoint="otel-collector:4317", insecure=True)
trace.get_tracer_provider().add_span_processor(BatchSpanProcessor(exporter))
tracer = trace.get_tracer("ai-service")

# 业务代码
def ask(prompt: str, model: str = "gpt-4o") -> str:
    with tracer.start_as_current_span("gen_ai.chat") as span:
        span.set_attributes({
            "gen_ai.provider.name": "openai",
            "gen_ai.operation.name": "chat",
            "gen_ai.request.model": model,
            "gen_ai.request.temperature": 0.7,
        })
        span.add_event("gen_ai.user.message", {
            "gen_ai.prompt.content": prompt[:500],  # 截断长度
        })
        try:
            resp = openai.OpenAI().chat.completions.create(
                model=model,
                messages=[{"role": "user", "content": prompt}],
            )
            msg = resp.choices[0].message.content
            usage = resp.usage
            span.set_attributes({
                "gen_ai.response.model": resp.model,
                "gen_ai.usage.input_tokens": usage.prompt_tokens,
                "gen_ai.usage.output_tokens": usage.completion_tokens,
                "gen_ai.response.finish_reasons": [resp.choices[0].finish_reason],
            })
            span.add_event("gen_ai.assistant.message", {
                "gen_ai.completion.content": msg[:500],
            })
            return msg
        except Exception as e:
            span.record_exception(e)
            span.set_status(Status(StatusCode.ERROR))
            raise

注意 prompt[:500] 这类截断。生产代码必须做:内容截断、PII 字段名脱敏(regex 替换邮箱/手机/身份证)、按用户控制 opt-in 标志。

OTel Collector 的角色是唯一值得信赖的数据网关,它要承担四个责任:

yaml 复制代码
receivers:
    otlp:
      protocols: { grpc: {}, http: {} }
processors:
    batch: { timeout: 5s }
    attributes/pii:
      actions:
        - key: gen_ai.prompt.content
          action: hash
        - key: gen_ai.completion.content
          action: hash
    filter/noisy:
      spans:
        drop:
          - 'attributes["gen_ai.operation.name"] == "embeddings"'
exporters:
    otlp/jaeger:
      endpoint: jaeger:4317
    otlp/langfuse:
      endpoint: langfuse:3000
    prometheus: { endpoint: 0.0.0.0:9464 }
service:
    pipelines:
        traces: { receivers: [otlp], processors: [batch, attributes/pii], exporters: [otlp/jaeger, otlp/langfuse] }
        metrics: { receivers: [otlp], processors: [batch], exporters: [prometheus] }

通过 Collector 一份入站、三份出站(Jaeger 看 trace、Langfuse 看 LLM 上下文、Prometheus 看指标),既保留了厂商无关性,又获得了专用 LLM 后端的可视化能力。这是"不锁定"和"有得用"之间的最优解。

七、Token 成本监控与告警策略

可观测性的最大商业价值不是"看 trace",而是"少亏钱"。下面是一套经过验证的成本告警模板:

  1. 每日预算硬告警sum(rate(gen_ai_client_token_usage_total[5m])) * 单价 > 80% * 日预算,触发后立刻熔断。
  2. 单请求成本 P99 突增histogram_quantile(0.99, sum(rate(gen_ai_client_token_usage_bucket[1h])) by (le)) 与昨日同时段对比,偏离 50% 报警。这能抓到"有人在 prompt 里塞了整个 PDF"。
  3. 按 feature/customer 分桶 :用 span attribute 注入 app.featurecustomer.tiercost.center,成本归属到业务线,才能推动产品方优化 prompt。
  4. 模型分布漂移gen_ai_request_model 的 cardinality 突然出现新值,可能意味着有人在调用没审核过的模型。
  5. Finish reason 分布变化finish_reasons{filter} 占比上升意味着安全过滤更频繁,要么是输入确实变脏,要么是模型升级后变得更保守。

告警一定要带 runbook。一条没有处理指南的"成本上涨 30%" 只会变成告警疲劳。

八、内容隐私与 PII 治理

GenAI 可观测性的最大合规风险是把用户隐私直接打到日志后端。三条铁律:

  • 捕获时机脱敏,不存后脱敏。在 SDK 层或 Collector 层做 hash/redact,避免后端成为 PII 数据库。
  • 默认 opt-out,敏感租户 opt-in。健康/金融/政企客户默认关闭内容捕获,仅在排查时按工单临时开启。
  • 保留期限与访问审计。trace 数据保留 30 天即销毁,跨租户查询需要审批工单,行为进 audit log。

OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true 这个环境变量应当被视为"开一次、合规审批一次",而不是默认值。

九、落地清单:上线第一周必须做的事

如果你的 LLM 应用明天就要上生产,按这个顺序加可观测性:

  • Day 1:接入 OpenTelemetry + GenAI auto-instrumentation,把 token 用量、延迟、错误率打到你现有的 Prometheus。设置日预算硬告警。
  • Day 2:把请求-响应的 trace_id 写入用户反馈系统的关联键,让"用户报问题"→"拉 trace"成为标准流程。
  • Day 3 :把 prompt 模板加版本号(hash 或语义版本),所有 LLM span 都带上 prompt.version。没有版本号就没法定位回归。
  • Day 4:上线 quality sampling:每 100 个请求采样 1 个跑 LLM-as-judge 评估,指标打点进 trace attribute。
  • Day 5:建立周度 trace review 机制,团队花 30 分钟看上周最贵、最慢、最差的各 10 条 trace。这是 LLM 应用真正的 retrospective。

结语

LLM 可观测性的成熟度还远低于传统 APM------GenAI 语义约定仍在 Development 状态,工具生态仍在洗牌(Helicone 进入维护、ClickHouse 收购 Langfuse、Datadog 加 LLM 模块),但工程要求已经定型:用 OTel 语义约定做骨架、用 Collector 做数据治理网关、用专用 LLM 后端做体验层、用版本化 prompt 做回归定位。

把这套架构搭起来之后,你得到的不是一份漂亮 dashboard,而是能在 P0 发生的那一刻,用 trace_id 在 5 分钟内回答"是哪个 prompt、哪次模型升级、哪个用户、哪个 feature 组合触发的"。这是把 LLM 应用从"能跑"推进到"能运营"的工程分水岭。

相关推荐
TunerT_TQ1 小时前
证据优先:从“我认为”到“我证明”——大模型工程的第一性原理(第1期)
安全·架构
广东帝工智能安防1 小时前
BCAS桥梁防撞预警系统五层架构深度解析:从感知层到对接层的技术实现与选型指南
开发语言·人工智能·架构·边缘计算·桥梁防撞预警系统
xencio3 小时前
企业资金管理系统技术选型:用友BIP全球司库与见知资金管理系统的架构、银企直联和ERP集成对比
架构·用友·资金管理系统·见知数据·见知银企通·资金系统选型
艾伦_耶格宇3 小时前
【ELK】-6 ELFK 索引架构详解
elk·架构
mldong3 小时前
一条命令,十分钟:jeeflow 工作流应用的六语言一键部署
前端·后端·架构
ting945200011 小时前
Humalike X Hermes 深度技术剖析:单指令注入群聊社交智能的底层架构、算法与跨 IM 平台实现
人工智能·算法·架构
ZGIAI12 小时前
ZGI 混合检索:汇集候选并统一重排
人工智能·架构
ZGIAI12 小时前
ZGI 运行日志:还原任务与节点状态
人工智能·架构
yurenpai(27届找实习中)13 小时前
从零读懂 AI 智能客服(一):模块职责与 SSE 聊天链路(后端架构)
java·人工智能·架构·langchain4j