快速结论:该问题发生在 Langfuse Python SDK 的异步 LangChain/LangGraph 调用中,导致只有根 span 被正确写入 user_id、session_id、tags,所有子 observation(尤其是产生费用的 GENERATION)都丢失这些属性。优先排查是否在异步链路中使用了 propagate_attributes 包裹整个应用调用,这是目前唯一确认有效的绕行方案。
适用环境:Langfuse Python SDK(v4 及以上版本影响更明显),LangChain/LangGraph 异步运行路径,Python 3.x(具体版本未在 Issue 中标注),不依赖特定显卡或 CUDA 环境。
最快修复方案:暂无确认的一步修复方案。可从两条路径选择:一是使用 propagate_attributes 上下文管理器包裹整个异步调用流;二是给 CallbackHandler 设置 run_inline = True,但后者会阻塞事件循环,未在实际生产中得到充分验证。
注意事项:propagate_attributes 绕行方案已被确认有效,但需要修改应用代码;run_inline = True 属于可优先尝试的方案,但可能因序列化回调导致事件循环阻塞,需根据实际耗时 I/O 评估风险。
问题场景
在 LangGraph 或 LangChain 的异步调用链路(如 astream_events、ainvoke)中使用 Langfuse 的 LangchainCallbackHandler 时,用户在每个 on_chain_start 回调中通过 propagate_attributes(...) 传入 user_id、session_id、tags 等元数据。结果是根 span(root span)能正确携带这些属性,但所有子 observation(包括承载调用成本的 GENERATION)均无法继承,导致 Langfuse v4 的控制台按用户统计费用时出现大量“未知用户”费用。
报错原文
bug(sdk-python): LangchainCallbackHandler attributes only the root span in async runs — child observations lose user_id/session_id/tags
In an **async** LangChain/LangGraph run, LangchainCallbackHandler lands user_id / session_id / tags / trace_name on the **root span only**. Every child observation — including the GENERATIONs that carry the cost — is exported with no user.id, no session.id and no langfuse.trace.tags.
原因分析
根本原因在 LangChain 的 AsyncCallbackManager 调度机制。Langfuse 的 LangchainCallbackHandler 是同步处理器且未设置 run_inline,因此 LangChain 将每个回调通过 run_in_executor 分发到线程池执行,并传入调用方上下文的 副本。回调内部执行的 otel_context_api.attach() 只修改了这份被丢弃的副本,后续回调(如 on_chat_model_start、on_tool_start)拿到的仍是未包含属性的新副本,导致子 span 全部丢失元数据。这是异步回调用 copy_context() 与 run_in_executor 组合的机制性缺陷,已由 Langfuse 维护方确认分析正确。
环境排查
- 确认 Langfuse Python SDK 版本(v4 及以上时下游影响更明显)
- 确认 LangChain / LangGraph 的异步调用路径(
astream_events、ainvoke、astream) - 确认回调管理器是
AsyncCallbackManager(非同步路径,同步调用无此问题) - 排查是否已在应用层统一使用
propagate_attributes包裹异步迭代器
解决步骤
- 首选绕行方案(已验证有效):在应用层用
propagate_attributes上下文管理器包裹整个异步调用流,而非仅在on_chain_start中设置。示例代码:from langfuse import propagate_attributes with propagate_attributes(user_id=..., session_id=..., tags=..., trace_name=...): async for event in graph.astream_events(..., config={"callbacks": [handler]}): ...这样 LangChain 在每次 executor 分发时都会将应用层上下文复制进回调,所有子 span 自然继承属性。
- 可优先尝试的 SDK 层修复方向:给
CallbackHandler设置run_inline = True,让异步回调直接在当前事件循环上下文中执行,使attach()立即可见。但需承担事件循环被回调阻塞的风险。 - 备选修复方向(未实现,仅供维护者参考):在
_RootRunState中存储解析后的属性,在每个子回调中直接用span.set_attribute重复写入,绕过 OTel 上下文传播机制。该方案在 Issue 中被提出但尚未落地。
验证方法
用 Issue 提供的 InMemorySpanExporter 复现代码跑一次异步链路,检查所有导出的 span(尤其是 GENERATION 类型)是否都包含 user.id、session.id 和 langfuse.trace.tags。同时对比同步运行与异步运行的输出差异——同步路径若有属性,异步路径缺失即可复现。修复后异步路径应与同步输出一致。
参考来源
AI 工具推荐
想把多个 AI 模型放在一个入口?
GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。
推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。
这个方案解决了吗?
可以继续搜索完整报错,或查看同一工具的其他排查指南。
![[Bug] Canary Docker image fails to start due to missing @swc/helpers runtime file](https://www.chat-gpts.plus/wp-content/uploads/2026/08/18323-56abae9d-768x403.jpg)

