
bug(sdk-python): LangchainCallbackHandler loses cache token metrics and inflates input costs
快速结论:在非流式(non‑streaming)调用 Anthropic 模型(通过 LiteLLM 代理)时,Langfuse Python SDK 的 _parse_usage_model 方法会丢弃令牌缓存指标(prompt_tokens_details 以字典格式传入时被忽略),并因未从 input 中减去缓存令牌而导致输入成本虚高。优先检查 llm_output["token_usage"] 中 prompt_tokens_details 是否为字典格式。
问题场景
使用 langchain_openai.ChatOpenAI 配合 Langfuse 的 LangchainCallbackHandler,通过 LiteLLM 代理调用 Anthropic 模型(如 claude-sonnet-4-20250514),并采用非流式模式(llm.ainvoke)。
报错原文
# 实际表现并非抛出异常,而是指标缺失和成本异常:
# - cache_read 令牌(prompt_tokens_details 中的 cached_tokens)完全丢失
# - 输入成本按全量 prompt_tokens 计费,远高于实际使用
# 核心代码路径:_parse_usage_model 中对 prompt_tokens_details 的处理缺失
# line 1220-1236 仅处理了 list 格式(Vertex AI),未处理 dict 格式
# line 1278 的 isinstance(v, int) 过滤器将 dict 格式丢弃
原因分析
LangchainCallbackHandler 中的 _parse_usage_model 方法在解析令牌使用数据时,对 prompt_tokens_details 字段的处理存在两个缺陷:
- 字典格式被忽略:LiteLLM 代理返回的
prompt_tokens_details是字典{"cached_tokens": 12000},但 SDK 仅处理列表格式(Vertex AI 风格)。字典格式在 line 1220 的类型检查中被跳过,最终在 line 1278 的整数过滤器中被静默丢弃。 - 未调整 input 令牌数:由于缓存令牌被丢弃,
input字段(即prompt_tokens)未减去cached_tokens,导致 Langfuse 按全价计算输入成本。缓存命中率越高,虚增倍数越大。
注意:cache_creation_input_tokens 作为顶层整数字段会被保留,但由于没有减去 cached_tokens,成本计算仍然错误。
环境排查
- langfuse(Python SDK)版本:需确认是否包含对
prompt_tokens_details字典格式的支持(截至 Issue 关闭时,官方尚未合并修复) - LiteLLM 版本:不同版本返回的字段名称可能不同,需验证
llm_output["token_usage"]中prompt_tokens_details的实际格式 - Anthropic 模型版本:本问题在
claude-sonnet-4-20250514上复现,但理论上影响所有支持缓存提示的模型 - 调用模式:仅非流式(non‑streaming)路径受影响;流式路径(streaming)使用
message.usage_metadata且工作正常
解决步骤
- 验证问题来源:在代码中添加临时日志,打印
llm_output["token_usage"]的内容,确认prompt_tokens_details是否为字典格式,以及cache_creation_input_tokens是否存在。 - 临时修复(Monkey Patch):如果等不及官方修复,可以在初始化
LangchainCallbackHandler前,对_parse_usage_model方法进行猴子补丁,增加对prompt_tokens_details字典格式的处理分支:将{"cached_tokens": N}映射为input_cached_tokens = N,并从input中减去。 - 关注官方 PR:跟踪相关 PR langfuse-python#1549 的进展(该 PR 针对另一同类问题,但触及同一方法)。
- 提交新 Issue 或 PR:如果暂无修复,可向 langfuse/langfuse-python 提交 PR,参考 Issue 中分析——在
_parse_usage_model的prompt_tokens_details处理分支中增加 dict 格式支持。 - 切换到流式模式(临时降级方案):如果业务允许,暂时改用流式调用(
llm.astream)规避问题,因为流式路径缓存指标正常。
验证方法
修复后,在 Langfuse 的 trace 详情页中检查对应调用的 usage 指标:
- 确认
input_cached_tokens字段存在,值应等于prompt_tokens_details.cached_tokens - 确认
input值已减去缓存令牌数:即input = prompt_tokens - cached_tokens - 成本对比:修复前后对同一请求的成本数应下降(下降幅度取决于缓存命中率)



