OpenTelemetry GenAI 语义约定:给线上格式做埋点,而不是给厂商做埋点。
你这周装的那个埋点库,决定了两年后换可观测性厂商要花多少钱,而几乎没人在当时给这件事定过价。发出由厂商 SDK 塑形的 span,换厂商就意味着把整个应用重新埋一遍;发出由 OpenTelemetry GenAI 约定塑形的 span,换厂商就是一次周二就能上线的 collector 配置改动。这些约定目前仍标着 Development——截至 2026 年年中,GenAI 这一组里没有任何一项是 Stable——而这是"把你构建所依据的版本钉死"的理由,不是"再等等"的理由。
你在选的是数据模型,不是看板。
智能体可观测性常常按看板的方式被评估:谁家的 trace 渲染得最好读、谁的评判器集成更顺手、谁的人工标注队列更好用。这些差异是真实的,而且都是可逆的那类。不可逆的决定,是你发出去的数据的形状,因为那是最终会被编译进你每一个服务里的东西。
失败模式很具体也很常见。一支团队装上某厂商的追踪 SDK,用那家的装饰器把智能体代码点缀一遍,然后上线。十八个月后价格变了,或者产品被收购了,或者团队只是想把评测挪到别处跑——而这次迁移不是一次配置改动,是要动代码库里每一个被埋点的调用点。埋点变成了锁定本身,而它在当初的厂商对比里从来不是一个条目。
OpenTelemetry 给出的答案,和它十年前对 HTTP 与数据库给出的是同一个:先就属性名与 span 形状达成一致,让一条 trace 无论由谁发出都是同一个意思,然后让厂商去比拼拿它做什么。对 GenAI 而言,这意味着为智能体真正会执行的那些操作定下一套固定词汇,以及在不动应用代码的前提下把同一条流扇出到多个后端的能力。
判断你有没有做对,测试是这样的:你能不能在不改代码的情况下,把 trace 并行地送到第二个后端、连送一周?如果能,你的埋点是可移植的,而这之后的每一个厂商决定都是可逆的。如果不能,你有的不是一个可观测性厂商,而是一个可观测性依赖。
这套约定实际给了你什么。
GenAI 约定不只是包一层模型调用。截至 v1.41 版约定,规范覆盖了智能体真正拥有的那几层,这也正是它们能用于智能体工作、而不只是用于 LLM 调用的原因:
- 模型 span——一次推理请求:厂商、模型名、请求参数、结束原因,以及按输入输出拆开的令牌数。
- 工具 span——一次工具调用,带工具名与调用标识,于是一次失败的工具调用是一个带 error 的 span,而不是日志里的一行。
- 智能体 span——把智能体作为一个整体,这让你可以把"智能体花了多久"和"它第十一次模型调用花了多久"分开来问。
- 工作流 span——智能体之上的编排层,于是多智能体与多步系统有地方可挂。
- 指标——把操作延迟与令牌用量作为一等仪表,而不是让你自己去聚合的属性。
最后这一点是实际收益最快显现的地方。把令牌用量做成按模型与操作分维的标准指标,意味着成本分析是一次查询而不是一个项目——你不用建任何东西,就能把开销归到某条路由、某个客户、或某一条病态 trace 上。同一批数字既喂给循环内的成本控制,也喂给智能体成本控制里主张的那个"每完成一个任务的成本"。
智能体 span 与工作流 span 才是把这套东西与通用 LLM 日志区分开来的部分。没有它们,一次二十步运行的 trace 就是二十个平级 span 加一个靠猜结构的人;有了它们,轨迹就在 trace 树里,而那是轨迹评测的前提,也是任何需要回答"这次运行在哪一步走偏"而不只是"它走偏了"的事故复盘的前提。
Development 状态是钉版本的理由,不是等待的理由。
成熟度要说清楚,因为厂商未必总会说。截至 2026 年年中,没有任何 GenAI 专属的 span、指标、事件或属性被标为 Stable——整组都带着 Development 状态,用 OpenTelemetry 的话说,就是这些名字可以变,不享有稳定约定所享有的那些保证。2026 年 6 月,gen_ai.* 这套约定被从主语义约定仓库移出,进入一个专用仓库,因而现在按自己的节奏发版,不再被主仓库那套更慢、受稳定性约束的节奏拖着。
这两个事实指向同一个方向。拆仓是一个信号,说明这块预计会走得很快,而 Development 状态就是给这种走动贴的诚实标签。两者都不构成"这期间继续发私有 span"的理由,因为替代方案并不是"等它稳定"——替代方案是攒下十八个月只有一家厂商读得懂的 trace。
管用的姿态是:
- 把你构建所依据的约定版本钉死,就像钉一个库那样。这样升级就成为一次刻意的、经过测试的改动,而不是随某个埋点包的版本跳动一起到来的东西。
- 把版本记进遥测本身。一个写明 schema 版本的 resource 属性,意味着一年之后你还能说清某条历史 trace 是照哪版约定写的。没有它,一个悄悄开始对不上的看板,就是一场没人喜欢的排查。
- 重命名在 collector 里做,别在应用里做。当上游改了某个属性名,一个 transform processor 可以在过渡窗口里同时发出新旧两个名字,从而把看板的迁移和服务的发布彻底解耦。
- 要预期约定走得比你厂商对它的支持更快。去核实你的后端实际索引了什么,而不是它市场页面上声称兼容什么;部分支持很常见,表现为某个属性你在原始 span 里看得见,却查不了。
内容问题:提示词与补全不是普通属性。
约定涵盖了消息内容的捕获,而这一块需要一个决定而不是一个默认值,因为提示词与补全的正文,同时是 trace 里最有用和最危险的东西。
有用,是因为没有内容的 trace 只能告诉你智能体做了十一次调用,却说不出它为什么做错了——内容是可观测性与指标之间的分界。危险,则有三个各自需要答案的独立理由:
- 体量。对智能体做完整提示词捕获的量是巨大的,因为对话记录每一步都会重发,而朴素的捕获会每次都存一遍。一次二十步的运行产生的遥测,可能比你服务其余全部还多。对内容做采样,或者按引用存储,让 span 只带一个指针。
- 敏感性。提示词里装着用户输入的一切,以及你为他们检索来的一切,其中经常包含你的可观测性厂商合同并未覆盖的个人数据。内容捕获是一项带审批的数据治理决定;见数据治理与数据驻留。
- 留存期不匹配。trace 默认是短留存,这对排障是对的,对审计记录是错的。如果监管者可能会问某个智能体做了什么,那份记录该待在一个有自己留存策略的持久存储里——trace 后端不是审计轨迹,无论它看上去多像。
能同时解掉这三个问题的模式,是在 collector 处把两个去向分开。结构性遥测——span、时延、令牌数、工具名、错误状态——以完整保真度送到可观测性后端,便宜且短留存。内容送到一个你自己控制的存储,在 collector 上做脱敏之后再走,按内容真正需要的留存与驻留规则来管,而 span 只带一个指向它的引用。这在 collector 流水线里只多一个 processor,却是"可观测性账单可预测"与"不可预测"之间的分界。
怎么接线,以及要先做对的那一件事。
机制就是普通的 OpenTelemetry,而这正是重点:
- 在应用里埋一次,用约定里的属性名。常见框架的自动埋点覆盖模型 span 与工具 span;智能体 span 与工作流 span 通常需要你来指明边界在哪,因为只有你知道什么算作一次运行。
- 导出到一个你自己跑的 collector,绝不直接导给厂商。这是让下游一切保持可逆的那一个决定。从应用直连厂商端点,等于把采用约定本要移除的那份锁定又造了回来。
- 从 collector 扇出。可观测性后端、评测存储、成本分析、审计归档——它们全都是 exporter,全都可配置,没有一个需要重新部署智能体。
- 按 trace 采样,不按 span 采样。半条轨迹比没有更糟:它看上去完整,其实不是。用尾部采样,让决定在运行结束之后做、并且可以按结果来做——凡是报错或跑得久的全留,例行成功的抽样。
在这一切之前要先做对的,是 trace 与会话的身份。一次智能体运行一条 trace,一步一个 span,并让一个会话或对话标识挂在这次运行的每个 span 上——设成 resource 或 baggage 属性,好让它能跨服务边界存活。几乎每一个"我们有 trace,但回答不了这个问题"的处境,都可以归结为缺一个关联标识;而现在加它,比事后回填便宜得多。更完整的论述在追踪与可观测性;概念底子是智能体可观测性。
评判器也要埋点。如果有模型在给你的输出打分,那次打分调用就是一次带成本、带延迟、带模型版本的推理请求,它该和被它打分的那次运行待在同一条 trace 里。团队常常发现自己的评测评判器占了模型开销中不小的一块,而且发现得很晚——因为评判器恰恰是那个没人埋点的调用。
这周该做什么。
不必立项做迁移,就能拿到大部分收益:
- 先看看你今天发的是什么。取一条生产 trace,看属性名。如果它们带的是厂商前缀而不是
gen_ai.*,你就知道了问题的规模——而且是在合同续签之前知道,不是在续签当中。 - 在链路里放一个 collector。哪怕只有一个后端、不做任何变换,光这一处改动就让之后每一个厂商决定变成一次配置编辑。它是一个下午的工作量,也是这份清单上回报最高的一项。
- 缺会话标识就补上。现在很便宜,事后不可能补,而它正是第一次真事故会需要的那个字段。
- 把内容策略显式定下来并写进文档。捕获什么、脱敏什么、存在哪里、留多久。如果没人决定,默认就是"全都存、永远存、存在厂商那儿"——那也是一个决定。
如果只做一件事:在你的服务与你今天在用的那个后端之间,放上你自己的 collector——哪怕你对那个后端十分满意。它花一个下午,什么可见的东西都不改,却把你的可观测性厂商从一个被编译进代码的依赖,变成了一行 YAML。本页其余每一条建议在此之后都会变容易,而再往后的那些——换后端、加评测存储、脱敏内容、评估期间并行跑两家厂商——才变得可能。
相关:线上评测与线下评测讲这条流可移植之后拿它做什么,模型退役与迁移讲为什么每个 span 上的模型标识比它看上去更值钱,审计轨迹讲 trace 后端不是的那份记录。