308 lines
10 KiB
Plaintext
308 lines
10 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "8744544f",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Lesson 04 - Tool Use Design Pattern\n",
|
|
"\n",
|
|
"In this lesson you will learn the **Tool Use** design pattern for AI agents using the Microsoft Agent Framework (Python). We cover:\n",
|
|
"\n",
|
|
"- Defining function tools with the `@tool` decorator and typed parameters\n",
|
|
"- Providing tool schemas so the model knows what each tool does\n",
|
|
"- Controlling tool execution with `approval_mode`\n",
|
|
"- Returning **structured output** via Pydantic models and `response_format`\n",
|
|
"\n",
|
|
"The scenario is a **travel booking agent** that can look up destinations, check availability, and retrieve flight information."
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "b1a2c3d4",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Setup"
|
|
]
|
|
},
|
|
{
|
|
"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": [
|
|
"## Defining Tools with the @tool Decorator\n",
|
|
"\n",
|
|
"The `@tool` decorator turns a plain Python function into a tool that an agent can call.\n",
|
|
"Key points:\n",
|
|
"\n",
|
|
"- The **docstring** becomes the tool description the model sees.\n",
|
|
"- **Type annotations** (including `Annotated` with descriptions) define the tool schema.\n",
|
|
"- `approval_mode` controls whether the user must approve each call before it executes."
|
|
]
|
|
},
|
|
{
|
|
"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": [
|
|
"## Creating an Agent with Multiple Tools\n",
|
|
"\n",
|
|
"Pass all three tools to the client so the model can invoke whichever ones it needs to answer the user's question."
|
|
]
|
|
},
|
|
{
|
|
"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": [
|
|
"## Structured Output with Tools\n",
|
|
"\n",
|
|
"By setting `response_format` to a Pydantic model, the agent is forced to return a well-typed JSON object instead of free-form text. This is useful when downstream code needs to consume the result programmatically."
|
|
]
|
|
},
|
|
{
|
|
"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": [
|
|
"## Tool Approval Patterns\n",
|
|
"\n",
|
|
"The `approval_mode` parameter on `@tool` controls whether tool calls require human approval before executing:\n",
|
|
"\n",
|
|
"| Mode | Behaviour |\n",
|
|
"|---|---|\n",
|
|
"| `\"never_require\"` | Tool runs automatically — no user confirmation needed. |\n",
|
|
"| `\"always_require\"` | Every call must be approved by the user before it executes. |\n",
|
|
"\n",
|
|
"Use `\"always_require\"` for tools that have side-effects (e.g. booking a flight, charging a credit card) so a human stays in the loop."
|
|
]
|
|
},
|
|
{
|
|
"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": [
|
|
"## Summary\n",
|
|
"\n",
|
|
"In this lesson you learned how to:\n",
|
|
"\n",
|
|
"1. **Define tools** using the `@tool` decorator with typed parameters and docstrings that serve as the tool schema.\n",
|
|
"2. **Compose multiple tools** so the agent can call them in sequence to answer complex queries.\n",
|
|
"3. **Return structured output** by passing a Pydantic model as `response_format`.\n",
|
|
"4. **Control tool approval** with `approval_mode` to keep a human in the loop for sensitive operations.\n",
|
|
"\n",
|
|
"These patterns form the foundation for building reliable, production-ready agents that can interact with external systems safely."
|
|
]
|
|
}
|
|
],
|
|
"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
|
|
}
|