InkdownInkdown
Start writing

OpenAI Agents Python

21 filesยท0 subfolders

Shared Workspace

OpenAI Agents Python
00_OVERVIEW.md

01_AGENT_SYSTEM

Shared from "OpenAI Agents Python" on Inkdown

Agent System - Comprehensive Deep Dive

Overview

The Agent system is the heart of the OpenAI Agents SDK. An Agent represents an AI assistant that can be configured with instructions, tools, guardrails, handoffs, and more. Think of an Agent as a "persona" or "role" that the LLM adopts, equipped with specific capabilities and constraints.

Core Classes

AgentBase

AgentBase is the base class for all agents. It provides the foundational attributes that are shared across different agent types (including Agent and RealtimeAgent).

01_AGENT_SYSTEM.md
02_RUNNER_SYSTEM.md
03_TOOL_SYSTEM.md
04_ITEMS_SYSTEM.md
05_GUARDRAILS.md
06_HANDOFFS.md
07_MEMORY_SESSIONS.md
08_MODEL_PROVIDERS.md
09_SANDBOX_SYSTEM.md
10_TRACING.md
11_RUN_STATE.md
12_CONTEXT.md
13_LIFECYCLE_HOOKS.md
14_CONFIGURATION.md
15_ERROR_HANDLING.md
16_STREAMING.md
17_EXTENSIONS.md
18_MCP_INTEGRATION.md
19_BEST_PRACTICES.md
20_ARCHITECTURE_PATTERNS.md

Location: src/agents/agent.py

Key Attributes:

  • name: str - The name of the agent. This is used for identification, tracing, and when the agent is exposed as a tool or handoff.

  • handoff_description: str | None - A human-readable description of what this agent does. This is crucial when the agent is used in handoffs, as it helps other agents understand when and why they should delegate to this agent.

  • tools: list[Tool] - A list of tools that this agent can use. Tools are functions or capabilities the agent can call to perform actions (e.g., web search, file operations, API calls).

  • mcp_servers: list[MCPServer] - A list of Model Context Protocol (MCP) servers that provide additional tools. MCP is a standard protocol for exposing tools to AI models.

  • mcp_config: MCPConfig - Configuration for MCP servers, including schema conversion settings and error handling.

Key Methods:

  • async def get_mcp_tools(run_context) - Fetches available tools from MCP servers. This is called each time the agent runs to get the current set of available tools.

  • async def get_all_tools(run_context) - Returns all tools available to the agent, combining both direct tools and MCP tools. It also filters out disabled tools and checks for tool name collisions.

Agent

Agent is the main agent class you'll use most often. It extends AgentBase and adds agent-specific configuration.

Location: src/agents/agent.py

Key Attributes:

  • instructions: str | Callable | None - The system prompt for the agent. This is the most important attribute as it defines the agent's behavior, personality, and capabilities. It can be:

    • A static string
    • A function that dynamically generates instructions based on context
    • None (no specific instructions)
  • prompt: Prompt | DynamicPromptFunction | None - A more advanced way to configure prompts using OpenAI's Prompt API. This allows dynamic configuration of instructions, tools, and other settings outside of your code. Only usable with OpenAI models using the Responses API.

  • handoffs: list[Agent | Handoff] - A list of sub-agents or handoff configurations that this agent can delegate to. This enables multi-agent workflows where specialized agents handle specific tasks.

  • model: str | Model | None - The model to use for this agent. If not specified, it uses the default model (currently "gpt-4.1"). You can specify:

    • A string model name (e.g., "gpt-4o")
    • A custom Model instance
  • model_settings: ModelSettings - Model-specific tuning parameters like temperature, top_p, max tokens, etc. These control the randomness and creativity of the model's responses.

  • input_guardrails: list[InputGuardrail] - Guardrails that run before the agent processes input. These are safety checks that can validate, filter, or reject input before it reaches the LLM.

  • output_guardrails: list[OutputGuardrail] - Guardrails that run after the agent produces output. These validate the final output to ensure it meets safety or quality standards.

  • output_type: type | AgentOutputSchemaBase | None - The expected type of the output. If not specified, output is a string. You can specify:

    • A Python type (dataclass, Pydantic model, TypedDict, etc.)
    • A custom AgentOutputSchemaBase for custom JSON schemas
    • AgentOutputSchema with strict_json_schema=False for non-strict schemas
  • hooks: AgentHooks | None - A class that receives callbacks on various lifecycle events for this specific agent. This allows you to hook into the agent's execution to add custom logic.

  • tool_use_behavior: Literal | StopAtTools | Callable - Controls how tool use is handled:

    • "run_llm_again" (default) - Tools are executed, then the LLM receives the results and can respond again
    • "stop_on_first_tool" - The first tool's output is treated as the final result
    • StopAtTools dict - Stop if specific tools are called
    • Custom function - Fine-grained control over tool-to-output logic
  • reset_tool_choice: bool - Whether to reset tool choice after a tool call. Defaults to True to prevent infinite loops of tool usage.

