{ "cells": [ { "cell_type": "markdown", "id": "263ac938", "metadata": {}, "source": [ "# Hotel Booking with Priority Member Middleware\n", "\n", "This notebook demonstrates **function-based middleware** using the Microsoft Agent Framework. We build upon the conditional workflow example by adding a middleware layer that gives priority members special privileges.\n", "\n", "## What You'll Learn:\n", "1. **Function-Based Middleware**: Intercept and modify function results\n", "2. **Context Access**: Read and modify `context.result` after execution\n", "3. **Business Logic Implementation**: Priority member benefits\n", "4. **Result Override**: Change function outcomes based on user status\n", "5. **Same Workflow, Different Outcomes**: Middleware-driven behavior changes\n", "\n", "## Workflow Architecture with Middleware:\n", "\n", "```\n", "User Input: \"I want to book a hotel in Paris\"\n", " โ\n", " [availability_agent]\n", " - Calls hotel_booking tool\n", " - ๐ priority_check middleware intercepts\n", " - Checks user membership status\n", " - IF priority + no rooms โ Override to available!\n", " - Returns BookingCheckResult\n", " โ\n", " Conditional Routing\n", " / \\\n", " [has_availability] [no_availability]\n", " โ โ\n", " [booking_agent] [alternative_agent]\n", " (Priority override!) (Regular users)\n", " โ โ\n", " [display_result executor]\n", "```\n", "\n", "## Key Difference from Conditional Workflow:\n", "\n", "**Without Middleware** (14-conditional-workflow.ipynb):\n", "- Paris has no rooms โ Route to alternative_agent\n", "\n", "**With Middleware** (this notebook):\n", "- Regular user + Paris โ No rooms โ Route to alternative_agent\n", "- Priority user + Paris โ ๐ Middleware overrides! โ Available โ Route to booking_agent\n", "\n", "## Prerequisites:\n", "- Microsoft Agent Framework installed\n", "- Understanding of conditional workflows (see 14-conditional-workflow.ipynb)\n", "- GitHub token or OpenAI API key\n", "- Basic understanding of middleware patterns" ] }, { "cell_type": "code", "execution_count": null, "id": "ec6cafec", "metadata": {}, "outputs": [], "source": [ "import asyncio\n", "import json\n", "import os\n", "from collections.abc import Awaitable, Callable\n", "from typing import Annotated, Any, Never\n", "\n", "from agent_framework import (\n", " AgentExecutor,\n", " AgentExecutorRequest,\n", " AgentExecutorResponse,\n", " FunctionInvocationContext,\n", " Message,\n", " WorkflowBuilder,\n", " WorkflowContext,\n", " executor,\n", " tool,\n", ")\n", "from agent_framework.foundry import FoundryChatClient\n", "from azure.identity import AzureCliCredential\n", "from dotenv import load_dotenv\n", "from IPython.display import HTML, display\n", "from pydantic import BaseModel\n", "\n", "print(\"โ All imports successful!\")\n" ] }, { "cell_type": "markdown", "id": "4d67d9a1", "metadata": {}, "source": [ "## Step 1: Define Pydantic Models for Structured Outputs\n", "\n", "These models define the **schema** that agents will return. We've added a `priority_override` field to track when middleware modifies the availability result." ] }, { "cell_type": "code", "execution_count": null, "id": "9973ac54", "metadata": {}, "outputs": [], "source": [ "class BookingCheckResult(BaseModel):\n", " \"\"\"Result from checking hotel availability at a destination.\"\"\"\n", "\n", " destination: str\n", " has_availability: bool\n", " message: str\n", " # Tracks if middleware overrode the result. The Azure structured-output\n", " # contract requires every property to be in the JSON schema's `required`\n", " # array, so we cannot give this a default value the way the original\n", " # notebook did.\n", " priority_override: bool\n", "\n", "\n", "class AlternativeResult(BaseModel):\n", " \"\"\"Suggested alternative destination when no rooms available.\"\"\"\n", "\n", " alternative_destination: str\n", " reason: str\n", "\n", "\n", "class BookingConfirmation(BaseModel):\n", " \"\"\"Booking suggestion when rooms are available.\"\"\"\n", "\n", " destination: str\n", " action: str\n", " message: str\n", "\n", "\n", "print(\"โ Pydantic models defined:\")\n", "print(\" - BookingCheckResult (availability check with priority_override)\")\n", "print(\" - AlternativeResult (alternative suggestion)\")\n", "print(\" - BookingConfirmation (booking confirmation)\")" ] }, { "cell_type": "markdown", "id": "8e3d0853", "metadata": {}, "source": [ "## Step 2: Define Priority Members Database\n", "\n", "For this demo, we'll simulate a priority membership database. In production, this would query a real database or API.\n", "\n", "**Priority Members:**\n", "- `alice@example.com` - VIP member\n", "- `bob@example.com` - Premium member \n", "- `priority_user` - Test account" ] }, { "cell_type": "code", "execution_count": null, "id": "c6fc0feb", "metadata": {}, "outputs": [], "source": [ "# Simulated priority members database\n", "PRIORITY_MEMBERS = {\n", " \"alice@example.com\",\n", " \"bob@example.com\",\n", " \"priority_user\",\n", "}\n", "\n", "# Global variable to track current user (in real app, use proper session management)\n", "current_user_id = \"regular_user\" # Default: regular user\n", "\n", "\n", "def set_user(user_id: str):\n", " \"\"\"Set the current user for the session.\"\"\"\n", " global current_user_id\n", " current_user_id = user_id\n", " is_priority = user_id in PRIORITY_MEMBERS\n", " status = \"๐ PRIORITY MEMBER\" if is_priority else \"๐ค Regular User\"\n", "\n", " display(\n", " HTML(f\"\"\"\n", "
\n",
" User: {current_user_id}
\n",
" Status: VIP Priority Member
\n",
" Action: Overriding \"No Availability\" for {destination}
\n",
" Result: โ
Rooms now available for priority booking!\n",
"
\n",
" Conditional Routing (Middleware-Aware):
\n",
" โข If NO availability โ alternative_agent โ display_result
\n",
" โข If availability (or ๐ priority override) โ booking_agent โ display_result\n",
"
Expected: No rooms โ No middleware override โ Alternative suggestion
\n", "Status: โ No rooms in Paris
\n", "Middleware: No priority override (regular user)
\n", "Alternative: ๐จ {result_regular.alternative_destination}
\n", "Reason: {result_regular.reason}
\n", "Expected: No rooms โ ๐ MIDDLEWARE OVERRIDE โ Rooms available โ Booking suggestion!
\n", "Status: โ Rooms Available (Priority Override!)
\n", "Middleware: ๐ OVERRIDE ACTIVATED!
\n", "Destination: ๐จ {result_priority.destination}
\n", "Action: {result_priority.action}
\n", "Message: {result_priority.message}
\n", "Expected: Rooms available โ No override needed โ Booking suggestion
\n", "Status: โ Rooms Available (Natural)
\n", "Middleware: No override needed
\n", "Destination: ๐จ {result_stockholm.destination}
\n", "Action: {result_stockholm.action}
\n", "Message: {result_stockholm.message}
\n", "