Skip to content
Learn Agentic AI
Learn Agentic AI9 min read32 views

Agents as Tools: The as_tool() Pattern for Orchestration

Learn how to use agent.as_tool() to turn entire agents into callable tools for other agents. Master approval workflows, output extraction, and dynamic tool enabling for multi-agent orchestration.

The Orchestration Problem

As your agent system grows, you will have specialized agents for different domains — one for billing, one for technical support, one for scheduling. The question becomes: how does a top-level agent delegate to these specialists?

The OpenAI Agents SDK offers two patterns for multi-agent coordination:

  1. Handoffs — transfer control entirely to another agent (the original agent stops)
  2. Agents as Tools — call another agent like a function, get back a result, and continue

The as_tool() pattern is ideal when your orchestrator agent needs to gather information from multiple specialists and synthesize a combined response, rather than handing off control entirely.

Basic as_tool() Usage

The as_tool() method converts an agent into a tool that another agent can call:

Hear it before you finish reading

Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.

Try Live Demo →
flowchart LR
    INPUT(["User input"])
    AGENT["Agent<br/>name plus instructions"]
    HAND{"Handoff to<br/>another agent?"}
    SUB["Sub-agent<br/>specialist"]
    GUARD{"Guardrail<br/>passed?"}
    TOOL["Tool call"]
    SDK[("Tracing<br/>OpenAI dashboard")]
    OUT(["Final output"])
    INPUT --> AGENT --> HAND
    HAND -->|Yes| SUB --> GUARD
    HAND -->|No| GUARD
    GUARD -->|Yes| TOOL --> AGENT
    GUARD -->|Block| OUT
    AGENT --> OUT
    AGENT --> SDK
    style AGENT fill:#4f46e5,stroke:#4338ca,color:#fff
    style GUARD fill:#f59e0b,stroke:#d97706,color:#1f2937
    style SDK fill:#ede9fe,stroke:#7c3aed,color:#1e1b4b
    style OUT fill:#059669,stroke:#047857,color:#fff
from agents import Agent, Runner

# Specialist agents
billing_agent = Agent(
    name="Billing Specialist",
    instructions="You are a billing expert. Answer questions about invoices, payments, and subscription plans. Be concise and precise.",
)

tech_agent = Agent(
    name="Tech Support",
    instructions="You are a technical support specialist. Help diagnose and resolve technical issues. Be concise.",
)

# Orchestrator uses specialists as tools
orchestrator = Agent(
    name="Customer Service Lead",
    instructions="You are the primary customer service agent. Use the billing and tech support tools to get specialist answers, then provide a unified response to the customer.",
    tools=[
        billing_agent.as_tool(
            tool_name="ask_billing",
            tool_description="Ask the billing specialist a question about invoices, payments, or subscriptions.",
        ),
        tech_agent.as_tool(
            tool_name="ask_tech_support",
            tool_description="Ask the tech support specialist to diagnose or resolve a technical issue.",
        ),
    ],
)

result = Runner.run_sync(
    orchestrator,
    "I was charged twice on my last invoice, and I also can't log into my dashboard.",
)
print(result.final_output)

When the orchestrator calls ask_billing, it runs the billing agent as a sub-agent, waits for its response, and receives the result as a tool output. The orchestrator then continues its own reasoning with that information.

Custom Output Extraction

By default, as_tool() returns the sub-agent's final_output as the tool result. You can customize this with custom_output_extractor to pull specific data from the run result:

from agents import Agent, RunResult

def extract_summary(run_result: RunResult) -> str:
    """Extract just the key findings from the research agent's output."""
    output = run_result.final_output
    # You could parse, truncate, or restructure the output here
    if len(output) > 500:
        return output[:500] + "... [truncated for brevity]"
    return output

research_agent = Agent(
    name="Deep Researcher",
    instructions="You perform thorough research on topics. Provide detailed analysis with sources.",
)

orchestrator = Agent(
    name="Report Writer",
    instructions="You write executive summaries. Use the research tool to gather information, then synthesize it into a concise report.",
    tools=[
        research_agent.as_tool(
            tool_name="research",
            tool_description="Perform deep research on a topic.",
            custom_output_extractor=extract_summary,
        ),
    ],
)

This is useful when the sub-agent produces verbose output but the orchestrator only needs key facts.

Approval Workflows with needs_approval

Some agent actions require human approval before execution. The needs_approval parameter on function tools integrates human-in-the-loop checks into your pipeline:

Still reading? Stop comparing — try CallSphere live.

CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.

from agents import Agent, Runner, function_tool

@function_tool(needs_approval=True)
def execute_refund(order_id: str, amount: float, reason: str) -> str:
    """Process a customer refund."""
    return f"Refund of ${amount:.2f} processed for order {order_id}."

@function_tool(needs_approval=True)
def delete_account(customer_id: str, confirmation: str) -> str:
    """Permanently delete a customer account."""
    return f"Account {customer_id} has been permanently deleted."

agent = Agent(
    name="Account Manager",
    instructions="You help manage customer accounts. Refunds and deletions require approval.",
    tools=[execute_refund, delete_account],
)

When the agent tries to call a tool with needs_approval=True, the SDK raises an ApprovalRequired event that your application must handle. The tool only executes after explicit approval is granted:

