{ "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", " ๐Ÿ‘ค Current User Set: {user_id}
\n", " Status: {status}\n", "
\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", "
\n", " ๐Ÿ” Tool Invoked: hotel_booking(\"{destination}\")\n", "
\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", "
\n", " ๐Ÿ”„ Middleware: Intercepting {function_name}...\n", "
\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", "
\n", "

๐ŸŒŸ PRIORITY OVERRIDE ACTIVATED! ๐ŸŒŸ

\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", "
\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", "
\n", " โ„น๏ธ Middleware: No priority override (user: {current_user_id})\n", "
\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", "
\n", " โœ… Condition Check: has_availability = {result.has_availability} for {result.destination}{override_indicator}\n", "
\n", " \"\"\")\n", " )\n", "\n", " return result.has_availability\n", " except Exception as e:\n", " display(\n", " HTML(f\"\"\"\n", "
\n", " โš ๏ธ Error: {str(e)}\n", "
\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", "
\n", " โŒ Condition Check: no_availability for {result.destination}\n", "
\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", "
\n", " ๐Ÿ“ค Display Executor: Yielding workflow output\n", "
\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", "
\n", " โœ… Created 3 Agents:\n", " \n", "
\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", "
\n", "

โœ… Workflow Built Successfully!

\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", "

\n", "
\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", "
\n", "

๐Ÿงช TEST CASE 1: Regular User + Paris

\n", "

Expected: No rooms โ†’ No middleware override โ†’ Alternative suggestion

\n", "
\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", "
\n", "

๐Ÿ“Š RESULT (Regular User)

\n", "
\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", "
\n", "
\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", "
\n", "

๐Ÿงช TEST CASE 2: ๐ŸŒŸ Priority User + Paris

\n", "

Expected: No rooms โ†’ ๐ŸŒŸ MIDDLEWARE OVERRIDE โ†’ Rooms available โ†’ Booking suggestion!

\n", "
\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", "
\n", "

๐Ÿ† RESULT (Priority Member) ๐ŸŒŸ

\n", "
\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", "
\n", " ๐Ÿ’ก What Just Happened:
\n", " 1. hotel_booking tool returned \"no availability\"
\n", " 2. priority_check_middleware intercepted the result
\n", " 3. Middleware checked user status: priority_user โœ…
\n", " 4. Middleware OVERRODE the result to \"available\"
\n", " 5. Workflow routed to booking_agent instead of alternative_agent!\n", "
\n", "
\n", "
\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", "
\n", "

๐Ÿงช TEST CASE 3: Priority User + Stockholm

\n", "

Expected: Rooms available โ†’ No override needed โ†’ Booking suggestion

\n", "
\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", "
\n", "

๐Ÿ† RESULT (Priority User - No Override Needed)

\n", "
\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", "
\n", " ๐Ÿ’ก Middleware Behavior:
\n", " โ€ข hotel_booking returned \"available\" naturally
\n", " โ€ข Middleware saw available = true โ†’ No override needed
\n", " โ€ข Workflow proceeded normally to booking_agent\n", "
\n", "
\n", "
\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 }