카테고리 없음

OPEN AI SDK - Customer support agent(3) - handoff

hyunsoo CHUNG 2026. 1. 1. 15:00

1. Handoff Data Model Definition

class HandoffData(BaseModel):
    to_agent_name: str
    issue_type: str
    issue_description: str
    reason: str
  • HandoffData: Pydantic model containing handoff information
  • Fields:
    • to_agent_name: Name of the target agent to hand off to
    • issue_type: Type of issue
    • issue_description: Description of the issue
    • reason: Reason for the handoff

2. Handoff Handler Function

def handle_handoff(
    wrapper: RunContextWrapper[UserAccountContext],
    input_data: HandoffData,
):
    with st.sidebar:
        st.write(
            f"""
            Handing off to {input_data.to_agent_name}
            Reason: {input_data.reason}
            Issue Type: {input_data.issue_type}
            Description: {input_data.issue_description}
        """
        )
  • handle_handoff: Callback function called when a handoff occurs
  • Parameters:
    • wrapper: Execution context wrapper (access UserAccountContext via wrapper.context)
    • input_data: HandoffData instance
  • Behavior: Displays handoff information in the sidebar

3. Handoff Factory Function

def make_handoff(agent):
    return handoff(
        agent=agent,
        on_handoff=handle_handoff,
        input_type=HandoffData, 
        input_filter=handoff_filters.remove_all_tools,
    )
  • make_handoff: Factory function that creates a Handoff object
  • Parameter:
    • agent: Target agent to hand off to
  • Returns: Handoff object created by handoff()
  • Configuration:
    • agent: Target agent
    • on_handoff: Callback when handoff occurs (handle_handoff)
    • input_type: Handoff data type (HandoffData)
    • input_filter: Input filter (handoff_filters.remove_all_tools to remove tools)

4. Role of input_type

        input_type=HandoffData, 
  • input_type=HandoffData: Triggers handoff when LLM output matches HandoffData format
  • Behavior:
    1. Runner parses LLM output
    2. Checks if it matches HandoffData structure
    3. If it matches, executes handoff

