返回 AIPA 笔记
AIPA Day 22

OTel GenAI semconv 精读

OTel GenAI semconv 精读

2026-07-06
opentelemetrygenai-semconvobservabilityspan-attributes

日期: 2026-07-06 阶段: Phase 1 - 产品定义×评测×可观测底座 标签: #opentelemetry #genai-semconv #observability #span-attributes

核心问题

要给 AML Copilot 算 $/案件、追每次 LLM 调用的 token 与 finish_reason、把 trace 与 eval 用同一执行 ID 串起来,前提是埋点字段有统一语义。各家厂商(OpenAI SDK、Anthropic SDK、LangChain)各埋各的字段,换厂商就要重写仪表盘——这正是 OpenTelemetry GenAI semantic conventions(semconv)要解决的:用一套标准属性名(gen_ai.request.modelgen_ai.usage.input_tokens…)描述「一次模型调用」,让后端(Datadog / Langfuse)零改代码就能解析。

但有个硬约束:截至 2026-03 这套规范仍是 experimental(Development 状态),属性名随时可能改。今天回答三问:(A) span/属性/事件三层约定到底定义了什么;(B) 为什么 prompt/completion 内容默认不采集(opt-in 的隐私设计);(C) experimental 状态意味着什么变更风险,以及怎么在埋点时就把这个风险隔离掉。

关键内容

A. 三层约定:span 名 + 元数据属性 + 内容事件

semconv 把「一次 GenAI 操作」拆成三层信息,分层是为了让元数据(永远安全)与内容(可能含 PII)物理分离

第一层 — span 名约定。官方规范(gen-ai-spans,2026-03 口径)规定 span 名 SHOULD 为 {gen_ai.operation.name} {gen_ai.request.model},例如 chat gpt-4o-mini。注意它不带租户/用户信息——span 名进了链路检索索引,若把内容塞进去会全量泄露,所以约定刻意只放「操作类型 + 模型」这两个低敏维度。

第二层 — span 属性(元数据)。这是默认采集的核心,描述「这次调用是什么、花了多少」。下表是规范定义的关键属性(requirement level 直接引自 gen-ai-spans 规范):

属性名类型是否默认采集语义
gen_ai.operation.namestringRequired操作类型(chat / embeddings / generate_content)
gen_ai.provider.namestringRequired提供方标识(openai / anthropic / azure.ai.inference)
gen_ai.request.modelstringConditionally Required请求的模型名(gpt-4o-mini)
gen_ai.response.modelstringRecommended实际响应的模型名(gpt-4o-mini-2024-07-18,常含日期戳)
gen_ai.request.temperaturedoubleRecommended采样温度
gen_ai.request.max_tokensintRecommended请求的最大输出 token
gen_ai.usage.input_tokensintRecommended输入/prompt token 数
gen_ai.usage.output_tokensintRecommended输出/completion token 数
gen_ai.response.finish_reasonsstring[]Recommended停止原因数组(["stop"] / ["length"] / ["content_filter"])
gen_ai.response.idstringRecommended响应唯一 ID(厂商侧)
error.typestringConditionally Required失败时的错误类(如超时、限流)

finish_reasons 设计成数组而非标量,不是随手为之:批量/多候选生成(n>1)一次调用会有多个结束原因,标量会丢信息;而对成本与 SLA 监控,length(被 max_tokens 截断)与 stop(正常收尾)必须可区分——前者意味着回答可能不完整,是质量告警信号。

第三层 — 内容事件/属性(opt-in)。prompt 与 completion 正文走单独的 opt-in 通道,见 B 节。

反直觉洞察(口径陷阱)gen_ai.request.modelgen_ai.response.model 经常不相等。你请求 gpt-4o-mini,响应回 gpt-4o-mini-2024-07-18。算成本/对账要用 response.model(真实计费模型),但做产品分组要用 request.model(用户视角的模型选择)。混用这两个字段是 LLM 可观测最常见的统计错误——它会让你的「模型 A vs B」对比表把同一模型的不同快照拆成两行。

B. 内容捕获 opt-in:默认零 prompt 的隐私设计

规范明确规定承载内容的三个属性默认不采集

  • gen_ai.system_instructions(Opt-In)— 与对话历史分离的系统提示
  • gen_ai.input.messages(Opt-In)— 作为模型输入的对话历史
  • gen_ai.output.messages(Opt-In)— 模型返回的消息

规范原文:「OpenTelemetry instrumentations SHOULD NOT capture them by default, but SHOULD provide an option for users to opt in.」开启方式在多数仪表化库里是环境变量 OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT=true(Greptime 2026-05 实测口径)。

