{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "a1b2c3d4",
   "metadata": {},
   "source": [
    "# Lesson 02 - Exploring Microsoft Agent Framework\n",
    "\n",
    "The **Microsoft Agent Framework (MAF)** is a unified framework for building AI agents. It provides a clean, composable architecture with four core building blocks:\n",
    "\n",
    "- **Client** – connects to an AI model endpoint and handles communication\n",
    "- **Agent** – wraps a client with instructions and tool definitions\n",
    "- **Tools** – extend agent capabilities with custom functions the model can call\n",
    "- **Session** – maintains conversation history for multi-turn interactions\n",
    "\n",
    "In this lesson, we'll build a **travel booking agent** that checks destination availability using these concepts."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b2c3d4e5",
   "metadata": {},
   "source": [
    "## Setup"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c3d4e5f6",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Install the Microsoft Agent Framework package\n",
    "! pip install agent-framework azure-ai-projects -U -q\n",
    "! pip install python-dotenv -q"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d4e5f6a7",
   "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 agent_framework import tool\n",
    "from agent_framework.foundry import FoundryChatClient\n",
    "from azure.identity import AzureCliCredential\n",
    "\n",
    "dotenv.load_dotenv(dotenv.find_dotenv())"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e5f6a7b8",
   "metadata": {},
   "source": [
    "## Understanding the Agent Framework Architecture\n",
    "\n",
    "The Microsoft Agent Framework follows a layered architecture:\n",
    "\n",
    "```\n",
    "Client  →  Agent  →  Tools\n",
    "                  →  Session\n",
    "```\n",
    "\n",
    "1. **Client** – A `FoundryChatClient` connects to an Azure OpenAI deployment. It handles authentication, request formatting, and response parsing.\n",
    "2. **Agent** – Created from the client via `provider.create_agent()`, the agent combines model access with instructions (system prompt) and tools.\n",
    "3. **Tools** – Python functions decorated with `@tool` that the agent can invoke to perform actions or retrieve data.\n",
    "4. **Session** – An `AgentSession` object (created via `agent.create_session()`) that stores conversation history, enabling multi-turn dialogue where the agent remembers prior context.\n",
    "\n",
    "Let's build each layer step by step."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f6a7b8c9",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Create the client – this is the connection to the AI model\n",
    "endpoint = os.getenv(\"AZURE_AI_PROJECT_ENDPOINT\")\n",
    "model = os.getenv(\"AZURE_AI_MODEL_DEPLOYMENT_NAME\")\n",
    "\n",
    "if not endpoint or not model:\n",
    "    raise ValueError(\n",
    "        \"Missing required environment variables. \"\n",
    "        \"Please set AZURE_AI_PROJECT_ENDPOINT and AZURE_AI_MODEL_DEPLOYMENT_NAME as environment variables (e.g., in your .env file or shell environment).\"\n",
    "    )\n",
    "\n",
    "provider = FoundryChatClient(\n",
    "    project_endpoint=endpoint,\n",
    "    model=model,\n",
    "    credential=AzureCliCredential()\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "a7b8c9d0",
   "metadata": {},
   "source": [
    "## Adding Tools with the @tool Decorator\n",
    "\n",
    "Tools let agents take actions beyond generating text. The `@tool` decorator converts a regular Python function into something the agent can call.\n",
    "\n",
    "Key points:\n",
    "- Use `Annotated[type, \"description\"]` so the model understands each parameter.\n",
    "- The docstring becomes the tool description the model sees.\n",
    "- `approval_mode=\"never_require\"` means the tool runs automatically without user confirmation."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b8c9d0e1",
   "metadata": {},
   "outputs": [],
   "source": [
    "@tool(approval_mode=\"never_require\")\n",
    "def check_destination_availability(\n",
    "    destination: Annotated[str, \"The destination to check availability for\"]\n",
    ") -> str:\n",
    "    \"\"\"Check if a vacation destination is currently available for booking.\"\"\"\n",
    "    available = {\n",
    "        \"Barcelona\": True,\n",
    "        \"Tokyo\": True,\n",
    "        \"Cape Town\": False,\n",
    "        \"Vancouver\": True,\n",
    "        \"Dubai\": False,\n",
    "    }\n",
    "    is_available = available.get(destination, False)\n",
    "    return f\"{destination} is {'available' if is_available else 'not available'} for booking.\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c9d0e1f2",
   "metadata": {},
   "source": [
    "## Creating an Agent with Tools\n",
    "\n",
    "Now we combine the client, instructions, and tools into an agent. The `instructions` act as the system prompt — they define the agent's persona and behaviour."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d0e1f2a3",
   "metadata": {},
   "outputs": [],
   "source": [
    "agent = provider.as_agent(\n",
    "    name=\"TravelAvailabilityAgent\",\n",
    "    instructions=(\n",
    "        \"You are a travel booking agent. Help users check destination availability \"\n",
    "        \"and make recommendations. Always check availability before recommending a destination.\"\n",
    "    ),\n",
    "    tools=[check_destination_availability],\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e1f2a3b4",
   "metadata": {},
   "source": [
    "## Multi-Turn Conversations with Sessions\n",
    "\n",
    "An `AgentSession` (created via `agent.create_session()`) keeps track of all messages in a conversation. By passing the same session to each `agent.run()` call, the agent has access to the full conversation history and can refer back to earlier messages.\n",
    "\n",
    "We pass `tools=[check_destination_availability]` so the agent can call our availability checker during each turn."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f2a3b4c5",
   "metadata": {},
   "outputs": [],
   "source": [
    "session = agent.create_session()\n",
    "\n",
    "# Turn 1: Ask about available destinations\n",
    "response = await agent.run(\n",
    "    \"Which destinations do you have available?\",\n",
    "    session=session,\n",
    ")\n",
    "print(f\"Agent: {response}\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a3b4c5d6",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Turn 2: Follow-up question — the agent remembers the conversation\n",
    "response = await agent.run(\n",
    "    \"I'd like to go somewhere warm. What's available?\",\n",
    "    session=session,\n",
    ")\n",
    "print(f\"Agent: {response}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b4c5d6e7",
   "metadata": {},
   "source": [
    "## Summary\n",
    "\n",
    "In this lesson you explored the four pillars of the Microsoft Agent Framework:\n",
    "\n",
    "| Concept | What You Learned |\n",
    "|---------|------------------|\n",
    "| **Client** | `FoundryChatClient` connects to Azure OpenAI with credential-based auth |\n",
    "| **Agent** | `provider.create_agent()` bundles a model connection with instructions and a name |\n",
    "| **Tools** | The `@tool` decorator exposes Python functions for the agent to call |\n",
    "| **Session** | `agent.create_session()` maintains conversation history across multiple turns |\n",
    "\n",
    "These building blocks compose together to create agents that can hold natural conversations, call external functions, and maintain context — the foundation for more advanced agentic patterns in later lessons."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "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
}
