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

473 lines
20 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": "2b91961c",
"metadata": {},
"source": [
"## Two Sequential Agents:\n",
"\n",
"1. **Front Desk Agent**: Makes initial attraction recommendations for the city\n",
"2. **Concierge Agent**: Reviews and rates the front desk recommendation based on popularity\n",
"\n",
"## Key Benefits of Sequential Orchestration:\n",
"\n",
"- **Iterative Refinement**: Second agent improves upon first agent's work\n",
"- **Specialization**: Each agent has a specific role in the process\n",
"- **Quality Control**: Built-in review and validation step\n",
"- **Clear Information Flow**: Structured handoff between agents\n",
"\n",
"## Prerequisites:\n",
"- Microsoft Agent Framework installed\n",
"- Microsoft Foundry project endpoint and model deployment configured (`AZURE_AI_PROJECT_ENDPOINT`, `AZURE_AI_MODEL_DEPLOYMENT_NAME`)\n",
"- Authenticated via Azure CLI (`az login`)\n",
"- Understanding of basic agent concepts\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0981c0bb",
"metadata": {},
"outputs": [],
"source": [
"import asyncio\n",
"import json\n",
"import os\n",
"from typing import Any, cast\n",
"\n",
"from agent_framework import Message, WorkflowBuilder\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": "790b74bd",
"metadata": {},
"source": [
"## Step 1: Define Pydantic Models for Structured Outputs\n",
"\n",
"These models define the schema that each agent will return. The front desk agent provides a recommendation, and the concierge agent provides a review and rating."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3436dc8d",
"metadata": {},
"outputs": [],
"source": [
"class AttractionRecommendation(BaseModel):\n",
" \"\"\"Attraction recommendation from the front desk agent.\"\"\"\n",
"\n",
" city: str\n",
" attraction_name: str\n",
" description: str\n",
" category: str # e.g., \"museum\", \"landmark\", \"park\", \"entertainment\"\n",
" recommended_duration: str # e.g., \"2-3 hours\", \"half day\"\n",
" why_recommended: str\n",
" best_time_to_visit: str\n",
"\n",
"\n",
"class AttractionReview(BaseModel):\n",
" \"\"\"Expert review and rating from the concierge agent.\"\"\"\n",
"\n",
" attraction_name: str\n",
" city: str\n",
" popularity_score: int # 1-10 scale\n",
" popularity_reasoning: str\n",
" visitor_rating: float # 1.0-5.0 scale\n",
" pros: list[str]\n",
" cons: list[str]\n",
" concierge_recommendation: str\n",
" alternative_suggestions: list[str]"
]
},
{
"cell_type": "markdown",
"id": "f4269b29",
"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 0113.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "2152c7d3",
"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": "e63c63dd",
"metadata": {},
"source": [
"## Step 3: Create Two Sequential Agents\n",
"\n",
"Each agent has a specific role in the sequential workflow. The front desk agent makes recommendations, and the concierge agent reviews and rates them.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "3f862eee",
"metadata": {},
"outputs": [],
"source": [
"# Agent 1: Front Desk Agent (Makes initial recommendations)\n",
"front_desk_agent = provider.as_agent(\n",
" name=\"front-desk-agent\",\n",
" instructions=(\n",
" \"You are a knowledgeable hotel front desk agent who specializes in local attractions. \"\n",
" \"When a guest asks about attractions in a city, provide a single, well-researched recommendation \"\n",
" \"for a popular tourist attraction. Focus on giving practical information including what makes \"\n",
" \"this attraction special, how long to spend there, and the best time to visit. \"\n",
" \"Be helpful and enthusiastic about your recommendation. \"\n",
" \"Return structured JSON matching the AttractionRecommendation schema.\"\n",
" ),\n",
")\n",
"\n",
"# Agent 2: Concierge Agent (Reviews and rates recommendations)\n",
"concierge_agent = provider.as_agent(\n",
" name=\"concierge-agent\",\n",
" instructions=(\n",
" \"You are an expert concierge with extensive knowledge of tourist attractions worldwide. \"\n",
" \"You will receive an attraction recommendation and must provide an expert review and rating. \"\n",
" \"Evaluate the recommendation based on the attraction's popularity, visitor satisfaction, \"\n",
" \"and overall quality. Provide a popularity score (1-10), visitor rating (1.0-5.0), \"\n",
" \"list pros and cons, and give your professional assessment. \"\n",
" \"Also suggest alternative attractions if appropriate. \"\n",
" \"Return structured JSON matching the AttractionReview schema.\"\n",
" ),\n",
")\n",
"\n"
]
},
{
"cell_type": "markdown",
"id": "0afa99f5",
"metadata": {},
"source": [
"## Step 4: Build the Sequential Workflow\n",
"\n",
"`WorkflowBuilder` creates a workflow where:\n",
"1. **Front Desk Agent** receives user input and makes a recommendation\n",
"2. **Concierge Agent** receives the front desk recommendation and provides expert review\n",
"3. **Output** contains both the original recommendation and the expert review"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d76c5b11",
"metadata": {},
"outputs": [],
"source": [
"# Build the sequential workflow with WorkflowBuilder\n",
"workflow = (\n",
" WorkflowBuilder(\n",
" start_executor=front_desk_agent,\n",
" output_executors=[front_desk_agent, concierge_agent],\n",
" )\n",
" .add_edge(front_desk_agent, concierge_agent)\n",
" .build()\n",
")\n",
"\n",
"display(HTML(\"\"\"\n",
"<div style='padding: 20px; background: linear-gradient(135deg, #ff7043 0%, #ff5722 100%); color: white; border-radius: 8px; margin: 10px 0;'>\n",
" <h3 style='margin: 0 0 15px 0;'>Sequential Workflow Built Successfully!</h3>\n",
" <p style='margin: 0; line-height: 1.6;'>\n",
" <strong>Flow:</strong><br>\n",
" • User Input → <strong>Front Desk Agent</strong> (recommendation)<br>\n",
" • Front Desk Output → <strong>Concierge Agent</strong> (review & rating)<br>\n",
" • Final Output → Combined recommendation + expert review\n",
" </p>\n",
"</div>\n",
"\"\"\"))"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "27a52679",
"metadata": {},
"outputs": [],
"source": [
"async def display_attraction_recommendation(city: str):\n",
" \"\"\"Run the sequential workflow and display formatted results.\"\"\"\n",
"\n",
" display(HTML(f\"\"\"\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;'>Processing Attraction Recommendation for {city}</h3>\n",
" <p style='margin: 0;'><strong>Status:</strong> Running sequential workflow...</p>\n",
" </div>\n",
" \"\"\"))\n",
"\n",
" # Run the workflow. With WorkflowBuilder(output_executors=[a1, a2]),\n",
" # outputs is a list of AgentResponse objects, one per output executor.\n",
" events = await workflow.run(f\"I want to visit an attraction in {city}\")\n",
" outputs = events.get_outputs()\n",
"\n",
" front_desk_response = outputs[0].text if len(outputs) > 0 else None\n",
" concierge_response = outputs[1].text if len(outputs) > 1 else None\n",
"\n",
" # Display results\n",
" display(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",
" <h2 style='margin: 0 0 20px 0;'>Attraction Recommendation for {city}</h2>\n",
" <p style='margin: 0; font-size: 14px; opacity: 0.9;'>Generated by sequential agent workflow</p>\n",
" </div>\n",
" \"\"\"))\n",
"\n",
" # Process and display responses\n",
" if front_desk_response:\n",
" try:\n",
" recommendation_data = AttractionRecommendation.model_validate_json(front_desk_response)\n",
" display_front_desk_section(recommendation_data)\n",
" except Exception as e:\n",
" display(HTML(f\"\"\"\n",
" <div style='padding: 15px; background: #ffcdd2; border-left: 4px solid #f44336; border-radius: 4px; margin: 10px 0;'>\n",
" <strong>Error parsing front desk response:</strong> {str(e)}\n",
" <details><summary>Raw response</summary>{front_desk_response}</details>\n",
" </div>\n",
" \"\"\"))\n",
"\n",
" if concierge_response:\n",
" try:\n",
" review_data = AttractionReview.model_validate_json(concierge_response)\n",
" display_concierge_section(review_data)\n",
" except Exception as e:\n",
" display(HTML(f\"\"\"\n",
" <div style='padding: 15px; background: #ffcdd2; border-left: 4px solid #f44336; border-radius: 4px; margin: 10px 0;'>\n",
" <strong>Error parsing concierge response:</strong> {str(e)}\n",
" <details><summary>Raw response</summary>{concierge_response}</details>\n",
" </div>\n",
" \"\"\"))\n",
"\n",
"\n",
"def display_front_desk_section(data: AttractionRecommendation):\n",
" \"\"\"Display front desk recommendation in a formatted section.\"\"\"\n",
"\n",
" display(HTML(f\"\"\"\n",
" <div style='padding: 20px; background: #e3f2fd; border-radius: 8px; margin: 15px 0; border-left: 4px solid #2196f3;'>\n",
" <h3 style='margin: 0 0 15px 0; color: #1976d2;'>🏨 Front Desk Recommendation</h3>\n",
" <div style='margin-bottom: 15px;'>\n",
" <h4 style='margin: 0 0 8px 0; color: #333;'>{data.attraction_name}</h4>\n",
" <span style='background: #2196f3; color: white; padding: 4px 8px; border-radius: 12px; font-size: 12px;'>{data.category}</span>\n",
" </div>\n",
" <div style='margin-bottom: 15px;'>\n",
" <strong style='color: #333;'>Description:</strong> {data.description}\n",
" </div>\n",
" <div style='margin-bottom: 10px;'>\n",
" <strong style='color: #333;'>Why Recommended:</strong> {data.why_recommended}\n",
" </div>\n",
" <div style='margin-bottom: 10px;'>\n",
" <strong style='color: #333;'>Recommended Duration:</strong> {data.recommended_duration}\n",
" </div>\n",
" <div>\n",
" <strong style='color: #333;'>Best Time to Visit:</strong> {data.best_time_to_visit}\n",
" </div>\n",
" </div>\n",
" \"\"\"))\n",
"\n",
"\n",
"def display_concierge_section(data: AttractionReview):\n",
" \"\"\"Display concierge review in a formatted section.\"\"\"\n",
"\n",
" # Create star rating display\n",
" star_rating = \"⭐\" * int(data.visitor_rating) + \"☆\" * (5 - int(data.visitor_rating))\n",
"\n",
" # Create popularity bar\n",
" popularity_bar = \"🟩\" * data.popularity_score + \"⬜\" * (10 - data.popularity_score)\n",
"\n",
" pros_list = \"\".join([f\"<li style='color: #4caf50;'>✓ {pro}</li>\" for pro in data.pros])\n",
" cons_list = \"\".join([f\"<li style='color: #f44336;'>✗ {con}</li>\" for con in data.cons])\n",
" alternatives_list = \"\".join([f\"<li>{alt}</li>\" for alt in data.alternative_suggestions])\n",
"\n",
" display(HTML(f\"\"\"\n",
" <div style='padding: 20px; background: #fff3e0; border-radius: 8px; margin: 15px 0; border-left: 4px solid #ff9800;'>\n",
" <h3 style='margin: 0 0 15px 0; color: #f57c00;'>🎩 Concierge Expert Review</h3>\n",
"\n",
" <div style='display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 20px;'>\n",
" <div style='background: rgba(255,152,0,0.1); padding: 15px; border-radius: 8px;'>\n",
" <h4 style='margin: 0 0 8px 0; color: #333;'>Popularity Score</h4>\n",
" <div style='font-size: 24px; font-weight: bold; color: #f57c00;'>{data.popularity_score}/10</div>\n",
" <div style='font-size: 12px; margin-top: 5px;'>{popularity_bar}</div>\n",
" </div>\n",
" <div style='background: rgba(255,152,0,0.1); padding: 15px; border-radius: 8px;'>\n",
" <h4 style='margin: 0 0 8px 0; color: #333;'>Visitor Rating</h4>\n",
" <div style='font-size: 20px; font-weight: bold; color: #f57c00;'>{data.visitor_rating}/5.0</div>\n",
" <div style='font-size: 16px; margin-top: 5px;'>{star_rating}</div>\n",
" </div>\n",
" </div>\n",
"\n",
" <div style='margin-bottom: 15px;'>\n",
" <strong style='color: #333;'>Popularity Reasoning:</strong> {data.popularity_reasoning}\n",
" </div>\n",
"\n",
" <div style='display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-bottom: 15px;'>\n",
" <div>\n",
" <h4 style='margin: 0 0 8px 0; color: #333;'>Pros:</h4>\n",
" <ul style='margin: 0; padding-left: 20px;'>{pros_list}</ul>\n",
" </div>\n",
" <div>\n",
" <h4 style='margin: 0 0 8px 0; color: #333;'>Cons:</h4>\n",
" <ul style='margin: 0; padding-left: 20px;'>{cons_list}</ul>\n",
" </div>\n",
" </div>\n",
" <div style='margin-bottom: 15px;'>\n",
" <strong style='color: #333;'>Concierge Recommendation:</strong> {data.concierge_recommendation}\n",
" </div>\n",
"\n",
" <div>\n",
" <h4 style='margin: 0 0 8px 0; color: #333;'>Alternative Suggestions:</h4>\n",
" <ul style='margin: 0; padding-left: 20px; color: #555;'>{alternatives_list}</ul>\n",
" </div>\n",
" </div>\n",
" \"\"\"))\n",
"\n",
"\n",
"# Test with Stockholm\n",
"await display_attraction_recommendation(\"Stockholm\")\n"
]
},
{
"cell_type": "markdown",
"id": "1840d000",
"metadata": {},
"source": [
"## Step 8: Workflow Analysis - Understanding Sequential Flow\n",
"\n",
"Let's examine how information flows between agents and analyze the conversation history."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "86e1bcc3",
"metadata": {},
"outputs": [],
"source": [
"async def analyze_sequential_flow(city: str):\n",
" \"\"\"Analyze the sequential flow between agents.\"\"\"\n",
"\n",
" display(HTML(f\"\"\"\n",
" <div style='padding: 20px; background: #f3e5f5; border-left: 4px solid #9c27b0; border-radius: 8px; margin: 20px 0;'>\n",
" <h3 style='margin: 0 0 10px 0; color: #7b1fa2;'>Sequential Flow Analysis for {city}</h3>\n",
" <p style='margin: 0;'>Examining agent interactions and information handoff...</p>\n",
" </div>\n",
" \"\"\"))\n",
"\n",
" # Run the workflow\n",
" user_input = f\"I want to visit an attraction in {city}\"\n",
" events = await workflow.run(user_input)\n",
" outputs = events.get_outputs()\n",
"\n",
" # Reconstruct the conversation flow as a list of (author, text) steps.\n",
" # outputs is a list of AgentResponse objects, ordered to match output_executors.\n",
" steps = [(\"user\", user_input)]\n",
" if len(outputs) > 0:\n",
" steps.append((\"front-desk-agent\", outputs[0].text))\n",
" if len(outputs) > 1:\n",
" steps.append((\"concierge-agent\", outputs[1].text))\n",
"\n",
" display(HTML(f\"\"\"\n",
" <div style='padding: 25px; background: #f3e5f5; border-radius: 12px; margin: 20px 0;'>\n",
" <h2 style='margin: 0 0 20px 0; color: #7b1fa2;'>Conversation Flow Analysis</h2>\n",
" </div>\n",
" \"\"\"))\n",
"\n",
" # Display each step in the sequence\n",
" for i, (author, text) in enumerate(steps, 1):\n",
" role_color = {\n",
" \"user\": \"#2196f3\",\n",
" \"front-desk-agent\": \"#4caf50\",\n",
" \"concierge-agent\": \"#ff9800\"\n",
" }.get(author, \"#666666\")\n",
"\n",
" role_name = {\n",
" \"user\": \"👤 User\",\n",
" \"front-desk-agent\": \"🏨 Front Desk Agent\",\n",
" \"concierge-agent\": \"🎩 Concierge Agent\"\n",
" }.get(author, \"Unknown\")\n",
"\n",
" # Truncate long messages for flow analysis\n",
" content_preview = text[:200] + \"...\" if len(text) > 200 else text\n",
" display(HTML(f\"\"\"\n",
" <div style='padding: 15px; background: white; border-left: 4px solid {role_color}; border-radius: 4px; margin: 10px 0; box-shadow: 0 2px 4px rgba(0,0,0,0.1);'>\n",
" <div style='display: flex; align-items: center; margin-bottom: 10px;'>\n",
" <span style='font-weight: bold; color: {role_color}; margin-right: 10px;'>Step {i}:</span>\n",
" <span style='font-weight: bold; color: {role_color};'>{role_name}</span>\n",
" </div>\n",
" <div style='color: #555; font-size: 14px; line-height: 1.4;'>\n",
" {content_preview}\n",
" </div>\n",
" </div>\n",
" \"\"\"))\n",
"\n",
" # Analyze the flow\n",
" display(HTML(f\"\"\"\n",
" <div style='padding: 20px; background: linear-gradient(135deg, #9c27b0 0%, #673ab7 100%); color: white; border-radius: 8px; margin: 20px 0;'>\n",
" <h3 style='margin: 0 0 15px 0;'>Flow Analysis Summary</h3>\n",
" <ul style='margin: 0; padding-left: 20px; line-height: 1.6;'>\n",
" <li><strong>Total Steps:</strong> {len(steps)}</li>\n",
" <li><strong>Agents Involved:</strong> 2 (Front Desk + Concierge)</li>\n",
" <li><strong>Flow Pattern:</strong> Linear sequential (User → Agent 1 → Agent 2)</li>\n",
" <li><strong>Information Handoff:</strong> Front desk recommendation becomes concierge input</li>\n",
" <li><strong>Output Quality:</strong> Enhanced through expert review and rating</li>\n",
" </ul>\n",
" </div>\n",
" \"\"\"))\n",
"\n",
"\n",
"# Analyze the flow for Barcelona\n",
"await analyze_sequential_flow(\"Barcelona\")\n"
]
}
],
"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
}