An agent as a tool with as_tool
as_tool is an Agent method that turns an agent into a tool the calling agent can invoke: specialist.as_tool(tool_name=..., tool_description=...) runs the specialist as one step and returns control to the caller.
Last updated: 28 Sep, 2026 · openai-agents 0.22.3
A handoff gives the whole run away. Sometimes you want a specialist to do one piece, then keep going as the main agent. Wrapping the specialist as a tool does that: the caller stays in charge.
The as_tool method
Call as_tool on the specialist to get a FunctionTool. Add it to another agent's tools. When the caller invokes it, the specialist runs, its answer comes back as the tool result, and the caller carries on.
translator = Agent(name="Translator", instructions="Translate to Spanish.")
tool = translator.as_tool(tool_name="translate_to_spanish",
tool_description="Translate the given text.")
# tool is a FunctionTool; after it runs, control returns to the callerThe specialist agent
The specialist is a normal Agent with its own model. Here FixedModel is a stand-in that always answers Hola, mundo.
translator = Agent(name="Translator", instructions="Translate to Spanish.",
model=FixedModel()) # always returns "Hola, mundo"Wrapping it as a tool
as_tool needs a name and a description, the way any tool does. The name is what the calling model uses to invoke it.
translate_tool = translator.as_tool(
tool_name="translate_to_spanish",
tool_description="Translate the given text to Spanish.",
)
print(translate_tool.name) # translate_to_spanishThe parent that calls the tool
Give the tool to a parent agent. The parent's stand-in ParentModel calls the tool, then writes the final answer itself, which is how you see that control came back to the parent.
parent = Agent(name="Assistant", instructions="Use your tools to help.",
tools=[translate_tool], model=ParentModel())
# ParentModel calls the tool, then answers, so the parent finishes the runCalling a translator agent as a tool
The whole program. The parent calls the translator tool, gets Hola, mundo, and writes the final line itself.
from agents import Agent, Runner, set_tracing_disabled
from agents.models.interface import Model
from agents.items import ModelResponse
from agents.usage import Usage
from openai.types.responses import (
ResponseOutputMessage, ResponseOutputText, ResponseFunctionToolCall,
)
set_tracing_disabled(True)
def _message(text):
return ResponseOutputMessage(
id="msg", role="assistant", type="message", status="completed",
content=[ResponseOutputText(text=text, type="output_text", annotations=[])],
)
def _tool_call(name, arguments):
return ResponseFunctionToolCall(
id="fc", call_id="call_1", name=name, arguments=arguments, type="function_call",
)
class FixedModel(Model):
async def get_response(self, *a, **k):
return ModelResponse(output=[_message("Hola, mundo")],
usage=Usage(), response_id=None)
async def stream_response(self, *a, **k):
raise NotImplementedError
class ParentModel(Model):
async def get_response(self, system_instructions, input, model_settings, tools,
output_schema, handoffs, tracing, **k):
if isinstance(input, list):
for it in reversed(input):
d = it if isinstance(it, dict) else it.__dict__
if d.get("type") == "function_call_output":
return ModelResponse(
output=[_message("Here is your translation: " + d.get("output", ""))],
usage=Usage(), response_id=None)
return ModelResponse(output=[_tool_call(tools[0].name, '{"input": "hello, world"}')],
usage=Usage(), response_id=None)
async def stream_response(self, *a, **k):
raise NotImplementedError
translator = Agent(name="Translator", instructions="Translate to Spanish.",
model=FixedModel())
translate_tool = translator.as_tool(
tool_name="translate_to_spanish",
tool_description="Translate the given text to Spanish.",
)
parent = Agent(name="Assistant", instructions="Use your tools to help.",
tools=[translate_tool], model=ParentModel())
result = Runner.run_sync(parent, "please translate hello, world")
print("Tool name:", translate_tool.name)
print("Last agent:", result.last_agent.name)
print("Final output:", result.final_output)What the run shows about control
- The tool name is translate_to_spanish:
as_toolmade a tool the parent can call by that name. - last_agent is Assistant: the parent, not the translator, so control returned after the tool ran.
- final_output came from the parent: it wrapped the translator's
Hola, mundoin its own sentence.
as_tool vs a handoff
| Handoff | agent.as_tool | |
|---|---|---|
| The specialist is | handed the run | called as a step |
| After it runs | it keeps the run | control returns to the caller |
| last_agent | the specialist | the calling agent |
| The final answer | from the specialist | from the caller |
When an agent belongs as a tool
- A sub-skill in the middle of a task: translate, then continue answering.
- Fan-out: one agent calls several specialist tools and combines the results.
as_tool takes a single input string. The calling model must send {"input": "..."}, not the specialist's own field names.Related
- Previous: Handoffs: passing control to another agent
- Next: Handoff vs agent-as-tool
- Reference: Tools: agents as tools
- Change
tool_nameto"translate"and read the new first line. - Make
FixedModelreturn"Bonjour"and see it flow into the parent's sentence. - Add a second specialist tool and have
ParentModelcalltools[1].nameinstead.
Little by little, you're building something great.