For a well-defined operation, the tool can simply be a script-based function or an API.
But once the task becomes more open-ended, it can be quite difficult to capture the problem-solving logic in a predefined script.
This leads to an interesting question:
The answer is yes. And this is the so-called “agent-as-a-tool” pattern.
In this post, we’ll explore this pattern using the OpenAI Agents SDK and illustrate it with a small case study.
1. The Agent-as-a-Tool Pattern
As implied by its name, in this pattern, agents are treated as tools called by a manager agent.
Commonly, this manager agent manages the overall tasks, while other agents serve as specialists. Whenever the manager needs help with a particular part of the problem, it passes that work to the relevant specialist.
The specialist can then solve the delegated task using its own instructions and tools. Once it completes the task, the results are fed back to the manager, who might continue coordinating the work or produce the final response.
This is one concrete pattern of a multi-agent system. It becomes useful when the boundary of a delegated task is clear, but the steps required to complete it are not.
This pattern gives us a clear division of responsibility. Different specialists can be configured accordingly, but we don’t need to burden the manager agent with every implementation detail.
Next, let’s build this pattern with the OpenAI Agents SDK.
2. Planning a Long Layover
In this case study, we build an agentic system to help travelers plan their activities during a long layover.
Think of this scenario: a family has a 10-hour layover in Munich, Germany. They would like to leave the airport, do some sightseeing, and enjoy a good meal without jeopardizing their onward flight.
To create a useful itinerary, the agent needs to answer a few questions, for example:
- Is there enough time to leave the airport at all?
- What activities and food options fit the travelers?
- What risks should the plan consider?
It’d be great if we can give the agent three purposely built tools to help answer those questions. Here is the agent shape we want:
```
pip install openai-agents
from agents import Agent, ModelSettings, OpenAIResponsesModel
from openai import AsyncAzureOpenAI
client = AsyncAzureOpenAI(
api_key=os.environ["OPENAI_API_KEY"],
azure_endpoint=os.environ["OPENAI_API_BASE"],
api_version=os.environ["OPENAI_API_VERSION"],
)
travel_planner_agent = Agent(
name="Travel planner",
instructions=(
"Create a travel plan for the user "
"using the available tools."
),
model=OpenAIResponsesModel(
model="gpt-5.4",
openai_client=client,
),
model_settings=ModelSettings(
reasoning={"effort": "medium"},
),
tools=[
logistics_tool,
local_experience_tool,
risk_tool,
],
output_type=LayoverPlan,
)
```
The three tools are:
logistics_tool: checks whether the trip is feasible based on transportation and timing.local_experience_tool: finds activities and food options.risk_tool: identifies potential risks and suggests ways to make the plan more robust.
The travel planner can then call those tools and synthesize the responses into the final itinerary.
But here is the problem: none of the tools can be easily implemented as a predefined function.
They are all open-ended tasks, and some of the tasks, like logistics checking, even require searching current transportation information online.
So, what should we do then?
This is where the agent-as-a-tool pattern comes into play.
Instead of using a predefined function, we use a specialist agent to act as the tool. Those specialist agents can reason about the task and use their own tools when needed. This way, we get the full flexibility of agentic problem-solving.
And the best part is, from the travel planner agent’s perspective, nothing changes: it still calls a tool and receives a response. But behind that tool, however, it’s another agent that does the work.
Now, let’s build these tools, or specialist agents, one at a time.
Note that we have also tasked the agent to produce a structured output, according to the schema ofLayoverPlan. We’ll come back to its content later.
2.1 Checking Travel Logistics
This first tool focuses on checking whether a proposed plan is feasible based on current transportation and timing information.
Completing this task would require searching online for up-to-date information. Therefore, we create a logistics agent with web search capability:
from agents import WebSearchTool
logistics_agent = Agent(
name="Logistics specialist",
instructions=(
"Check whether a travel plan is feasible using "
"current transportation and timing information."
),
model=OpenAIResponsesModel(
model="gpt-5.4",
openai_client=client,
),
model_settings=ModelSettings(
reasoning={"effort": "medium"},
),
tools=[WebSearchTool()],
)
Here comes the magic:
logistics_tool = logistics_agent.as_tool(
tool_name="check_logistics",
tool_description=(
"Check travel timing, transportation feasibility, "
"and buffers."
),
max_turns=3,
)
This is how we expose this agent as the logistics_tool.
One thing worth mentioning: the instructions we specified for logistics_agent is to tell the logistics agent what role to perform after it is invoked. So it is specialist agent facing.
For the tool_name and tool_description, they are shown to the travel planner agent, who uses them to decide when to call this specialist.
We have also set max_turns=3, meaning that the logistics agent can take up to three turns to complete the delegated task and return its response.
WebSearchToolis a hosted Responses API tool. With Azure OpenAI, the search is provided through Grounding with Bing.
2.2 Finding Activities and Food Options
Following the same pattern, let’s now define the second agent and turn it into a tool:
local_experience_agent = Agent(
name="Local experience specialist",
instructions=(
"Suggest activities and food options that fit "
"the traveler's preferences and constraints."
),
model=OpenAIResponsesModel(
model="gpt-5.4",
openai_client=client,
),
model_settings=ModelSettings(
reasoning={"effort": "medium"},
),
tools=[WebSearchTool()],
)
As the work of finding activities and food options also requires open-ended exploration, we give our specialist the web search capability.
We then expose the specialist to the travel planner:
local_experience_tool = local_experience_agent.as_tool(
tool_name="suggest_local_options",
tool_description=(
"Suggest activities and food options "
"that fit the traveler."
),
max_turns=3,
)
2.3 Identifying Potential Risks
We can now define the final tool, which will review the proposed itinerary and identify where it could fail.
Since this task only requires judgment, we don’t give the risk specialist any additional tool:
risk_agent = Agent(
name="Risk specialist",
instructions=(
"Identify practical risks in a travel plan and "
"suggest ways to make it more robust."
),
model=OpenAIResponsesModel(
model="gpt-5.4",
openai_client=client,
),
model_settings=ModelSettings(
reasoning={"effort": "medium"},
),
)
We expose it to the travel planner in the same way:
risk_tool = risk_agent.as_tool(
tool_name="review_risks",
tool_description=(
"Review practical risks and robustness "
"of the travel plan."
),
max_turns=3,
)
2.4 Running the Munich Example
Earlier, we configured the travel planner agent to return a structured output. Here is the output schema LayoverPlan:
from pydantic import BaseModel
class LayoverPlan(BaseModel):
summary: str
itinerary: list[str]
airport_return_time: str
backup_plan: str
rationale: str
Now, we can test the complete system with our Munich layover scenario:
user_request = """
We have a 10-hour layover in Munich.
We arrive at Munich Airport at 8:00 AM and depart at 6:00 PM.
We are two adults and one 7-year-old.
We want to see something memorable, eat good food, and avoid
missing the flight.
Please keep the plan relaxed.
"""
Here is how we run the agent:
from agents import Runner
result = await Runner.run(
travel_planner_agent,
user_request,
max_turns=10,
)
We can see the results this way:
print(
result.final_output.model_dump_json(indent=2)
)
In my run, the travel planner agent actually recommended visiting a nearby town called Freising rather than central Munich. The produced itinerary included some sightseeing with an old-town walk, as well as a Bavarian lunch. Then return to the airport by 1:45 PM. This leaves plenty of time before the 6:00 PM flight.
The agent’s rationale for not suggesting a central Munich visit is that although central Munich was possible, the additional transportation time would make the outing less relaxed for a family with a seven-year-old. Freising, on the other hand, offered sightseeing and food with a larger flight buffer.
3. When to Use This Pattern
Agent-as-a-tool is a power pattern to add to your LLM application development kit. But before using it, ask yourself:
- Is part of the task open-ended enough to require an agent rather than a predefined function?
- Can that work be delegated as a clearly bounded specialist task?
- Should the original agent remain responsible for the overall result?
If the answer to these questions is yes, the agent-as-a-tool pattern is likely a good fit.
If delegated operations can be captured in predefined logic, then just opt for a regular function tool.