Description
When an agent uses multiple tools in a single turn, execute_tool spans are intermittently missing from exported traces. The root cause is that _try_execute_function_calls in
_tools.py
executes tool calls concurrently via asyncio.gather without copying the OTel context into each task. This causes span context race conditions — some execute_tool spans fail to attach to their parent and are silently dropped.
The issue is non-deterministic: the same agent invocation sometimes produces complete traces (with all execute_tool spans) and sometimes produces incomplete traces (missing one or more execute_tool spans).
Expected behavior: Every tool execution should produce a correctly-parented execute_tool span, regardless of concurrent execution.
Proposed fix: Wrap each concurrent task with contextvars.copy_context() to ensure OTel context isolation:
Code Sample
from dotenv import load_dotenv
load_dotenv()
import os
os.environ["OTEL_EXPORTER_OTLP_TRACES_ENDPOINT"] = os.getenv("AAMS_OTLP_TRACES_ENDPOINT")
os.environ["OTEL_EXPORTER_OTLP_HEADERS"] = f"x-api-key={os.getenv('AAMS_API_KEY')}"
os.environ["OTEL_EXPORTER_OTLP_PROTOCOL"] = "http/protobuf"
from agent_framework.observability import configure_otel_providers
configure_otel_providers(enable_sensitive_data="true")
from agent_framework import Agent
from agent_framework.openai import OpenAIChatClient
@tool(approval_mode="never_require")
async def tool_a(city: str) -> str:
return f"Result A for {city}"
@tool(approval_mode="never_require")
async def tool_b(city: str) -> str:
return f"Result B for {city}"
client = OpenAIChatClient(model="gpt-4o-mini", api_key=os.getenv("API_KEY"), base_url="...")
agent = Agent(client=client, name="TestAgent", instructions="...", tools=[tool_a, tool_b])
# When LLM returns parallel tool calls (tool_a + tool_b),
# sometimes only one execute_tool span appears in the trace.
result = await agent.run("Call both tools", session=agent.create_session())
Error Messages / Stack Traces
No error is raised. The issue is silent span loss. In the exported trace, execute_tool spans are intermittently missing. Example:
Expected spans: invoke_agent → chat → execute_tool tool_a + execute_tool tool_b → chat
Actual (intermittent): invoke_agent → chat → execute_tool tool_a → chat (tool_b span missing)
Root cause location:
_tools.py
, function _try_execute_function_calls, line:
execution_results = await asyncio.gather(*[
invoke_with_termination_handling(function_call, seq_idx)
for seq_idx, function_call in enumerate(function_calls)
])
Python Version: 3.12
Package Versions
agent-framework-core==1.1.0 agent-framework-openai==1.1.0 opentelemetry-sdk==1.40.0 opentelemetry-exporter-otlp-proto-http==1.40.0
Python Version
3.12
Additional Context
No response
Description
When an agent uses multiple tools in a single turn, execute_tool spans are intermittently missing from exported traces. The root cause is that _try_execute_function_calls in
_tools.py
executes tool calls concurrently via asyncio.gather without copying the OTel context into each task. This causes span context race conditions — some execute_tool spans fail to attach to their parent and are silently dropped.
The issue is non-deterministic: the same agent invocation sometimes produces complete traces (with all execute_tool spans) and sometimes produces incomplete traces (missing one or more execute_tool spans).
Expected behavior: Every tool execution should produce a correctly-parented execute_tool span, regardless of concurrent execution.
Proposed fix: Wrap each concurrent task with contextvars.copy_context() to ensure OTel context isolation:
Code Sample
Error Messages / Stack Traces
Package Versions
agent-framework-core==1.1.0 agent-framework-openai==1.1.0 opentelemetry-sdk==1.40.0 opentelemetry-exporter-otlp-proto-http==1.40.0
Python Version
3.12
Additional Context
No response