跳到主要内容

一条语义约定在 YAML 里怎么写

本章讲语法:不碰 GenAI 具体字段含义(那是第 2 章),只看「一条约定是怎么被定义和拼装出来的」。看完你能读懂 model/ 下任何一个 YAML。

两个文件,别记混: 字段原子(registry. 组)住在 model/**/registry-deprecated.yaml;span/metric/event 信号分子住在 model/**/spans-deprecated.yaml(及 metrics-/events-)。本章的字段定义引用都指向 registry-deprecated.yaml,信号组引用才指向 spans-deprecated.yaml

1.1 两层结构:先定义字段,再组装信号

语义约定的核心心智模型只有一句:字段(attribute)是原子,信号(span/metric/event)是分子。

  • 原子 = 字段定义:活在 idregistry. 开头的 attribute_group 里(registry-deprecated.yaml),只说「这个字段叫什么、什么类型、有哪些枚举值、什么意思」。一处定义,处处引用。
  • 分子 = 信号:type: span / metric / event 的组(spans-deprecated.yaml 等),不重新定义字段,而是用 ref 把已有字段拉进来,再补上「在这个信号里它是不是必填」。

这种「定义一次、引用多处」的设计,保证同一个 gen_ai.request.model 不管出现在 chat span 还是 embeddings span,类型和语义永远一致

1.2 定义一个字段(registry 组)

最简单的字段:给名字、类型、说明、例子。看 gen_ai.usage.input_tokens 的真实定义:

# model/gen-ai/deprecated/registry-deprecated.yaml:577 起,registry.gen_ai 组内
- id: gen_ai.usage.input_tokens
stability: development
type: int
brief: The number of tokens used in the GenAI input (prompt).
examples: [100]

四个关键键:

作用
id字段全名,点分命名空间(gen_ai.usage.input_tokens)
type标量类型:int / double / string / boolean / string[] / any
stability成熟度:development(实验)/ stable(承诺不破坏)
brief / examples一句话说明 + 示例值,会渲染进文档表

真实库里这些字段当前还多带一段 deprecated:annotations.dependency_resolution.exclude——那是「搬家」留下的标记,语法上属于可选项,第 4 章细讲。这里先忽略,聚焦字段本身的形状。

1.3 枚举字段:type.members

当字段只允许有限取值,type 不再是裸标量,而是带 members 列表。gen_ai.operation.name(操作类型)就是枚举:

# model/gen-ai/deprecated/registry-deprecated.yaml:914 起,gen_ai.operation.name
type:
members:
- id: chat # 标识符(代码里的常量名片段)
value: "chat" # 实际写进遥测的字符串
brief: 'Chat completion operation ...'
stability: development
- id: embeddings
value: "embeddings"
...
- id: execute_tool
value: "execute_tool"
...

要点:id 是生成代码时的符号名,value运行时落到遥测里的字符串。二者通常一致,但改名时会故意不一致——比如 gen_ai.token.type 里旧成员 completionvalue 已经是 "output"、且 deprecated.renamed_to: output(model/gen-ai/deprecated/registry-deprecated.yaml:663-669),实现「老符号、新值」的平滑过渡。

1.4 组装一个 span:extends + ref + requirement_level

信号组的精髓是复用。看 GenAI 推理 client span 的骨架:

# model/gen-ai/deprecated/spans-deprecated.yaml:135 起
- id: span.gen_ai.inference.client
type: span
span_kind: client
extends: attributes.gen_ai.inference.client # ← 继承一整组已定义好的字段
attributes:
- ref: gen_ai.provider.name # ← 引用字段,不重新定义
requirement_level: required # ← 只在这里补「必填级别」
sampling_relevant: true
- ref: gen_ai.request.model
sampling_relevant: true

三个机制叠在一起:

  • extends —— 继承另一个组的全部字段。这里 attributes.gen_ai.inference.clientextendsattributes.gen_ai.common.client,后者再 extends attributes.gen_ai.common(spans-deprecated.yaml:29-57)。形成一条继承链,公共字段(error.typeserver.address)只在最底层写一次。
  • ref —— 把 registry 里定义过的字段「拉进来」。绝不在信号里重复写 type/brief(可按需 note: 覆盖说明)。
  • requirement_level —— 这是信号组最有价值的产出:同一个字段在不同信号里软硬不同。

必填级别(requirement_level)的四档:

级别含义
required必须有,否则不合规
conditionally_required: <条件>满足条件时必填(如 gen_ai.request.seed:if applicable and if the request includes a seed,spans-deprecated.yaml:74-76)
recommended应该有,可缺
opt_in默认不发,用户显式开启才发(常用于含敏感内容的字段,如 gen_ai.input.messages)

1.5 三种信号的差异:span vs metric vs event

同一批字段,挂在不同信号上语义不同。一句话区分:

span = 一次操作的「一条带时长的记录」 (调用 chat 这一次:何时开始、多久、什么参数、什么结果)
metric = 一个可聚合的「数」 (token 用量直方图、操作时延直方图 —— 跨很多次操作汇总)
event = 一条结构化「日志/消息」 (这次操作里的一条用户消息、一个工具结果、一次评估打分)
  • span:type: span + span_kind(client/internal/server)。如 span.gen_ai.inference.client(client)、span.gen_ai.execute_tool.internal(internal,spans-deprecated.yaml:597)。
  • metric:type: metric + metric_name + instrument + unit。如 metric.gen_ai.client.token.usagehistogram、单位 {token}(model/gen-ai/deprecated/metrics-deprecated.yaml:54-73)。
  • event:type: event + name + 一个 body(结构化字段树)。如 event.gen_ai.choice 的 body 里嵌套了 finish_reasontool_calls 等字段(model/gen-ai/deprecated/events-deprecated.yaml:219-355)。

为什么要三种? 因为问题不同:

  • 「这次调用慢不慢?」→ 看 span 的时长。
  • 「过去一小时 p99 时延多少?」→ 聚合 metric 直方图。
  • 「模型当时具体回了什么?」→ 翻 event 的内容体。

1.6 一张图收束:从字段到信号

registry.gen_ai (定义原子字段,registry-deprecated.yaml)
├─ gen_ai.provider.name (enum: openai/anthropic/...)
├─ gen_ai.operation.name (enum: chat/embeddings/...)
├─ gen_ai.usage.input_tokens (int)
└─ ...
│ 被 ref 引用

attributes.gen_ai.common ← error.type, operation.name, request.model
│ extends

attributes.gen_ai.common.client ← + server.address / server.port
│ extends

attributes.gen_ai.inference.client ← + 一堆 request.*/usage.* 字段
│ extends

span.gen_ai.inference.client (span, CLIENT) ← 最终钉死 required/recommended

继承链让公共字段只写一次,叶子信号只关心「在我这它该多硬」。这就是整套约定既不重复精确到每个信号的根本。

1.7 代码地图

主题文件符号名
字段(原子)定义集中地model/gen-ai/deprecated/registry-deprecated.yamlregistry.gen_ai
枚举字段示例model/gen-ai/deprecated/registry-deprecated.yamlgen_ai.operation.name, gen_ai.output.type
继承链顶端公共字段(信号组)model/gen-ai/deprecated/spans-deprecated.yamlattributes.gen_ai.common
span 组装示例model/gen-ai/deprecated/spans-deprecated.yamlspan.gen_ai.inference.client
metric 组示例model/gen-ai/deprecated/metrics-deprecated.yamlmetric.gen_ai.client.token.usage
event 组(带 body)示例model/gen-ai/deprecated/events-deprecated.yamlevent.gen_ai.choice