Skip to content

Agent.as_tool hides nested tool‑call events — blocks parallel sub‑agents with streaming #864

Open
@macedoti13

Description

@macedoti13

Agent.as_tool hides nested tool‑call events — blocks parallel sub‑agents with streaming

Summary

Agent.as_tool() makes it easy to plug one agent inside another, but it does so by running the wrapped agent non‑streamed and returning only its final string. As a result all inner events (including inner tool calls) are invisible to the outer run. This is merely confusing in simple cases, but it becomes a hard blocker when you need to run multiple sub‑agents in parallel and surface their progress to the user in real time.

Environment

Library Version (tested)
openai‑agents <fill in>
Python 3.12.x
Model gpt‑4o-2024‑08‑06
OS macOS 15.5 (Apple Silicon)

Minimal Reproduction

import asyncio
from agents import Agent, Runner, function_tool

@function_tool
async def grab_user_purchases(user_name: str):
    return [{"name": user_name, "purchase_id": "123"}]

# ► Sub‑agent that *must* call the tool above
a_purchase_agent = Agent(
    name="purchase_agent_A",
    instructions="Grab purchases from source A.",
    tools=[grab_user_purchases],
)
b_purchase_agent = Agent(
    name="purchase_agent_B",
    instructions="Grab purchases from source B.",
    tools=[grab_user_purchases],
)

# ► Main agent runs BOTH sub‑agents concurrently
main_agent = Agent(
    name="main_agent",
    instructions=(
        "Run both purchase agents in parallel, then merge their results."
    ),
    tools=[
        a_purchase_agent.as_tool(tool_name="fetch_A"),
        b_purchase_agent.as_tool(tool_name="fetch_B"),
    ],
)

async def demo():
    run = Runner.run_streamed(main_agent, input="John Doe")
    async for ev in run.stream_events():
        print(ev)

asyncio.run(demo())

Observed Output (trimmed)

RunItemStreamEvent(name='tool_called', item=… name='fetch_A')
RunItemStreamEvent(name='tool_called', item=… name='fetch_B')
RunItemStreamEvent(name='tool_output', item=… name='fetch_A')
RunItemStreamEvent(name='tool_output', item=… name='fetch_B')

No grab_user_purchases events appear for either sub‑agent.

Expected Output

The stream should also contain, for each sub‑agent:

RunItemStreamEvent(name='tool_called', item=… name='grab_user_purchases')
RunItemStreamEvent(name='tool_output', item=… name='grab_user_purchases')

That would let the UI show two independent spinners while both sub‑agents work.

Analysis  — Why events are lost

Internally Agent.as_tool() wraps the agent in a FunctionTool whose
on_invoke_tool implementation calls Runner.run (non‑streamed). All
events from the nested run are consumed privately, and only the final string is
returned to the outer agent.

Why current work‑arounds don’t solve it

Work‑around Limitation
Handoff to the sub‑agent Only one agent can own the run at a time → cannot run two sub‑agents concurrently.
Manual Runner.run_streamed inside a proxy tool There is no public push_stream_event API, so you cannot forward nested events into the parent stream; plus it adds boilerplate and defeats the “simple orchestration” goal of the SDK.
Separate runs outside the parent Breaks the single unified event stream; forces custom front‑end multiplexing logic; again loses the simplicity of Agents SDK.

Feature Request

Expose an official way to wrap an agent as a streaming tool that forwards
all nested events.
Two concrete ideas:

  1. Agent.as_streaming_tool(tool_name=…, description=…)
  2. Agent.as_tool(..., stream_inner_events=True)

Either option should:

  • Call Runner.run_streamed under the hood.
  • Forward each sub_event onto the parent run’s queue with proper ordering.
  • Preserve the ability to invoke multiple such tools in parallel when
    parallel_tool_calls=True.

Impact

Without this, any application that relies on parallel agent composition with real‑time progress (e.g., multi‑source data gathering, meta‑analysis agents, canvas dashboards) must abandon as_tool() and implement fragile custom logic, undermining the SDK’s promise of easy composition.


Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions