323 lines
12 KiB
Plaintext
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
|
|
} |