OTel extractor: official gen_ai.usage.cache_write.input_tokens missing from cache-creation aliases — cache writes double-counted in input ag

当 OTel 上报的 span 使用官方 semconv 属性名 gen_ai.usage.cache_write.input_tokens 时,Langfuse 的通用 GenAI usage extractor 无法识别这个 cache-write 桶,导致 cache-write token

快速结论:当 OTel 上报的 span 使用官方 semconv 属性名 gen_ai.usage.cache_write.input_tokens 时,Langfuse 的通用 GenAI usage extractor 无法识别这个 cache-write 桶,导致 cache-write token 既没有从 input 中扣除,又按 $0 计价。

适用环境:Langfuse 自托管与云端(self-hosted affected equally),Issue 版本为 main @ 7637df1e1aadddbbfd0a45b960ecc97451381ce5;相关代码位于 packages/shared/src/server/otel/OtelIngestionProcessor.tsextractGenericGenAiUsageDetails。Issue 未提供操作系统、Python、CUDA、显卡或依赖版本信息。

最快修复方案:暂无确认的一步修复方案。Issue 中提出的可优先尝试方向是:在 extractGenericGenAiUsageDetailscacheCreationTokens fallback 链中加入 rawUsageDetails["cache_write.input_tokens"],并把 "cache_write.input_tokens" 加入 excluded-keys 列表。

注意事项:上述修改方向在 Issue 中只是 suggested fix,未标明已在合并版本中验证;fixture 与测试集尚未合并(讨论提及 #17118)。另外需注意 spec 违反场景:若 emitter 已经上报 exclusive 的 input_tokens,盲目执行 subtract-then-classify 可能产生负数 cache-read,需在实现中 clamp 并标记。

问题场景

使用 Langfuse 的 OTel 接入路径,通过通用 GenAI extractor 摄取带有官方 semconv 属性名的 span 时触发。典型触发 span 同时包含:

gen_ai.usage.input_tokens = 300        // inclusive, per spec
gen_ai.usage.cache_read.input_tokens = 40
gen_ai.usage.cache_write.input_tokens = 25

此时 extractor 产出 usage_details = {input: 260, input_cached_tokens: 40, cache_write.input_tokens: 25},但 cache_write.input_tokens 是未被识别的原始 key。

报错原文

OTel extractor: official gen_ai.usage.cache_write.input_tokens missing from cache-creation aliases — cache writes double-counted in input aggregates and priced $0

原因分析

可能原因:cache-read 的官方属性 gen_ai.usage.cache_read.input_tokens 已在 alias 列表中,但 cache-creation alias 列表没有对应加入官方属性 gen_ai.usage.cache_write.input_tokens。该原始 key 穿过 exclusion 列表后,任何对包含 “input” 的 usage key 做求和的聚合(例如 events-table 的 Input Tokens 列,eventsTable.ts 中的 arraySum(mapFilter(x -positionCaseInsensitive(x.1,'input')>0, usage_details)))会把 300 的请求报成 325;当 emitter 未提供 total 时,自动计算的 total 也会重复计算 cache write。

计价侧同样受影响:calculateUsageCostsusageType === key 精确匹配 price key,cache_write.input_tokens 匹配不到任何默认 price key(Anthropic 默认使用 input_cache_creation / input_cache_creation_5m / input_cache_creation_1h),于是 cache write 被按 $0 计费。

讨论中进一步指出,这是 alias 列表手工维护机制的结构性问题:spec 对 read/write 两个 cache 属性的规则是同一句(input_tokens SHOULD include all types of input tokens, including cached tokens),extractor 应对所有 alias 执行统一的“先扣除、再归类、total 不动”三步,而不是逐个 alias 手工补列表。

环境排查

  • 确认 Langfuse 版本是否包含 commit 7637df1e1aadddbbfd0a45b960ecc97451381ce5 或其后。
  • 确认 span 中使用的属性名是官方 gen_ai.usage.cache_write.input_tokens,还是 vendor alias(如 cache_creation_input_tokenscache_write_tokens)。
  • 在仓库中搜索 cache_write.input_tokens,确认 extractor 及测试中是否存在该 key(Issue 指出该字符串在包括测试在内的整个 repo 中均无出现)。
  • 确认 OtelIngestionProcessor.ts 中 cache-creation alias 列表与 excluded-keys 列表的具体内容。
  • Issue 未提供 Python、CUDA、PyTorch、显卡或 Langfuse 依赖版本信息,这些项目无需补充。

解决步骤

  1. extractGenericGenAiUsageDetails 中,把 rawUsageDetails["cache_write.input_tokens"] 加入 cacheCreationTokens 的 fallback 链,使官方属性可被识别为 cache-creation 桶。
  2. "cache_write.input_tokens" 加入 excluded-keys 列表,避免其作为额外 input 参与聚合求和。
  3. 补充以 spec 文本为基准的 fixture:输入官方属性集(gen_ai.usage.cache_read.input_tokensgen_ai.usage.cache_write.input_tokens),断言 input == total - read - write,且 cache-write 桶按模型的 cache-write 价格计费。
  4. fixture 中同时覆盖 inverse double-subtraction 场景,以及 emitter 已经上报 exclusive input_tokens 的 spec 违反场景;对负数 cache-read 结果做 clamp 并标记。
  5. 讨论中提到 fixture 计划附到 #17118 的测试集,可在该 PR 落地后验证行为。

验证方法

用包含上述三个官方属性的 span 作为输入,确认 usage_details 变为 {input: 235, input_cached_tokens: 40, input_cache_creation: 25}(或文档化等价形式),events-table 的 Input Tokens 聚合显示 300 而非 325,且 cache-write 桶按配置的 cache-write 价格产生非零费用。对于 exclusive input 的边界用例,确认不会产生负的 cached-read 贡献。

参考来源

langfuse/langfuse #17117

GamsGo AI

AI 工具推荐

想把多个 AI 模型放在一个入口?

GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。

了解 GamsGo AI

推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。

这个方案解决了吗?

celebrityanime
celebrityanime
文章: 23526

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注