OpenTelemetry GenAI怎样记录模型调用 token 属性
来源:17golang原创
时间:2026-11-04 13:31:08 414浏览 收藏
模型调用已经返回结果,但监控里只有请求耗时,没有输入和输出 token,通常不是“OpenTelemetry 不支持”,而是 provider 的 usage 字段还没有被映射到 GenAI 约定的 span 属性。实际落地时,先从响应里取出输入、输出 token,再分别写入 gen_ai.usage.input_tokens 与 gen_ai.usage.output_tokens;提示词正文、模型输出和工具参数则默认留在业务侧,不要顺手塞进遥测。
官方规范入口:https://opentelemetry.io/docs/specs/semconv/gen-ai/gen-ai-spans/
- token 数量的来源是模型响应里的 usage,不是根据字符数自行估算。
- 总输入 token 写入
gen_ai.usage.input_tokens,总输出 token 写入gen_ai.usage.output_tokens;缓存和推理 token 只做可选细分。 - span 记录数量和必要维度即可,原始 prompt、output、tool arguments 要按隐私策略过滤或截断。
先界定要保护的观测资产
一条 GenAI 调用的观测资产至少有三类:模型与操作名,用来回答“哪种调用”;usage 数量,用来回答“消耗了多少”;内容字段,用来回答“传了什么”。这三类数据的风险不同。前两类通常适合做低基数维度和汇总指标,内容字段可能包含个人信息、业务机密或密钥,不应因为调试方便就默认记录。
本篇只处理 usage 数量。当前 GenAI 语义约定把输入和输出总量分别放在 gen_ai.usage.input_tokens、gen_ai.usage.output_tokens;如果 provider 还提供缓存读取、缓存写入或 reasoning token,可以按约定增加细分属性,但细分值仍应纳入对应的总量口径。

模型响应里的 usage 怎样映射成 OTel 属性
不同 SDK 对同一概念的字段名可能不同,例如 prompt_tokens、input_tokens 或某个 usage_metadata 子对象。适配层要做的是“读取 provider 字段并统一输出”,而不是把某一家 SDK 的字段名直接扩散到业务代码中。下面的函数展示一个可复用的手工映射边界:
from opentelemetry.trace import Span
def record_token_usage(span: Span, usage: dict) -> None:
# 兼容不同 SDK 的常见命名,但最终只写统一的 OTel 属性。
input_tokens = usage.get("input_tokens", usage.get("prompt_tokens"))
output_tokens = usage.get("output_tokens", usage.get("completion_tokens"))
if input_tokens is not None:
# 输入总量包含 provider 报告的缓存输入,避免指标口径少算。
span.set_attribute("gen_ai.usage.input_tokens", int(input_tokens))
if output_tokens is not None:
# 输出总量包含 provider 报告的普通输出和推理输出。
span.set_attribute("gen_ai.usage.output_tokens", int(output_tokens))
# 缺少 usage 时保持属性缺省,不用字符数或估算值冒充 token 数。
调用方在模型响应返回后、span 结束前调用它即可。若 provider 没有返回某个数值,就不要把 None 转成 0;0 表示明确没有 token,缺省则表示“这次没有可用报告”。对于 Gemini 一类响应,适配器可以从其 usage metadata 中取出 prompt token count 和 response token count,再传入同一个函数。
把 token 写进当前 span,同时切断内容泄露路径
token 属性应属于描述这次模型调用的 GenAI client span,而不是单独创建一个孤立 span。这样请求耗时、模型名、操作名和 token 量才有同一个关联上下文。下面的示例只保留数量和必要标识,故意不记录 prompt 与 output:
from opentelemetry import trace
tracer = trace.get_tracer("sample.genai")
def observe_model_call(model_name: str, response: dict) -> None:
# span 名称描述调用边界;高基数 response_id 不放进名称。
with tracer.start_as_current_span("chat") as span:
span.set_attribute("gen_ai.operation.name", "chat")
span.set_attribute("gen_ai.request.model", model_name)
# 只取 provider 返回的 usage,内容字段不进入遥测。
usage = response.get("usage", {})
record_token_usage(span, usage)
# 不要把 response["prompt"]、response["output"] 或工具参数写入 span。
如果业务确实需要定位某个会话,可以使用经过权限控制的内部关联 ID,并先确认它不是用户原文、邮箱或订单号。调试原文更适合在业务侧做脱敏、短时留存和访问审计,不能用“开发环境”作为永久放开内容采集的理由。
从 span 到指标时怎样避免重复统计
span 属性适合追查单次调用,指标适合看一段时间的消耗趋势。最容易出现的错误是:客户端和服务端都把同一次推理写成 gen_ai.usage.input_tokens,汇总时被加了两次;或者先把缓存 token 单独累加,又把它重复加到输入总量。

| 检查对象 | 建议口径 | 常见风险 |
|---|---|---|
| 输入总量 | 使用 provider 报告的 gen_ai.usage.input_tokens | 缓存输入被漏算或再次相加 |
| 输出总量 | 使用 gen_ai.usage.output_tokens | reasoning 细分未纳入总量 |
| 统计边界 | 明确只统计 client span 或只统计 server span | 一次推理被采集两次 |
| 内容字段 | 默认不采集,必要时脱敏、截断和限权 | prompt 中的密钥或个人信息进入日志 |
换句话说,先选定“哪一个 span 是计费和用量的权威来源”,再让 Collector 或指标导出器按这个边界汇总。对于多模态响应,还可以记录文本、图片或音频的细分 token,但这些细分不能替代总输入、总输出的主属性。
发布前用清单复查 token 属性
- 响应里是否真的有 provider 报告的 usage,而不是用字符数、字节数或本地估算值替代?
- 输入和输出是否分别写入
gen_ai.usage.input_tokens、gen_ai.usage.output_tokens? - 缓存 token、reasoning token 是否同时纳入总量,且没有在指标层再次相加?
- 是否只有一侧(client 或 server)承担用量汇总,避免重复统计?
- span、日志和事件中是否排除了原始 prompt、output、tool arguments 等敏感内容?
常见问题
没有 usage 字段时能不能按字符数换算 token?
不建议。字符数与 tokenizer、语言和模型实现有关,估算值不能冒充 provider 报告的 token。可以暂时保持属性缺省,并把“provider 未返回 usage”作为适配问题处理。
缓存输入 token 要不要单独从输入总量里扣掉?
不要直接扣。缓存读取或写入属于细分维度时,总输入仍应保持完整的 provider 口径;只有在明确展示“未命中缓存输入”等派生指标时,才按文档定义计算。
为什么 span 里不建议直接记录 prompt?
因为 prompt 可能携带个人信息、业务数据、密钥或系统指令,而且 span 往往会被复制到多个后端。先记录 token 数量和必要关联信息,通常已经能满足成本与容量分析。
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
501 收藏
-
科技周边 · 人工智能 | 56分钟前 | 性能优化 · 人工智能 · 深度学习 · PyTorch CUDA SDPA scaled_dot_product_attention Flash Attention214 收藏
-
科技周边 · 人工智能 | 57分钟前 | 性能优化 · python · ONNX Runtime InferenceSession SessionOptions 图优化 Execution Provider281 收藏
-
157 收藏
-
234 收藏
-
135 收藏
-
230 收藏
-
344 收藏
-
312 收藏
-
458 收藏
-
394 收藏
-
科技周边 · 人工智能 | 2小时前 | python · 人工智能 · gemini · 结构化输出 · Pydantic JSON Schema Gemini API google-genai structured output136 收藏
-
486 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 立即学习 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 立即学习 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 立即学习 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 立即学习 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 立即学习 485次学习