{ "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", "
\n",
" Handoff Flow:
\n",
" • User Request → Customer Support Agent (triage)
\n",
" • Support Agent → Specialist Agent (dynamic handoff)
\n",
" • Specialist → Resolution (expert handling)
\n",
" • System → User Response (final result)\n",
"
Expected Flow: Customer Support → Booking Agent
\n", "Expected Flow: Customer Support → Disputes Agent
\n", "Expected Flow: Customer Support → Trip Check Agent
\n", "Testing different request types to show routing decisions...
\n", "