Agent Lifecycle

1. Creation
Python
from agents import Agent

agent = Agent(
    name="research_agent",
    instructions="You are a research assistant that helps find and summarize information.",
    tools=[web_search_tool],
)

When you create an agent, the __post_init__ method runs extensive validation:

  • Checks that name is a string
  • Validates that tools is a list
  • Ensures instructions is either a string, callable, or None
  • Validates model settings compatibility
  • Checks that guardrails are lists
  • Validates output_type is a type or schema
  • Ensures hooks is an AgentHooks instance if provided
  • Validates tool_use_behavior is a valid option

This validation happens at creation time, so you get immediate feedback if configuration is invalid.

2. Execution

When you run an agent via Runner.run(), the following happens:

  1. Context Setup - A RunContextWrapper is created with your user context
  2. Tool Resolution - All available tools (direct + MCP) are gathered
  3. Input Preparation - Input is converted to the format expected by the model
  4. Guardrail Check - Input guardrails run (if configured)
  5. Model Call - The LLM is called with instructions, input, and tools
  6. Tool Execution - If the model calls tools, they're executed
  7. Response Processing - Model output is processed
  8. Output Guardrail Check - Output guardrails run (if configured)
  9. Result Return - Final output is returned
3. Cloning

Agents support shallow cloning via the clone() method:

Python
new_agent = agent.clone(instructions="New instructions")

This uses dataclasses.replace() which:

  • Creates a new agent instance
  • Copies all attributes
  • Allows overriding specific attributes
  • Performs a shallow copy - mutable objects like tools and handoffs lists are shared unless explicitly overridden

This is useful for creating variations of an agent without duplicating all configuration.

Agents as Tools

One of the most powerful features is that agents can be exposed as tools to other agents. This is done via the as_tool() method:

Python
research_agent = Agent(name="researcher", instructions="Research information...")
main_agent = Agent(
    name="coordinator",
    instructions="Coordinate tasks",
    tools=[research_agent.as_tool("research_tool", "Research a topic")]
)

How it works:

  1. The agent is wrapped in a FunctionTool
  2. When called, it runs the nested agent with the provided input
  3. The nested agent's output is returned to the calling agent
  4. The nested agent runs in isolation - it doesn't automatically see the parent conversation history
  5. You can configure input parameters, streaming, approval requirements, and more

Key Parameters of as_tool():

  • tool_name - Name of the tool (defaults to agent name transformed to function style)
  • tool_description - Description for the LLM to understand when to use it
  • custom_output_extractor - Function to extract output from the nested agent
  • is_enabled - Whether the tool is enabled (can be dynamic)
  • on_stream - Callback to receive streaming events from nested runs
  • run_config - Run configuration for the nested agent
  • max_turns - Maximum turns for the nested agent
  • needs_approval - Whether the tool requires approval
  • parameters - Structured input type for the tool
  • input_builder - Function to build nested agent input from structured data

Difference from Handoffs:

  • Handoffs - The new agent receives the full conversation history and takes over the conversation
  • Agent as Tool - The new agent receives generated input and returns as a tool result, conversation continues with original agent

Context and Generics

Agents are generic over a context type:

Python
class Agent(Generic[TContext]):
    ...

What is Context?

Context is a mutable object you create that is passed to:

  • Tool functions
  • Handoff functions
  • Guardrail functions
  • Lifecycle hooks

Why use Context?

Context allows you to:

  • Share state across tool calls
  • Track information during agent execution
  • Pass configuration to tools
  • Maintain application-specific state

Example:

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

@dataclass
class MyContext:
    user_id: str
    session_data: dict