这个「默认关」的设计是隐私优先(privacy-by-default),对金融场景是刚需而非可选项:AML 案件的 prompt 里塞满了客户姓名、账号、交易对手——这些是 PII/受监管数据,一旦默认进了链路存储,就等于把受监管数据复制进了一个本不该存它的系统,直接触发数据驻留与最小化合规问题。把内容捕获做成 opt-in,意味着安全默认不需要任何配置;要采内容时是一次显式的、可审计的决定(谁开的、在哪个环境开的)。

设计上还有第二重价值:元数据(token 数、模型、耗时)与内容解耦后,绝大多数可观测需求(成本、延迟、错误率、finish_reason 分布)根本不需要内容。也就是说默认配置已经能支撑 $/案件、P95 延迟、截断率等全部运营指标——内容捕获只在调试单条失败 trace 时才临时按需开启。

决策流:是否对某次调用开启内容捕获?
  if 环境 == production 且 数据含 PII:
      capture_content = false            # 默认,合规基线
  elif 环境 == staging 且 正在调试某 failure_class:
      capture_content = true (限时)       # 显式 opt-in,留审计记录
      并对 input/output 跑 PII 脱敏管道再落库
  else:
      capture_content = false

反直觉洞察(隐性成本):很多团队以为「先把内容全采下来,以后要用再说」是安全的——恰恰相反。内容是链路数据里体积最大、最敏感、留存合规风险最高的部分。默认采内容会让你的可观测后端在不知不觉中变成一个影子 PII 数据库,而它的访问控制、留存策略、加密等级通常远低于你的核心业务库。opt-in 不是限制,是把这个高危决定从「默认发生」改成「必须有人负责」。

C. experimental(2026-03)的变更风险与版本迁移

semconv 当前 Status 徽章是 Development(即 experimental),且没有公开的稳定化时间表——规范原文:「Attribute names and structures may still change」(Greptime 2026-05 转述官方迁移说明)。这不是理论风险,已经发生过:v1.36 之前的旧属性格式与之后的新格式不兼容,OTel 用环境变量做迁移闸门。

关键的版本迁移机制有两个互相独立的开关,初学者极易混淆:

环境变量控制什么取值示例误用后果
OTEL_SEMCONV_STABILITY_OPT_IN用哪个版本的属性约定gen_ai_latest_experimental(发最新实验版,不再发 ≤v1.36 旧版)不设→仍发旧属性名,后端解析不到新字段
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT是否采集内容(B 节)true / false(默认)误设 true→PII 入库

v1.36 是「过渡基线」:现存仪表化默认仍发旧格式,必须显式 OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental 才切到新版(Greptime 2026-05)。Datadog LLM Observability 于 2025-12-01 宣布原生支持 v1.37+ 的 GenAI semconv,映射 gen_ai.request.model / gen_ai.usage.input_tokens / gen_ai.usage.output_tokens / gen_ai.usage.total_tokens / gen_ai.provider.name / gen_ai.operation.name,无需改代码即可在 OTLP 管道里解析。

变更风险的真正落点是**「转 stable 时属性改名 → 埋点全返工」**:

反直觉洞察(埋点的真正成本不在写,在改名):一次性把属性名硬编码进 50 处仪表化调用很快,但当 semconv 从 experimental 转 stable 时若 gen_ai.usage.input_tokens 改成别的名字(实验阶段完全允许),你要改的不只是 50 处埋点——还有所有仪表盘查询、所有 eval 里按属性名做的成本聚合、所有告警规则。改名的爆炸半径远大于埋点本身。这就是为什么 Day 23 要做一个独立属性映射层:自有稳定字段 → semconv 单向映射,semconv 改名时只改映射层一处,业务代码与仪表盘查询全程用自有字段,零返工。

一个完整 chat span 的属性 JSON(默认配置,无内容)长这样:

{
  "name": "chat gpt-4o-mini",
  "attributes": {
    "gen_ai.operation.name": "chat",
    "gen_ai.provider.name": "openai",
    "gen_ai.request.model": "gpt-4o-mini",
    "gen_ai.response.model": "gpt-4o-mini-2024-07-18",
    "gen_ai.request.temperature": 0.2,
    "gen_ai.request.max_tokens": 1024,
    "gen_ai.usage.input_tokens": 142,
    "gen_ai.usage.output_tokens": 87,
    "gen_ai.response.finish_reasons": ["stop"],
    "gen_ai.response.id": "chatcmpl-..."
  }
}

注意整段 JSON 里没有任何 prompt/completion 正文——这就是 B 节「默认零内容」的具体形态。

设计要点/决策表

