![[Bug]: `ContextVar` not propagated on non-`async` tool execution.](https://www.chat-gpts.plus/wp-content/uploads/2026/06/21555-b799ab6c.jpg)
[Bug]: `ContextVar` not propagated on non-`async` tool execution.
快速结论:在使用 LlamaIndex 的 FunctionTool 且传入 fn(同步函数)而非 async_fn 时,ContextVar 上下文会丢失,导致 OpenTelemetry span 出现孤儿 span 或自定义 ContextVar 读取异常。优先排查工具定义中是否使用了同步 fn,并考虑改用 async_fn 或等待 LlamaIndex 内部修复上下文传播。
问题场景
用户在 FunctionAgent 中定义一个 FunctionTool,并传入同步函数 fn(而非 async_fn)。工具在执行时无法正确传播外层的 ContextVar 值,导致 ContextVar.get() 返回默认值而非调用方设置的值。此问题在 OpenTelemetry 场景中尤为突出,会导致 span 无法正确关联父级。
报错原文
# 无显式报错,但行为不符合预期:
# ContextVar.get() 返回默认值(如 ["starting value"])而非调用方设置的值(如 ["updated value: False"])
# OpenTelemetry 场景下 span 显示为孤儿 span(orphan span),父级关系丢失
原因分析
问题根因在于 LlamaIndex 内部对同步工具 fn 的处理路径。Issue 讨论指出,FunctionTool 在执行同步 fn 时,代码通过 run_in_executor 或 asyncio.to_thread 跳转到线程池执行,但未使用 contextvars.copy_context().run() 包裹执行代码,导致 Python 的 ContextVar 上下文未能从调用线程传播到执行线程。而异步分支 async_fn 则因直接在事件循环中运行,上下文传播正常。
这是 LlamaIndex 的一个已知 edge case——即便是 FunctionAgent 内部会尝试将同步工具包装为异步调用,但在实际执行时仍会丢失上下文。
环境排查
- LlamaIndex 版本:
llama-index-core >= 0.14.18(Issue 确认受影响的版本范围) - Python 版本(任何支持 asyncio 的版本理论上都可能受影响)
- 是否使用了 OpenTelemetry、Sentry、ddtrace 等依赖
ContextVar的追踪库 - 确认工具定义是通过
fn=还是async_fn=传入的
解决步骤
- 优先尝试:将工具定义从同步函数改为异步函数,即传入
async_fn=而非fn=。即使你的业务逻辑是同步的,也可以编写一个 async 包装器(async def wrapper 内部调用同步函数),以避免上下文丢失。 - 如果无法修改工具定义(例如第三方工具),可以考虑在工具外部提前读取
ContextVar值并通过参数传入工具,作为临时绕行方案。 - 等待 LlamaIndex 官方修复:Issue 已由维护者确认,并计划在
function_tool.py的同步分支中加入contextvars.copy_context().run(self.fn, ...)包装。可关注 function_tool.py L44 附近的修改。 - 如果你是库作者,建议在所有工具执行边界(包括同步和异步路径)统一执行
contextvars.copy_context().run()以确保上下文传播。
验证方法
编写如下测试确认问题修复:
some_context_var = ContextVar("test", default="default")
# 在函数内部读取
def my_tool(ctx: Context) -> str:
return some_context_var.get()
tool = FunctionTool.from_defaults(fn=my_tool, ...)
some_context_var.set("expected_value")
# 执行后验证返回值为 "expected_value" 而非 "default"
或者使用 OpenTelemetry 检查 span 的 parent_span_id 是否正常关联。



