Skip to content

OpenAIAgentsIntegration: no gen_ai.execute_tool spans on streaming runs (Runner.run_streamed) #6657

Description

@shotamakino-ml

How do you use Sentry?

Sentry Saas (sentry.io)

Version

2.63.0

Steps to Reproduce

Function-tool spans (gen_ai.execute_tool) are emitted for Runner.run(...) but not for Runner.run_streamed(...). gen_ai.chat spans are present in both, so streamed agent traces show the model calls but none of the tool calls.

Minimal repro (one tool, tool_choice="required" so a tool call is guaranteed; spans inspected via before_send_transaction):

import asyncio

import sentry_sdk
from sentry_sdk.integrations.openai_agents import OpenAIAgentsIntegration
from agents import Agent, ModelSettings, Runner, function_tool

captured: list[str] = []


def record(event, _hint):
    captured.extend(s.get("op") for s in event.get("spans", []) if s.get("op"))
    return event


sentry_sdk.init(
    dsn="<your-dsn>",
    traces_sample_rate=1.0,
    integrations=[OpenAIAgentsIntegration()],
    before_send_transaction=record,
)


@function_tool
def get_tool(x: int) -> str:
    """Return an illustrative value."""
    return f"${x}"


agent = Agent(
    name="probe",
    instructions="Use the get_tool tool, then state the result.",
    model="gpt-4o-mini",
    tools=[get_tool],
    model_settings=ModelSettings(tool_choice="required"),  # force a tool call
)


async def show(label, coro_factory):
    captured.clear()
    await coro_factory()
    sentry_sdk.get_client().flush()
    print(f"{label:16} {sorted(set(captured))}")


async def run_nonstreaming():
    await Runner.run(agent, input="Use tool.")


async def run_streaming():
    result = Runner.run_streamed(agent, input="Use tool.")
    async for _ in result.stream_events():
        pass


asyncio.run(show("run():", run_nonstreaming))
asyncio.run(show("run_streamed():", run_streaming))

(Requires OPENAI_API_KEY.)

Expected Result

Both runs emit a gen_ai.execute_tool span:

run():           ['gen_ai.chat', 'gen_ai.execute_tool', 'gen_ai.invoke_agent']
run_streamed():  ['gen_ai.chat', 'gen_ai.execute_tool', 'gen_ai.invoke_agent']

Actual Result

The streamed run is missing gen_ai.execute_tool (and gen_ai.invoke_agent):

run():           ['gen_ai.chat', 'gen_ai.execute_tool', 'gen_ai.invoke_agent']
run_streamed():  ['gen_ai.chat']

Root cause

OpenAIAgentsIntegration.setup_once (the openai-agents >= 0.8 branch) patches the tool wrapper onto agents.run.get_all_tools:

agents.run.get_all_tools = new_wrapped_get_all_tools   # wraps run_loop.get_all_tools

…but it never patches agents.run_internal.run_loop.get_all_tools. In openai-agents 0.17.2 the two run paths resolve tools through different references:

  • non-streaming → agents/run.py calls the (patched) get_all_tools → function tools get their on_invoke_tool wrapped → gen_ai.execute_tool spans ✅
  • streaming → agents/run_internal/run_loop.py calls its own module-level get_all_tools, which is left unpatched → function tools are never wrapped → no spans ❌

Note get_model is patched at the run_loop level (agents.run_internal.run_loop.get_model = ...), which is why gen_ai.chat still works under streaming — get_all_tools was simply missed there. The streamed runner wrapper also doesn't appear to finish gen_ai.invoke_agent spans, which likely explains the second missing span.

Suggested fix

Apply the same _get_all_tools wrapper to the symbol the streamed loop calls, e.g. also patch agents.run_internal.run_loop.get_all_tools in setup_once. Confirmed working as a monkeypatch:

from functools import wraps
import agents.run_internal.run_loop as run_loop
from sentry_sdk.integrations.openai_agents.patches import _get_all_tools

_orig = run_loop.get_all_tools

@wraps(_orig)
async def _wrapped(agent, context_wrapper):
    return await _get_all_tools(_orig, agent, context_wrapper)

run_loop.get_all_tools = _wrapped

With this applied, run_streamed() emits gen_ai.execute_tool as expected.

Environment

  • sentry-sdk: 2.63.0 (also reproduced on 2.59.0; 2.63.0 is the latest on PyPI)
  • openai-agents: 0.17.2
  • Python: 3.12
  • Not affected by the stream_gen_ai_spans option (reproduces with it on or off).

Metadata

Metadata

Assignees

No one assigned
    No fields configured for issues without a type.

    Projects

    Status
    Waiting for: Product Owner

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions