InkdownInkdown
Start writing

OpenAI Agents Python

21 filesยท0 subfolders

Shared Workspace

OpenAI Agents Python
00_OVERVIEW.md

12_CONTEXT

Shared from "OpenAI Agents Python" on Inkdown

Context - Comprehensive Deep Dive

Overview

Context in the OpenAI Agents SDK is a powerful mechanism for sharing state across tool calls, guardrails, handoffs, and lifecycle hooks. Think of Context as a "shared workspace" or "carrying case" that travels with the agent run, allowing you to pass custom data, configuration, and state to various components without relying on global variables or complex threading mechanisms.

Core Concepts

What is Context?

Context is a user-defined object that:

  • Carries custom data throughout an agent run
  • Is accessible from tools, guardrails, handoffs, and hooks
  • Is mutable - can be modified during execution
  • Is type-safe - uses generics for compile-time checking
  • Is isolated - each run has its own context instance
Why Context Matters
  1. - Share data across tool calls without globals
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
State Sharing
  • Configuration - Pass configuration to tools and guardrails
  • User Data - Track user-specific information
  • Session Data - Maintain session-specific state
  • Business Logic - Implement custom business rules
  • Type Safety - Ensure data consistency with types
  • Context Basics

    Defining Context

    Context is typically a dataclass:

    Python
    from dataclasses import dataclass
    
    @dataclass
    class MyContext:
        user_id: str
        session_data: dict
        preferences: dict
    Using Context

    Pass context when running an agent:

    Python
    context = MyContext(
        user_id="user-123",
        session_data={},
        preferences={"theme": "dark"},
    )
    
    result = await Runner.run(agent, input, context=context)
    Context in Tools

    Tools can access context:

    Python
    from agents import function_tool, RunContextWrapper
    
    @function_tool
    def get_user_preferences(context: RunContextWrapper[MyContext]) -> str:
        """Get user preferences."""
        preferences = context.context.preferences
        return f"Theme: {preferences.get('theme', 'default')}"

    RunContextWrapper

    RunContextWrapper Class

    RunContextWrapper wraps the context and provides additional functionality:

    Python
    @dataclass
    class RunContextWrapper(Generic[TContext]):
        context: TContext
        """The user-provided context object."""
        
        usage: Usage
        """Token usage tracking."""
        
        approve_tool: Callable[[ToolApprovalItem], None]
        """Function to approve a tool."""
        
        reject_tool: Callable[[ToolApprovalItem], None]
        """Function to reject a tool."""
        
        tool_name: str | None
        """Current tool name (if in tool execution)."""
        
        tool_call_id: str | None
        """Current tool call ID (if in tool execution)."""
        
        tool_arguments: str | None
        """Current tool arguments (if in tool execution)."""
        
        tool_call: ResponseFunctionToolCall | None
        """Current tool call (if in tool execution)."""
        
        run_config: RunConfig
        """Run configuration."""
        
        agent: Agent[TContext]
        """Current agent."""
    Accessing Context
    Python
    @function_tool
    def my_tool(context: RunContextWrapper[MyContext]) -> str:
        """Tool with context."""
        # Access custom context
        user_id = context.context.user_id
        
        # Access usage
        usage = context.usage
        
        # Access run config
        config = context.run_config
        
        # Access agent
        agent = context.agent
        
        return f"User: {user_id}"

    ToolContext

    ToolContext Class

    ToolContext extends RunContextWrapper with tool-specific metadata:

    Python
    @dataclass
    class ToolContext(Generic[TContext]):
        # All RunContextWrapper fields...
        
        tool_namespace: str | None
        """Tool namespace if configured."""
        
        tool_origin: ToolOrigin | None
        """Origin of the tool."""
    Using ToolContext
    Python
    from agents import ToolContext
    
    @function_tool
    def detailed_tool(context: ToolContext[MyContext]) -> str:
        """Tool with detailed context."""
        # Tool-specific metadata
        tool_name = context.tool_name
        tool_call_id = context.tool_call_id
        tool_arguments = context.tool_arguments
        tool_namespace = context.tool_namespace
        
        return f"Tool: {tool_name}, Call: {tool_call_id}"

    Context and Generics

    Generic Context

    Agents are generic over context type:

    Python
    from agents import Agent
    
    # Define context type
    @dataclass
    class MyContext:
        user_id: str
    
    # Agent is generic over MyContext
    agent = Agent[MyContext](
        name="my_agent",
        instructions="...",
    )
    
    # Type checking ensures correct context
    context = MyContext(user_id="123")
    result = await Runner.run(agent, input, context=context)
    Type Safety

    Generics provide compile-time type checking:

    Python
    # Good - correct type
    agent = Agent[MyContext](...)
    context = MyContext(user_id="123")
    result = await Runner.run(agent, input, context=context)
    
    # Error - wrong type (caught by type checker)
    agent = Agent[MyContext](...)
    context = OtherContext()  # Type error!
    result = await Runner.run(agent, input, context=context)
    Context Inference

    Type inference works in many cases:

    Python
    # Type inferred from context
    @function_tool
    def my_tool(context: RunContextWrapper[MyContext]) -> str:
        # MyContext is inferred
        user_id = context.context.user_id
        return user_id

    Context Patterns

    1. User Session Context

    Track user-specific data:

    Python
    @dataclass
    class UserContext:
        user_id: str
        username: str
        preferences: dict
        session_start: datetime
    
    @function_tool
    def get_user_info(context: RunContextWrapper[UserContext]) -> str:
        """Get user information."""
        return f"User: {context.context.username}"
    
    context = UserContext(
        user_id="123",
        username="john",
        preferences={"theme": "dark"},
        session_start=datetime.now(),
    )
    2. Database Context

    Pass database connection:

    Python
    @dataclass
    class DatabaseContext:
        connection: Any
        transaction_id: str
    
    @function_tool
    def query_database(context: RunContextWrapper[DatabaseContext]) -> str:
        """Query database."""
        result = context.context.connection.execute("SELECT * FROM users")
        return str(result)
    
    context = DatabaseContext(
        connection=db_connection,
        transaction_id="txn-123",
    )
    3. API Client Context

    Pass API clients:

    Python
    @dataclass
    class APIContext:
        github_client: GitHubClient
        slack_client: SlackClient
    
    @function_tool
    def create_github_issue(context: RunContextWrapper[APIContext]) -> str:
        """Create GitHub issue."""
        issue = context.context.github_client.create_issue(...)
        return f"Issue {issue.id} created"
    4. Configuration Context

    Pass configuration:

    Python
    @dataclass
    class ConfigContext:
        api_keys: dict
        feature_flags: dict
        environment: str
    
    @function_tool
    def check_feature(context: RunContextWrapper[ConfigContext]) -> str:
        """Check if feature is enabled."""
        enabled = context.context.feature_flags.get("new_feature", False)
        return "enabled" if enabled else "disabled"
    5. Accumulator Context

    Accumulate results across tool calls:

    Python
    @dataclass
    class AccumulatorContext:
        results: list[str]
        metadata: dict
    
    @function_tool
    def add_result(context: RunContextWrapper[AccumulatorContext], result: str) -> str:
        """Add a result to accumulator."""
        context.context.results.append(result)
        return f"Added: {result}"

    Context Mutability

    Modifying Context

    Context is mutable:

    Python
    @dataclass
    class MutableContext:
        counter: int
        history: list[str]
    
    @function_tool
    def increment_counter(context: RunContextWrapper[MutableContext]) -> str:
        """Increment counter."""
        context.context.counter += 1
        return f"Counter: {context.context.counter}"
    
    @function_tool
    def add_to_history(context: RunContextWrapper[MutableContext], item: str) -> str:
        """Add to history."""
        context.context.history.append(item)
        return f"History: {context.context.history}"
    Thread Safety Considerations

    Context is not thread-safe by default:

    Python
    # Each run has its own context instance
    # So no cross-run interference
    
    # But if you share mutable objects across runs:
    shared_data = {}  # Not thread-safe!
    
    @dataclass
    class SharedContext:
        data: dict  # This is shared!
    
    # Use with caution or use thread-safe structures

    Context and Guardrails

    Input Guardrails with Context
    Python
    from agents import input_guardrail
    
    @input_guardrail
    def check_user_limits(
        context: RunContextWrapper[UserContext],
        agent: Agent,
        input: str | list[TResponseInputItem],
    ) -> InputGuardrailFunctionOutput:
        """Check if user has reached limits."""
        user_id = context.context.user_id
        limits = get_user_limits(user_id)
        
        if limits["remaining"] <= 0:
            return InputGuardrailFunctionOutput(
                output_info="User has reached limits",
                tripwire_triggered=True,
            )
        
        return InputGuardrailFunctionOutput(
            output_info="User has capacity",
            tripwire_triggered=False,
        )
    Output Guardrails with Context
    Python
    @output_guardrail
    def check_output_size(
        context: RunContextWrapper[UserContext],
        agent: Agent,
        agent_output: str,
    ) -> OutputGuardrailFunctionOutput:
        """Check output size against user limits."""
        max_size = context.context.preferences.get("max_output_size", 1000)
        
        if len(agent_output) > max_size:
            return OutputGuardrailFunctionOutput(
                output_info=f"Output too large (max {max_size})",
                tripwire_triggered=True,
            )
        
        return OutputGuardrailFunctionOutput(
            output_info="Output size acceptable",
            tripwire_triggered=False,
        )

    Context and Handoffs

    Handoff Functions with Context
    Python
    def on_handoff(
        context: RunContextWrapper[UserContext],
        input: HandoffInputData,
    ) -> None:
        """Called during handoff."""
        user_id = context.context.user_id
        print(f"Handing off for user: {user_id}")
    
    handoff(
        specialist,
        on_handoff=on_handoff,
    )
    Handoff Input Filter with Context
    Python
    def filter_handoff_input(
        data: HandoffInputData,
    ) -> HandoffInputData:
        """Filter handoff input."""
        # Access context via run_context
        user_id = data.run_context.context.user_id
        
        # Filter based on user
        if user_id == "special":
            return data  # Full context
        else:
            return data.clone(new_items=data.new_items[-5:])  # Limited context

    Context and Lifecycle Hooks

    Run Hooks with Context
    Python
    from agents import RunHooks
    
    class MyRunHooks(RunHooks[UserContext]):
        async def on_agent_start(
            self,
            context: AgentHookContext[UserContext],
            agent: Agent,
        ) -> None:
            """Called when agent starts."""
            user_id = context.context.user_id
            print(f"Agent {agent.name} starting for user {user_id}")
        
        async def on_agent_end(
            self,
            context: AgentHookContext[UserContext],
            agent: Agent,
            output: Any,
        ) -> None:
            """Called when agent ends."""
            user_id = context.context.user_id
            print(f"Agent {agent.name} ended for user {user_id}")
    
    hooks = MyRunHooks()
    result = await Runner.run(agent, input, hooks=hooks)
    Agent Hooks with Context
    Python
    from agents import AgentHooks
    
    class MyAgentHooks(AgentHooks[UserContext]):
        async def on_start(
            self,
            context: AgentHookContext[UserContext],
            agent: Agent,
        ) -> None:
            """Called when this agent starts."""
            user_id = context.context.user_id
            print(f"My agent starting for user {user_id}")
    
    agent = Agent(
        name="my_agent",
        instructions="...",
        hooks=MyAgentHooks(),
    )

    Context and Tool Approvals

    Approval Decisions with Context
    Python
    @function_tool(needs_approval=True)
    def sensitive_operation(context: RunContextWrapper[UserContext]) -> str:
        """Sensitive operation requiring approval."""
        user_id = context.context.user_id
        
        # Auto-approve for admin users
        if context.context.preferences.get("role") == "admin":
            context.approve_tool(...)  # Auto-approve
        
        return "Operation performed"
    Custom Approval Logic
    Python
    def should_approve(
        context: RunContextWrapper[UserContext],
        tool_call: ToolApprovalItem,
    ) -> bool:
        """Custom approval logic."""
        user_id = context.context.user_id
        tool_name = tool_call.tool_name
        
        # Check user permissions
        permissions = get_user_permissions(user_id)
        return tool_name in permissions.get("allowed_tools", [])

    Context and Usage Tracking

    Accessing Usage
    Python
    @function_tool
    def check_usage(context: RunContextWrapper[UserContext]) -> str:
        """Check token usage."""
        usage = context.usage
        return f"Tokens used: {usage.total_tokens}"
    Context-Based Usage Limits
    Python
    @input_guardrail
    def check_usage_limits(
        context: RunContextWrapper[UserContext],
        agent: Agent,
        input: str | list[TResponseInputItem],
    ) -> InputGuardrailFunctionOutput:
        """Check if user has exceeded usage limits."""
        user_id = context.context.user_id
        current_usage = context.usage.total_tokens
        max_usage = get_user_max_usage(user_id)
        
        if current_usage > max_usage:
            return InputGuardrailFunctionOutput(
                output_info=f"Usage limit exceeded ({current_usage}/{max_usage})",
                tripwire_triggered=True,
            )
        
        return InputGuardrailFunctionOutput(
            output_info="Usage within limits",
            tripwire_triggered=False,
        )

    Context and RunConfig

    Accessing RunConfig
    Python
    @function_tool
    def get_config(context: RunContextWrapper[UserContext]) -> str:
        """Get run configuration."""
        config = context.run_config
        return f"Workflow: {config.workflow_name}"
    Context-Based Configuration
    Python
    def get_model_for_user(context: RunContextWrapper[UserContext]) -> str:
        """Select model based on user tier."""
        tier = context.context.preferences.get("tier", "free")
        
        if tier == "premium":
            return "gpt-4o"
        elif tier == "enterprise":
            return "gpt-4-turbo"
        return "gpt-4o-mini"

    Context and RunState

    Context in RunState

    RunState includes context:

    Python
    result = await Runner.run(agent, input, context=my_context)
    state = result.to_state()
    
    # Access context
    saved_context = state.context
    Context Serialization

    Context requires serializers for RunState:

    Python
    def serialize_context(context: MyContext) -> str:
        """Serialize context."""
        return json.dumps({
            "user_id": context.user_id,
            "session_data": context.session_data,
            "preferences": context.preferences,
        })
    
    def deserialize_context(data: str) -> MyContext:
        """Deserialize context."""
        parsed = json.loads(data)
        return MyContext(**parsed)
    
    result = await Runner.run(
        agent,
        input,
        context=my_context,
        context_serializer=serialize_context,
        context_deserializer=deserialize_context,
    )

    Context Best Practices

    1. Use Dataclasses

    Use dataclasses for context:

    Python
    # Good
    @dataclass
    class MyContext:
        user_id: str
        data: dict
    
    # Avoid
    class MyContext:
        def __init__(self, user_id, data):
            self.user_id = user_id
            self.data = data
    2. Keep Context Focused

    Keep context focused on relevant data:

    Python
    # Good - focused context
    @dataclass
    class UserContext:
        user_id: str
        preferences: dict
    
    # Avoid - bloated context
    @dataclass
    class EverythingContext:
        user_id: str
        preferences: dict
        database_connection: Any  # Don't put heavy resources here
        cache: dict
        logger: Any
    3. Use Type Hints

    Always use type hints:

    Python
    # Good
    @dataclass
    class MyContext:
        user_id: str
        data: dict[str, Any]
    
    # Avoid
    @dataclass
    class MyContext:
        user_id  # No type hint
        data
    4. Document Context Fields

    Document context fields:

    Python
    # Good
    @dataclass
    class MyContext:
        """Context for user operations."""
        
        user_id: str
        """Unique identifier for the user."""
        
        preferences: dict
        """User preferences (theme, language, etc.)."""
    
    # Avoid
    @dataclass
    class MyContext:
        user_id: str  # What is this?
        preferences: dict  # What keys?
    5. Avoid Circular Dependencies

    Avoid circular references in context:

    Python
    # Good - no circular references
    @dataclass
    class ContextA:
        data: str
    
    @dataclass
    class ContextB:
        context_a: ContextA
    
    # Avoid - circular reference
    @dataclass
    class ContextA:
        context_b: ContextB
    
    @dataclass
    class ContextB:
        context_a: ContextA

    Common Context Patterns

    1. Request Context

    Track request-specific data:

    Python
    @dataclass
    class RequestContext:
        request_id: str
        timestamp: datetime
        user_agent: str
        ip_address: str
    
    @function_tool
    def log_request(context: RunContextWrapper[RequestContext]) -> str:
        """Log request information."""
        return f"Request {context.context.request_id} from {context.context.ip_address}"
    2. Multi-Tenant Context

    Handle multi-tenant applications:

    Python
    @dataclass
    class TenantContext:
        tenant_id: str
        tenant_config: dict
        user_id: str
    
    @function_tool
    def get_tenant_config(context: RunContextWrapper[TenantContext]) -> str:
        """Get tenant configuration."""
        config = context.context.tenant_config
        return f"Tenant: {context.context.tenant_id}, Config: {config}"
    3. Audit Context

    Track audit information:

    Python
    @dataclass
    class AuditContext:
        audit_log: list[dict]
        audit_enabled: bool
    
    @function_tool
    def audit_action(context: RunContextWrapper[AuditContext], action: str) -> str:
        """Audit an action."""
        if context.context.audit_enabled:
            context.context.audit_log.append({
                "action": action,
                "timestamp": datetime.now().isoformat(),
            })
        return "Action logged"
    4. Cache Context

    Implement caching:

    Python
    @dataclass
    class CacheContext:
        cache: dict
        cache_ttl: int
    
    @function_tool
    def cached_lookup(context: RunContextWrapper[CacheContext], key: str) -> str:
        """Lookup with caching."""
        if key in context.context.cache:
            return f"Cached: {context.context.cache[key]}"
        
        value = expensive_lookup(key)
        context.context.cache[key] = value
        return f"Fetched: {value}"
    5. State Machine Context

    Implement state machine:

    Python
    @dataclass
    class StateMachineContext:
        current_state: str
        state_history: list[str]
        transitions: dict[str, list[str]]
    
    @function_tool
    def transition_state(
        context: RunContextWrapper[StateMachineContext],
        new_state: str,
    ) -> str:
        """Transition to new state."""
        current = context.context.current_state
        
        if new_state in context.context.transitions.get(current, []):
            context.context.state_history.append(current)
            context.context.current_state = new_state
            return f"Transitioned: {current} -> {new_state}"
        
        return f"Invalid transition: {current} -> {new_state}"

    Context and Testing

    Mock Context for Testing
    Python
    # Test with mock context
    @dataclass
    class TestContext:
        mock_data: str
    
    test_context = TestContext(mock_data="test")
    
    result = await Runner.run(
        agent,
        input,
        context=test_context,
    )
    Context Fixtures

    Use fixtures for common contexts:

    Python
    @pytest.fixture
    def user_context():
        """Fixture for user context."""
        return UserContext(
            user_id="test-user",
            preferences={"theme": "light"},
            session_data={},
        )
    
    def test_with_context(user_context):
        """Test with context fixture."""
        result = await Runner.run(agent, input, context=user_context)
        assert result.final_output is not None

    Summary

    Context enables state sharing across agent runs. Key takeaways:

    1. Context is a user-defined object for state sharing
    2. RunContextWrapper wraps context with additional functionality
    3. ToolContext extends RunContextWrapper with tool metadata
    4. Generics provide type safety
    5. Dataclasses are the recommended context type
    6. Mutability allows context modification during runs
    7. Tools can access context via RunContextWrapper
    8. Guardrails can use context for validation
    9. Handoffs can access context during delegation
    10. Lifecycle hooks receive context in callbacks
    11. Approvals can use context for decisions
    12. Usage tracking is available in context
    13. RunConfig is accessible from context
    14. RunState includes context for serialization
    15. Serialization requires custom serializers for complex context
    16. Type hints ensure type safety
    17. Documentation helps understand context fields
    18. Focused context avoids bloat
    19. Testing with mock contexts
    20. Patterns exist for common use cases

    Context is essential for building stateful, configurable, and type-safe agent workflows.