[Bug]: `ContextVar` not propagated on non-`async` tool execution.

用户在 FunctionAgent 中定义一个 FunctionTool ,并传入同步函数 fn (而非 async_fn )。工具在执行时无法正确传播外层的 ContextVar 值,导致 ContextVar.get() 返回默认值而非调用方设置的值。此问题在 OpenTelemetry 场景中

[Bug]: `ContextVar` not propagated on non-`async` tool execution.

[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_executorasyncio.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= 传入的

解决步骤

  1. 优先尝试:将工具定义从同步函数改为异步函数,即传入 async_fn= 而非 fn=。即使你的业务逻辑是同步的,也可以编写一个 async 包装器(async def wrapper 内部调用同步函数),以避免上下文丢失。
  2. 如果无法修改工具定义(例如第三方工具),可以考虑在工具外部提前读取 ContextVar 值并通过参数传入工具,作为临时绕行方案。
  3. 等待 LlamaIndex 官方修复:Issue 已由维护者确认,并计划在 function_tool.py 的同步分支中加入 contextvars.copy_context().run(self.fn, ...) 包装。可关注 function_tool.py L44 附近的修改。
  4. 如果你是库作者,建议在所有工具执行边界(包括同步和异步路径)统一执行 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 是否正常关联。

参考来源

run-llama/llama_index #21555

GamsGo AI

AI 工具推荐

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

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

了解 GamsGo AI

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

celebrityanime
celebrityanime
文章: 7412

发表回复

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