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

959 lines
38 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"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",
" <div style='padding: 15px; background: {\"linear-gradient(135deg, #FFD700 0%, #FFA500 100%)\" if is_priority else \"#e3f2fd\"}; \n",
" border-left: 4px solid {\"#FF6B35\" if is_priority else \"#2196f3\"}; border-radius: 4px; margin: 10px 0;'>\n",
" <strong>👤 Current User Set:</strong> {user_id}<br>\n",
" <strong>Status:</strong> {status}\n",
" </div>\n",
" \"\"\")\n",
" )\n",
"\n",
"\n",
"print(\"✅ Priority members database created\")\n",
"print(f\" Priority members: {len(PRIORITY_MEMBERS)} users\")"
]
},
{
"cell_type": "markdown",
"id": "1b503f57",
"metadata": {},
"source": [
"## Step 3: Create the Hotel Booking Tool\n",
"\n",
"Same as the conditional workflow, but now it will be intercepted by middleware!"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "1e17f7f2",
"metadata": {},
"outputs": [],
"source": [
"@tool(description=\"Check hotel room availability for a destination city\")\n",
"def hotel_booking(destination: Annotated[str, \"The destination city to check for hotel rooms\"]) -> str:\n",
" \"\"\"\n",
" Simulates checking hotel room availability.\n",
"\n",
" Returns JSON string with availability status.\n",
" \"\"\"\n",
" display(\n",
" HTML(f\"\"\"\n",
" <div style='padding: 15px; background: #e3f2fd; border-left: 4px solid #2196f3; border-radius: 4px; margin: 10px 0;'>\n",
" <strong>🔍 Tool Invoked:</strong> hotel_booking(\"{destination}\")\n",
" </div>\n",
" \"\"\")\n",
" )\n",
"\n",
" # Simulate availability check\n",
" cities_with_rooms = [\"stockholm\", \"seattle\", \"tokyo\", \"london\", \"amsterdam\"]\n",
" has_rooms = destination.lower() in cities_with_rooms\n",
"\n",
" result = {\"has_availability\": has_rooms, \"destination\": destination}\n",
"\n",
" return json.dumps(result)\n",
"\n",
"\n",
"print(\"✅ hotel_booking tool created with @tool decorator\")"
]
},
{
"cell_type": "markdown",
"id": "88ffd637",
"metadata": {},
"source": [
"## Step 4: 🌟 Create Priority Check Middleware (THE KEY FEATURE!)\n",
"\n",
"This is the **core functionality** of this notebook. The middleware:\n",
"\n",
"1. **Intercepts** the hotel_booking function call\n",
"2. **Executes** the function normally by calling `next(context)`\n",
"3. **Inspects** the result in `context.result`\n",
"4. **Overrides** the result if user is priority and no rooms available\n",
"5. **Returns** the modified result back to the agent\n",
"\n",
"**Key Pattern:**\n",
"```python\n",
"async def my_middleware(context, next):\n",
" await next(context) # Execute function\n",
" # Now context.result contains the function's output\n",
" if some_condition:\n",
" context.result = new_value # Override!\n",
"```"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "ba160f08",
"metadata": {},
"outputs": [],
"source": [
"async def priority_check_middleware(\n",
" context: FunctionInvocationContext,\n",
" next: Callable[[FunctionInvocationContext], Awaitable[None]],\n",
") -> None:\n",
" \"\"\"\n",
" Function middleware that overrides hotel_booking results for priority members.\n",
" \n",
" Workflow:\n",
" 1. Let the function execute normally\n",
" 2. Check if user is a priority member\n",
" 3. If priority + no availability → Override to make rooms available!\n",
" 4. Agent will then route to booking path instead of alternative path\n",
" \"\"\"\n",
" function_name = context.function.name\n",
"\n",
" display(\n",
" HTML(f\"\"\"\n",
" <div style='padding: 12px; background: #fff3e0; border-left: 4px solid #ff9800; border-radius: 4px; margin: 10px 0;'>\n",
" <strong>🔄 Middleware:</strong> Intercepting {function_name}...\n",
" </div>\n",
" \"\"\")\n",
" )\n",
"\n",
" # Execute the original function\n",
" await next(context)\n",
"\n",
" # Now inspect and potentially modify the result\n",
" if context.result and function_name == \"hotel_booking\":\n",
" result_data = json.loads(context.result)\n",
" destination = result_data.get(\"destination\", \"\")\n",
" has_availability = result_data.get(\"has_availability\", False)\n",
"\n",
" # Check if user is priority member\n",
" is_priority = current_user_id in PRIORITY_MEMBERS\n",
"\n",
" # Override logic: Priority member + no availability → Make available!\n",
" if is_priority and not has_availability:\n",
" display(\n",
" HTML(f\"\"\"\n",
" <div style='padding: 20px; background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%); \n",
" border-radius: 8px; margin: 10px 0; box-shadow: 0 4px 12px rgba(255,165,0,0.4);'>\n",
" <h3 style='margin: 0 0 10px 0; color: #333;'>🌟 PRIORITY OVERRIDE ACTIVATED! 🌟</h3>\n",
" <p style='margin: 0; color: #555; line-height: 1.6;'>\n",
" <strong>User:</strong> {current_user_id}<br>\n",
" <strong>Status:</strong> VIP Priority Member<br>\n",
" <strong>Action:</strong> Overriding \"No Availability\" for {destination}<br>\n",
" <strong>Result:</strong> ✅ Rooms now available for priority booking!\n",
" </p>\n",
" </div>\n",
" \"\"\")\n",
" )\n",
"\n",
" # Override the result!\n",
" result_data[\"has_availability\"] = True\n",
" result_data[\"priority_override\"] = True\n",
" context.result = json.dumps(result_data)\n",
"\n",
" elif not has_availability:\n",
" display(\n",
" HTML(f\"\"\"\n",
" <div style='padding: 12px; background: #ffebee; border-left: 4px solid #f44336; border-radius: 4px; margin: 10px 0;'>\n",
" <strong>️ Middleware:</strong> No priority override (user: {current_user_id})\n",
" </div>\n",
" \"\"\")\n",
" )\n",
"\n",
"\n",
"print(\"✅ priority_check_middleware created\")\n",
"print(\" - Intercepts hotel_booking function\")\n",
"print(\" - Overrides availability for priority members\")"
]
},
{
"cell_type": "markdown",
"id": "630b6c2b",
"metadata": {},
"source": [
"## Step 5: Define Condition Functions for Routing\n",
"\n",
"Same condition functions as the conditional workflow - they inspect the structured output to determine routing."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "c82b0633",
"metadata": {},
"outputs": [],
"source": [
"def has_availability_condition(message: Any) -> bool:\n",
" \"\"\"Condition for routing when hotels ARE available (including priority overrides!).\"\"\"\n",
" if not isinstance(message, AgentExecutorResponse):\n",
" return True\n",
"\n",
" try:\n",
" result = BookingCheckResult.model_validate_json(message.agent_run_response.text)\n",
"\n",
" # Check if this was a priority override\n",
" override_indicator = \" 🌟\" if result.priority_override else \"\"\n",
"\n",
" display(\n",
" HTML(f\"\"\"\n",
" <div style='padding: 12px; background: #c8e6c9; border-left: 4px solid #4caf50; border-radius: 4px; margin: 10px 0;'>\n",
" <strong>✅ Condition Check:</strong> has_availability = <strong>{result.has_availability}</strong> for {result.destination}{override_indicator}\n",
" </div>\n",
" \"\"\")\n",
" )\n",
"\n",
" return result.has_availability\n",
" except Exception as e:\n",
" display(\n",
" HTML(f\"\"\"\n",
" <div style='padding: 12px; background: #ffcdd2; border-left: 4px solid #f44336; border-radius: 4px; margin: 10px 0;'>\n",
" <strong>⚠️ Error:</strong> {str(e)}\n",
" </div>\n",
" \"\"\")\n",
" )\n",
" return False\n",
"\n",
"\n",
"def no_availability_condition(message: Any) -> bool:\n",
" \"\"\"Condition for routing when hotels are NOT available.\"\"\"\n",
" if not isinstance(message, AgentExecutorResponse):\n",
" return False\n",
"\n",
" try:\n",
" result = BookingCheckResult.model_validate_json(message.agent_run_response.text)\n",
"\n",
" display(\n",
" HTML(f\"\"\"\n",
" <div style='padding: 12px; background: #ffecb3; border-left: 4px solid #ff9800; border-radius: 4px; margin: 10px 0;'>\n",
" <strong>❌ Condition Check:</strong> no_availability for {result.destination}\n",
" </div>\n",
" \"\"\")\n",
" )\n",
"\n",
" return not result.has_availability\n",
" except Exception:\n",
" return False\n",
"\n",
"\n",
"print(\"✅ Condition functions defined\")"
]
},
{
"cell_type": "markdown",
"id": "2189c529",
"metadata": {},
"source": [
"## Step 6: Create Custom Display Executor\n",
"\n",
"Same executor as before - displays the final workflow output."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "a4cc88da",
"metadata": {},
"outputs": [],
"source": [
"@executor(id=\"display_result\")\n",
"async def display_result(response: AgentExecutorResponse, ctx: WorkflowContext[Never, str]) -> None:\n",
" \"\"\"Display the final result as workflow output.\"\"\"\n",
" display(\n",
" HTML(\"\"\"\n",
" <div style='padding: 15px; background: #f3e5f5; border-left: 4px solid #9c27b0; border-radius: 4px; margin: 10px 0;'>\n",
" <strong>📤 Display Executor:</strong> Yielding workflow output\n",
" </div>\n",
" \"\"\")\n",
" )\n",
"\n",
" await ctx.yield_output(response.agent_run_response.text)\n",
"\n",
"\n",
"print(\"✅ display_result executor created\")"
]
},
{
"cell_type": "markdown",
"id": "18b1857c",
"metadata": {},
"source": [
"## Step 7: Load Environment Variables\n",
"\n",
"Configure the LLM client (GitHub Models or OpenAI)."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "5e6584c2",
"metadata": {},
"outputs": [],
"source": [
"# Load environment variables\n",
"load_dotenv()\n",
"\n",
"# Configure the Microsoft Foundry provider with keyless authentication\n",
"provider = FoundryChatClient(\n",
" project_endpoint=os.environ[\"AZURE_AI_PROJECT_ENDPOINT\"],\n",
" model=os.environ[\"AZURE_AI_MODEL_DEPLOYMENT_NAME\"],\n",
" credential=AzureCliCredential(),\n",
")\n"
]
},
{
"cell_type": "markdown",
"id": "e4993e63",
"metadata": {},
"source": [
"## Step 8: Create AI Agents with Middleware\n",
"\n",
"**KEY DIFFERENCE:** When creating the availability_agent, we pass the `middleware` parameter!\n",
"\n",
"This is how we inject the priority_check_middleware into the agent's function invocation pipeline."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "8218790d",
"metadata": {},
"outputs": [],
"source": [
"# Agent 1: Check availability with tool + middleware\n",
"availability_agent = AgentExecutor(\n",
" provider.as_agent(\n",
" name=\"availability-agent\",\n",
" instructions=(\n",
" \"You are a hotel booking assistant that checks room availability. \"\n",
" \"Use the hotel_booking tool to check if rooms are available at the destination. \"\n",
" \"Return JSON with fields: destination (string), has_availability (bool), message (string), \"\n",
" \"and priority_override (bool, true if priority member got special access). \"\n",
" \"The message should summarize the availability status and mention if priority override occurred.\"\n",
" ),\n",
" tools=[hotel_booking],\n",
" default_options={\"response_format\": BookingCheckResult},\n",
" middleware=[priority_check_middleware], # 🌟 MIDDLEWARE INJECTION!\n",
" ),\n",
" id=\"availability_agent\",\n",
")\n",
"\n",
"# Agent 2: Suggest alternative (when no rooms)\n",
"alternative_agent = AgentExecutor(\n",
" provider.as_agent(\n",
" name=\"alternative-agent\",\n",
" instructions=(\n",
" \"You are a helpful travel assistant. When a user cannot find hotels in their requested city, \"\n",
" \"suggest an alternative nearby city that has availability. \"\n",
" \"Return JSON with fields: alternative_destination (string) and reason (string). \"\n",
" \"Make your suggestion sound appealing and helpful.\"\n",
" ),\n",
" default_options={\"response_format\": AlternativeResult},\n",
" ),\n",
" id=\"alternative_agent\",\n",
")\n",
"\n",
"# Agent 3: Suggest booking (when rooms available)\n",
"booking_agent = AgentExecutor(\n",
" provider.as_agent(\n",
" name=\"booking-agent\",\n",
" instructions=(\n",
" \"You are a booking assistant. The user has found available hotel rooms. \"\n",
" \"Encourage them to book by highlighting the destination's appeal. \"\n",
" \"If priority_override is true in the input, mention that they received priority member access. \"\n",
" \"Return JSON with fields: destination (string), action (string), and message (string). \"\n",
" \"The action should be 'book_now' and message should be encouraging.\"\n",
" ),\n",
" default_options={\"response_format\": BookingConfirmation},\n",
" ),\n",
" id=\"booking_agent\",\n",
")\n",
"\n",
"display(\n",
" HTML(\"\"\"\n",
" <div style='padding: 15px; background: #e3f2fd; border-left: 4px solid #2196f3; border-radius: 4px; margin: 10px 0;'>\n",
" <strong>✅ Created 3 Agents:</strong>\n",
" <ul style='margin: 10px 0 0 0;'>\n",
" <li><strong>availability_agent</strong> - WITH priority_check_middleware 🌟</li>\n",
" <li><strong>alternative_agent</strong> - Suggests alternative cities</li>\n",
" <li><strong>booking_agent</strong> - Encourages booking</li>\n",
" </ul>\n",
" </div>\n",
"\"\"\")\n",
")\n"
]
},
{
"cell_type": "markdown",
"id": "c6c2ac90",
"metadata": {},
"source": [
"## Step 9: Build the Workflow\n",
"\n",
"Same workflow structure as before - conditional routing based on availability."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9cc9b853",
"metadata": {},
"outputs": [],
"source": [
"# Build the workflow with conditional routing\n",
"workflow = (\n",
" WorkflowBuilder(\n",
" start_executor=availability_agent,\n",
" output_executors=[display_result],\n",
" )\n",
" # NO AVAILABILITY PATH\n",
" .add_edge(availability_agent, alternative_agent, condition=no_availability_condition)\n",
" .add_edge(alternative_agent, display_result)\n",
" # HAS AVAILABILITY PATH (can be triggered by middleware override!)\n",
" .add_edge(availability_agent, booking_agent, condition=has_availability_condition)\n",
" .add_edge(booking_agent, display_result)\n",
" .build()\n",
")\n",
"\n",
"display(\n",
" HTML(\"\"\"\n",
" <div style='padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border-radius: 8px; margin: 10px 0;'>\n",
" <h3 style='margin: 0 0 15px 0;'>✅ Workflow Built Successfully!</h3>\n",
" <p style='margin: 0; line-height: 1.6;'>\n",
" <strong>Conditional Routing (Middleware-Aware):</strong><br>\n",
" • If <strong>NO availability</strong> → alternative_agent → display_result<br>\n",
" • If <strong>availability</strong> (or 🌟 <strong>priority override</strong>) → booking_agent → display_result\n",
" </p>\n",
" </div>\n",
"\"\"\")\n",
")"
]
},
{
"cell_type": "markdown",
"id": "56c53031",
"metadata": {},
"source": [
"## Step 10: Test Case 1 - Regular User in Paris (No Override)\n",
"\n",
"A regular user tries to book Paris → No rooms → Routes to alternative_agent"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "e8cf4e21",
"metadata": {},
"outputs": [],
"source": [
"# Set as regular user\n",
"set_user(\"regular_user\")\n",
"\n",
"display(\n",
" 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: Regular User + Paris</h3>\n",
" <p style='margin: 0;'><strong>Expected:</strong> No rooms → No middleware override → Alternative suggestion</p>\n",
" </div>\n",
"\"\"\")\n",
")\n",
"\n",
"# Create request\n",
"request_regular = AgentExecutorRequest(\n",
" messages=[Message(role=\"user\", text=\"I want to book a hotel in Paris\")], should_respond=True\n",
")\n",
"\n",
"# Run workflow\n",
"events_regular = await workflow.run(request_regular)\n",
"outputs_regular = events_regular.get_outputs()\n",
"\n",
"# Display results\n",
"if outputs_regular:\n",
" result_regular = AlternativeResult.model_validate_json(outputs_regular[0])\n",
"\n",
" display(\n",
" HTML(f\"\"\"\n",
" <div style='padding: 25px; background: #fff; border: 2px solid #ff9800; border-radius: 12px; margin: 20px 0;'>\n",
" <h3 style='margin: 0 0 15px 0; color: #e65100;'>📊 RESULT (Regular User)</h3>\n",
" <div style='background: #fff3e0; padding: 20px; border-radius: 8px;'>\n",
" <p style='margin: 0 0 10px 0;'><strong>Status:</strong> ❌ No rooms in Paris</p>\n",
" <p style='margin: 0 0 10px 0;'><strong>Middleware:</strong> No priority override (regular user)</p>\n",
" <p style='margin: 0 0 10px 0;'><strong>Alternative:</strong> 🏨 {result_regular.alternative_destination}</p>\n",
" <p style='margin: 0;'><strong>Reason:</strong> {result_regular.reason}</p>\n",
" </div>\n",
" </div>\n",
" \"\"\")\n",
" )"
]
},
{
"cell_type": "markdown",
"id": "cea981b0",
"metadata": {},
"source": [
"## Step 11: Test Case 2 - 🌟 Priority User in Paris (WITH Override!)\n",
"\n",
"A priority member tries to book Paris → No rooms initially → 🌟 Middleware overrides! → Routes to booking_agent\n",
"\n",
"**This is the key demonstration of middleware power!**"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d390f5ac",
"metadata": {},
"outputs": [],
"source": [
"# Set as priority user\n",
"set_user(\"priority_user\")\n",
"\n",
"display(\n",
" HTML(\"\"\"\n",
" <div style='padding: 20px; background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%); border-radius: 8px; margin: 20px 0;'>\n",
" <h3 style='margin: 0 0 10px 0; color: #333;'>🧪 TEST CASE 2: 🌟 Priority User + Paris</h3>\n",
" <p style='margin: 0; color: #555;'><strong>Expected:</strong> No rooms → 🌟 MIDDLEWARE OVERRIDE → Rooms available → Booking suggestion!</p>\n",
" </div>\n",
"\"\"\")\n",
")\n",
"\n",
"# Create request\n",
"request_priority = AgentExecutorRequest(\n",
" messages=[Message(role=\"user\", text=\"I want to book a hotel in Paris\")], should_respond=True\n",
")\n",
"\n",
"# Run workflow\n",
"events_priority = await workflow.run(request_priority)\n",
"outputs_priority = events_priority.get_outputs()\n",
"\n",
"# Display results\n",
"if outputs_priority:\n",
" result_priority = BookingConfirmation.model_validate_json(outputs_priority[0])\n",
"\n",
" display(\n",
" HTML(f\"\"\"\n",
" <div style='padding: 25px; background: linear-gradient(135deg, #FFD700 0%, #FFA500 100%); border-radius: 12px;\n",
" box-shadow: 0 8px 16px rgba(255,165,0,0.4); margin: 20px 0;'>\n",
" <h3 style='margin: 0 0 15px 0; color: #333;'>🏆 RESULT (Priority Member) 🌟</h3>\n",
" <div style='background: white; padding: 20px; border-radius: 8px;'>\n",
" <p style='margin: 0 0 10px 0; font-size: 16px;'><strong>Status:</strong> ✅ Rooms Available (Priority Override!)</p>\n",
" <p style='margin: 0 0 10px 0; font-size: 16px;'><strong>Middleware:</strong> 🌟 OVERRIDE ACTIVATED!</p>\n",
" <p style='margin: 0 0 10px 0; font-size: 16px;'><strong>Destination:</strong> 🏨 {result_priority.destination}</p>\n",
" <p style='margin: 0 0 10px 0; font-size: 16px;'><strong>Action:</strong> {result_priority.action}</p>\n",
" <p style='margin: 0; font-size: 14px; color: #666;'><strong>Message:</strong> {result_priority.message}</p>\n",
" <div style='margin-top: 15px; padding: 15px; background: #fff3cd; border-radius: 6px; border-left: 4px solid #FF6B35;'>\n",
" <strong>💡 What Just Happened:</strong><br>\n",
" 1. hotel_booking tool returned \"no availability\"<br>\n",
" 2. priority_check_middleware intercepted the result<br>\n",
" 3. Middleware checked user status: priority_user ✅<br>\n",
" 4. Middleware OVERRODE the result to \"available\"<br>\n",
" 5. Workflow routed to booking_agent instead of alternative_agent!\n",
" </div>\n",
" </div>\n",
" </div>\n",
" \"\"\")\n",
" )"
]
},
{
"cell_type": "markdown",
"id": "baab4b1a",
"metadata": {},
"source": [
"## Step 12: Test Case 3 - Priority User in Stockholm (Already Available)\n",
"\n",
"Priority user tries Stockholm → Rooms available → No override needed → Routes to booking_agent\n",
"\n",
"This shows that middleware only acts when needed!"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "316a0d31",
"metadata": {},
"outputs": [],
"source": [
"# Priority user is still set from previous test\n",
"\n",
"display(\n",
" HTML(\"\"\"\n",
" <div style='padding: 20px; background: #e8f5e9; border-left: 4px solid #4caf50; border-radius: 8px; margin: 20px 0;'>\n",
" <h3 style='margin: 0 0 10px 0; color: #1b5e20;'>🧪 TEST CASE 3: Priority User + Stockholm</h3>\n",
" <p style='margin: 0;'><strong>Expected:</strong> Rooms available → No override needed → Booking suggestion</p>\n",
" </div>\n",
"\"\"\")\n",
")\n",
"\n",
"# Create request\n",
"request_stockholm = AgentExecutorRequest(\n",
" messages=[Message(role=\"user\", text=\"I want to book a hotel in Stockholm\")], should_respond=True\n",
")\n",
"\n",
"# Run workflow\n",
"events_stockholm = await workflow.run(request_stockholm)\n",
"outputs_stockholm = events_stockholm.get_outputs()\n",
"\n",
"# Display results\n",
"if outputs_stockholm:\n",
" result_stockholm = BookingConfirmation.model_validate_json(outputs_stockholm[0])\n",
"\n",
" display(\n",
" HTML(f\"\"\"\n",
" <div style='padding: 25px; background: linear-gradient(135deg, #4caf50 0%, #8bc34a 100%); color: white; border-radius: 12px;\n",
" box-shadow: 0 4px 12px rgba(76,175,80,0.3); margin: 20px 0;'>\n",
" <h3 style='margin: 0 0 15px 0;'>🏆 RESULT (Priority User - No Override Needed)</h3>\n",
" <div style='background: white; color: #333; padding: 20px; border-radius: 8px;'>\n",
" <p style='margin: 0 0 10px 0; font-size: 16px;'><strong>Status:</strong> ✅ Rooms Available (Natural)</p>\n",
" <p style='margin: 0 0 10px 0; font-size: 16px;'><strong>Middleware:</strong> No override needed</p>\n",
" <p style='margin: 0 0 10px 0; font-size: 16px;'><strong>Destination:</strong> 🏨 {result_stockholm.destination}</p>\n",
" <p style='margin: 0 0 10px 0; font-size: 16px;'><strong>Action:</strong> {result_stockholm.action}</p>\n",
" <p style='margin: 0; font-size: 14px; color: #666;'><strong>Message:</strong> {result_stockholm.message}</p>\n",
" <div style='margin-top: 15px; padding: 15px; background: #e8f5e9; border-radius: 6px; border-left: 4px solid #4caf50;'>\n",
" <strong>💡 Middleware Behavior:</strong><br>\n",
" • hotel_booking returned \"available\" naturally<br>\n",
" • Middleware saw available = true → No override needed<br>\n",
" • Workflow proceeded normally to booking_agent\n",
" </div>\n",
" </div>\n",
" </div>\n",
" \"\"\")\n",
" )"
]
},
{
"cell_type": "markdown",
"id": "e498ee16",
"metadata": {},
"source": [
"## Key Takeaways and Middleware Concepts\n",
"\n",
"### ✅ What You've Learned:\n",
"\n",
"#### **1. Function-Based Middleware Pattern**\n",
"\n",
"Middleware intercepts function calls using a simple async function:\n",
"\n",
"```python\n",
"async def my_middleware(\n",
" context: FunctionInvocationContext,\n",
" next: Callable,\n",
") -> None:\n",
" # Before function execution\n",
" print(\"Intercepting...\")\n",
" \n",
" # Execute the function\n",
" await next(context)\n",
" \n",
" # After function execution - inspect result\n",
" if context.result:\n",
" # Modify result if needed\n",
" context.result = modified_value\n",
"```\n",
"\n",
"#### **2. Context Access and Result Override**\n",
"\n",
"- `context.function` - Access the function being called\n",
"- `context.arguments` - Read function arguments\n",
"- `context.kwargs` - Access additional parameters\n",
"- `await next(context)` - Execute the function\n",
"- `context.result` - Read/modify the function's output\n",
"\n",
"#### **3. Business Logic Implementation**\n",
"\n",
"Our middleware implements priority member benefits:\n",
"- **Regular users**: No modifications, standard workflow\n",
"- **Priority users**: Override \"no availability\" → \"available\"\n",
"- **Conditional logic**: Only overrides when needed\n",
"\n",
"#### **4. Same Workflow, Different Outcomes**\n",
"\n",
"The power of middleware:\n",
"- ✅ No changes to the workflow structure\n",
"- ✅ No changes to the tool function\n",
"- ✅ No changes to conditional routing logic\n",
"- ✅ Just middleware → Different behavior!\n",
"\n",
"### 🚀 Real-World Applications:\n",
"\n",
"1. **VIP/Premium Features**\n",
" - Override rate limits for premium users\n",
" - Provide priority access to resources\n",
" - Unlock premium features dynamically\n",
"\n",
"2. **A/B Testing**\n",
" - Route users to different implementations\n",
" - Test new features with specific users\n",
" - Gradual feature rollouts\n",
"\n",
"3. **Security & Compliance**\n",
" - Audit function calls\n",
" - Block sensitive operations\n",
" - Enforce business rules\n",
"\n",
"4. **Performance Optimization**\n",
" - Cache results for specific users\n",
" - Skip expensive operations when possible\n",
" - Dynamic resource allocation\n",
"\n",
"5. **Error Handling & Retry**\n",
" - Catch and handle errors gracefully\n",
" - Implement retry logic\n",
" - Fallback to alternative implementations\n",
"\n",
"6. **Logging & Monitoring**\n",
" - Track function execution times\n",
" - Log parameters and results\n",
" - Monitor usage patterns\n",
"\n",
"### 🔑 Key Differences from Decorators:\n",
"\n",
"| Feature | Decorator | Middleware |\n",
"|---------|-----------|------------|\n",
"| **Scope** | Single function | All functions in agent |\n",
"| **Flexibility** | Fixed at definition | Dynamic at runtime |\n",
"| **Context** | Limited | Full agent context |\n",
"| **Composition** | Multiple decorators | Middleware pipeline |\n",
"| **Agent-Aware** | No | Yes (access to agent state) |\n",
"\n",
"### 📚 When to Use Middleware:\n",
"\n",
"✅ **Use middleware when:**\n",
"- You need to modify behavior based on user/session state\n",
"- You want to apply logic to multiple functions\n",
"- You need access to agent-level context\n",
"- You're implementing cross-cutting concerns (logging, auth, etc.)\n",
"\n",
"❌ **Don't use middleware when:**\n",
"- Simple input validation (use Pydantic)\n",
"- Function-specific logic (keep in function)\n",
"- One-time modifications (just change the function)\n",
"\n",
"### 🎓 Advanced Patterns:\n",
"\n",
"```python\n",
"# Multiple middleware (execution order matters!)\n",
"middleware=[\n",
" logging_middleware, # Logs first\n",
" auth_middleware, # Then checks auth\n",
" cache_middleware, # Then checks cache\n",
" rate_limit_middleware, # Then rate limits\n",
" priority_check_middleware # Finally priority check\n",
"]\n",
"\n",
"# Conditional middleware execution\n",
"async def conditional_middleware(context, next):\n",
" if should_execute(context):\n",
" await next(context)\n",
" # Modify result\n",
" else:\n",
" # Skip execution entirely\n",
" context.result = cached_value\n",
"```\n",
"\n",
"### 🔗 Related Concepts:\n",
"\n",
"- **Agent Middleware**: Intercepts agent.run() calls\n",
"- **Function Middleware**: Intercepts tool function calls (what we used!)\n",
"- **Middleware Pipeline**: Chain of middleware executing in order\n",
"- **Context Propagation**: Pass state through middleware chain"
]
}
],
"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
}