{ "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 }