快速结论:当 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.ts 的 extractGenericGenAiUsageDetails。Issue 未提供操作系统、Python、CUDA、显卡或依赖版本信息。
最快修复方案:暂无确认的一步修复方案。Issue 中提出的可优先尝试方向是:在 extractGenericGenAiUsageDetails 的 cacheCreationTokens 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。
计价侧同样受影响:calculateUsageCosts 用 usageType === 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_tokens、cache_write_tokens)。 - 在仓库中搜索
cache_write.input_tokens,确认 extractor 及测试中是否存在该 key(Issue 指出该字符串在包括测试在内的整个 repo 中均无出现)。 - 确认
OtelIngestionProcessor.ts中 cache-creation alias 列表与 excluded-keys 列表的具体内容。 - Issue 未提供 Python、CUDA、PyTorch、显卡或 Langfuse 依赖版本信息,这些项目无需补充。
解决步骤
- 在
extractGenericGenAiUsageDetails中,把rawUsageDetails["cache_write.input_tokens"]加入cacheCreationTokens的 fallback 链,使官方属性可被识别为 cache-creation 桶。 - 把
"cache_write.input_tokens"加入 excluded-keys 列表,避免其作为额外 input 参与聚合求和。 - 补充以 spec 文本为基准的 fixture:输入官方属性集(
gen_ai.usage.cache_read.input_tokens、gen_ai.usage.cache_write.input_tokens),断言input == total - read - write,且 cache-write 桶按模型的 cache-write 价格计费。 - fixture 中同时覆盖 inverse double-subtraction 场景,以及 emitter 已经上报 exclusive
input_tokens的 spec 违反场景;对负数 cache-read 结果做 clamp 并标记。 - 讨论中提到 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 贡献。
参考来源
AI 工具推荐
想把多个 AI 模型放在一个入口?
GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。
推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。
这个方案解决了吗?
可以继续搜索完整报错,或查看同一工具的其他排查指南。


