一条语义约定在 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)是分子。
- 原子 = 字段定义:活在
id以registry.开头的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 里旧成员 completion 的 value 已经是 "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.client又extends了attributes.gen_ai.common.client,后者再extendsattributes.gen_ai.common(spans-deprecated.yaml:29-57)。形成一条继承链,公共字段(error.type、server.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.usage是histogram、单位{token}(model/gen-ai/deprecated/metrics-deprecated.yaml:54-73)。 - event:
type: event+name+ 一个body(结构化字段树)。如event.gen_ai.choice的 body 里嵌套了finish_reason、tool_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.yaml | registry.gen_ai |
| 枚举字段示例 | model/gen-ai/deprecated/registry-deprecated.yaml | gen_ai.operation.name, gen_ai.output.type |
| 继承链顶端公共字段(信号组) | model/gen-ai/deprecated/spans-deprecated.yaml | attributes.gen_ai.common |
| span 组装示例 | model/gen-ai/deprecated/spans-deprecated.yaml | span.gen_ai.inference.client |
| metric 组示例 | model/gen-ai/deprecated/metrics-deprecated.yaml | metric.gen_ai.client.token.usage |
| event 组(带 body)示例 | model/gen-ai/deprecated/events-deprecated.yaml | event.gen_ai.choice |