{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "lesson-intro",
   "metadata": {},
   "source": [
    "# 第03课 - 代理设计模式\n",
    "\n",
    "在本课中，我们将探索构建高效 AI 代理的三个基础设计模式：\n",
    "\n",
    "1. <strong>清晰的代理指令</strong> — 制作精确的、定义角色的提示，以指导代理行为\n",
    "2. **使用 Pydantic 模型的结构化输出** — 确保代理返回可预测、已验证的数据\n",
    "3. <strong>单一职责代理</strong> — 设计专注的代理，每个代理专注做好一件事\n",
    "\n",
    "我们将把每种模式应用于一个<strong>旅游目的地推荐系统</strong>场景，逐步构建一个能够推荐目的地、检查可用性和处理物流的系统。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "setup-header",
   "metadata": {},
   "source": [
    "## 设置\n"
   ]
  },
  {
   "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": [
    "## 模式1：明确的代理指令\n",
    "\n",
    "最有影响力的模式也是最简单的：为你的代理编写清晰、详细的指令。\n",
    "\n",
    "良好的指令应定义：\n",
    "- <strong>代理是谁</strong>（角色和语气）\n",
    "- <strong>代理该做什么</strong>（逐步职责）\n",
    "- <strong>代理应如何表现</strong>（约束和风格）\n",
    "\n",
    "下面，我们创建一个旅行礼宾代理，带有明确的指令来塑造它生成的每个回复。\n"
   ]
  },
  {
   "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": [
    "## 模式 2：使用 Pydantic 模型的结构化输出\n",
    "\n",
    "自由形式文本对对话很有用，但下游系统需要结构化数据。\n",
    "通过将 **Pydantic 模型** 与 <strong>工具函数</strong> 配对，我们可以：\n",
    "\n",
    "- 定义代理输出的精确定义模式\n",
    "- 自动验证响应\n",
    "- 可靠地将代理结果集成到应用逻辑中\n",
    "\n",
    "执行的关键是在运行代理时传递 `response_format`。这会强制\n",
    "模型返回一个经过验证的 `TravelRecommendations` 对象（可通过 `response.value` 访问）\n",
    "，而不是自由格式文本。`get_destination_details` 工具也返回类型化的\n",
    "`DestinationRecommendation`，因此数据从始至终保持结构化。\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": [
    "## 模式 3：单一职责代理\n",
    "\n",
    "复杂任务通过将工作拆分为多个专注的代理来执行，每个代理负责单一职责：\n",
    "\n",
    "- 一个了解地点和可用性的 <strong>目的地专家</strong>\n",
    "- 一个处理航班、酒店和行程的 <strong>物流规划师</strong>\n",
    "\n",
    "这与软件工程中的<em>关注点分离</em>原则相呼应——每个代理都更容易独立测试、维护和改进。\n"
   ]
  },
  {
   "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": [
    "## 总结\n",
    "\n",
    "在本课中，我们将三个主动设计模式应用于旅游推荐场景：\n",
    "\n",
    "| 模式 | 关键思想 | 优势 |\n",
    "|---|---|---|\n",
    "| <strong>明确指令</strong> | 预先定义角色、职责和约束 | 保持一致、符合品牌形象的代理行为 |\n",
    "| <strong>结构化输出</strong> | 使用Pydantic模型作为响应格式 | 经过验证、机器可读的结果 |\n",
    "| <strong>单一职责</strong> | 让每个代理专注于一项工作 | 更易测试、维护和组合 |\n",
    "\n",
    "这些模式自然组合——你可以将明确指令与结构化输出结合到单一职责代理中，构建健壮、适合生产的系统。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n\n<!-- CO-OP TRANSLATOR DISCLAIMER START -->\n**免责声明**：\n本文件由 AI 翻译服务 [Co-op Translator](https://github.com/Azure/co-op-translator) 翻译完成。尽管我们力求准确，但请注意，自动翻译可能包含错误或不准确之处。原始语言版文件应视为权威来源。对于重要信息，建议使用专业人工翻译。我们对因使用本翻译而产生的任何误解或误释不承担责任。\n<!-- CO-OP TRANSLATOR DISCLAIMER END -->\n"
   ]
  }
 ],
 "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
}