Agents
Agents are the core building block in your apps. An agent is a large language model (LLM), configured with instructions and tools.
Basic configuration
The most common properties of an agent you'll configure are:
name: A required string that identifies your agent.instructions: also known as a developer message or system prompt.model: which LLM to use, and optionalmodel_settingsto configure model tuning parameters like temperature, top_p, etc.tools: Tools that the agent can use to achieve its tasks.
fromagentsimportAgent,ModelSettings,function_tool@function_tooldefget_weather(city:str)->str:"""returns weather info for the specified city."""returnf"The weather in{city} is sunny"agent=Agent(name="Haiku agent",instructions="Always respond in haiku form",model="gpt-5-nano",tools=[get_weather],)Context
Agents are generic on theircontext type. Context is a dependency-injection tool: it's an object you create and pass toRunner.run(), that is passed to every agent, tool, handoff etc, and it serves as a grab bag of dependencies and state for the agent run. You can provide any Python object as the context.
@dataclassclassUserContext:name:struid:stris_pro_user:boolasyncdeffetch_purchases()->list[Purchase]:return...agent=Agent[UserContext](...,)Output types
By default, agents produce plain text (i.e.str) outputs. If you want the agent to produce a particular type of output, you can use theoutput_type parameter. A common choice is to usePydantic objects, but we support any type that can be wrapped in a PydanticTypeAdapter - dataclasses, lists, TypedDict, etc.
frompydanticimportBaseModelfromagentsimportAgentclassCalendarEvent(BaseModel):name:strdate:strparticipants:list[str]agent=Agent(name="Calendar extractor",instructions="Extract calendar events from text",output_type=CalendarEvent,)Note
When you pass anoutput_type, that tells the model to usestructured outputs instead of regular plain text responses.
Multi-agent system design patterns
There are many ways to design multi‑agent systems, but we commonly see two broadly applicable patterns:
- Manager (agents as tools): A central manager/orchestrator invokes specialized sub‑agents as tools and retains control of the conversation.
- Handoffs: Peer agents hand off control to a specialized agent that takes over the conversation. This is decentralized.
Seeour practical guide to building agents for more details.
Manager (agents as tools)
Thecustomer_facing_agent handles all user interaction and invokes specialized sub‑agents exposed as tools. Read more in thetools documentation.
fromagentsimportAgentbooking_agent=Agent(...)refund_agent=Agent(...)customer_facing_agent=Agent(name="Customer-facing agent",instructions=("Handle all direct user communication. ""Call the relevant tools when specialized expertise is needed."),tools=[booking_agent.as_tool(tool_name="booking_expert",tool_description="Handles booking questions and requests.",),refund_agent.as_tool(tool_name="refund_expert",tool_description="Handles refund questions and requests.",)],)Handoffs
Handoffs are sub‑agents the agent can delegate to. When a handoff occurs, the delegated agent receives the conversation history and takes over the conversation. This pattern enables modular, specialized agents that excel at a single task. Read more in thehandoffs documentation.
fromagentsimportAgentbooking_agent=Agent(...)refund_agent=Agent(...)triage_agent=Agent(name="Triage agent",instructions=("Help the user with their questions. ""If they ask about booking, hand off to the booking agent. ""If they ask about refunds, hand off to the refund agent."),handoffs=[booking_agent,refund_agent],)Dynamic instructions
In most cases, you can provide instructions when you create the agent. However, you can also provide dynamic instructions via a function. The function will receive the agent and context, and must return the prompt. Both regular andasync functions are accepted.
defdynamic_instructions(context:RunContextWrapper[UserContext],agent:Agent[UserContext])->str:returnf"The user's name is{context.context.name}. Help them with their questions."agent=Agent[UserContext](name="Triage agent",instructions=dynamic_instructions,)Lifecycle events (hooks)
Sometimes, you want to observe the lifecycle of an agent. For example, you may want to log events, or pre-fetch data when certain events occur. You can hook into the agent lifecycle with thehooks property. Subclass theAgentHooks class, and override the methods you're interested in.
Guardrails
Guardrails allow you to run checks/validations on user input in parallel to the agent running, and on the agent's output once it is produced. For example, you could screen the user's input and agent's output for relevance. Read more in theguardrails documentation.
Cloning/copying agents
By using theclone() method on an agent, you can duplicate an Agent, and optionally change any properties you like.
pirate_agent=Agent(name="Pirate",instructions="Write like a pirate",model="gpt-5.2",)robot_agent=pirate_agent.clone(name="Robot",instructions="Write like a robot",)Forcing tool use
Supplying a list of tools doesn't always mean the LLM will use a tool. You can force tool use by settingModelSettings.tool_choice. Valid values are:
auto, which allows the LLM to decide whether or not to use a tool.required, which requires the LLM to use a tool (but it can intelligently decide which tool).none, which requires the LLM tonot use a tool.- Setting a specific string e.g.
my_tool, which requires the LLM to use that specific tool.
fromagentsimportAgent,Runner,function_tool,ModelSettings@function_tooldefget_weather(city:str)->str:"""Returns weather info for the specified city."""returnf"The weather in{city} is sunny"agent=Agent(name="Weather Agent",instructions="Retrieve weather details.",tools=[get_weather],model_settings=ModelSettings(tool_choice="get_weather"))Tool Use Behavior
Thetool_use_behavior parameter in theAgent configuration controls how tool outputs are handled:
"run_llm_again": The default. Tools are run, and the LLM processes the results to produce a final response."stop_on_first_tool": The output of the first tool call is used as the final response, without further LLM processing.
fromagentsimportAgent,Runner,function_tool,ModelSettings@function_tooldefget_weather(city:str)->str:"""Returns weather info for the specified city."""returnf"The weather in{city} is sunny"agent=Agent(name="Weather Agent",instructions="Retrieve weather details.",tools=[get_weather],tool_use_behavior="stop_on_first_tool")StopAtTools(stop_at_tool_names=[...]): Stops if any specified tool is called, using its output as the final response.
fromagentsimportAgent,Runner,function_toolfromagents.agentimportStopAtTools@function_tooldefget_weather(city:str)->str:"""Returns weather info for the specified city."""returnf"The weather in{city} is sunny"@function_tooldefsum_numbers(a:int,b:int)->int:"""Adds two numbers."""returna+bagent=Agent(name="Stop At Stock Agent",instructions="Get weather or sum numbers.",tools=[get_weather,sum_numbers],tool_use_behavior=StopAtTools(stop_at_tool_names=["get_weather"]))ToolsToFinalOutputFunction: A custom function that processes tool results and decides whether to stop or continue with the LLM.
fromagentsimportAgent,Runner,function_tool,FunctionToolResult,RunContextWrapperfromagents.agentimportToolsToFinalOutputResultfromtypingimportList,Any@function_tooldefget_weather(city:str)->str:"""Returns weather info for the specified city."""returnf"The weather in{city} is sunny"defcustom_tool_handler(context:RunContextWrapper[Any],tool_results:List[FunctionToolResult])->ToolsToFinalOutputResult:"""Processes tool results to decide final output."""forresultintool_results:ifresult.outputand"sunny"inresult.output:returnToolsToFinalOutputResult(is_final_output=True,final_output=f"Final weather:{result.output}")returnToolsToFinalOutputResult(is_final_output=False,final_output=None)agent=Agent(name="Weather Agent",instructions="Retrieve weather details.",tools=[get_weather],tool_use_behavior=custom_tool_handler)Note
To prevent infinite loops, the framework automatically resetstool_choice to "auto" after a tool call. This behavior is configurable viaagent.reset_tool_choice. The infinite loop is because tool results are sent to the LLM, which then generates another tool call because oftool_choice, ad infinitum.