给智能体链路记录定形状的四种方式里,恰好有一种自称是标准,而它偏偏是唯一一种你无法固定到某个版本的。2026 年 6 月 12 日,OpenTelemetry 的 v1.42.0 把核心语义约定里整个 gen_ai 命名空间标为弃用,并挪进了一个专用仓库——而那个仓库如今有 653 次提交、没有任何打了标签的发布,README 里关于 schema URL 的那一节仍写着 TODO。每一个 gen_ai.* 属性、span、指标与事件都挂着 Development 稳定性徽章;没有一个是 Stable。所以「我们对着开放标准做埋点」并不是它听起来那样稳妥的答案;而真正决定你的仪表盘能不能活过下个季度的,是你的后端把哪一套 span 词表当作功能分派的依据。
速览
这四者里两个是规范、两个是 SDK,而大多数混乱正是从这个区分开始的。
| 项目 | 出品方 | 它究竟是什么 | 能固定版本吗? |
|---|---|---|---|
| OTel GenAI 约定 | OpenTelemetry GenAI SIG | 属性、span、指标与事件的命名。自身不含 SDK。 | 无标签发布;只能钉提交或快照。 |
| OpenInference | Arize AI | 一套规范,外加 Python、JS、Java、Go 的埋点器。 | 可以——有版本化包,span kind 稳定。 |
| OpenLLMetry | Traceloop | 一个自带 span 形状的 SDK,部分已上游进 OTel。 | 可以——有版本,但形状正在迁移中。 |
| OpenLIT | OpenLIT 项目 | OTel 原生的 SDK 加一个平台,覆盖面最广。 | 可以——有版本,并随 gen_ai.* 的变动跟进。 |
社区规模的大致数字,仅供参照而非排名:OpenLLMetry 约 7.5k GitHub star,OpenLIT 约 2.8k,OpenInference 约 1.3k,而 OTel GenAI 约定仓库约 414——这个数字主要反映的是「规范不会像库那样被 star」。
中立的那个选项,恰恰是还在动的那个
先从 6 月发生的事说起,因为它会把整场比较重新框定。语义约定 v1.42.0 把核心仓库里全部六十个 gen_ai 属性标为弃用,并把这个命名空间——连同 MCP 约定一起——挪进了 open-telemetry/semantic-conventions-genai,其明确理由就是让这个领域能以快于核心稳定性门槛所允许的速度迭代。六十个当中,五十个保留了完全相同的字符串、只是换了地址,八个被改名,两个被移除且没有任何东西接替它们。
真正要紧的是挪动了的那十个。gen_ai.usage.prompt_tokens 变成了 gen_ai.usage.input_tokens,gen_ai.usage.completion_tokens 变成了 gen_ai.usage.output_tokens,于是任何建在旧名字上的成本仪表盘,在某个发送方一升级的那一刻就开始静默少算——没有报错、图上没有缺口,只是数字小了一点。gen_ai.system 变成了 gen_ai.provider.name,以同样的方式破掉按供应商的拆分。而 gen_ai.prompt 与 gen_ai.completion 是被删掉而非改名:内容采集现在走需显式开启的 gen_ai.input.messages 与 gen_ai.output.messages,再加 gen_ai.system_instructions。如果你的回放工具是从 span 上读提示词的,那这不是一次 sed 能搞定的改名。
这些都不是管理不善。一个快速变动的领域被安置到了一个变动更快的家里,这是正确的决定。但它确实把「厂商中立的埋点」那套惯常论证翻了过来。宣传语是:中立能帮你省掉一次迁移;而 2026 年的现实是,中立的那一层正是在发出迁移的那一层,而一个两年前就把形状冻住的厂商 SDK 一次迁移都没发过。放在长周期上,中立仍然赢。放在一年的周期上,它不免费,而这笔账落在「对着属性名字写过查询」的那个人头上。
span kind 才是契约;属性名字只是扰动
下面这个区分,几乎每一篇比较文章都漏掉了。属性名字是贴在 span 上的一个标签;它变了,一条查询就坏了,而你去把查询改好。而 span kind 是后端据以分派的那个东西——这条 span 要被渲染成一次 LLM 调用、一个检索步骤、一次工具调用还是一个智能体回合,它要不要进入评测流水线,它能不能被回放。把那个破掉,功能不是降级,而是消失。
四个项目对此的回答大不相同。OpenInference 把 span kind 做成显式且必填:一个 openinference.span.kind 属性,取十一个值——LLM、EMBEDDING、CHAIN、RETRIEVER、RERANKER、TOOL、AGENT、GUARDRAIL、EVALUATOR、PROMPT 与 DECISION——旁边配上承载载荷的那些命名空间(llm.*、document.*、message.*、tool.*、evaluation.*、session.*)。这套词表是四者中最「智能体形状」的,同时也是这场比较里最久未变动的东西,因为 Arize 很早就把它冻住,并在它上面建了一款产品。
OTel GenAI 走的是「按操作名」而不是「一个 span kind 枚举」,而与智能体相关的工作是近期才有的:v1.41.0 把 invoke_workflow 加为一种操作,并把 invoke_agent 拆成 client 与 internal 两类 span,同时带来了首批流式指标(gen_ai.client.operation.time_to_first_chunk 与 .time_per_output_chunk)。那次拆分是正确的建模决定,同时也是一次会改变智能体 span 嵌套方式的破坏性变更——而这恰恰是你最不希望在一个产品功能底下发生的那种挪动。
OpenLLMetry 卡在尴尬的中间,是历史而非选择所致。Traceloop 在 GenAI 约定存在之前就发布了自己的 span 形状,随后把自己的语义约定上游进了 OpenTelemetry——项目自己的 README 就这么写——结果是一个输出横跨两套词表、且尚未完全收敛的 SDK。它的 issue 列表里带着那条可以预料的报告:gen_ai.prompt 与 gen_ai.completion 现已在上游被弃用。OpenLIT 走的是另一条路:从一开始就 OTel 原生,随 gen_ai.* 的变动跟进,且覆盖面是四者中最宽的——它会记录 LLM 调用、工具调用、检索、嵌入、向量存储操作、MCP 请求与子智能体活动;而在编码智能体上,它还会为文件读取与编辑、shell 命令与搜索生成 span,并与来自 NVIDIA、AMD、Intel 采集器的 GPU 利用率相关联。
覆盖面买到了什么,又要付出什么
自动埋点的覆盖面,是各团队在第一周里真正能感受到的那根轴,而三个 SDK 在这里确实不同。OpenInference 为 Python、JavaScript、Java 与 Go 都出了埋点器,覆盖一长串供应商与框架——OpenAI、Anthropic、Bedrock、VertexAI、Mistral、Groq、LangChain、LlamaIndex、CrewAI、Haystack、PydanticAI 都在其中——这并不意外,因为它覆盖的每一个框架,都是 Phoenix 需要渲染的框架。OpenLLMetry 的长处是在供应商与向量存储一侧的同等宽度,Python 优先、JS/TS 走一个独立发行版,并且在四者中拥有最深的第三方后端集成集合。OpenLIT 的长处是范围而非语言数量:GPU 关联与编码智能体的那些 span,是另两者都没有尝试的。
覆盖面的代价是:自动埋点正是「span 形状在无人作决定的情况下渗进你数据」的地方。一个给四十个客户端打补丁的库,发出的是它作者为每一个所决定的映射;而当底下的约定挪动时,这些映射会跟着一次你本来只是为了修 bug 而做的升级一起挪动。这正是「把变换放在采集器而不是信任 SDK」的现实理由:一个在数据出门时改名属性、归一化 span kind 的处理器,是一次性交付的配置改动,而它把扰动挡在四十个调用点之外。另一条路——在应用代码里撒上某个厂商的辅助装饰器——才是「对着线上格式而不是对着厂商做埋点」所要避免的那种锁定。
还有一处不对称值得点名。后端对「偏好哪套词表」并不对称:Phoenix 是 OpenInference 原生的,Langfuse 与 Braintrust 接受 OTLP 并尽力映射,而翻译在「流向更丰富的那套词表」的方向上是有损的。把 OpenInference 的 span 转成 gen_ai.*,会丢掉那些没有对应物的 span kind 区分;反向转换则是在凭空发明它们。如果你的评测或标注流程依赖一个只有一套词表能表达的区分,那这就不是一个映射问题,而是一个产品功能问题。
什么时候选哪一个
| 情形 | 选 | 为什么 |
|---|---|---|
| 你在跑 Phoenix,或者你今天就想要智能体形状的 span | OpenInference | 十一个显式 span kind,多年稳定,而后端原生读它们。 |
| 你在跨多个服务与厂商做标准化 | OTel GenAI 约定 | 这是一切最终汇聚的地方——但请钉一个提交而不是「latest」,并预期会有改名。 |
| 你需要最宽的供应商与向量存储覆盖,且以 Python 为先 | OpenLLMetry | 集成面最深;代价是接受 span 形状仍在收敛中。 |
| 你自建推理,或者你在运营编码智能体 | OpenLIT | GPU 关联与 shell/文件/搜索 span,在这一组里别处都没有。 |
| 你在搭一个成本或用量仪表盘 | 都别直接用 | 从 span 里派生你自己的指标,并自己承担改名。不要让图表去查 gen_ai.*。 |
最后那一行是最要紧的建议,也是没人会写进比较表格的那一条。一个直接读 gen_ai.usage.input_tokens 的仪表盘,是在对着「一个没有标签发布的仓库里、稳定性为 Development 的名字」写查询。请把 token 与成本指标计算一次——在采集器处理器里或一个小作业里——以你自己掌控的名字发出来,然后让每一张图都指向它们。这是一个下午的活,而它决定了一次改名究竟是一处配置改动,还是一个季度的、静默算错的财务数字。
常见问题
OpenTelemetry 的 GenAI 约定稳定了吗?
没有。注册表里每一个 gen_ai.* 属性、span、指标与事件都挂着 Development 稳定性徽章,而专用的 semantic-conventions-genai 仓库既没有打标签的发布,也没有最终确定的 schema URL。你可以钉一个提交或一份带日期的快照;你没法钉一个版本。
OpenLLMetry 的约定是不是变成了 OpenTelemetry 的约定?
部分是。Traceloop 把自己的语义约定上游进了 OpenTelemetry,所以 GenAI 约定里反映着那份贡献。但 OpenLLMetry 这个 SDK 在那些约定存在之前就发布了自己的 span 形状,而且尚未完全向它们收敛,所以实践中你发出的是介于两者之间的东西。
2026 年 6 月那次弃用究竟弄坏了什么?
六十个属性里有八处改名、两处删除。改名包括两个 token 用量属性,以及 gen_ai.system 变为 gen_ai.provider.name;删除的是 gen_ai.prompt 与 gen_ai.completion,由需显式开启的消息类属性替代。其余五十个仍从一个新家发出同样的字符串。
以后能不动应用代码就在它们之间迁移吗?
大体能,前提是你的埋点保持通用、并把映射放在一个采集器处理器里。属性改名可以干净地翻译。span kind 的区分不行:一套从未表达过 GUARDRAIL 或 EVALUATOR span 的词表,无法把它们翻译进来,于是依赖这些的后端功能就不会出现。
一个小团队该默认用哪个?
用你已经选定的那个后端原生读取的那套词表,并在你与它之间放一个采集器侧的处理器。先选埋点、后选消费方,正是各团队最终发出一套「自己跑的任何东西都用不上的词表」的原因。
延伸阅读
本站:
- OpenTelemetry GenAI 约定——为什么不可逆的选择是数据模型,而不是仪表盘。
- 面向智能体的链路追踪与可观测性——一条有用的智能体 span 里装着什么。
- LangSmith、Langfuse、Braintrust 与 Phoenix 对比——本文所垫在其下的那场后端比较。
- 链路采样与保留——「什么都发」的成本那一面。
- 协议修订与弃用窗口——如何吸收一套在你脚下移动的约定。