AI 工程实践
OpenTelemetry GenAI Trace 合同实验:Agent 成功为什么不等于业务成功
用 18 条合成 Agent 轨迹校准 OpenTelemetry GenAI 观测合同,实际验证模型、检索与工具失败归因、token 成本归集、PII 防泄漏和最终业务结果关联。
时间与证据
本实验于 2026 年 7 月 15 日在仓库内完成,研究对象不是某个 Agent 框架的宣传功能,而是一份可以被程序拒绝的观测合同:当一次 Agent 任务经过模型推理、知识检索和工具执行后,现有 trace 是否足以回答“哪一层失败”“用了多少 token”“能否估算成本”“有没有把原始内容或秘密写进遥测”“技术调用结束后业务到底完成没有”。这些问题如果只能靠排查人员阅读 prompt 和日志猜测,就还没有形成可运营的可观测性。
上游证据固定到 OpenTelemetry 的 open-telemetry/semantic-conventions-genai 仓库完整 commit 63f8200eee093730ce845d26ce2aafb621b0807e。本文引用的仓库树、Agent span 文档和通用 GenAI span 文档都使用该 40 位 commit 的 /tree 或 /blob URL,没有使用 main、latest 或 release tag。固定 commit 很重要:GenAI semantic conventions 在这个快照中仍明确标记为 Development,属性名、要求级别和模型可能继续变化;如果只链接当前页面,未来读者无法区分“实验当时遵循的合同”与“后来更新后的规范”。
固定文档能够证明四类 operation 已被定义,并说明它们的 span kind、属性要求与内容风险:同进程 Agent 调用使用 invoke_agent,模型推理可以使用 chat,知识检索使用 retrieval,工具执行使用 execute_tool。其中固定的 model/gen-ai/spans.yaml 将检索明确定义为 gen_ai.retrieval.client,并写出 kind: client;本实验因此把 retrieval span 校正为 CLIENT,不再使用 INTERNAL。文档还说明 error.type 在操作以错误结束时是条件必需属性;gen_ai.conversation.id 只应在会话标识本来就 readily available 时记录,不能为了填字段而临时生成 UUID、trace id 或请求内容哈希;输入消息、输出消息、system instructions、retrieval query、tool arguments 等内容属性可能携带敏感信息,并不适合默认采集。
这些上游文档不证明本文的 evaluator 正确,也不自动提供业务结果。OpenTelemetry 约定解决的是遥测字段如何命名和解释,不会知道某个订单最终是否成交、某个工单是否被客户接受、某笔动作是否仍在异步确认。失败归因算法、metadata-only 内容策略、合成价格表、业务事件双键关联和所有拒绝条件,都是本实验在上游语义基础上增加的本地合同,证据来自实际运行的仓库工件。
可复核工件位于 labs/genai-trace-contract/:
data/contract.json固定上游 commit、四种 operation、error.type、conversation id、内容禁止项和业务结果枚举。data/traces.json保存 18 条确定性 synthetic fixtures,其中 6 条用于校准结论,12 条是故意损坏的 negative controls。data/pricing.json保存两种虚构模型的合成单价,只用于验证成本公式,不是厂商报价。data/business-outcomes.json保存独立业务事件,以traceId和rootSpanId关联技术调用,并带有业务transactionId。src/evaluate.mjs完成结构校验、失败归因、隐私扫描、token 与成本归集、业务事件关联和汇总。test/evaluate.test.mjs包含 20 个自动测试,覆盖正例、负例、顺序不变量、hash 和 replay。results/2026-07-15-results.json是本次固定 capture,共 31,978 bytes,SHA-256 为8f877139c703ba19020b5e39042db5ee30b8fb44b3f2745d318093a72519a82b。
本文证据等级为 reproduced,因为 fixtures、evaluator、测试、结果和重放步骤已经实际执行,固定工件与当前输出在剥离非决定性字段后逐项一致。但这里的 reproduced 只表示“观测合同在合成输入上可复现”,不表示生产系统已经接入 OpenTelemetry SDK,不表示采样、collector、exporter 和后端查询已经验证,更不表示真实流量的失败率、成本或 PII 泄漏率等于本文数字。
实验设计
把问题拆成四个可拒绝的验收条件
实验没有把“trace 看起来完整”当作验收标准,而是提出四个必须同时成立的条件。
第一,结构可归因。每条轨迹只能有一个 invoke_agent 根节点,trace id 与 span id 必须是非全零的合法十六进制标识,其他 span 的 parentSpanId 必须存在,整棵树不能有 orphan 或 cycle。若根 span 因子调用失败而记录 child_failure,evaluator 只在唯一最深 ERROR span 存在时选择 decisive failure;若最大深度并列多个错误,则返回按 span id 排序的 candidates 并标记 ambiguous,不能让输入数组顺序偷偷决定归因。
第二,用量可归集。成本只统计真正调用模型的 chat span,读取实际 gen_ai.response.model、gen_ai.usage.input_tokens 与 gen_ai.usage.output_tokens。invoke_agent 负责组织调用,不再把子调用 token 汇总后计费一次,否则同一批 token 会在父子层重复。价格目录缺少实际 response model 时必须返回 pricing_missing,而不是把未知成本静默记为零。
第三,内容默认不可见。trace 可以保存 operation、provider、model、data source、top-k、tool name、tool call id、token 数和错误类型,但不能保存原始 prompt、system instructions、input/output messages、retrieval query、documents、tool arguments、tool result 或 prompt variables。扫描器还检查属性名与嵌套对象中的 authorization、API key、password、secret,以及 email、Bearer token、API key 形状和 private-key marker。扫描范围覆盖 span attributes,也覆盖会写入结果的 trace id、caseType、description 与业务 transactionId、outcome、reasonCode;命中字段写成 [REDACTED],findings 只返回来源、字段路径和原因。
第四,技术与业务分层。技术 span status 只回答调用是否以错误结束,不替代业务终态。独立业务事件必须同时匹配 traceId 与 rootSpanId,并携带稳定的 transactionId;最终 outcome 允许 fulfilled、rejected、pending 或 failed。一次没有技术错误的 Agent 运行仍可能被业务规则拒绝,或者因为外部确认尚未完成而保持 pending。
四类 span 的本地合同
| 层级 | operation | kind | fixtures 的必需元数据 | 明确禁止的内容 |
|---|---|---|---|---|
| Agent | invoke_agent |
INTERNAL |
operation;可用时的 agent name 与 conversation id | instructions、input/output messages |
| Model | chat |
CLIENT |
provider、request model、response model、input/output tokens;可用时的 conversation id | input/output messages、prompt variables |
| Retrieval | retrieval |
CLIENT |
data source id、top-k | raw query、retrieved documents |
| Tool | execute_tool |
INTERNAL |
tool name;可记录 type 与 call id | raw arguments、raw result |
这里必须区分“上游 requirement level”和“实验准入条件”。上游快照中有些字段是 Recommended 或 Conditionally Required;为了让成本、检索来源和会话关联在这组 fixtures 上可判定,本地合同把 response model、token usage、data source id 与 top-k 提升为必需项。这不是声称 OpenTelemetry 已把它们全部定义为无条件 Required,而是说:如果团队希望回答本文的运营问题,就必须在自己的 instrumentation contract 中把相应证据列为门禁。
本地合同也比最小语义约定更严格地处理 error.type。任何 status=ERROR 的 span 必须有非空、低基数的 error.type;非错误 span 不允许保留旧的 error.type。后一个规则用于捕捉 instrumentation 复用对象或异常清理不完整造成的状态冲突。error.type 保存 timeout、vector_store_unavailable 或 tool_timeout 这类可聚合类别,而不是异常消息、URL、用户输入或堆栈全文。
conversation id 的规则同样是双向的。当框架已经维护 session、thread 或 conversation 时,适用的 invoke_agent 与 chat span 必须记录同一个 gen_ai.conversation.id;缺失或不一致都拒绝。若运行本来没有 conversation 概念,则这两个 span 都不应凭空填值。这样既能跨多次模型调用关联同一会话,又不会把 trace id 或内容哈希伪装成业务会话标识。
六条校准轨迹
6 条 calibration fixtures 不是从生产日志抽样,而是逐条构造来验证决定性分支:
happy-path含invoke_agent、retrieval、chat、execute_tool,技术成功且业务fulfilled。model-failure的根节点记录传播错误,最深的chatspan 记录timeout,业务failed。retrieval-failure在调用模型之前由retrievalspan 记录vector_store_unavailable,业务failed。tool-failure的 retrieval 与 chat 成功,execute_tool记录tool_timeout,业务failed。business-rejected-after-technical-success所有技术 span 都成功,但独立业务事件为rejected,原因是policy_denied。business-pending-after-technical-success技术执行结束,但业务仍等待外部确认,因此 outcome 为pending。
前三类失败轨迹的根 span 也标记为错误,这刻意模拟“子调用失败导致整个 Agent 失败”的常见传播。若只按根 span 聚合,三条都会落入 invoke_agent/child_failure;选择最深错误节点后,才能分别归到 model、retrieval 和 tool。
十二条负向控制
12 条 negative controls 不进入 token、成本、失败率或业务结果汇总,它们只证明检测器会拒绝坏遥测:
| fixture | 唯一改变的主要变量 | 预期拒绝原因 |
|---|---|---|
missing-error-type |
工具 span 为 ERROR 却没有错误类型 | error_type_missing |
error-type-on-success |
成功的 chat 残留 timeout | error_type_unexpected |
raw-content-leak |
写入 input messages、raw query 与 tool arguments | 内容属性、email 与 key 形状命中 |
orphan-parent |
chat 指向不存在的 parent | parent_missing |
missing-conversation-id |
明知已有会话却不记录 | 两个 conversation_id_missing |
unpriced-model |
response model 不在价格目录 | pricing_missing |
business-root-mismatch |
业务事件只匹配 trace,root span 错误 | business_root_mismatch |
fabricated-conversation-id |
无会话时填入 trace 派生值 | 两个 conversation_id_fabricated |
result-description-pii |
将保留域 email 放进 trace description | 检测、拒绝并把结果字段写成 [REDACTED] |
result-transaction-secret |
将 synthetic key 形状放进 transaction id | 检测、拒绝并脱敏业务字段 |
result-reason-pii |
将保留域 email 放进 reason code | 检测、拒绝并脱敏业务字段 |
all-zero-identifiers |
trace id 与根 span id 都是全零 | trace_id_invalid 与 span_id_invalid |
测试还在内存中改变 conversation id,证明 Agent 根与 chat 使用不同会话标识时会触发 conversation_id_mismatch;另一个 mutation 同时删除 provider、写入负 token、改变 span kind 和 status,确保 required attribute、kind、status 与 usage 分支不是只存在于代码里。等深双错误 mutation 分别以正序和逆序传入 spans,两次都返回相同的排序 candidates 与 ambiguous: true,证明归因不依赖 JSON 数组顺序。
合成价格与确定性重放
价格目录使用两种虚构模型:synthetic-model-fast-v1 的 input/output 单价为每百万 token 2/8 美元,synthetic-model-reasoning-v1 为 5/20 美元。换算使用整数 micro-USD:
cost_micro_usd
= input_tokens × input_usd_per_million_tokens
+ output_tokens × output_usd_per_million_tokens
因为 1 美元等于 1,000,000 micro-USD,这个表达式不需要先做浮点除法。最终展示美元时才除以 1,000,000。公式验证的是“实际模型 + token 用量 + 有版本的价格目录”如何关联,不代表任何真实供应商、区域、缓存折扣、批处理价格或合同价。
evaluator 使用 Node.js 标准库,不请求模型、数据库、向量服务、工具端点或遥测后端。capture 顶层保留 capturedAt 和 {node, platform, arch},用来说明工件生成环境;确定性比较会同时删除这两个字段,再比较剩余 JSON。这样时间与机器差异不会制造伪回归,operation、violations、usage、business relation 等决定性字段仍必须完全一致。
复现命令为:
node --test labs/genai-trace-contract/test/*.test.mjs
node labs/genai-trace-contract/src/evaluate.mjs \
--out /tmp/genai-trace-contract-replay.json
shasum -a 256 \
labs/genai-trace-contract/results/2026-07-15-results.json
jq 'del(.capturedAt, .environment)' \
labs/genai-trace-contract/results/2026-07-15-results.json \
> /tmp/genai-trace-checked.json
jq 'del(.capturedAt, .environment)' \
/tmp/genai-trace-contract-replay.json \
> /tmp/genai-trace-replayed.json
diff -u /tmp/genai-trace-checked.json \
/tmp/genai-trace-replayed.json
结果
20 个自动测试全部通过。6/6 calibration fixtures 满足合同,12/12 negative controls 都至少触发一个决定性拒绝分支;校准集合覆盖 invoke_agent、chat、retrieval 和 execute_tool 四种 operation。固定结果摘要如下。
| 指标 | 固定结果 | 能够支持的结论 |
|---|---|---|
| fixtures 总数 | 18 | 6 条校准、12 条负向控制 |
| 自动测试 | 20/20 | 合同分支、顺序不变量、hash 与 replay 在当前实现通过 |
| calibration 合同通过 | 6/6 | 正例没有被本地合同误拒绝 |
| negative controls 被识别 | 12/12 | 十二类预设坏遥测均被拒绝 |
| 校准集合 operation 覆盖 | 4/4 | Agent、模型、检索、工具都进入 trace |
| 可归因技术失败 | 3 | model、retrieval、tool 各 1 条 |
| 计费 chat span | 5 | retrieval 先失败的一条没有调用模型 |
| input / output tokens | 4,940 / 620 | 只统计 calibration 中的 chat span |
| 合成成本 | $0.02414 |
只验证合成价格目录下的归集公式 |
| calibration 隐私合同通过 | 6/6 | 没有出现本合同禁止的内容属性或模式 |
| 技术成功但业务未成功 | 2 | 分别为 rejected 与 pending |
故障定位没有停在 Agent 根节点
model-failure 的 decisive span 是 chat,error.type=timeout;retrieval-failure 的 decisive span 是 retrieval,error.type=vector_store_unavailable;tool-failure 的 decisive span 是 execute_tool,error.type=tool_timeout。三条根 span 都有传播错误,但统计结果仍分别为 model: 1、retrieval: 1、tool: 1。
这说明 parent-child 树与一致的 operation/error 属性足以支持这组三层归因。等深双错误测试进一步证明:没有唯一最深 span 时,evaluator 不会挑选数组中的第一个,而是给出稳定排序的 candidateSpanIds 并把 decisive 字段留空。它仍没有证明所有生产故障都能自动找到“根因”:最深错误更接近直接失败组件,却可能不是组织层面的根因。例如工具 timeout 可能源于上游网络、错误凭证或限流;向量库不可用可能源于部署变更。trace contract 提供可查询的第一定位点,后续仍需关联基础设施、deployment 和 change events。
Token 归集避免了父子双算
5 条发生模型调用的 calibration 轨迹成本拆分如下:
| fixture | input | output | 合成成本 |
|---|---|---|---|
happy-path |
1,200 | 180 | $0.00384 |
model-failure |
640 | 0 | $0.00320 |
tool-failure |
900 | 120 | $0.00276 |
business-rejected-after-technical-success |
1,500 | 240 | $0.01230 |
business-pending-after-technical-success |
700 | 80 | $0.00204 |
| 合计 | 4,940 | 620 | $0.02414 |
retrieval-failure 在模型调用前结束,因此 token 与成本都是零。model-failure 虽然 output 为零,仍有 640 个 input tokens;失败请求不应自动从成本分母中消失。所有 invoke_agent 根 span 都不计 token,所以父节点组织成本没有与五个 chat span 重复。
价格目录查询还必须区分“目录自有模型名”和 JavaScript 对象原型上的继承名称。mutation 把实际 response model 分别改成 __proto__ 与 constructor,两者都得到 pricing_missing、pricedModelSpanCount=0 和 pricingComplete=false;否则普通对象属性查找会把它们误当成价格项,并把 NaN 序列化成 null,形成看似通过但不可用的成本证据。
最贵的一条是技术成功但业务被拒绝的 reasoning 模型轨迹,占合成总成本的一半以上。这不是生产经济结论,却暴露了正确的分析单位:优化“每次模型调用成本”不够,还要看“每个 fulfilled 业务结果成本”。如果 rejected、pending、人工接管和失败重试消耗大量 token,调用成功率可以很好看,单位业务成果仍可能恶化。
Metadata-only 策略在正例保持零命中
6 条 calibration 的 privacy verdict 全部为 safe。raw-content-leak 负向控制故意放入 .invalid 保留域名和明显标记为非真实凭证的字符串,单独产生 7 个不含原值的 findings:3 个 forbidden content attributes、1 个敏感嵌套 key、2 个 email pattern 和 1 个 API key shape。另三条负例分别把同类模式放进 description、transactionId 和 reasonCode;它们都被拒绝,三个输出字段都变为 [REDACTED]。固定结果 JSON 经测试确认不含四条隐私负例中的 email 或 synthetic key 原文。
结果字段不能假定输入一定是 string。mutation 进一步把 description、业务 transactionId 和错误 span 的 error.type 改成包含 email 的嵌套对象;扫描器不仅报告嵌套路径,还在序列化前把整个结果字段替换成 [REDACTED]。这避免了“privacy verdict 已失败,但原对象仍从摘要字段回显”的旁路。
这项结果证明的是“已列出的禁止字段和四类模式会被当前扫描器发现”,不是 PII 已被完整清除。真正关键的是控制方向:业务排障默认依赖低基数元数据,原始 prompt、query、tool arguments 不进入通用 trace;确有内容调试需求时,应采用独立 opt-in、过滤、访问控制、保留期和审计,而不是先把全部内容发送到共享可观测平台再补救。
技术成功与业务成功出现两次分叉
6 条 calibration 的关系分布为:1 条 technical_success_business_success,3 条 technical_failure_business_non_success,2 条 technical_success_business_non_success。后两条正是本实验最重要的反例:一条所有技术 span 成功但被业务 policy 拒绝,另一条技术链已完成但业务仍 pending。
如果仪表盘把“根 span 没有 ERROR”直接命名为“Agent 成功率”,这两条都会进入成功分子;如果产品真正承诺的是订单完成、工单解决或动作生效,它们都不能算 fulfilled。独立 outcome 事件让团队同时保留两个指标:技术执行健康度用于工程排障,业务完成率用于产品与商业判断。双键关联也成功拒绝了只匹配 trace id、却引用错误 root span 的负向事件。
固定工件能够重放
结果文件的原始字节 SHA-256 为 8f877139c703ba19020b5e39042db5ee30b8fb44b3f2745d318093a72519a82b。重新运行 evaluator 后,删除 checked 与 replay 两份 JSON 的 capturedAt 和 environment,diff 无输出;artifact hash 测试同时固定原始文件字节,防止历史结果被新 capture 静默覆盖。
hash 解决的是身份问题,不是事实正确性。错误算法也可以稳定地产生同一个 hash,所以测试还分别断言 operation 覆盖、故障层、conversation 分支、PII findings、token 数、合成成本、业务关系和所有 negative controls。工件完整性与语义断言缺一不可。
失败与边界
第一,所有 18 条都是 synthetic fixtures。它们由作者构造,不来自客户请求、线上 trace 或任何厂商模型。6/6 通过不能解释为生产合规率 100%,12/12 负例识别也不能解释为检测器覆盖所有事故。数据集只校准已写出的合同分支,没有概率分布、延迟、并发、用户满意度或真实故障频率。
第二,OpenTelemetry GenAI conventions 仍处于 Development。本文固定 commit 是为了可重放,不是冻结行业标准。未来属性重命名、requirement level 调整、事件模型变化或稳定版发布时,应新增合同版本和迁移测试,旧工件继续指向旧 commit。直接原地改写历史 fixture 会让 artifact hash 和文章解释失去意义。
第三,实验没有运行真实 telemetry pipeline。没有初始化 OpenTelemetry SDK,没有验证 span processor、sampling、OTLP exporter、collector queue、backend indexing、attribute truncation 或查询语言。应用内对象满足合同,不等于导出后仍完整。下一阶段必须把同一 fixture 通过真实 SDK 与 collector 回环,再比较接收端 span tree 和业务 join。
第四,“唯一最深 ERROR span”是本地归因策略,不是 OpenTelemetry 自动根因分析。同步树中它能避开根节点传播错误;同深度并列时本实验只报告 ambiguous candidates,不伪造唯一结论。异步队列、span links、多 Agent handoff、并行 fan-out、重试 attempt 和跨 trace saga 还可能没有可比较的树深度。生产合同需保存 attempt、workflow、deployment、tenant、region 等受控维度,并明确 links 如何进入归因。
第五,metadata-only 不等于零隐私风险。conversation id、data source id、tool call id、错误类型甚至自定义业务 reason code 都可能被错误设计为用户标识或自由文本。本文的正则只覆盖少数明显格式,不能识别中文姓名、地址、内部账号、编码后的秘密、图片或间接可识别组合。真实系统需要数据分类、schema allowlist、SDK hook、collector processor、后端权限和保留策略共同执行。
第六,禁止 raw content 会降低部分调试能力。没有 query 和 documents 时,工程师很难单靠 trace 判断检索语义是否正确;没有 tool arguments 时,也无法直接重放参数问题。本实验选择的是通用生产遥测的保守默认值。需要内容级评测的团队应把样本放入隔离评测存储,使用脱敏、抽样、审批和更短保留期,并在 trace 中只记录受控引用;不能因为排障方便就让所有运维读者默认获得用户内容。
第七,$0.02414 完全是合成成本。两种模型名与价格均为 fixture,不对应真实 API。公式没有覆盖 cache read/write tokens、reasoning tokens、批处理、区域税费、免费额度、重试、工具与检索服务费用、collector 存储或人工排障。它只证明当前五个 chat span 不重复计费,并在未知模型价格时 fail closed。
第八,business outcome 是应用自定义证据,不由 GenAI semantic conventions 自动产生。traceId + rootSpanId 可以阻止一类错链,却不能保证业务事件真实、唯一或最终。长事务还需要业务主键、幂等键、事件版本、发生时间、权威状态来源和 reconciliation。若 outcome producer 自己有 bug,trace join 仍可能得到稳定但错误的结论。
第九,技术 status 只按是否存在 ERROR span 判断。OpenTelemetry 通常建议成功 span 保持 UNSET,部分库会显式使用 OK;本实验两者都视为没有技术错误。但 cancelled、deadline、partial response、fallback 成功和被采样掉的子错误需要更细的状态机。不能把“没有观察到 ERROR”误写成“证明没有错误”。
第十,固定 hash 不能替代来源审计。结果工件可以证明没有被无声改写,却无法证明输入代表现实、价格合理或结论完整。本文同时公开 contract、fixtures、测试、负例和边界,正是为了让读者检查“固定了什么”,而不是只相信一串摘要。
商业价值
最可能为这套能力付费的不是只调用一次模型的 demo 团队,而是已经让 Agent 访问知识库、工单、CRM、支付前置流程、代码仓库或内部工具的组织。它们面对的成本不只是 token:一次失败可能消耗模型调用、检索、工具重试、人工排查和客户等待;一次看似成功但业务未完成的调用还会制造错误 KPI。统一 trace contract 的价值,是把这些成本落到同一条可查询证据链,而不是把四个系统的日志靠人工时间戳拼接。
它增强的是现有 APM、审计与业务分析流程,而不是替代它们。APM 继续回答服务延迟和错误,GenAI span 增加 model、token、retrieval 与 tool 维度,业务事件提供最终 outcome,安全策略阻止内容进入错误的存储层。全栈工程中的 request id、数据库主键、错误分类、schema validation、RBAC 和成本中心,在 Agent 系统中分别迁移为 trace tree、transaction id、error.type、semantic contract、内容采集策略与模型用量归因。
第一轮生产试点不应只做一个“Agent 成功率”面板,而应至少定义以下指标:
业务关联完整率 = 有唯一合法 outcome 的根 trace / 应产生 outcome 的根 trace
故障可归因率 = 能落到 model、retrieval、tool 或明确 agent 层的失败 / 技术失败
价格覆盖率 = 有版本化价格记录的 billable model spans / 全部 billable model spans
内容违规率 = 命中禁止字段或敏感模式的 spans / 全部接收 spans
技术与业务分叉率 = 技术成功但 outcome 非 fulfilled / 技术成功 traces
每个 fulfilled 成本 = 模型 + 检索 + 工具 + 遥测 + 人工成本 / fulfilled 数
本文的结果已经展示为什么分母必须是 fulfilled,而不是“模型返回”或“根 span 无 ERROR”。business-rejected-after-technical-success 使用最多的合成成本却没有形成业务成功;business-pending-after-technical-success 也不能在终态确认前进入完成分子。只优化每次调用 token,可能把任务拆成更多低价调用却增加失败和人工接管;只有单位业务结果成本才能检验自动化是否真正创造价值。
落地成本主要来自五部分。第一是 instrumentation:不同 SDK 和 Agent 框架对 GenAI conventions 的覆盖不一致,需要 adapter 与 contract tests。第二是数据治理:字段 allowlist、脱敏、collector 过滤、访问控制和保留期都要维护。第三是存储与查询:高基数 conversation、agent、model 和 tool 维度会增加索引成本。第四是价格与业务目录:模型价格、折扣和 outcome 定义必须版本化。第五是人工流程:故障分类、业务 reconciliation 和数据泄漏告警仍需要 owner。
适合优先采用的场景具有清晰 transaction id、可观测的最终状态、多个模型/检索/工具阶段和可控的 instrumentation,例如内部客服工单、文档审批、代码任务或可撤销的运营动作。只做单次无状态文本生成、没有工具和业务终态的系统,使用普通 tracing 加 token 指标可能已经足够,不必为了追求“Agent observability”引入完整 join 层。
不适合直接上线的是:必须把敏感 prompt 全量写入共享后端才能排障、无法定义业务完成状态、没有价格版本、工具副作用不可核对,或者 collector 会在高峰丢 span 却仍把缺失视为成功的系统。观测合同不能弥补业务状态本身不可验证,也不能替代工具授权、幂等与人工审批。
未来 90 天可以设置可证伪的试点门槛:先让 shadow traffic 通过真实 SDK 与 collector,连续保存至少一个完整业务周期;要求禁止内容命中后在 exporter 前被阻断,未知价格不进入成本总额,业务事件错链和重复能被隔离,技术与业务分叉有明确 owner,抽样策略不会系统性漏掉错误 trace。若这些条件无法满足,团队应维持内部实验,而不是把 synthetic 6/6 宣传为生产成熟。
复盘
最初的问题是“OpenTelemetry 有没有 GenAI 字段”,实验后更准确的问题变成“哪些业务判断必须由字段合同、树结构和独立事件共同支持”。只记录 gen_ai.operation.name 可以画出调用链,却不足以算成本;只记录 token 可以计量调用,却不能知道钱花在成功、失败还是被拒绝的业务上;记录全部 prompt 可以方便调试,却可能把观测系统变成未经治理的用户内容仓库。
本次最重要的设计决定是把 semantic convention 与 organization policy 分开。OpenTelemetry 提供共同语言,本地合同决定哪些 recommended 字段在当前任务里变成硬要求、哪些 opt-in 内容永远不进通用 trace、未知价格是否 fail closed、业务结果怎样关联。这样升级上游版本时可以明确比较差异,而不是把框架默认上报误认为组织已经完成治理。
第二个收获是正例不足以验证可观测性。若只运行 happy path,parent 检查、错误条件、隐私禁止项、价格缺口和业务错链都可能是死代码。12 条 negative controls 让每条安全与完整性门禁必须产生拒绝;mutation test 又补上 conversation mismatch、非法 status、错误 kind、负 token、缺失 provider 与等深错误换序。观测合同和 API schema、数据库约束一样,需要能失败的测试。
第三个收获是“Agent 成功”必须拆开。技术层至少要区分 model、retrieval、tool 和 orchestration;业务层还要区分 fulfilled、rejected、pending 与 failed。本文故意保留两条技术成功但业务未成功的 calibration,不是为了制造低成功率,而是防止仪表盘把错误分母固化进采购、容量和产品决策。
它也展示了从全栈到 AI 工程的连续性:parent-child 校验来自分布式 tracing,conversation 与 transaction 双标识来自会话和数据库设计,内容 allowlist 来自安全边界,token 账本来自计量计费,业务 outcome 来自领域事件。AI 系统增加了模型、检索和工具的不确定性,但没有取消 schema、主键、状态机、成本中心和审计责任。
下一阶段不应继续增加更多手写 JSON 来追求 fixture 数,而应把同一合同放进真实遥测回路:用至少一种 OpenTelemetry SDK 生成 spans,经本地 collector 导出到可查询后端,注入 sampling、attribute limit、export retry 和异步 tool handoff,再把接收端数据送入同一个 evaluator。随后加入价格版本、cache/reasoning token、attempt id 和持久化业务事件,验证 collector 之后而不是应用内对象的证据完整性。
完成这些工作前,本文结论保持窄而明确:在固定 commit 的语义基础上,仓库内合同可以对 18 条 synthetic fixtures 稳定区分结构、错误、内容、成本和业务关联;它尚未证明生产 instrumentation、真实 PII 防护、真实成本或现场可靠性。
方法披露
实验使用 Node.js v22.23.1、darwin arm64 生成固定 capture;evaluator 只依赖 Node.js 标准库和本目录 JSON,执行时没有调用模型、向量库、工具服务、OpenTelemetry backend 或其他外部服务。结果的机器环境只是来源说明,不参与确定性 payload。复现时允许 Node/platform/arch 和 capture 时间不同,但删除 .capturedAt 与 .environment 后其余结构必须完全一致。
本文使用 AI 工具协助枚举负向分支、审查 evaluator、组织文字和检查重复表述。固定 OpenTelemetry 来源、contract、18 条 fixtures、6/12 分组、20 个测试、4,940/620 tokens、合成 $0.02414、业务关系和 SHA-256 均由作者对照仓库文件与实际命令输出逐项确认。没有把 AI 生成文字、上游示例或 synthetic fixture 描述成生产观测。
raw-content-leak fixture 中只使用 .invalid 保留域名和明确写明非凭证的 synthetic key marker,用于证明检测器会拒绝并且结果不会回显原值。它不包含真实用户数据或真实 secret。扫描器的有限 pattern 在正文中如实列出,没有将其包装为完整 DLP 或合规认证。
结构化来源只使用 OpenTelemetry 固定 40 位 commit 的 commit-scoped tree/blob 页面。来源发布日期采用该快照 commit 的公开日期,访问日期为 2026-07-15。正文中关于上游 requirement level、内容敏感性和 Development 状态只依据该快照;关于 evaluator 行为和结果只依据本仓库实验,不用上游文档替代本地复现。
封面与移动图是对“Agent 根、三类子 span、隐私门禁和独立业务 outcome”关系的原创视觉表达,只展示经工件核对的摘要。所有数值以 results/2026-07-15-results.json 为准,原始 artifact hash 为 8f877139c703ba19020b5e39042db5ee30b8fb44b3f2745d318093a72519a82b。
修订记录
2026-07-15:初版发布并完成合同硬化;固定 OpenTelemetry GenAI semantic conventions commit63f8200eee093730ce845d26ce2aafb621b0807e,公开 18 条 synthetic fixtures、6 条 calibration、12 条 negative controls、20 个测试、CLIENT retrieval、顺序无关的 ambiguous 归因、嵌套结果字段脱敏、非全零 ID、价格目录自有属性检查、token 与合成成本归集、独立业务 outcome 关联和可重放结果工件。
Reusable projects
关联可复用项目
本文已经进入以下工程项目;项目页提供固定版本、运行命令和结果工件。
- 已独立复现
本文定义 Agent、model、retrieval 与 tool span 合同,并校准隐私、token、合成成本、失败层和业务结果关联。
Source ledger
来源账本
以下来源用于核对事实、日期与当时可用范围。厂商自报性能不视为本站独立复现。
讨论