5. Role of RECOMMENDED_PROMPT_PREFIX

    return f"""
    {RECOMMENDED_PROMPT_PREFIX}
  • RECOMMENDED_PROMPT_PREFIX: Prompt prefix that guides the LLM on how to perform handoffs
  • Content:
    • How to output in HandoffData format when a handoff is needed
    • How to fill to_agent_name, issue_type, issue_description, reason
    • When to perform a handoff

6. Registering Handoff with Main Agent

triage_agent = Agent(
    name="Triage Agent",
    instructions=dynamic_triage_agent_instructions,
    input_guardrails=[
        off_topic_guardrail,
    ],
    handoffs=[
        make_handoff(technical_agent),
        make_handoff(billing_agent),
        make_handoff(account_agent),
        make_handoff(order_agent),
    ],
)
  • handoffs: List to register multiple handoffs
  • Registered agents:
    • technical_agent: Technical support
    • billing_agent: Billing/payment
    • account_agent: Account management
    • order_agent: Order management

7. Target Agent Example

def dynamic_order_agent_instructions(
    wrapper: RunContextWrapper[UserAccountContext],
    agent: Agent[UserAccountContext],
):
    return f"""
    You are an Order Management specialist helping {wrapper.context.name}.
    Customer tier: {wrapper.context.tier} {"(Premium Shipping)" if wrapper.context.tier != "basic" else ""}
    
    YOUR ROLE: Handle order status, shipping, returns, and delivery issues.
    
    ORDER MANAGEMENT PROCESS:
    1. Look up order details by order number
    2. Provide current status and tracking information
    3. Resolve shipping or delivery issues
    4. Process returns and exchanges
    5. Update shipping preferences if needed
    
    ORDER INFORMATION TO PROVIDE:
    - Current order status (processing, shipped, delivered)
    - Tracking numbers and carrier information
    - Expected delivery dates
    - Return/exchange options and policies
    
    RETURN POLICY:
    - 30-day return window for most items
    - Free returns for premium customers
    - Exchange options available
    - Refund processing time: 3-5 business days
    
    {"PREMIUM PERKS: Free expedited shipping and returns, priority processing." if wrapper.context.tier != "basic" else ""}
    """

order_agent = Agent(
    name="Order Management Agent",
    instructions=dynamic_order_agent_instructions,
)
  • Target agents also use Context
  • Generate personalized responses using wrapper.context.name, wrapper.context.tier, etc.

8. Complete Execution Flow (Detailed)

[Step 1] User Input
   User: "Check my order status"
   ↓
[Step 2] Call Runner.run_streamed()
   Runner.run_streamed(
       triage_agent,
       "Check my order status",
       session=session,
       context=user_account_ctx
   )
   ↓
[Step 3] Input Guardrail Validation
   off_topic_guardrail() executes
   → Not off-topic (passes)
   ↓
[Step 4] Triage Agent Execution Starts
   dynamic_triage_agent_instructions() called
   → Includes {RECOMMENDED_PROMPT_PREFIX}
   → Uses wrapper.context.name, tier
   → "The customer's name is nico."
   → "The customer's tier is basic."
   ↓
[Step 5] Triage Agent's LLM Execution
   LLM processes conversation
   → Analyzes issue: "Order-related inquiry"
   → Classifies: "ORDER MANAGEMENT"
   ↓
[Step 6A] Normal Response Case
   LLM responds with normal text
   → "I'll help you with that..."
   → input_type doesn't match
   → Handoff not triggered
   → Conversation continues
   ↓
[Step 6B] HandoffData Format Case
   LLM responds in HandoffData format:
   {
     "to_agent_name": "Order Management Agent",
     "issue_type": "ORDER MANAGEMENT",
     "issue_description": "Order status check request",
     "reason": "Route to order-related specialist agent"
   }
   ↓
[Step 7] Runner Detects HandoffData Format
   Runner parses LLM output
   → Detects HandoffData format
   → Checks triage_agent.handoffs list
   → Finds handoff with input_type=HandoffData
   → Finds handoff object matching "Order Management Agent"
   ↓
[Step 8] Handoff Validation and Preparation
   - input_type validation: Checks if output is HandoffData format
   - Applies input_filter: handoff_filters.remove_all_tools
     → Removes unnecessary tools
   - Verifies target agent: order_agent exists
   ↓
[Step 9] on_handoff Callback Called
   handle_handoff(wrapper, input_data) executes
   - wrapper: Current RunContextWrapper (includes context)
   - input_data: Parsed HandoffData instance
   → Displays handoff information in sidebar:
     "Handing off to Order Management Agent
      Reason: Route to order-related specialist agent
      Issue Type: ORDER MANAGEMENT
      Description: Order status check request"
   ↓
[Step 10] Context Wrapping Maintained
   Existing wrapper.context maintained as-is
   → user_account_ctx is passed through
   ↓
[Step 11] Recursive Execution to Target Agent
   Inside Runner.run_streamed():
   Runner.run_streamed(
       order_agent,              ← Target agent
       input_data.issue_description,  ← Issue description from Handoff data as input
       session=session,          ← Same session maintained
       context=wrapper.context,   ← Context passed
   )
   ↓
[Step 12] Order Agent Execution Starts
   - Creates new RunContextWrapper (uses same context)
   - Calls dynamic_order_agent_instructions(wrapper, agent)
   - Can access wrapper.context.name, tier, etc.
   - "You are an Order Management specialist helping nico."
   - "Customer tier: basic"
   ↓
[Step 13] Order Agent Processes
   Order Agent's LLM processes order status check
   → Context information continues to be available
   → Generates personalized response

9. Internal Transfer Mechanism

A. Handoff Detection Mechanism

  • Triage Agent LLM Output:
    • "I'll connect you with our Order Management specialist..." + Structured output in HandoffData format
  • Inside Runner:
    • Parses LLM output
    • Checks if output_type is HandoffData
    • Finds matching handoff from handoffs list

B. Context Passing Mechanism

  • Triage Agent Execution: context=user_account_ctx → wrapper.context = user_account_ctx
  • Handoff Occurs: handle_handoff(wrapper, input_data) → wrapper.context is maintained as-is
  • Order Agent Execution: Runner.run_streamed(order_agent, ..., context=wrapper.context) → Same context is passed
  • Inside Order Agent: dynamic_order_agent_instructions(wrapper, agent) → Can access wrapper.context.name, tier, etc.

C. Session Maintenance Mechanism

  • Triage Agent Execution: session=session (SQLiteSession)
  • Handoff Occurs: Same session object maintained
  • Order Agent Execution: session=session (same session)
  • Result: Conversation history is stored continuously. Conversations from both Triage Agent and Order Agent are recorded in the same session.

10. input_type vs output_type Comparison

A. output_type (Guardrail Agent)

input_guardrail_agent = Agent(
    # ...
    output_type=InputGuardRailOutput,
)
  • output_type: Forces the agent to always output in this format.
  • Restricts LLM to respond only in InputGuardRailOutput format.

B. input_type (Handoff)

        input_type=HandoffData, 
  • input_type: Triggers handoff only when output is in this format.
  • Triage Agent can also output normal text.
  • If output is in HandoffData format, handoff is executed.

11. Input Filtering Process

        input_filter=handoff_filters.remove_all_tools,
  • input_filter: Filters input during handoff
  • remove_all_tools: Removes all tools
  • Effects:
    • Tools from Triage Agent are not passed to Order Agent
    • Order Agent uses only its own tools
    • Clean transfer

12. Roles and Benefits of Handoff

  • Specialist Agent Routing: Routes issues to appropriate specialist agents
  • Context Maintenance: Customer information is maintained after handoff
  • Traceability: Displays handoff information in sidebar
  • Extensibility: Multiple agents can be registered
  • Filtering: Removes unnecessary tools with input_filter
  • Flexibility: Conditional handoff trigger with input_type

13. Key Concepts Summary

Concept Description
HandoffData Pydantic model containing handoff information
handoff() Function that creates a Handoff object
on_handoff Callback function called when handoff occurs
input_type Output data type that triggers handoff
input_filter Input filtering option during handoff
handoffs List that connects handoffs to agent
RECOMMENDED_PROMPT_PREFIX Prompt that guides LLM on how to perform handoffs

14. Complete System Integration Diagram

 
┌─────────────────────────────────────────┐
│  Runner.run_streamed(triage_agent, ...) │
│  context=user_account_ctx               │
└──────────────┬──────────────────────────┘
               │
               ▼
┌─────────────────────────────────────────┐
│  Input Guardrail Validation             │
│  off_topic_guardrail()                  │
└──────────────┬──────────────────────────┘
               │ Passes
               ▼
┌─────────────────────────────────────────┐
│  Triage Agent Execution                 │
│  - Includes RECOMMENDED_PROMPT_PREFIX   │
│  - Uses wrapper.context.name, tier      │
└──────────────┬──────────────────────────┘
               │
               ▼
┌─────────────────────────────────────────┐
│  LLM Output Generation                  │
│  - Normal text or HandoffData format    │
└──────────────┬──────────────────────────┘
               │
        ┌──────┴──────┐
        │             │
        ▼             ▼
┌──────────────┐  ┌──────────────────────┐
│ Normal Text  │  │ HandoffData Format   │
│              │  │ {                    │
│ Continue     │  │   to_agent_name:     │
│ Conversation │  │   issue_type:        │
│              │  │   ...                │
│              │  │ }                    │
└──────────────┘  └──────┬───────────────┘
                         │
                         ▼
            ┌────────────────────────────┐
            │ Runner Detects HandoffData │
            │ - input_type matches       │
            │ - Checks handoffs list     │
            └──────────────┬─────────────┘
                           │
                           ▼
            ┌────────────────────────────┐
            │ Apply input_filter         │
            │ - remove_all_tools         │
            └──────────────┬─────────────┘
                           │
                           ▼
            ┌────────────────────────────┐
            │ Call on_handoff Callback   │
            │ handle_handoff(wrapper,    │
            │              input_data)   │
            │ - Display info in sidebar  │
            └──────────────┬─────────────┘
                           │
                           ▼
            ┌────────────────────────────┐
            │ Maintain Context           │
            │ Pass wrapper.context as-is │
            └──────────────┬─────────────┘
                           │
                           ▼
            ┌────────────────────────────┐
            │ Runner.run_streamed(       │
            │   order_agent,             │
            │   issue_description,       │
            │   context=wrapper.context  │
            │ )                          │
            │ (Recursive call)           │
            └──────────────┬─────────────┘
                           │
                           ▼
            ┌────────────────────────────┐
            │ Order Agent Execution      │
            │ - dynamic_order_agent_     │
            │   instructions()           │
            │ - Uses wrapper.context.name,│
            │   tier                     │
            │ - Processes order status   │
            │   check                    │
            └────────────────────────────┘