Skip to content
Learn Agentic AI
Learn Agentic AI9 min read13 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:

flowchart TD
    START["Agents as Tools: The as_tool Pattern for Orchestr…"] --> A
    A["The Orchestration Problem"]
    A --> B
    B["Basic as_tool Usage"]
    B --> C
    C["Custom Output Extraction"]
    C --> D
    D["Approval Workflows with needs_approval"]
    D --> E
    E["Dynamic Tool Enabling with is_enabled"]
    E --> F
    F["Combining Patterns for Production Orche…"]
    F --> G
    G["Key Takeaways"]
    G --> DONE["Key Takeaways"]
    style START fill:#4f46e5,stroke:#4338ca,color:#fff
    style DONE 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:

See AI Voice Agents Handle Real Calls

Book a free demo or calculate how much you can save with AI voice automation.

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:

flowchart TD
    CENTER(("Core Concepts"))
    CENTER --> N0["Handoffs — transfer control entirely to…"]
    CENTER --> N1["Agents as Tools — call another agent li…"]
    CENTER --> N2["Use agent.as_tool when you need to dele…"]
    CENTER --> N3["Set custom_output_extractor to control …"]
    CENTER --> N4["Use needs_approval for sensitive operat…"]
    CENTER --> N5["Use is_enabled to dynamically show or h…"]
    style CENTER fill:#4f46e5,stroke:#4338ca,color:#fff
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
C

Written by

CallSphere Team

Expert insights on AI voice agents and customer communication automation.

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