# Copyright (c) Microsoft. All rights reserved. """ Sample: Hotel Booking Conditional Workflow This sample demonstrates a conditional workflow using the Microsoft Agent Framework that routes based on hotel availability. Workflow: 1. User provides a destination city 2. Agent checks hotel availability using a tool 3. Conditional routing: - If NO availability → Suggest alternative city - If availability → Suggest booking 4. Display result with HTML formatting Key Concepts: - WorkflowBuilder with conditional edges - AgentExecutor wrapping AI agents - @executor decorator for custom logic - Pydantic models for structured outputs - @ai_function decorator for tools - OpenAIChatClient integration """ import asyncio import json import os from typing import Annotated, Any, Never from agent_framework import ( AgentExecutor, AgentExecutorRequest, AgentExecutorResponse, ChatMessage, Role, WorkflowBuilder, WorkflowContext, ai_function, executor, ) from agent_framework.openai import OpenAIChatClient from azure.identity import AzureCliCredential from dotenv import load_dotenv from pydantic import BaseModel # ============================================================================ # STEP 1: PYDANTIC MODELS FOR STRUCTURED OUTPUTS # ============================================================================ class BookingCheckResult(BaseModel): """Result from checking hotel availability at a destination.""" destination: str has_availability: bool message: str class AlternativeResult(BaseModel): """Suggested alternative destination when no rooms available.""" alternative_destination: str reason: str class BookingConfirmation(BaseModel): """Booking suggestion when rooms are available.""" destination: str action: str message: str # ============================================================================ # STEP 2: HOTEL BOOKING TOOL (AI FUNCTION) # ============================================================================ @ai_function(description="Check hotel room availability for a destination city") def hotel_booking(destination: Annotated[str, "The destination city to check for hotel rooms"]) -> str: """ Simulates checking hotel room availability. For demo purposes: - Stockholm, Seattle, Tokyo have rooms - All other cities don't have rooms Returns: JSON string with availability status """ print(f"šŸ” Checking hotel availability in {destination}...") # Simulate availability check cities_with_rooms = ["stockholm", "seattle", "tokyo", "london", "amsterdam"] has_rooms = destination.lower() in cities_with_rooms result = {"has_availability": has_rooms, "destination": destination} return json.dumps(result) # ============================================================================ # STEP 3: CONDITION FUNCTIONS FOR ROUTING # ============================================================================ def has_availability_condition(message: Any) -> bool: """ Condition for routing when hotels ARE available. Args: message: Message from upstream executor (should be AgentExecutorResponse) Returns: True if availability exists, False otherwise """ if not isinstance(message, AgentExecutorResponse): return True # Default to True if not the expected type try: result = BookingCheckResult.model_validate_json(message.agent_run_response.text) print(f"āœ… Availability check: {result.has_availability} for {result.destination}") return result.has_availability except Exception as e: print(f"āš ļø Error parsing availability result: {e}") return False def no_availability_condition(message: Any) -> bool: """ Condition for routing when hotels are NOT available. Args: message: Message from upstream executor Returns: True if no availability, False otherwise """ if not isinstance(message, AgentExecutorResponse): return False try: result = BookingCheckResult.model_validate_json(message.agent_run_response.text) print(f"āŒ No availability for {result.destination}") return not result.has_availability except Exception as e: print(f"āš ļø Error parsing availability result: {e}") return False # ============================================================================ # STEP 4: DISPLAY EXECUTOR (Custom transformation) # ============================================================================ @executor(id="display_result") async def display_result(response: AgentExecutorResponse, ctx: WorkflowContext[Never, str]) -> None: """ Display the final result as workflow output. This executor receives the final agent response and yields it as output. """ print(f"šŸ“¤ Yielding workflow output...") await ctx.yield_output(response.agent_run_response.text) # ============================================================================ # STEP 5: MAIN WORKFLOW FUNCTION # ============================================================================ async def main() -> None: """ Main function to build and execute the hotel booking workflow. """ # Load environment variables load_dotenv() # Verify configuration print("=" * 80) print("šŸØ HOTEL BOOKING CONDITIONAL WORKFLOW") print("=" * 80) # Provider selection: Azure OpenAI (Responses API), OpenAI, or MiniMax # The OpenAIChatClient works with any OpenAI-compatible API, and targets the # Azure OpenAI Responses API when given an azure_endpoint + credential. minimax_api_key = os.getenv("MINIMAX_API_KEY") azure_openai_endpoint = os.getenv("AZURE_OPENAI_ENDPOINT") openai_api_key = os.getenv("OPENAI_API_KEY") if minimax_api_key: # MiniMax: OpenAI-compatible API with large context window (up to 204K tokens). # Defaults to MiniMax-M3; override MINIMAX_MODEL_ID if your account/region # doesn't have access to it (e.g. set it to MiniMax-M2.7). chat_client = OpenAIChatClient( base_url=os.environ.get("MINIMAX_BASE_URL", "https://api.minimax.io/v1"), api_key=minimax_api_key, model_id=os.environ.get("MINIMAX_MODEL_ID", "MiniMax-M3"), ) print("Using MiniMax provider") elif azure_openai_endpoint: # Azure OpenAI (Responses API). Sign in with `az login` for keyless Entra ID auth. # GitHub Models is deprecated (retiring July 2026) and does not support the Responses API. chat_client = OpenAIChatClient( azure_endpoint=azure_openai_endpoint, credential=AzureCliCredential(), model_id=os.environ.get("AZURE_OPENAI_DEPLOYMENT", "gpt-4o-mini"), ) print("Using Azure OpenAI (Responses API) provider") else: # Default: OpenAI chat_client = OpenAIChatClient(model_id="gpt-4o") print("Using OpenAI provider") print("\n" + "=" * 80) print("STEP 1: Creating AI Agents with Structured Outputs") print("=" * 80) # Agent 1: Check availability availability_agent = AgentExecutor( chat_client.create_agent( instructions=( "You are a hotel booking assistant that checks room availability. " "Use the hotel_booking tool to check if rooms are available at the destination. " "Return JSON with fields: destination (string), has_availability (bool), and message (string). " "The message should summarize the availability status." ), tools=[hotel_booking], response_format=BookingCheckResult, ), id="availability_agent", ) print("āœ… Created availability_agent with hotel_booking tool") # Agent 2: Suggest alternative (when no rooms) alternative_agent = AgentExecutor( chat_client.create_agent( instructions=( "You are a helpful travel assistant. When a user cannot find hotels in their requested city, " "suggest an alternative nearby city that has availability. " "Return JSON with fields: alternative_destination (string) and reason (string). " "Choose from: Stockholm, Seattle, Tokyo, London, or Amsterdam (these have rooms). " "Make your suggestion sound appealing and helpful." ), response_format=AlternativeResult, ), id="alternative_agent", ) print("āœ… Created alternative_agent for suggesting other cities") # Agent 3: Suggest booking (when rooms available) booking_agent = AgentExecutor( chat_client.create_agent( instructions=( "You are a booking assistant. The user has found available hotel rooms. " "Encourage them to book by highlighting the destination's appeal. " "Return JSON with fields: destination (string), action (string), and message (string). " "The action should be 'book_now' and message should be encouraging." ), response_format=BookingConfirmation, ), id="booking_agent", ) print("āœ… Created booking_agent for confirming bookings") print("\n" + "=" * 80) print("STEP 2: Building Workflow with Conditional Edges") print("=" * 80) # Build the workflow workflow = ( WorkflowBuilder() .set_start_executor(availability_agent) # NO AVAILABILITY PATH: availability_agent → alternative_agent → display_result .add_edge(availability_agent, alternative_agent, condition=no_availability_condition) .add_edge(alternative_agent, display_result) # HAS AVAILABILITY PATH: availability_agent → booking_agent → display_result .add_edge(availability_agent, booking_agent, condition=has_availability_condition) .add_edge(booking_agent, display_result) .build() ) print("āœ… Workflow built with conditional routing:") print(" - If NO availability → suggest alternative") print(" - If availability → suggest booking") # ============================================================================ # TEST CASE 1: City WITHOUT availability (Paris) # ============================================================================ print("\n" + "=" * 80) print("TEST CASE 1: Checking Paris (NO AVAILABILITY)") print("=" * 80) request1 = AgentExecutorRequest( messages=[ChatMessage(Role.USER, text="I want to book a hotel in Paris")], should_respond=True ) events1 = await workflow.run(request1) outputs1 = events1.get_outputs() if outputs1: print("\nšŸ“Š WORKFLOW OUTPUT (Paris):") print("-" * 80) result1 = AlternativeResult.model_validate_json(outputs1[0]) print(f"šŸØ Alternative Destination: {result1.alternative_destination}") print(f"šŸ’” Reason: {result1.reason}") print("-" * 80) # ============================================================================ # TEST CASE 2: City WITH availability (Stockholm) # ============================================================================ print("\n" + "=" * 80) print("TEST CASE 2: Checking Stockholm (HAS AVAILABILITY)") print("=" * 80) request2 = AgentExecutorRequest( messages=[ChatMessage(Role.USER, text="I want to book a hotel in Stockholm")], should_respond=True ) events2 = await workflow.run(request2) outputs2 = events2.get_outputs() if outputs2: print("\nšŸ“Š WORKFLOW OUTPUT (Stockholm):") print("-" * 80) result2 = BookingConfirmation.model_validate_json(outputs2[0]) print(f"šŸØ Destination: {result2.destination}") print(f"āœ… Action: {result2.action}") print(f"šŸ’¬ Message: {result2.message}") print("-" * 80) print("\n" + "=" * 80) print("āœ… WORKFLOW DEMO COMPLETE!") print("=" * 80) if __name__ == "__main__": asyncio.run(main())