Files
2026-07-13 12:59:43 +08:00

725 lines
31 KiB
Plaintext

{
"cells": [
{
"cell_type": "markdown",
"id": "c3858df8",
"metadata": {},
"source": [
"# Travel Customer Support with Handoff Orchestration\n",
"\n",
"This notebook demonstrates **handoff orchestration** using the Microsoft Agent Framework. We'll build a travel customer support system where agents can transfer control to specialists based on the customer's needs.\n",
"\n",
"## What You'll Learn:\n",
"1. **Handoff Orchestration**: Dynamic agent routing based on context and expertise\n",
"2. **HandoffBuilder**: High-level API for building handoff workflows\n",
"3. **Specialist Routing**: Agents can hand off to other agents dynamically\n",
"4. **Multi-turn Conversations**: Seamless context preservation across handoffs\n",
"5. **Customer Support Flow**: Real-world application of agent handoffs"
]
},
{
"cell_type": "markdown",
"id": "90d3eeb1",
"metadata": {},
"source": [
"## Prerequisites:\n",
"- Microsoft Agent Framework installed\n",
"- GitHub token or OpenAI API key configured\n",
"- Understanding of basic agent concepts"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8360426f",
"metadata": {},
"outputs": [],
"source": [
"import asyncio\n",
"import json\n",
"import os\n",
"from collections.abc import AsyncIterable\n",
"from typing import Any, cast\n",
"\n",
"from agent_framework import (\n",
" ChatMessage,\n",
" HandoffBuilder,\n",
" HandoffUserInputRequest,\n",
" RequestInfoEvent,\n",
" WorkflowEvent,\n",
" WorkflowOutputEvent,\n",
" WorkflowRunState,\n",
" WorkflowStatusEvent,\n",
")\n",
"\n",
"# GitHub Models or OpenAI client integration\n",
"from agent_framework.openai import OpenAIChatClient\n",
"from dotenv import load_dotenv\n",
"from IPython.display import HTML, display\n",
"from pydantic import BaseModel"
]
},
{
"cell_type": "markdown",
"id": "33e3e754",
"metadata": {},
"source": [
"## Step 1: Define Pydantic Models for Structured Outputs\n",
"\n",
"These models define the schema that each specialized agent will return. This ensures consistent and parseable responses from all agents."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3c36ef1d",
"metadata": {},
"outputs": [],
"source": [
"class FlightBookingResult(BaseModel):\n",
" \"\"\"Flight booking confirmation from the booking agent.\"\"\"\n",
"\n",
" destination: str\n",
" departure_date: str\n",
" return_date: str\n",
" booking_reference: str\n",
" passenger_name: str\n",
" flight_details: str\n",
" total_cost: str\n",
" status: str\n",
"\n",
"\n",
"class DisputeResult(BaseModel):\n",
" \"\"\"Dispute resolution result from the disputes agent.\"\"\"\n",
"\n",
" dispute_type: str\n",
" original_booking: str\n",
" refund_amount: str\n",
" refund_method: str\n",
" processing_time: str\n",
" reference_number: str\n",
" status: str\n",
"\n",
"\n",
"class TripCheckResult(BaseModel):\n",
" \"\"\"Trip confirmation result from the trip check agent.\"\"\"\n",
"\n",
" trip_reference: str\n",
" destination: str\n",
" travel_dates: str\n",
" confirmation_status: str\n",
" special_notes: str\n",
" contact_info: str"
]
},
{
"cell_type": "markdown",
"id": "668bb379",
"metadata": {},
"source": [
"## Step 2: Load Environment Variables\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "bfe15197",
"metadata": {},
"outputs": [],
"source": [
"# Load environment variables\n",
"load_dotenv()\n",
"\n",
"from azure.identity import AzureCliCredential\n",
"\n",
"# Azure OpenAI via the Responses API. Sign in with `az login` for keyless Entra ID auth.\n",
"# GitHub Models is deprecated (retiring July 2026) and does not support the Responses API,\n",
"# so this sample calls Azure OpenAI directly. OpenAIChatClient uses the Responses API.\n",
"chat_client = OpenAIChatClient(\n",
" azure_endpoint=os.environ[\"AZURE_OPENAI_ENDPOINT\"],\n",
" credential=AzureCliCredential(),\n",
" model_id=os.environ.get(\"AZURE_OPENAI_DEPLOYMENT\", \"gpt-4o-mini\"),\n",
")\n"
]
},
{
"cell_type": "markdown",
"id": "8cd52bf8",
"metadata": {},
"source": [
"## Step 3: Create Four Specialized Travel Support Agents\n",
"\n",
"Each agent has specific expertise and can hand off to appropriate specialists based on customer needs."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "32f31143",
"metadata": {},
"outputs": [],
"source": [
"# Agent 1: Customer Support Agent (Main triage agent)\n",
"customer_support_agent = chat_client.as_agent(\n",
" instructions=(\n",
" \"You are a friendly customer support agent for a travel company. \"\n",
" \"Assess customer requests and route them to the appropriate specialist: \"\n",
" \"- For flight bookings or reservations: call handoff_to_booking_agent \"\n",
" \"- For refunds, disputes, or billing issues: call handoff_to_disputes_agent \"\n",
" \"- For trip confirmations or travel plan checks: call handoff_to_trip_check_agent \"\n",
" \"Be welcoming and ensure customers feel heard before routing them.\"\n",
" ),\n",
" name=\"customer_support_agent\",\n",
")\n",
"\n",
"\n",
"# Agent 2: Booking Agent (Flight booking specialist)\n",
"booking_agent = chat_client.as_agent(\n",
" instructions=(\n",
" \"You are a flight booking specialist. Handle all flight reservations and bookings. \"\n",
" \"When a customer wants to book a flight, collect their destination, travel dates, \"\n",
" \"and confirm the booking. Always provide a booking reference number. \"\n",
" \"Return structured JSON with booking details. \"\n",
" \"The flight is always confirmed as booked regardless of destination.\"\n",
" ),\n",
" name=\"booking_agent\",\n",
" response_format=FlightBookingResult,\n",
")\n",
"\n",
"# Agent 3: Disputes Agent (Refund and billing specialist)\n",
"disputes_agent = chat_client.as_agent(\n",
" instructions=(\n",
" \"You are a disputes and refunds specialist. Handle customer complaints, \"\n",
" \"refund requests, and billing disputes. Always approve refunds and provide \"\n",
" \"a reference number. Process refunds back to the original payment method. \"\n",
" \"Return structured JSON with refund details. \"\n",
" \"All refund requests are approved and processed immediately.\"\n",
" ),\n",
" name=\"disputes_agent\",\n",
" response_format=DisputeResult,\n",
")\n",
"# Agent 4: Trip Check Agent (Travel confirmation specialist)\n",
"trip_check_agent = chat_client.as_agent(\n",
" instructions=(\n",
" \"You are a travel confirmation specialist. Verify and confirm customer \"\n",
" \"travel plans, check itineraries, and provide travel status updates. \"\n",
" \"Always confirm that travel plans are in order and provide reassurance. \"\n",
" \"Return structured JSON with confirmation details. \"\n",
" \"All travel plans are confirmed as valid and ready.\"\n",
" ),\n",
" name=\"trip_check_agent\",\n",
" response_format=TripCheckResult,\n",
")\n",
"\n",
"\n",
"\n"
]
},
{
"cell_type": "markdown",
"id": "c67e2486",
"metadata": {},
"source": [
"## Step 4: Build the Handoff Workflow\n",
"\n",
"The HandoffBuilder creates a workflow where the customer support agent can dynamically hand off to specialists based on customer needs.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "111b6a7c",
"metadata": {},
"outputs": [],
"source": [
"workflow = (\n",
" HandoffBuilder(\n",
" name=\"travel_support_handoff\",\n",
" participants=[customer_support_agent, booking_agent, disputes_agent, trip_check_agent],\n",
" )\n",
" .set_coordinator(customer_support_agent) # Main agent that receives initial requests\n",
" .add_handoff(customer_support_agent, [booking_agent, disputes_agent, trip_check_agent])\n",
" .with_termination_condition(\n",
" lambda conv: sum(1 for msg in conv if msg.role.value == \"user\") > \n",
" ) # Stop after 3 user messages\n",
" .build()\n",
")\n",
"\n",
"display(HTML(\"\"\"\n",
"<div style='padding: 20px; background: linear-gradient(135deg, #ff7043 0%, #ff5722 100%); color: white; border-radius: 8px; margin: 10px 0;'>\n",
" <h3 style='margin: 0 0 15px 0;'>Handoff Workflow Built Successfully!</h3>\n",
" <p style='margin: 0; line-height: 1.6;'>\n",
" <strong>Handoff Flow:</strong><br>\n",
" • User Request → <strong>Customer Support Agent</strong> (triage)<br>\n",
" • Support Agent → <strong>Specialist Agent</strong> (dynamic handoff)<br>\n",
" • Specialist → <strong>Resolution</strong> (expert handling)<br>\n",
" • System → <strong>User Response</strong> (final result)\n",
" </p>\n",
"</div>\n",
"\"\"\"))\n",
"\n"
]
},
{
"cell_type": "markdown",
"id": "0380843e",
"metadata": {},
"source": [
"## Step 5: Helper Functions for Event Processing\n",
"\n",
"These functions help us process workflow events and handle user input requests during the handoff process."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "66a9dbe0",
"metadata": {},
"outputs": [],
"source": [
"async def drain_events(stream: AsyncIterable[WorkflowEvent]) -> list[WorkflowEvent]:\n",
" \"\"\"Collect all events from an async stream into a list.\"\"\"\n",
" return [event async for event in stream]\n",
"\n",
"\n",
"def handle_workflow_events(events: list[WorkflowEvent]) -> list[RequestInfoEvent]:\n",
" \"\"\"Process workflow events and extract pending user input requests.\"\"\"\n",
" requests: list[RequestInfoEvent] = []\n",
" \n",
" for event in events:\n",
" if isinstance(event, WorkflowStatusEvent) and event.state in {\n",
" WorkflowRunState.IDLE,\n",
" WorkflowRunState.IDLE_WITH_PENDING_REQUESTS,\n",
" }:\n",
" print(f\"[Workflow Status] {event.state.name}\")\n",
"\n",
" elif isinstance(event, WorkflowOutputEvent):\n",
" conversation = cast(list[ChatMessage], event.data)\n",
" if isinstance(conversation, list):\n",
" print(\"\\n=== Final Conversation ===\")\n",
" for message in conversation:\n",
" # Filter out messages with no text (tool calls)\n",
" if not message.text.strip():\n",
" continue\n",
" speaker = message.author_name or message.role.value\n",
" print(f\"- {speaker}: {message.text}\")\n",
" print(\"==========================\")\n",
"\n",
" elif isinstance(event, RequestInfoEvent):\n",
" if isinstance(event.data, HandoffUserInputRequest):\n",
" print_handoff_request(event.data)\n",
" requests.append(event)\n",
"\n",
" return requests\n",
"\n",
"\n",
"def print_handoff_request(request: HandoffUserInputRequest) -> None:\n",
" \"\"\"Display a user input request with conversation context.\"\"\"\n",
" print(\"\\n=== User Input Requested ===\")\n",
" # Filter out messages with no text for cleaner display\n",
" messages_with_text = [\n",
" msg for msg in request.conversation if msg.text.strip()]\n",
" print(f\"Last {len(messages_with_text)} messages in conversation:\")\n",
" for message in messages_with_text[-3:]: # Show last 3 for brevity\n",
" speaker = message.author_name or message.role.value\n",
" text = message.text[:100] + \\\n",
" \"...\" if len(message.text) > 100 else message.text\n",
" print(f\" {speaker}: {text}\")\n",
" print(\"============================\")\n",
"\n",
"\n",
"print(\"Helper functions defined for event processing\")"
]
},
{
"cell_type": "markdown",
"id": "ff51e63c",
"metadata": {},
"source": [
"## Step 6: Test Case 1 - Flight Booking Request\n",
"\n",
"Let's test our handoff workflow with a flight booking request. The customer support agent should hand off to the booking agent.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "558a73d4",
"metadata": {},
"outputs": [],
"source": [
"async def test_booking_handoff():\n",
" \"\"\"Test handoff workflow for flight booking requests.\"\"\"\n",
"\n",
" display(HTML(\"\"\"\n",
" <div style='padding: 20px; background: #fff3e0; border-left: 4px solid #ff9800; border-radius: 8px; margin: 20px 0;'>\n",
" <h3 style='margin: 0 0 10px 0; color: #e65100;'>Test Case 1: Flight Booking Request</h3>\n",
" <p style='margin: 0;'><strong>Expected Flow:</strong> Customer Support → Booking Agent</p>\n",
" </div>\n",
" \"\"\"))\n",
"\n",
" # Start the workflow\n",
" print(\"[User]: I want to book a flight to Paris for next month\")\n",
" events = await drain_events(\n",
" workflow.run_stream(\"I want to book a flight to Paris for next month\")\n",
" )\n",
" pending_requests = handle_workflow_events(events)\n",
"\n",
" # Handle any additional user input requests\n",
" scripted_responses = [\n",
" \"I'd like to travel from New York to Paris on December 15th and return on December 22nd.\",\n",
" \"Yes, please confirm the booking under the name John Smith.\"\n",
" ]\n",
"\n",
" response_index = 0\n",
" while pending_requests and response_index < len(scripted_responses):\n",
" user_response = scripted_responses[response_index]\n",
" print(f\"\\n[User]: {user_response}\")\n",
"\n",
" responses = {req.request_id: user_response for req in pending_requests}\n",
" events = await drain_events(workflow.send_responses_streaming(responses))\n",
" pending_requests = handle_workflow_events(events)\n",
"\n",
" response_index += 1\n",
"\n",
" # Extract and display the final booking result\n",
" if events:\n",
" for event in events:\n",
" if isinstance(event, WorkflowOutputEvent):\n",
" conversation = cast(list[ChatMessage], event.data)\n",
" for message in conversation:\n",
" if message.author_name == \"booking_agent\" and message.text.strip():\n",
" try:\n",
" booking_data = FlightBookingResult.model_validate_json(\n",
" message.text)\n",
" display_booking_result(booking_data)\n",
" except Exception as e:\n",
" print(f\"Could not parse booking result: {e}\")\n",
"\n",
"\n",
"def display_booking_result(booking: FlightBookingResult):\n",
" \"\"\"Display flight booking result in a formatted section.\"\"\"\n",
"\n",
" display(HTML(f\"\"\"\n",
" <div style='padding: 20px; background: #e8f5e9; border-radius: 8px; margin: 15px 0; border-left: 4px solid #4caf50;'>\n",
" <h3 style='margin: 0 0 15px 0; color: #2e7d32;'>✈️ Flight Booking Confirmed</h3>\n",
" <div style='display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-bottom: 15px;'>\n",
" <div>\n",
" <strong style='color: #333;'>Booking Reference:</strong> {booking.booking_reference}<br>\n",
" <strong style='color: #333;'>Passenger:</strong> {booking.passenger_name}<br>\n",
" <strong style='color: #333;'>Status:</strong> <span style='color: #4caf50; font-weight: bold;'>{booking.status}</span>\n",
" </div>\n",
" <div>\n",
" <strong style='color: #333;'>Destination:</strong> {booking.destination}<br>\n",
" <strong style='color: #333;'>Total Cost:</strong> {booking.total_cost}<br>\n",
" <strong style='color: #333;'>Departure:</strong> {booking.departure_date}\n",
" </div>\n",
" </div>\n",
" <div style='margin-bottom: 10px;'>\n",
" <strong style='color: #333;'>Flight Details:</strong> {booking.flight_details}\n",
" </div>\n",
" <div style='background: rgba(76,175,80,0.1); padding: 10px; border-radius: 4px; margin-top: 10px;'>\n",
" <strong style='color: #2e7d32;'>✅ Success:</strong> Flight booking completed through handoff to booking specialist\n",
" </div>\n",
" </div>\n",
" \"\"\"))\n",
"\n",
"\n",
"# Run the booking test\n",
"await test_booking_handoff()"
]
},
{
"cell_type": "markdown",
"id": "794c1e25",
"metadata": {},
"source": [
"## Step 7: Test Case 2 - Dispute/Refund Request\n",
"\n",
"Let's test our handoff workflow with a refund request. The customer support agent should hand off to the disputes agent."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d61756c0",
"metadata": {},
"outputs": [],
"source": [
"async def test_dispute_handoff():\n",
" \"\"\"Test handoff workflow for dispute/refund requests.\"\"\"\n",
"\n",
" display(HTML(\"\"\"\n",
" <div style='padding: 20px; background: #fff3e0; border-left: 4px solid #ff9800; border-radius: 8px; margin: 20px 0;'>\n",
" <h3 style='margin: 0 0 10px 0; color: #e65100;'>Test Case 2: Refund Request</h3>\n",
" <p style='margin: 0;'><strong>Expected Flow:</strong> Customer Support → Disputes Agent</p>\n",
" </div>\n",
" \"\"\"))\n",
"\n",
" # Start the workflow\n",
" print(\"[User]: I need to cancel my flight and get a refund\")\n",
" events = await drain_events(\n",
" workflow.run_stream(\"I need to cancel my flight and get a refund\")\n",
" )\n",
" pending_requests = handle_workflow_events(events)\n",
"\n",
" # Handle any additional user input requests\n",
" scripted_responses = [\n",
" \"My booking reference is FL12345. I can't travel due to a family emergency.\",\n",
" \"Yes, please process the full refund back to my credit card.\"\n",
" ]\n",
"\n",
" response_index = 0\n",
" while pending_requests and response_index < len(scripted_responses):\n",
" user_response = scripted_responses[response_index]\n",
" print(f\"\\n[User]: {user_response}\")\n",
"\n",
" responses = {req.request_id: user_response for req in pending_requests}\n",
" events = await drain_events(workflow.send_responses_streaming(responses))\n",
" pending_requests = handle_workflow_events(events)\n",
"\n",
" response_index += 1\n",
"\n",
" # Extract and display the final dispute result\n",
" if events:\n",
" for event in events:\n",
" if isinstance(event, WorkflowOutputEvent):\n",
" conversation = cast(list[ChatMessage], event.data)\n",
" for message in conversation:\n",
" if message.author_name == \"disputes_agent\" and message.text.strip():\n",
" try:\n",
" dispute_data = DisputeResult.model_validate_json(\n",
" message.text)\n",
" display_dispute_result(dispute_data)\n",
" except Exception as e:\n",
" print(f\"Could not parse dispute result: {e}\")\n",
"\n",
"\n",
"def display_dispute_result(dispute: DisputeResult):\n",
" \"\"\"Display dispute resolution result in a formatted section.\"\"\"\n",
"\n",
" display(HTML(f\"\"\"\n",
" <div style='padding: 20px; background: #fff3e0; border-radius: 8px; margin: 15px 0; border-left: 4px solid #ff9800;'>\n",
" <h3 style='margin: 0 0 15px 0; color: #f57c00;'>💰 Refund Processed</h3>\n",
" <div style='display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-bottom: 15px;'>\n",
" <div>\n",
" <strong style='color: #333;'>Reference Number:</strong> {dispute.reference_number}<br>\n",
" <strong style='color: #333;'>Dispute Type:</strong> {dispute.dispute_type}<br>\n",
" <strong style='color: #333;'>Status:</strong> <span style='color: #ff9800; font-weight: bold;'>{dispute.status}</span>\n",
" </div>\n",
" <div>\n",
" <strong style='color: #333;'>Refund Amount:</strong> {dispute.refund_amount}<br>\n",
" <strong style='color: #333;'>Refund Method:</strong> {dispute.refund_method}<br>\n",
" <strong style='color: #333;'>Processing Time:</strong> {dispute.processing_time}\n",
" </div>\n",
" </div>\n",
" <div style='margin-bottom: 10px;'>\n",
" <strong style='color: #333;'>Original Booking:</strong> {dispute.original_booking}\n",
" </div>\n",
" <div style='background: rgba(255,152,0,0.1); padding: 10px; border-radius: 4px; margin-top: 10px;'>\n",
" <strong style='color: #f57c00;'>✅ Success:</strong> Refund processed through handoff to disputes specialist\n",
" </div>\n",
" </div>\n",
" \"\"\"))\n",
"\n",
" # Run the dispute test\n",
"await test_dispute_handoff()"
]
},
{
"cell_type": "markdown",
"id": "9a855d8d",
"metadata": {},
"source": [
"## Step 8: Test Case 3 - Trip Confirmation Request\n",
"\n",
"Let's test our handoff workflow with a trip confirmation request. The customer support agent should hand off to the trip check agent."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "34ea038c",
"metadata": {},
"outputs": [],
"source": [
"async def test_trip_check_handoff():\n",
" \"\"\"Test handoff workflow for trip confirmation requests.\"\"\"\n",
"\n",
" display(HTML(\"\"\"\n",
" <div style='padding: 20px; background: #fff3e0; border-left: 4px solid #ff9800; border-radius: 8px; margin: 20px 0;'>\n",
" <h3 style='margin: 0 0 10px 0; color: #e65100;'>Test Case 3: Trip Confirmation</h3>\n",
" <p style='margin: 0;'><strong>Expected Flow:</strong> Customer Support → Trip Check Agent</p>\n",
" </div>\n",
" \"\"\"))\n",
"\n",
" # Start the workflow\n",
" print(\"[User]: Can you confirm my travel plans are all set?\")\n",
" events = await drain_events(\n",
" workflow.run_stream(\"Can you confirm my travel plans are all set?\")\n",
" )\n",
" pending_requests = handle_workflow_events(events)\n",
"\n",
" # Handle any additional user input requests\n",
" scripted_responses = [\n",
" \"I'm traveling to London next week. My confirmation number is TR98765.\",\n",
" \"Perfect, thank you for checking everything is ready!\"\n",
" ]\n",
"\n",
" response_index = 0\n",
" while pending_requests and response_index < len(scripted_responses):\n",
" user_response = scripted_responses[response_index]\n",
" print(f\"\\n[User]: {user_response}\")\n",
" \n",
" responses = {req.request_id: user_response for req in pending_requests}\n",
" events = await drain_events(workflow.send_responses_streaming(responses))\n",
" pending_requests = handle_workflow_events(events)\n",
" \n",
" response_index += 1\n",
" # Extract and display the final trip check result\n",
" if events:\n",
" for event in events:\n",
" if isinstance(event, WorkflowOutputEvent):\n",
" conversation = cast(list[ChatMessage], event.data)\n",
" for message in conversation:\n",
" if message.author_name == \"trip_check_agent\" and message.text.strip():\n",
" try:\n",
" trip_data = TripCheckResult.model_validate_json(\n",
" message.text)\n",
" display_trip_check_result(trip_data)\n",
" except Exception as e:\n",
" print(f\"Could not parse trip check result: {e}\")\n",
"\n",
"\n",
"def display_trip_check_result(trip: TripCheckResult):\n",
" \"\"\"Display trip confirmation result in a formatted section.\"\"\"\n",
"\n",
" display(HTML(f\"\"\"\n",
" <div style='padding: 20px; background: #f3e5f5; border-radius: 8px; margin: 15px 0; border-left: 4px solid #9c27b0;'>\n",
" <h3 style='margin: 0 0 15px 0; color: #7b1fa2;'>🎯 Trip Confirmed</h3>\n",
" <div style='display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-bottom: 15px;'>\n",
" <div>\n",
" <strong style='color: #333;'>Trip Reference:</strong> {trip.trip_reference}<br>\n",
" <strong style='color: #333;'>Destination:</strong> {trip.destination}<br>\n",
" <strong style='color: #333;'>Status:</strong> <span style='color: #9c27b0; font-weight: bold;'>{trip.confirmation_status}</span>\n",
" </div>\n",
" <div>\n",
" <strong style='color: #333;'>Travel Dates:</strong> {trip.travel_dates}<br>\n",
" <strong style='color: #333;'>Contact Info:</strong> {trip.contact_info}\n",
" </div>\n",
" </div>\n",
" <div style='margin-bottom: 10px;'>\n",
" <strong style='color: #333;'>Special Notes:</strong> {trip.special_notes}\n",
" </div>\n",
" <div style='background: rgba(156,39,176,0.1); padding: 10px; border-radius: 4px; margin-top: 10px;'>\n",
" <strong style='color: #7b1fa2;'>✅ Success:</strong> Trip confirmed through handoff to trip check specialist\n",
" </div>\n",
" </div>\n",
" \"\"\"))\n",
"\n",
"\n",
"# Run the trip check test\n",
"await test_trip_check_handoff()\n",
"\n",
" "
]
},
{
"cell_type": "markdown",
"id": "ae3cb474",
"metadata": {},
"source": [
"## Step 9: Workflow Analysis - Understanding Handoff Flow\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0b21ce2b",
"metadata": {},
"outputs": [],
"source": [
"async def analyze_handoff_patterns():\n",
" \"\"\"Analyze different handoff patterns and routing decisions.\"\"\"\n",
"\n",
" display(HTML(\"\"\"\n",
" <div style='padding: 20px; background: #f3e5f5; border-left: 4px solid #9c27b0; border-radius: 8px; margin: 20px 0;'>\n",
" <h3 style='margin: 0 0 10px 0; color: #7b1fa2;'>Handoff Pattern Analysis</h3>\n",
" <p style='margin: 0;'>Testing different request types to show routing decisions...</p>\n",
" </div>\n",
" \"\"\"))\n",
"\n",
" test_requests = [\n",
" \"I want to book a round-trip flight to Tokyo\",\n",
" \"I need a refund for my cancelled flight\",\n",
" \"Please check if my travel itinerary is confirmed\",\n",
" \"Can you help me with a billing dispute?\"\n",
" ]\n",
"\n",
" for i, request in enumerate(test_requests, 1):\n",
" print(f\"\\n--- Test Request {i} ---\")\n",
" print(f\"User: {request}\")\n",
"\n",
" # Run workflow and capture routing decision\n",
" events = await drain_events(workflow.run_stream(request))\n",
"\n",
" # Analyze which agent was activated\n",
" for event in events:\n",
" if isinstance(event, WorkflowOutputEvent):\n",
" conversation = cast(list[ChatMessage], event.data)\n",
" for message in conversation:\n",
" if message.author_name == \"customer_support_agent\":\n",
" print(f\"Support Agent: {message.text[:100]}...\")\n",
" elif message.author_name in [\"booking_agent\", \"disputes_agent\", \"trip_check_agent\"]:\n",
" agent_type = {\n",
" \"booking_agent\": \"🛫 BOOKING SPECIALIST\",\n",
" \"disputes_agent\": \"💰 DISPUTES SPECIALIST\",\n",
" \"trip_check_agent\": \"🎯 TRIP CHECK SPECIALIST\"\n",
" }[message.author_name]\n",
" print(f\"Routed to: {agent_type}\")\n",
" break\n",
" break\n",
" display(HTML(\"\"\"\n",
" <div style='padding: 25px; background: linear-gradient(135deg, #9c27b0 0%, #673ab7 100%); color: white; border-radius: 12px; \n",
" box-shadow: 0 4px 12px rgba(156,39,176,0.4); margin: 20px 0;'>\n",
" <h2 style='margin: 0 0 20px 0;'>Handoff Analysis Results</h2>\n",
" <div style='background: rgba(255,255,255,0.15); padding: 15px; border-radius: 8px;'>\n",
" <h4 style='margin: 0 0 10px 0;'>Key Observations</h4>\n",
" <ul style='margin: 0; padding-left: 20px; line-height: 1.6;'>\n",
" <li><strong>Dynamic Routing:</strong> Customer support agent analyzes request intent</li>\n",
" <li><strong>Context Preservation:</strong> Full conversation history maintained</li>\n",
" <li><strong>Specialist Focus:</strong> Each agent handles their expertise area</li>\n",
" <li><strong>Seamless Handoff:</strong> Users don't need to repeat information</li>\n",
" </ul>\n",
" </div>\n",
" </div>\n",
" \"\"\"))\n",
"\n",
" # Run the analysis\n",
"await analyze_handoff_patterns()"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".venv (3.12.12)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.12.12"
}
},
"nbformat": 4,
"nbformat_minor": 5
}