登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  科技周边 >  人工智能

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 字段映射到 OpenTelemetry GenAI token 属性的静态结构说明图
图1:结构说明图,展示模型响应字段与 OpenTelemetry 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 单独累加,又把它重复加到输入总量。

GenAI client span、OpenTelemetry Collector、token 指标与隐私策略之间的静态关系说明图
图2:边界说明图,展示 token 指标与隐私策略的静态关系,不是监控系统截图。
检查对象建议口径常见风险
输入总量使用 provider 报告的 gen_ai.usage.input_tokens缓存输入被漏算或再次相加
输出总量使用 gen_ai.usage.output_tokensreasoning 细分未纳入总量
统计边界明确只统计 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 数量和必要关联信息,通常已经能满足成本与容量分析。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>