from agents import Runner

async def run_with_approval():
    result = await Runner.run(
        agent,
        "Please refund $50 for order ORD-12345 due to a shipping delay.",
    )
    # In practice, you would check result for approval requests
    # and implement your approval UI/logic here
    print(result.final_output)

You can also make needs_approval dynamic by passing a function instead of a boolean:

from agents import RunContextWrapper

def approve_large_refunds(ctx: RunContextWrapper, tool_name: str, tool_input: dict) -> bool:
    """Only require approval for refunds over $100."""
    if tool_name == "execute_refund":
        return tool_input.get("amount", 0) > 100
    return False

Dynamic Tool Enabling with is_enabled

Sometimes a tool should only be available under certain conditions. The is_enabled parameter on function tools lets you dynamically control tool availability:

from agents import function_tool, RunContextWrapper
from dataclasses import dataclass

@dataclass
class UserContext:
    role: str
    is_verified: bool

def admin_only(ctx: RunContextWrapper[UserContext], tool_name: str) -> bool:
    """Only enable this tool for admin users."""
    return ctx.context.role == "admin"

@function_tool(is_enabled=admin_only)
def view_audit_log(days: int) -> str:
    """View the system audit log for the past N days."""
    return f"Showing audit log for the past {days} days..."

When is_enabled returns False, the tool is not included in the agent's tool list for that run. The agent does not even know the tool exists, so it will not try to call it or hallucinate about its availability.

Combining Patterns for Production Orchestration

Here is a more complete example combining agents-as-tools with approval and dynamic enabling:

from dataclasses import dataclass
from agents import Agent, Runner, function_tool, RunContextWrapper

@dataclass
class SessionContext:
    user_id: str
    role: str
    tier: str

# Specialist agents
analyst = Agent(
    name="Data Analyst",
    instructions="You analyze data and provide insights. Be concise and data-driven.",
)

writer = Agent(
    name="Report Writer",
    instructions="You write clear, professional reports based on provided data.",
)

def premium_only(ctx: RunContextWrapper[SessionContext], tool_name: str) -> bool:
    return ctx.context.tier == "premium"

@function_tool(is_enabled=premium_only)
def export_to_pdf(content: str) -> str:
    """Export content to a PDF document."""
    return "PDF generated and saved to /reports/output.pdf"

orchestrator = Agent(
    name="Report Orchestrator",
    instructions="You coordinate data analysis and report generation. Use the analyst for data questions, the writer for formatting, and the PDF export for premium users.",
    tools=[
        analyst.as_tool(
            tool_name="analyze_data",
            tool_description="Ask the data analyst to analyze a dataset or answer a data question.",
        ),
        writer.as_tool(
            tool_name="write_report",
            tool_description="Ask the report writer to format findings into a professional report.",
        ),
        export_to_pdf,
    ],
)

ctx = SessionContext(user_id="u_123", role="manager", tier="premium")
result = Runner.run_sync(
    orchestrator,
    "Analyze our Q1 sales data and write an executive summary report. Export it to PDF.",
    context=ctx,
)
print(result.final_output)

Key Takeaways

  • Use agent.as_tool() when you need to delegate and continue, not hand off entirely
  • Set custom_output_extractor to control what the orchestrator sees from sub-agents
  • Use needs_approval for sensitive operations that require human confirmation
  • Use is_enabled to dynamically show or hide tools based on user context
  • Combine these patterns for production-grade multi-agent orchestration systems
Share

Try CallSphere AI Voice Agents

See how AI voice agents work for your industry. Live demo available -- no signup required.

Related Articles You May Like

Agentic AI

Human-in-the-Loop Hybrid Agents: 73% Fewer Errors in 2026

Fully autonomous agents are still a fantasy in production. LangGraph's interrupt() lets you pause for human approval mid-graph without losing state. We cover approve/edit/reject/respond actions and CallSphere's escalation ladder.

Agentic AI

Building Your First Agent with the OpenAI Agents SDK in 2026: A Hands-On Walkthrough

Step-by-step build of a working agent with the OpenAI Agents SDK — Agent class, tools, handoffs, tracing — plus an eval pipeline that catches regressions before merge.

Agentic AI

Browser Agents with LangGraph + Playwright: Visual Evaluation Pipelines That Don't Lie

Build a browser agent with LangGraph and Playwright that does multi-step web tasks, then ground-truth its work with visual diffs and DOM-based evaluators.

Agentic AI

OpenAI Computer-Use Agents (CUA) in Production: Build + Evaluate a Real Workflow (2026)

Build a working computer-use agent with the OpenAI Computer Use tool — clicks, types, scrolls a real browser — then evaluate task success on a benchmark suite.

Funding & Industry

OpenAI revenue run-rate — April 2026 read — April 2026 update

OpenAI's April 2026 reported revenue run-rate cleared $13B annualized, on continued ChatGPT growth, agentic Operator monetization, and enterprise API expansion.

Funding & Industry

Stargate progress update — April 2026 site and capex

OpenAI's Stargate with Oracle and SoftBank crossed a milestone in April 2026 with the first Texas site partially energized and three additional sites under construction.