fix(google-genai): propagate context to threaded tool calls (#38) - #271
Open
RichardoMrMu wants to merge 4 commits into
Open
RichardoMrMu wants to merge 4 commits into
RichardoMrMu wants to merge 4 commits into
Conversation
Update tool_call_wrapper.py
Update test_tool_call_wrapper.py
Update CHANGELOG.md
github-actions
Bot
requested review from
123liuziming,
Cirilla-zmh and
ralf0131
September 21, 2026 16:29
…t imports) CI ruff check failed on this package (which cascaded into the precommit, Lint 0, and package-test jobs, since ruff runs first): F401 for an unused 'import contextvars' left over from an earlier approach (the fix uses opentelemetry.context, not contextvars directly), and PLC0415 for function-local imports in the new alibaba#38 regression tests. Remove the dead import and hoist 'concurrent.futures' to the top; the two function-local 'from opentelemetry.trace import get_tracer_provider' were redundant (already imported at module top), so drop them. No behavior change.
Contributor
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Out-of-order context-token detachment leaks the captured trace context into reused workers.
Get a fresh assessment by requesting another Copilot review.
Review effort: Balanced
Findings: 1
Open (1)
What changed in this PR
Propagates OpenTelemetry context to threaded Google GenAI tool calls.
Changes:
- Captures and reattaches parent context for tool spans.
- Adds thread-pool regression tests.
- Documents the fix.
| File | Description |
|---|---|
tool_call_wrapper.py |
Adds context propagation. |
test_tool_call_wrapper.py |
Tests threaded tool calls. |
CHANGELOG.md |
Records the fix. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+182
to
+184
| finally: | ||
| if token is not None: | ||
| otel_context.detach(token) |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

What
Fixes #38. Parallel/concurrent tool calls in an agent produce multiple
disconnected traces instead of one trace with multiple spans.
Root cause
tool_call_wrapper.wrapped_tool()wraps each tool while the agent/LLM span isactive (
generate_content._wrapped_config_with_tools). The wrapped tool buildsits
ToolInvocation->start_execute_tool, whose span is parented to whateveris in the OpenTelemetry context at call time. The Google GenAI SDK's
automatic function calling (and agent frameworks) execute those wrapped tools in
a
ThreadPoolExecutor/run_in_executorworker. Worker threads do not inheritcontextvars, so the worker sees an empty context and each tool span starts anew root trace.
This matches the maintainer's confirmed analysis on #38, and is the same class
of issue already worked around per-instrumentation in
bfclv4(
threading_propagation.py) andopenhands(session_context.py).Fix
tool_call_wrappernow captures the active context when a tool is wrapped(only when a span is active) and re-attaches it around invocation creation in
both the sync and async wrappers, then detaches immediately. The tool function
itself runs in its normal context; only the invocation/span creation is
re-parented. When no span is active the capture returns
Noneand behavior isunchanged, so single-threaded execution is unaffected.
Only
tool_call_wrapper.pychanges on the source side;_compat.pyandgenerate_content.pyare untouched.Tests
Added two regression tests in
tests/utils/test_tool_call_wrapper.py:test_parallel_tool_calls_share_parent_traceruns two wrapped tools in aThreadPoolExecutorunder an agent span and asserts bothexecute_toolspansshare the agent's
trace_id.test_run_in_executor_tool_call_shares_parent_tracecovers theasyncio.run_in_executorpath named in the issue.Both fail on the current wrapper (tool spans land on new traces) and pass with
the fix. The existing single-threaded tool-wrapper tests are unchanged and still
pass.