{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "8744544f",
   "metadata": {},
   "source": [
    "# 课程 04 - 工具使用设计模式\n",
    "\n",
    "在本课中，您将学习使用 Microsoft Agent Framework (Python) 的 AI 代理的<strong>工具使用</strong>设计模式。我们涵盖：\n",
    "\n",
    "- 使用 `@tool` 装饰器和类型化参数定义函数工具\n",
    "- 提供工具模式，让模型了解每个工具的功能\n",
    "- 使用 `approval_mode` 控制工具执行\n",
    "- 通过 Pydantic 模型和 `response_format` 返回<strong>结构化输出</strong>\n",
    "\n",
    "方案是一个<strong>旅游预订代理</strong>，可以查询目的地，检查可用性，并检索航班信息。\n"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b1a2c3d4",
   "metadata": {},
   "source": [
    "## 设置\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "59c0feeb",
   "metadata": {},
   "outputs": [],
   "source": [
    "%pip install agent-framework azure-ai-projects azure-identity python-dotenv -U -q"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c0df8a52",
   "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",
    "\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",
    "    )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a6141584",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Create the Microsoft Foundry client\n",
    "client = FoundryChatClient(\n",
    "    project_endpoint=endpoint,\n",
    "    model=deployment_name,\n",
    "    credential=DefaultAzureCredential()\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d5e6f7a8",
   "metadata": {},
   "source": [
    "## 使用 @tool 装饰器定义工具\n",
    "\n",
    "`@tool` 装饰器将普通的 Python 函数转换为代理可以调用的工具。\n",
    "关键点：\n",
    "\n",
    "- <strong>文档字符串</strong> 成为模型看到的工具描述。\n",
    "- <strong>类型注解</strong>（包括带描述的 `Annotated`）定义工具的模式。\n",
    "- `approval_mode` 控制是否必须在执行前让用户批准每次调用。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a6507f83",
   "metadata": {},
   "outputs": [],
   "source": [
    "@tool(approval_mode=\"never_require\")\n",
    "def get_destinations() -> list[str]:\n",
    "    \"\"\"Get available vacation destinations.\"\"\"\n",
    "    return [\"Barcelona\", \"Paris\", \"Berlin\", \"Tokyo\", \"Sydney\", \"New York City\"]\n",
    "\n",
    "\n",
    "@tool(approval_mode=\"never_require\")\n",
    "def check_availability(\n",
    "    destination: Annotated[str, \"The destination to check\"],\n",
    ") -> str:\n",
    "    \"\"\"Check booking availability for a destination.\"\"\"\n",
    "    availability = {\n",
    "        \"Barcelona\": \"Available - 3 spots left\",\n",
    "        \"Paris\": \"Available\",\n",
    "        \"Berlin\": \"Sold out\",\n",
    "        \"Tokyo\": \"Available - 1 spot left\",\n",
    "        \"Sydney\": \"Available\",\n",
    "        \"New York City\": \"Available\",\n",
    "    }\n",
    "    return availability.get(destination, \"Unknown destination\")\n",
    "\n",
    "\n",
    "@tool(approval_mode=\"never_require\")\n",
    "def get_flight_info(\n",
    "    origin: Annotated[str, \"Origin airport code\"],\n",
    "    destination: Annotated[str, \"Destination airport code\"],\n",
    ") -> str:\n",
    "    \"\"\"Get flight information between two cities.\"\"\"\n",
    "    flights = {\n",
    "        \"LHR-BCN\": \"BA 2042, Departs 08:30, Arrives 11:45, $350\",\n",
    "        \"LHR-CDG\": \"AF 1081, Departs 09:15, Arrives 11:30, $280\",\n",
    "        \"LHR-NRT\": \"JL 044, Departs 11:00, Arrives 07:00+1, $890\",\n",
    "    }\n",
    "    return flights.get(\n",
    "        f\"{origin}-{destination}\",\n",
    "        f\"No direct flights from {origin} to {destination}\",\n",
    "    )"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e9f0a1b2",
   "metadata": {},
   "source": [
    "## 创建一个拥有多种工具的代理\n",
    "\n",
    "将所有三种工具传递给客户端，这样模型就可以调用它们中的任意一个来回答用户的问题。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "be18ac4f",
   "metadata": {},
   "outputs": [],
   "source": [
    "travel_tools = [get_destinations, check_availability, get_flight_info]\n",
    "\n",
    "agent = client.as_agent(\n",
    "    name=\"TravelToolAgent\",\n",
    "    instructions=\"You are a travel agent. Use the available tools to answer questions about destinations, availability, and flights.\",\n",
    "    tools=travel_tools,\n",
    ")\n",
    "\n",
    "response = await agent.run(\n",
    "    \"What destinations do you have? Which ones are still available?\"\n",
    ")\n",
    "print(response)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c3d4e5f6",
   "metadata": {},
   "source": [
    "## 使用工具进行结构化输出\n",
    "\n",
    "通过将 `response_format` 设置为 Pydantic 模型，代理被强制返回一个类型良好的 JSON 对象，而不是自由格式的文本。当下游代码需要以编程方式消费结果时，这很有用。\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "772e9481",
   "metadata": {},
   "outputs": [],
   "source": [
    "class BookingRecommendation(BaseModel):\n",
    "    destination: str\n",
    "    available: bool\n",
    "    flight_details: str\n",
    "    estimated_cost: int\n",
    "\n",
    "\n",
    "class TravelPlan(BaseModel):\n",
    "    recommendations: list[BookingRecommendation]\n",
    "\n",
    "\n",
    "structured_agent = client.as_agent(\n",
    "    name=\"StructuredTravelAgent\",\n",
    "    instructions=(\n",
    "        \"You are a travel agent. Use the available tools to find destinations, \"\n",
    "        \"check availability, and get flight info. Return structured results.\"\n",
    "    ),\n",
    "    tools=[get_destinations, check_availability, get_flight_info],\n",
    ")\n",
    "\n",
    "response = await structured_agent.run(\n",
    "    \"I want to fly from London Heathrow to somewhere warm in Europe. \"\n",
    "    \"Check what's available.\"\n",
    ")\n",
    "if response:\n",
    "    print(response)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a7b8c9d0",
   "metadata": {},
   "source": [
    "## 工具批准模式\n",
    "\n",
    "`@tool` 上的 `approval_mode` 参数控制工具调用在执行前是否需要人工批准：\n",
    "\n",
    "| 模式 | 行为 |\n",
    "|---|---|\n",
    "| `\"never_require\"` | 工具自动运行 — 不需要用户确认。 |\n",
    "| `\"always_require\"` | 每次调用都必须得到用户批准后才能执行。 |\n",
    "\n",
    "对于有副作用的工具（例如预订航班、扣费信用卡），使用 `\"always_require\"`，以确保有人介入。 \n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a731b547",
   "metadata": {},
   "outputs": [],
   "source": [
    "@tool(approval_mode=\"always_require\")\n",
    "def book_flight(\n",
    "    origin: Annotated[str, \"Origin airport code\"],\n",
    "    destination: Annotated[str, \"Destination airport code\"],\n",
    "    passenger_name: Annotated[str, \"Full name of the passenger\"],\n",
    ") -> str:\n",
    "    \"\"\"Book a flight for a passenger. Requires approval before executing.\"\"\"\n",
    "    return (\n",
    "        f\"Flight booked from {origin} to {destination} \"\n",
    "        f\"for {passenger_name}. Confirmation #TRV-2024-{hash(passenger_name) % 10000:04d}\"\n",
    "    )\n",
    "\n",
    "\n",
    "print(\"Tool name:\", book_flight.name)\n",
    "print(\"Approval mode:\", book_flight.approval_mode)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f1e2d3c4",
   "metadata": {},
   "source": [
    "## 总结\n",
    "\n",
    "在本课中，您学习了如何：\n",
    "\n",
    "1. 使用带有类型参数和文档字符串的 `@tool` 装饰器<strong>定义工具</strong>，这些文档字符串用作工具模式。\n",
    "2. <strong>组合多个工具</strong>，以便代理能够按顺序调用它们来回答复杂查询。\n",
    "3. 通过传递 Pydantic 模型作为 `response_format`，<strong>返回结构化输出</strong>。\n",
    "4. 使用 `approval_mode` <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.0"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}