要点说明与朴素做法差异
span 名只放 operation+model检索索引层不含敏感信息朴素做法把用户 query 塞进 span 名→全量泄露
request.model ≠ response.model成本用 response、分组用 request混用导致同模型被拆成多行
finish_reasons 用数组区分 stop/length/content_filter,支持 n>1标量丢截断信号
内容默认 opt-inprivacy-by-default,元数据已覆盖运营指标默认采内容→影子 PII 库
两个 OPT_IN 开关独立版本开关 vs 内容开关分离混淆导致要么解析不到新字段、要么误采 PII
锁定 experimental 版本显式 gen_ai_latest_experimental不设→发旧格式,后端对不上

对本项目的落地

  • 新建 src/aml/observability/attributeMap.ts(Day 23 详设):定义自有稳定字段(如 aiCallModel / aiCallInputTokens / aiCallFinishReasons)→ semconv 属性的单向映射常量表,全仓库埋点只引用自有字段,semconv 改名时仅改此文件。这是对本节 C「改名爆炸半径」的直接防御。
  • $/案件 成本聚合的字段来源:成本计算(W7 gateway 实测)用 gen_ai.response.model × token 数算单价,不能用 request.model(C 节 A 小节口径陷阱)。聚合逻辑将与 src/aml/evalBaseline.ts 的产出按同一执行 ID 关联,使「这条 eval 失败的案件花了多少钱」可查。
  • 内容捕获策略:生产环境对 AML 案件 prompt 一律 CAPTURE_MESSAGE_CONTENT=false(B 节合规基线);仅在 staging 调试某个 failure taxonomy 类别时限时开启,且开启时输入须先过 src/aml/generator.ts 已有的合成数据通道,避免真实 PII 进链路。
  • 截断率作为质量信号:把 finish_reasonslength 占比做成一条监控指标——SAR 草稿被 max_tokens 截断意味着证据链不完整,这是 Day 3 PRD「不产生幻觉/不丢证据」指标的可观测对应项。
  • 静态导出约束的诚实标注(Day 24 展开):本项目 output:export 无后端 collector,semconv span 当前在前端本地可视化(对接 src/agent/trace/useTraceStore.ts),真实 OTLP collector 是计划态,不谎称已接生产 Datadog。

参考资料

  1. OpenTelemetry — Semantic conventions for generative client AI spans(span 名约定、全套属性 requirement level、内容属性 opt-in 原文)官方规范,Development 状态 (2026-03 口径)
  2. OpenTelemetry — Gen AI 属性注册表 registry/attributes/gen-ai/(属性类型权威来源)(2026-03)
  3. Greptime — How OpenTelemetry Traces LLM Calls, Agent Reasoning, and MCP Tools(OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental、v1.36 过渡基线、CAPTURE_MESSAGE_CONTENT、示例 span JSON)(2026-05-09)
  4. Datadog — Datadog LLM Observability natively supports OpenTelemetry GenAI Semantic Conventions(v1.37+ 原生支持、映射的 gen_ai.* 属性、OTLP 管道)(2025-12-01)
  5. OpenTelemetry Blog — Inside the LLM Call: GenAI Observability with OpenTelemetry(traces/metrics/events 整体框架)(2026)

SOTA 检查 (2026-06-11)

  • OTel GenAI semconv 当前状态:仍是 Development(experimental),无公开稳定化时间表,官方明示「属性名与结构可能继续变」——这是本笔记最重要的时效结论,直接驱动 Day 23 的属性映射层设计。
  • 是否仍是 SOTA:✅ 是。GenAI 可观测的事实标准就是 OTel semconv,2025-2026 厂商(Datadog 2025-12 原生、Langfuse、Arize Phoenix、Traceloop OpenLLMetry)全部向其对齐,无竞争性替代约定。
  • 2026 进展:约定已从「单次 LLM 调用」扩展到 agent 编排 span(gen-ai-agent-spans)、MCP 工具调用、内容捕获、质量评估(gen_ai.evaluation.result 事件)多层;v1.37 是 Datadog 接入基准。
  • 变更风险监控:W4 埋点开工前须复查 semconv 是否已转 stable;若已转,核对 gen_ai.usage.* / gen_ai.response.finish_reasons 是否改名,并据此回填本笔记与 attributeMap.ts。
  • 过时认知警示:2024 年那套各 SDK 自定义 llm.* / ai.* 私有属性的埋点法已被 semconv 取代——引用旧博客(如 2024-otel-generative-ai)的属性名时须核对是否仍有效,旧属性在 gen_ai_latest_experimental 下已不发出。
  • WebSearch 验证关键词: "OpenTelemetry gen_ai semantic conventions stable 2026", "OTEL_SEMCONV_STABILITY_OPT_IN gen_ai"