{ "cells": [ { "cell_type": "markdown", "id": "69f48405", "metadata": {}, "source": [ "# Travel Recommendations with Concurrent Orchestration\n", "\n", "This notebook demonstrates **concurrent orchestration** using the Microsoft Agent Framework. We'll build a travel recommendation system with three specialized agents that work in parallel to provide comprehensive travel insights.\n", "\n", "## What You'll Learn:\n", "1. **Concurrent Orchestration**: Running multiple agents in parallel (fan-out/fan-in pattern)\n", "2. **ConcurrentBuilder**: High-level API for building concurrent workflows\n", "3. **Travel Recommendations**: Three specialized agents working together\n", "4. **Default Aggregation**: Combining multiple agent responses\n", "5. **Performance Benefits**: Parallel execution vs sequential processing\n", "\n", "\n", "## Three Specialized Agents:\n", "\n", "1. **Attractions Agent**: Tourist attractions, activities, landmarks\n", "2. **Dining Agent**: Local cuisine, restaurants, food experiences\n", "3. **History Agent**: Historical facts, cultural significance, context" ] }, { "cell_type": "code", "execution_count": null, "id": "1c8918b5", "metadata": {}, "outputs": [], "source": [ "import asyncio\n", "import json\n", "import os\n", "from typing import Any, cast\n", "\n", "from agent_framework import (\n", " Executor,\n", " Message,\n", " WorkflowBuilder,\n", " WorkflowContext,\n", " handler,\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": "2733c34d", "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": "markdown", "id": "0d144c4c", "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": "e0749818", "metadata": {}, "outputs": [], "source": [ "class AttractionsRecommendation(BaseModel):\n", " \"\"\"Tourist attractions and activities recommendations.\"\"\"\n", "\n", " destination: str\n", " top_attractions: list[str]\n", " activities: list[str]\n", " best_time_to_visit: str\n", " transportation_tips: str \n", "\n", "\n", "class DiningRecommendation(BaseModel):\n", " \"\"\"Food and dining recommendations.\"\"\"\n", "\n", " destination: str\n", " local_cuisine: str\n", " must_try_dishes: list[str]\n", " recommended_restaurants: list[str]\n", " food_experiences: list[str]\n", " dining_etiquette: str\n", "\n", "\n", "class HistoryRecommendation(BaseModel):\n", " \"\"\"Historical and cultural information.\"\"\"\n", "\n", " destination: str\n", " historical_significance: str\n", " cultural_highlights: list[str]\n", " important_periods: list[str]\n", " cultural_experiences: list[str]\n", " interesting_facts: list[str]" ] }, { "cell_type": "markdown", "id": "979521fb", "metadata": {}, "source": [ "## Step 2: Load Environment Variables and Configure the Foundry Provider\n", "\n", "Use `FoundryChatClient` with keyless `AzureCliCredential` authentication, matching the pattern used in lessons 01–13.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "c5179ca3", "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", "\n", "print(\"Microsoft Foundry provider configured successfully!\")\n" ] }, { "cell_type": "markdown", "id": "8e4eb398", "metadata": {}, "source": [ "## Step 3: Create Three Specialized Travel Agents\n" ] }, { "cell_type": "code", "execution_count": null, "id": "6a9571c6", "metadata": {}, "outputs": [], "source": [ "# Agent 1: Tourist Attractions Expert\n", "attractions_agent = provider.as_agent(\n", " name=\"attractions-agent\",\n", " instructions=(\n", " \"You are a tourism expert specializing in attractions and activities. \"\n", " \"When given a travel destination, provide comprehensive recommendations for \"\n", " \"tourist attractions, activities, best times to visit, and transportation tips. \"\n", " \"Focus on popular landmarks, unique experiences, and practical travel advice. \"\n", " \"Return structured JSON matching the AttractionsRecommendation schema.\"\n", " ),\n", ")\n", "\n", "# Agent 2: Food and Dining Expert\n", "dining_agent = provider.as_agent(\n", " name=\"dining-agent\",\n", " instructions=(\n", " \"You are a culinary expert specializing in local food and dining experiences. \"\n", " \"When given a travel destination, provide recommendations for local cuisine, \"\n", " \"must-try dishes, recommended restaurants, and unique food experiences. \"\n", " \"Include dining etiquette and cultural food customs. \"\n", " \"Return structured JSON matching the DiningRecommendation schema.\"\n", " ),\n", ")\n", "\n", "\n", "# Agent 3: History and Culture Expert\n", "history_agent = provider.as_agent(\n", " name=\"history-agent\",\n", " instructions=(\n", " \"You are a historian and cultural expert. \"\n", " \"When given a travel destination, provide historical context, cultural significance, \"\n", " \"important historical periods, cultural experiences, and interesting facts. \"\n", " \"Focus on helping travelers understand the cultural heritage and historical importance. \"\n", " \"Return structured JSON matching the HistoryRecommendation schema.\"\n", " ),\n", ")\n" ] }, { "cell_type": "markdown", "id": "2b22d245", "metadata": {}, "source": [ "# Step 4: Build the Concurrent Workflow\n", "\n", "`WorkflowBuilder` with a small dispatcher executor and `add_fan_out_edges`:\n", "1. **Dispatcher** broadcasts the same input to all three agents\n", "2. **Three agents** run in parallel\n", "3. **Output** collects each agent's response separately" ] }, { "cell_type": "code", "execution_count": null, "id": "4bad34b0", "metadata": {}, "outputs": [], "source": [ "# A passthrough executor that broadcasts the user input to every agent in parallel.\n", "class InputDispatcher(Executor):\n", " \"\"\"Forward the user input unchanged to all participating agents.\"\"\"\n", "\n", " @handler\n", " async def forward(self, text: str, ctx: WorkflowContext[str]) -> None:\n", " await ctx.send_message(text)\n", "\n", "\n", "dispatcher = InputDispatcher(id=\"dispatcher\")\n", "agents = [attractions_agent, dining_agent, history_agent]\n", "\n", "workflow = (\n", " WorkflowBuilder(\n", " start_executor=dispatcher,\n", " output_executors=agents,\n", " )\n", " .add_fan_out_edges(dispatcher, agents)\n", " .build()\n", ")\n", "\n", "display(HTML(\"\"\"\n", "
\n",
" Architecture:
\n",
" • Input → Dispatcher (fan-out)
\n",
" • 3 Agents run in parallel (attractions, dining, history)
\n",
" • Output → 3 AgentResponse objects, one per agent\n",
"
Status: Running 3 agents concurrently...
\n", "Generated by 3 concurrent agents
\n", "Testing with destination: Barcelona
\n", "{concurrent_time:.2f}s
\n", "{concurrent_count} agent responses
\n", "{sequential_time:.2f}s
\n", "{sequential_count} agent responses
\n", "{improvement:.1f}% faster
\n", "\n", " Saved {sequential_time - concurrent_time:.2f} seconds with concurrent execution\n", "
\n", "