@function_tool
def get_user_preferences(context: RunContextWrapper[MyContext]) -> str:
    user_id = context.context.user_id
    # Fetch preferences for this user
    return f"Preferences for {user_id}"

agent = Agent(
    name="personalized_agent",
    instructions="Help the user with personalized recommendations",
    tools=[get_user_preferences],
)

# When running
context = MyContext(user_id="123", session_data={})
result = await Runner.run(agent, "What do you recommend?", context=context)

Dynamic Instructions

Instructions can be dynamic functions that generate the system prompt based on context:

Python
def dynamic_instructions(
    context: RunContextWrapper[MyContext],
    agent: Agent[MyContext],
) -> str:
    user_id = context.context.user_id
    return f"You are helping user {user_id}. Be friendly and helpful."

agent = Agent(
    name="dynamic_agent",
    instructions=dynamic_instructions,
)

This is useful when:

  • You need to personalize instructions based on user data
  • Instructions depend on runtime state
  • You want to A/B test different instructions
  • Instructions need to be generated from external sources

Tool Management

Adding Tools
Python
from agents import Agent, function_tool

@function_tool
def calculate_sum(a: int, b: int) -> int:
    return a + b

agent = Agent(
    name="math_agent",
    instructions="Help with math calculations",
    tools=[calculate_sum],
)
Tool Namespaces

Tools can be organized into namespaces to avoid name collisions:

Python
from agents import Agent, function_tool, tool_namespace

@tool_namespace("math")
@function_tool
def add(a: int, b: int) -> int:
    return a + b

@tool_namespace("math")
@function_tool
def multiply(a: int, b: int) -> int:
    return a * b

agent = Agent(
    name="calculator",
    instructions="Use math tools for calculations",
    tools=[add, multiply],
)
Dynamic Tool Enablement

Tools can be dynamically enabled or disabled:

Python
@function_tool
def admin_function(context: RunContextWrapper[MyContext]) -> str:
    return "Admin-only function"

agent = Agent(
    name="admin_agent",
    instructions="Admin functions",
    tools=[admin_function],
)

# The tool can have an is_enabled function
admin_function.is_enabled = lambda ctx, agent: ctx.context.user_id == "admin"

Handoff Management

Adding Handoffs
Python
from agents import Agent, handoff

specialist_agent = Agent(
    name="specialist",
    instructions="Handle specialized tasks",
    handoff_description="Specialist for complex technical issues",
)

general_agent = Agent(
    name="general",
    instructions="Handle general queries",
    handoffs=[handoff(specialist_agent)],
)
Handoff Input Filters

You can filter what information is passed to the next agent:

Python
def filter_handoff_input(data: HandoffInputData) -> HandoffInputData:
    # Only pass the last 5 items
    return data.clone(new_items=data.new_items[-5:])

agent = Agent(
    name="general",
    instructions="General agent",
    handoffs=[handoff(specialist_agent, input_filter=filter_handoff_input)],
)

Guardrails

Input Guardrails
Python
from agents import Agent, input_guardrail, InputGuardrailFunctionOutput

@input_guardrail
def check_off_topic(
    context: RunContextWrapper,
    agent: Agent,
    input: str | list[TResponseInputItem],
) -> InputGuardrailFunctionOutput:
    if isinstance(input, str) and "politics" in input.lower():
        return InputGuardrailFunctionOutput(
            output_info="Political content detected",
            tripwire_triggered=True,
        )
    return InputGuardrailFunctionOutput(
        output_info="Input is appropriate",
        tripwire_triggered=False,
    )

agent = Agent(
    name="safe_agent",
    instructions="Help with safe topics",
    input_guardrails=[check_off_topic],
)
Output Guardrails
Python
from agents import Agent, output_guardrail

@output_guardrail
def check_output_length(
    context: RunContextWrapper,
    agent: Agent,
    agent_output: str,
) -> InputGuardrailFunctionOutput:
    if len(agent_output) > 1000:
        return InputGuardrailFunctionOutput(
            output_info="Output too long",
            tripwire_triggered=True,
        )
    return InputGuardrailFunctionOutput(
        output_info="Output length acceptable",
        tripwire_triggered=False,
    )

agent = Agent(
    name="concise_agent",
    instructions="Be concise",
    output_guardrails=[check_output_length],
)

