OpenTelemetry GenAI 语义约定 本节摘要:每家厂商都自创 span 名,运维团队最后要为每个框架单独搭仪表盘——Agent 的追踪在 Datadog、Grafana、Jaeger、Honeycomb 里长得都不一样,根本无法横向比较。OpenTelemetry 的 GenAI SIG(2024 年 4 月成立)给出了一份标准模式,让整个生态都向它对齐。三类 span:模型/客户端 span(覆盖裸 LLM 调用,由提供者 SDK 与框架适配器发射)、Agent span( 构造时、 运行时)、工具 span(每次工具调用一个,经父子关系挂到 Agent span 上)。
本节摘要:每家厂商都自创 span 名,运维团队最后要为每个框架单独搭仪表盘——Agent 的追踪在 Datadog、Grafana、Jaeger、Honeycomb 里长得都不一样,根本无法横向比较。OpenTelemetry 的 GenAI SIG(2024 年 4 月成立)给出了一份标准模式,让整个生态都向它对齐。三类 span:模型/客户端 span(覆盖裸 LLM 调用,由提供者 SDK 与框架适配器发射)、Agent span(
create_agent构造时、invoke_agent运行时)、工具 span(每次工具调用一个,经父子关系挂到 Agent span 上)。invoke_agent又分两种 span kind:CLIENT(远程 Agent 服务,如 OpenAI Assistants、Bedrock Agents)与 INTERNAL(进程内 Agent 框架,如 LangChain、CrewAI、本地 ReAct)。关键属性:gen_ai.provider.name(anthropic/openai/aws.bedrock/google.vertex)、gen_ai.request.model、gen_ai.response.model、gen_ai.agent.name、gen_ai.operation.name、gen_ai.data_source.id(RAG 命中哪个语料库)。内容捕获(content capture)是本节最重要的安全契约:默认不捕获输入/输出(防止 PII、密钥、客户数据进可被运维读取的追踪);要捕获必须显式 opt-in(gen_ai.system_instructions、gen_ai.input.messages、gen_ai.output.messages),且推荐生产里内容外部存储(S3/日志库)、span 上只记指针 ID——这正是第 27 节内容投毒防御在可观测性层的接线。本节吃透这套约定,并用标准库实现一个对齐 GenAI 约定的 span 发射器。
对应原课程:Phase 14 · Lesson 23 ·
otel-genai-conventions(原英文phases/14-agent-engineering/23-otel-genai-conventions/docs/en.md)。前置:第 13 节(LangGraph)、第 24 节(可观测性平台)。
阅读完本节,你应当能够:
invoke_agent 的 CLIENT 与 INTERNAL 两种 span kind,以及各自适用场景。OTEL_SEMCONV_STABILITY_OPT_IN、外部引用推荐。每个 Agent 框架都自己定义遥测:CrewAI 的 span 名和 LangGraph 的不一样,OpenAI 的又和 Claude 的不一样。结果:运维团队为每个框架单独搭仪表盘,跨框架的告警无法共享,「这个工具错误是不是只发生在某个模型上」这种基本问题答不出来。
OpenTelemetry 的 GenAI SIG 解决的就是这件事——一份标准模式,整个生态都对齐。这样,无论你的 Agent 用什么框架,它的追踪在 Datadog、Grafana、Jaeger、Honeycomb 里长得一样、能横向比较。
create_agent(Agent 构造时)与 invoke_agent(Agent 运行时)。invoke_agent {gen_ai.agent.name}(若有命名);否则回退 invoke_agent。gen_ai.provider.name —— anthropic、openai、aws.bedrock、google.vertex。gen_ai.request.model —— 请求的模型 ID。gen_ai.response.model —— 实际解析到的模型(可能因路由与请求不同)。gen_ai.agent.name —— Agent 标识符。gen_ai.operation.name —— chat、completion、invoke_agent、tool_call。gen_ai.data_source.id —— RAG 用:查的是哪个语料库或存储。针对 Anthropic、Azure AI Inference、AWS Bedrock、OpenAI 各有技术专用约定。
默认规则:仪表化**不应(SHOULD NOT)**默认捕获输入/输出。捕获是显式 opt-in:
gen_ai.system_instructionsgen_ai.input.messagesgen_ai.output.messages推荐的生产模式:内容外部存储(S3、你的日志库),在 span 上记录引用(指针 ID,而非原文)。这是第 27 节内容投毒防御在可观测性层的接线——避免把 PII、密钥、客户数据写进运维可读的追踪。
截至 2026 年 3 月,大多数约定仍是实验性的。用稳定预览 opt-in:
OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental
Datadog v1.37+ 把 GenAI 属性原生映射进其 LLM 可观测性模式;其他后端(Grafana、Honeycomb、Jaeger)支持原始属性。
⚠️ 四种失败模式:① 在 span 里捕完整提示——PII、密钥、客户数据进可被运维读取的追踪;应外部存储。② 缺
gen_ai.provider.name——多提供者仪表盘在归因缺失时崩。③ span 无父链——孤立的工具 span;永远要传播上下文(第 17 节的 W3C)。④ 未设稳定性 opt-in——你的属性可能在后端升级时被重命名。
原课程 code/main.py 实现一个对齐 GenAI 约定的标准库 span 发射器:
Span,带 GenAI 属性模式。Tracer,带 start_span、嵌套上下文。create_agent、invoke_agent(INTERNAL)、每工具 span、LLM 调用的 chat span。@dataclass class Span: name: str kind: str # CLIENT / INTERNAL parent: "Span" = None attrs: dict = field(default_factory=dict) def agent_span(tracer, agent_name, kind="INTERNAL"): return tracer.start_span( name=f"invoke_agent {agent_name}", kind=kind, attrs={"gen_ai.agent.name": agent_name, "gen_ai.operation.name": "invoke_agent"})
def tool_span(tracer, parent, tool_name): return tracer.start_span( name=f"tool {tool_name}", parent=parent, attrs={"gen_ai.operation.name": "tool_call"}) def chat_span(tracer, parent, provider, model): return tracer.start_span( name="chat", parent=parent, attrs={"gen_ai.provider.name": provider, "gen_ai.request.model": model, "gen_ai.operation.name": "chat"})
class ExternalStore: def __init__(self): self.rows = {} def put(self, content): rid = uuid4().hex; self.rows[rid] = content; return rid # 生产模式:span 上只记引用 ID,原文留外部存储 span.attrs["gen_ai.input.messages"] = f"ref://{store.put(prompt)}"
运行 python3 code/main.py 会输出:一棵带全部必需 GenAI 属性的 span 树,以及一个展示 opt-in 内容引用的「外部存储」。
💡 设计要点:内容捕获「默认关」是安全契约而非偷懒——Agent 处理的提示里常有 PII、密钥、客户数据,默认捕获等于把它们写进任何能读追踪的人手里。把内容推到访问受控的外部存储、span 上只留指针 ID,是「可观测性」与「数据安全」的平衡点,也是第 27 节防御在追踪层的落点。
| 后端/平台 | GenAI 支持程度 |
|---|---|
| Datadog LLM Observability(v1.37+) | 原生映射 GenAI 属性 |
| Langfuse / Phoenix / Opik(第 24 节) | 自动仪表化生态 |
| Jaeger / Honeycomb / Grafana Tempo | 原始 OTel 追踪,按 GenAI 属性建仪表盘 |
| 自托管 | OTel Collector + GenAI 处理器 |
原课程 outputs/skill-otel-genai.md:把 OTel GenAI span 接进一个已有 Agent,带内容捕获默认关、外部引用存储、稳定性 opt-in 配置。
invoke_agent(INTERNAL)+ 每工具 span,发到一个 Jaeger 实例。gen_ai.data_source.id 规范,把它接进你第 09 节的 Mem0 搜索。OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental,验证你的属性没被 collector 重命名。OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental 钉实验约定,防升级重命名。下一节,我们站在这些约定之上,看 Agent 可观测性平台——Langfuse、Phoenix、Opik、Arize 等如何用这套标准 span 形状,提供会话回放、成本归因、评估闭环与告警。