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

323 lines
12 KiB
Plaintext

{
"cells": [
{
"cell_type": "markdown",
"id": "lesson-intro",
"metadata": {},
"source": [
"# Lesson 03 - Agentic Design Patterns\n",
"\n",
"In this lesson, we explore three foundational design patterns for building effective AI agents:\n",
"\n",
"1. **Clear Agent Instructions** — Crafting precise, role-defining prompts that guide agent behavior\n",
"2. **Structured Output with Pydantic Models** — Ensuring agents return predictable, validated data\n",
"3. **Single Responsibility Agents** — Designing focused agents that each do one thing well\n",
"\n",
"We'll apply each pattern to a **travel destination recommender** scenario, progressively building a system that can suggest destinations, check availability, and handle logistics."
]
},
{
"cell_type": "markdown",
"id": "setup-header",
"metadata": {},
"source": [
"## Setup"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "setup-code",
"metadata": {},
"outputs": [],
"source": [
"%pip install agent-framework azure-ai-projects azure-identity pydantic python-dotenv --quiet"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "imports",
"metadata": {},
"outputs": [],
"source": [
"import logging\n",
"logging.getLogger(\"agent_framework.foundry\").setLevel(logging.ERROR)\n",
"\n",
"import os\n",
"import asyncio\n",
"import dotenv\n",
"from typing import Annotated\n",
"from pydantic import BaseModel\n",
"from agent_framework import tool\n",
"from agent_framework.foundry import FoundryChatClient\n",
"from azure.identity import DefaultAzureCredential\n",
"\n",
"dotenv.load_dotenv(dotenv.find_dotenv())\n",
"\n",
"endpoint = os.getenv(\"AZURE_AI_PROJECT_ENDPOINT\")\n",
"deployment_name = os.getenv(\"AZURE_AI_MODEL_DEPLOYMENT_NAME\")\n",
"\n",
"missing = [k for k, v in {\n",
" \"AZURE_AI_PROJECT_ENDPOINT\": endpoint,\n",
" \"AZURE_AI_MODEL_DEPLOYMENT_NAME\": deployment_name\n",
"}.items() if not v]\n",
"\n",
"if missing:\n",
" raise ValueError(\n",
" f\"Missing required environment variables: {', '.join(missing)}. \"\n",
" \"Please set them as environment variables (e.g., in your .env file or shell environment).\"\n",
" )\n",
"\n",
"provider = FoundryChatClient(\n",
" project_endpoint=endpoint,\n",
" model=deployment_name,\n",
" credential=DefaultAzureCredential()\n",
")"
]
},
{
"cell_type": "markdown",
"id": "pattern1-header",
"metadata": {},
"source": [
"## Pattern 1: Clear Agent Instructions\n",
"\n",
"The most impactful pattern is also the simplest: writing clear, detailed instructions for your agent.\n",
"\n",
"Good instructions define:\n",
"- **Who** the agent is (persona and tone)\n",
"- **What** it should do (step-by-step responsibilities)\n",
"- **How** it should behave (constraints and style)\n",
"\n",
"Below, we create a travel concierge agent with explicit instructions that shape every response it produces."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "pattern1-code",
"metadata": {},
"outputs": [],
"source": [
"agent = provider.as_agent(\n",
" name=\"TravelConcierge\",\n",
" instructions=\"\"\"You are a luxury travel concierge named Alex. Your role is to:\n",
"1. Understand the traveler's preferences (budget, climate, activities)\n",
"2. Check destination availability before making recommendations\n",
"3. Provide detailed, personalized travel suggestions\n",
"4. Always mention visa requirements and best travel seasons\n",
"Be warm, professional, and enthusiastic about travel.\"\"\",\n",
")\n",
"\n",
"response = await agent.run(\n",
" \"I'd love a week-long vacation somewhere with great food and history. Budget around $2500.\"\n",
")\n",
"print(response)"
]
},
{
"cell_type": "markdown",
"id": "pattern2-header",
"metadata": {},
"source": [
"## Pattern 2: Structured Output with Pydantic Models\n",
"\n",
"Free-form text is useful for conversation, but downstream systems need structured data.\n",
"By pairing **Pydantic models** with a **tool function**, we can:\n",
"\n",
"- Define an exact schema for the agent's output\n",
"- Validate responses automatically\n",
"- Integrate agent results into application logic reliably\n",
"\n",
"The key to enforcement is passing `response_format` when we run the agent. This forces the\n",
"model to return a validated `TravelRecommendations` object (available on `response.value`)\n",
"instead of free-form text. The `get_destination_details` tool also returns a typed\n",
"`DestinationRecommendation`, so the data stays structured end to end.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "pattern2-code",
"metadata": {},
"outputs": [],
"source": [
"class DestinationRecommendation(BaseModel):\n",
" destination: str\n",
" available: bool\n",
" best_season: str\n",
" highlights: list[str]\n",
" estimated_budget_usd: int\n",
"\n",
"\n",
"class TravelRecommendations(BaseModel):\n",
" recommendations: list[DestinationRecommendation]\n",
" personalized_note: str\n",
"\n",
"\n",
"@tool(approval_mode=\"never_require\")\n",
"def get_destination_details(\n",
" destination: Annotated[str, \"The destination to look up\"]\n",
") -> DestinationRecommendation:\n",
" \"\"\"Get structured details about a vacation destination.\"\"\"\n",
" details = {\n",
" \"Barcelona\": DestinationRecommendation(\n",
" destination=\"Barcelona\",\n",
" available=True,\n",
" best_season=\"May-Jun\",\n",
" highlights=[\"Beach\", \"Architecture\", \"Nightlife\"],\n",
" estimated_budget_usd=2000,\n",
" ),\n",
" \"Tokyo\": DestinationRecommendation(\n",
" destination=\"Tokyo\",\n",
" available=True,\n",
" best_season=\"Mar-Apr\",\n",
" highlights=[\"Culture\", \"Food\", \"Technology\"],\n",
" estimated_budget_usd=2500,\n",
" ),\n",
" \"Cape Town\": DestinationRecommendation(\n",
" destination=\"Cape Town\",\n",
" available=False,\n",
" best_season=\"Nov-Mar\",\n",
" highlights=[\"Nature\", \"Wine\", \"Adventure\"],\n",
" estimated_budget_usd=1800,\n",
" ),\n",
" }\n",
" return details.get(\n",
" destination,\n",
" DestinationRecommendation(\n",
" destination=destination,\n",
" available=False,\n",
" best_season=\"Unknown\",\n",
" highlights=[],\n",
" estimated_budget_usd=0,\n",
" ),\n",
" )\n",
"\n",
"\n",
"structured_agent = provider.as_agent(\n",
" name=\"StructuredTravelExpert\",\n",
" instructions=\"You are a travel expert. Recommend destinations based on traveler preferences. Use the get_destination_details tool.\",\n",
" tools=[get_destination_details],\n",
")\n",
"\n",
"# Passing `response_format` forces the agent to return a validated\n",
"# TravelRecommendations object instead of free-form text.\n",
"response = await structured_agent.run(\n",
" \"Recommend 3 destinations for a culture-loving traveler with a $2500 budget\",\n",
" options={\"response_format\": TravelRecommendations},\n",
")\n",
"\n",
"if response and response.value:\n",
" result: TravelRecommendations = response.value\n",
" for rec in result.recommendations:\n",
" status = \"Available\" if rec.available else \"Not available\"\n",
" print(f\"{rec.destination} ({status})\")\n",
" print(f\" Best season: {rec.best_season}\")\n",
" print(f\" Highlights: {', '.join(rec.highlights)}\")\n",
" print(f\" Estimated budget: ${rec.estimated_budget_usd}\")\n",
" print()\n",
" print(f\"Note: {result.personalized_note}\")\n",
"else:\n",
" print(\"No validated structured response was returned.\")\n",
" print(response)\n"
]
},
{
"cell_type": "markdown",
"id": "pattern3-header",
"metadata": {},
"source": [
"## Pattern 3: Single Responsibility Agents\n",
"\n",
"Complex tasks benefit from splitting work across multiple focused agents, each with a single responsibility:\n",
"\n",
"- A **Destination Expert** that knows about places and availability\n",
"- A **Logistics Planner** that handles flights, hotels, and itineraries\n",
"\n",
"This mirrors the software engineering principle of *separation of concerns* — each agent is easier to test, maintain, and improve independently."
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "pattern3-code",
"metadata": {},
"outputs": [],
"source": [
"destination_agent = provider.as_agent(\n",
" name=\"DestinationExpert\",\n",
" tools=[get_destination_details],\n",
" instructions=\"\"\"You are a destination research specialist. Your only job is to:\n",
"1. Evaluate destinations based on traveler preferences\n",
"2. Check availability using the provided tool\n",
"3. Return a short ranked list with pros/cons\n",
"Do NOT discuss flights, hotels, or logistics — another agent handles that.\"\"\",\n",
")\n",
"\n",
"logistics_agent = provider.as_agent(\n",
" name=\"LogisticsPlanner\",\n",
" instructions=\"\"\"You are a travel logistics planner. Your only job is to:\n",
"1. Create a day-by-day itinerary for the chosen destination\n",
"2. Suggest flight and hotel options within the stated budget\n",
"3. Note visa requirements and travel insurance recommendations\n",
"Do NOT recommend destinations — another agent handles that.\"\"\",\n",
")\n",
"\n",
"# Step 1: Destination Expert picks the best options\n",
"dest_response = await destination_agent.run(\n",
" \"I want a week of culture and food for under $2500. Where should I go?\"\n",
")\n",
"print(\"=== Destination Expert ===\")\n",
"print(dest_response)\n",
"\n",
"# Step 2: Logistics Planner builds the trip plan\n",
"logistics_response = await logistics_agent.run(\n",
" f\"Plan a week-long trip based on this recommendation:\\n{dest_response}\"\n",
")\n",
"print(\"\\n=== Logistics Planner ===\")\n",
"print(logistics_response)"
]
},
{
"cell_type": "markdown",
"id": "summary",
"metadata": {},
"source": [
"## Summary\n",
"\n",
"In this lesson we applied three agentic design patterns to a travel recommender scenario:\n",
"\n",
"| Pattern | Key Idea | Benefit |\n",
"|---|---|---|\n",
"| **Clear Instructions** | Define persona, responsibilities, and constraints up front | Consistent, on-brand agent behavior |\n",
"| **Structured Output** | Use Pydantic models as the response format | Validated, machine-readable results |\n",
"| **Single Responsibility** | Give each agent one focused job | Easier to test, maintain, and compose |\n",
"\n",
"These patterns compose naturally — you can combine clear instructions with structured output inside a single-responsibility agent to build robust, production-ready systems."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"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.13"
}
},
"nbformat": 4,
"nbformat_minor": 5
}