Model Configuration

Model Selection
Python
from agents import Agent

agent = Agent(
    name="fast_agent",
    instructions="Quick responses",
    model="gpt-4o-mini",  # Faster, cheaper model
)
Model Settings
Python
from agents import Agent, ModelSettings

agent = Agent(
    name="creative_agent",
    instructions="Be creative",
    model_settings=ModelSettings(
        temperature=0.9,  # More creative
        top_p=0.9,
    ),
)
GPT-5 Special Handling

The SDK has special handling for GPT-5 models, which require specific reasoning settings. If you specify a non-GPT-5 model but the default is GPT-5, the SDK automatically adjusts the model settings to be compatible.

Output Types

String Output (Default)
Python
agent = Agent(
    name="simple_agent",
    instructions="Simple responses",
)
# Output is a string
Structured Output
Python
from dataclasses import dataclass
from agents import Agent

@dataclass
class Summary:
    title: str
    points: list[str]

agent = Agent(
    name="summarizer",
    instructions="Summarize the input",
    output_type=Summary,
)
# Output is a Summary dataclass instance
Custom Schema
Python
from agents import Agent, AgentOutputSchema

custom_schema = AgentOutputSchema(
    type="object",
    properties={
        "answer": {"type": "string"},
        "confidence": {"type": "number"},
    },
)

agent = Agent(
    name="structured_agent",
    instructions="Provide structured answers",
    output_type=custom_schema,
)

Tool Use Behavior

Default Behavior (run_llm_again)
Python
agent = Agent(
    name="default_agent",
    instructions="Use tools as needed",
    tool_use_behavior="run_llm_again",  # Default
)
# Tools run, LLM sees results, can respond again
Stop on First Tool
Python
agent = Agent(
    name="direct_agent",
    instructions="Call one tool and return",
    tool_use_behavior="stop_on_first_tool",
)
# First tool output is the final result
Stop at Specific Tools
Python
from agents import Agent, StopAtTools

agent = Agent(
    name="controlled_agent",
    instructions="Use tools carefully",
    tool_use_behavior=StopAtTools(stop_at_tool_names=["sensitive_tool"]),
)
# Stops if sensitive_tool is called
Custom Behavior
Python
from agents import Agent, ToolsToFinalOutputResult, ToolsToFinalOutputFunction

async def custom_tool_handler(
    context: RunContextWrapper,
    tool_results: list[FunctionToolResult],
) -> ToolsToFinalOutputResult:
    # Custom logic to decide if tool results are final
    if any("error" in r.output for r in tool_results):
        return ToolsToFinalOutputResult(is_final_output=True, final_output="Error occurred")
    return ToolsToFinalOutputResult(is_final_output=False)

agent = Agent(
    name="custom_agent",
    instructions="Custom tool handling",
    tool_use_behavior=custom_tool_handler,
)

Lifecycle Hooks

Agent-level hooks allow you to react to events specific to this agent:

Python
from agents import Agent, AgentHooks

class MyAgentHooks(AgentHooks):
    async def on_start(self, context, agent):
        print(f"Agent {agent.name} starting")
    
    async def on_end(self, context, agent, output):
        print(f"Agent {agent.name} finished with output: {output}")

agent = Agent(
    name="hooked_agent",
    instructions="Agent with hooks",
    hooks=MyAgentHooks(),
)

Best Practices

1. Clear Instructions

Write clear, specific instructions:

Python
# Good
agent = Agent(
    name="summarizer",
    instructions="You are a summarization expert. Given a text, provide a concise summary in 3 bullet points. Each bullet should be under 20 words.",
)

# Avoid
agent = Agent(
    name="vague_agent",
    instructions="Summarize things",  # Too vague
)
2. Tool Naming

Use descriptive tool names:

Python
# Good
@function_tool
def search_web_for_academic_papers(query: str) -> str:
    ...

# Avoid
@function_tool
def search(q: str) -> str:  # Not descriptive enough
    ...
3. Handoff Descriptions

Write clear handoff descriptions:

Python
specialist = Agent(
    name="technical_support",
    instructions="Handle technical issues",
    handoff_description="Technical support specialist for debugging and troubleshooting",
)
4. Context Design

Keep context focused:

Python
# Good - focused context
@dataclass
class UserContext:
    user_id: str
    preferences: dict

# Avoid - bloated context
@dataclass
class EverythingContext:
    user_id: str
    preferences: dict
    session_history: list
    cache: dict
    database_connection: Any  # Don't put heavy resources here
5. Guardrail Granularity

Use guardrails at appropriate levels:

Python
# Input guardrail - for filtering what comes in
@input_guardrail
def check_safety(input: str) -> GuardrailFunctionOutput:
    ...

# Output guardrail - for validating what goes out
@output_guardrail
def check_quality(output: str) -> GuardrailFunctionOutput:
    ...

# Tool guardrail - for specific tool validation
@tool_input_guardrail
def check_tool_args(args: dict) -> ToolGuardrailFunctionOutput:
    ...

Common Patterns

1. Specialist Pattern

Create specialized agents for specific tasks:

Python
coder = Agent(name="coder", instructions="Write code", ...)
researcher = Agent(name="researcher", instructions="Research topics", ...)
writer = Agent(name="writer", instructions="Write content", ...)

coordinator = Agent(
    name="coordinator",
    instructions="Coordinate tasks",
    handoffs=[handoff(coder), handoff(researcher), handoff(writer)],
)
2. Supervisor Pattern

One agent supervises others:

Python
supervisor = Agent(
    name="supervisor",
    instructions="Review work and delegate to appropriate specialists",
    handoffs=[...],
)
3. Sequential Pattern

Chain agents in sequence:

Python
# First agent processes input
agent1 = Agent(name="step1", instructions="Process step 1")

# Second agent takes output of first
agent2 = Agent(name="step2", instructions="Process step 2")

result1 = await Runner.run(agent1, input)
result2 = await Runner.run(agent2, result1.final_output)
4. Parallel Pattern

Run multiple agents in parallel:

Python
import asyncio

agent1 = Agent(name="parallel1", instructions="Task 1")
agent2 = Agent(name="parallel2", instructions="Task 2")

results = await asyncio.gather(
    Runner.run(agent1, input),
    Runner.run(agent2, input),
)

Error Handling in Agents

Model Behavior Errors

When the model behaves unexpectedly:

Python
from agents import Agent, ModelBehaviorError

try:
    result = await Runner.run(agent, input)
except ModelBehaviorError as e:
    print(f"Model behaved unexpectedly: {e}")
Tool Errors

Tools can raise errors:

Python
@function_tool
def risky_operation() -> str:
    raise ValueError("Something went wrong")

# The SDK will catch this and format an error message for the model
Guardrail Tripwires

When guardrails trigger:

Python
from agents import InputGuardrailTripwireTriggered, OutputGuardrailTripwireTriggered

try:
    result = await Runner.run(agent, input)
except InputGuardrailTripwireTriggered as e:
    print(f"Input guardrail triggered: {e}")
except OutputGuardrailTripwireTriggered as e:
    print(f"Output guardrail triggered: {e}")

Advanced Topics

Agent Cloning for Variations
Python
base_agent = Agent(
    name="base",
    instructions="Base instructions",
    tools=[base_tool],
)

# Create variations
agent_a = base_agent.clone(name="agent_a", instructions="A-specific instructions")
agent_b = base_agent.clone(name="agent_b", instructions="B-specific instructions")
Dynamic Tool Addition
Python
agent = Agent(name="dynamic", instructions="Dynamic tools")

# Add tools later
agent.tools.append(new_tool)
MCP Integration
Python
from agents import Agent, MCPServerStdio

mcp_server = MCPServerStdio(params=MCPServerStdioParams(...))

agent = Agent(
    name="mcp_agent",
    instructions="Use MCP tools",
    mcp_servers=[mcp_server],
)

Summary

The Agent system is the foundation of the SDK. Key takeaways:

  1. Agent is the main class representing an AI assistant
  2. Instructions define the agent's behavior and personality
  3. Tools give agents capabilities to perform actions
  4. Guardrails provide safety and validation
  5. Handoffs enable multi-agent workflows
  6. Context allows state sharing across the agent lifecycle
  7. Agents as tools enables powerful composition patterns
  8. Generics provide type safety for context
  9. Lifecycle hooks allow custom logic at key points
  10. Cloning enables agent variations efficiently

Understanding the Agent system is crucial for building effective multi-agent workflows with the SDK.