{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Lesson 13 - Agent Memory" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Setup\n", "\n", "This notebook demonstrates how to build a travel booking agent with **persistent memory** using the **Microsoft Agent Framework** (MAF).\n", "\n", "You will learn how different types of agent memory — working, short-term, and long-term — affect how an agent retains and uses information across conversations.\n", "\n", "**Prerequisites:**\n", "- An Azure AI Foundry project with a deployed chat model (e.g. `gpt-4o-mini`).\n", "- Logged in with the Azure CLI — run `az login` in your terminal.\n", "- `AZURE_AI_PROJECT_ENDPOINT` — your Azure AI Foundry project endpoint.\n", "- `AZURE_AI_MODEL_DEPLOYMENT_NAME` — the name of your deployed model." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%pip install agent-framework azure-ai-projects azure-identity -q" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import logging\n", "logging.getLogger(\"agent_framework.azure\").setLevel(logging.ERROR)\n", "\n", "import os\n", "import json\n", "from typing import Annotated\n", "from datetime import datetime\n", "\n", "from dotenv import load_dotenv\n", "\n", "from agent_framework import tool\n", "from agent_framework.azure import AzureAIProjectAgentProvider\n", "from azure.identity import AzureCliCredential\n", "\n", "load_dotenv()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "provider = AzureAIProjectAgentProvider(credential=AzureCliCredential())\n", "\n", "print(\"✅ AzureAIProjectAgentProvider created\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Types of Agent Memory\n", "\n", "AI agents can leverage different kinds of memory, each serving a distinct purpose:\n", "\n", "### Working Memory\n", "The conversation thread itself — the messages exchanged in a single session. The agent can refer back to earlier messages in the same thread to maintain coherence. In MAF this is created via **`agent.create_session()`**, which returns an `AgentSession`.\n", "\n", "### Short-Term Memory\n", "Information that persists for the duration of a task or session but is not stored permanently. For example, the agent may accumulate facts during a multi-turn planning conversation and use them to produce a final itinerary.\n", "\n", "### Long-Term Memory\n", "Preferences and facts that persist **across sessions**. A returning user should not have to repeat their dietary restrictions or travel style. Long-term memory is typically backed by an external store — a database, file, or vector index — and surfaced to the agent via tools." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Working Memory with Sessions\n", "\n", "The simplest form of memory is the conversation session. When you pass the same session (created via `agent.create_session()`) to successive `agent.run()` calls, the agent sees the full history of that conversation and can recall earlier details.\n", "\n", "Let's create a travel agent and demonstrate working memory." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "agent = await provider.create_agent(\n", " tools=[save_preference, get_preferences],\n", " name=\"TravelMemoryAgent\",\n", " instructions=(\n", " \"You are a travel agent who remembers user preferences across conversations. \"\n", " \"Track destinations mentioned, budget constraints, and travel dates.\"\n", " ),\n", ")\n", "\n", "session = agent.create_session()\n", "\n", "# First message — the user shares preferences\n", "response = await agent.run(\n", " \"I love beach destinations and my budget is $3000\",\n", " session=session,\n", ")\n", "print(\"Agent:\", response)\n", "\n", "# Second message — the agent should recall the budget from the thread\n", "response = await agent.run(\n", " \"What did I say my budget was?\",\n", " session=session,\n", ")\n", "print(\"Agent:\", response)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The agent correctly recalled the budget because both messages share the same session. This is **working memory** — it exists only for the lifetime of the session.\n", "\n", "### What happens with a new thread?\n", "\n", "If we create a **new** session, the agent has no memory of the previous conversation:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "new_session = agent.create_session()\n", "\n", "response = await agent.run(\n", " \"What is my budget?\",\n", " session=new_session,\n", ")\n", "print(\"Agent:\", response)\n", "print(\"\\n💡 The agent has no memory of the previous conversation — it's a fresh session.\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Long-Term Memory Pattern\n", "\n", "To remember user preferences **across sessions**, we need a persistent store that lives outside the conversation thread. The agent accesses this store through **tools** — functions it can call to save and retrieve information.\n", "\n", "Below we implement a simple in-memory preference store (in production you would back this with a database or vector index) and expose it as tools the agent can use.\n", "\n", "### Architecture\n", "```\n", "┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐\n", "│ MAF Agent │────▶│ @tool functions │────▶│ Preference │\n", "│ (LLM) │ │ save / retrieve │ │ Store (dict) │\n", "└─────────────────┘ └──────────────────┘ └─────────────────┘\n", " │ │\n", " AgentSession Persists across\n", " (working memory) sessions\n", "```" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# --- Persistent preference store (simulated) ---\n", "preference_store: dict[str, list[str]] = {}\n", "\n", "\n", "@tool(approval_mode=\"never_require\")\n", "def save_preference(\n", " user_id: Annotated[str, \"User identifier\"],\n", " preference: Annotated[str, \"A travel preference to remember\"],\n", ") -> str:\n", " \"\"\"Save a user travel preference to long-term memory.\"\"\"\n", " preference_store.setdefault(user_id, []).append(preference)\n", " return f\"✅ Stored: {preference}\"\n", "\n", "\n", "@tool(approval_mode=\"never_require\")\n", "def get_preferences(\n", " user_id: Annotated[str, \"User identifier\"],\n", ") -> str:\n", " \"\"\"Retrieve all saved travel preferences for a user.\"\"\"\n", " prefs = preference_store.get(user_id, [])\n", " if not prefs:\n", " return f\"No saved preferences for {user_id}.\"\n", " return \"Saved preferences:\\n- \" + \"\\n- \".join(prefs)\n", "\n", "\n", "@tool(approval_mode=\"never_require\")\n", "def search_hotels(\n", " query: Annotated[str, \"Search query — location, amenities, or tags\"],\n", ") -> str:\n", " \"\"\"Search the hotel database for matching properties.\"\"\"\n", " hotels = [\n", " {\"name\": \"Le Meurice Paris\", \"location\": \"Paris, France\", \"price\": 850, \"tags\": [\"luxury\", \"romantic\", \"spa\"]},\n", " {\"name\": \"Four Seasons Maui\", \"location\": \"Maui, Hawaii\", \"price\": 695, \"tags\": [\"beach\", \"family\", \"resort\"]},\n", " {\"name\": \"Aman Tokyo\", \"location\": \"Tokyo, Japan\", \"price\": 780, \"tags\": [\"luxury\", \"city\", \"spa\"]},\n", " {\"name\": \"Hotel Sacher Vienna\", \"location\": \"Vienna, Austria\", \"price\": 420, \"tags\": [\"historic\", \"accessible\", \"cultural\"]},\n", " {\"name\": \"Fairmont Whistler\", \"location\": \"Whistler, Canada\", \"price\": 380, \"tags\": [\"ski\", \"family\", \"mountain\"]},\n", " ]\n", " q = query.lower()\n", " matches = [\n", " h for h in hotels\n", " if q in h[\"name\"].lower()\n", " or q in h[\"location\"].lower()\n", " or any(q in t for t in h[\"tags\"])\n", " ]\n", " if not matches:\n", " matches = hotels[:3]\n", " return json.dumps(matches, indent=2)\n", "\n", "\n", "print(\"✅ Tools defined: save_preference, get_preferences, search_hotels\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Scenario 1 — First-time user books an anniversary trip\n", "\n", "Sarah visits for the first time. The agent should store her preferences via the tools and use them to recommend hotels." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "travel_agent = await provider.create_agent(\n", " tools=[save_preference, get_preferences],\n", " name=\"TravelBookingAssistant\",\n", " instructions=(\n", " \"You are a personalized travel booking assistant with long-term memory.\\n\"\n", " \"WORKFLOW:\\n\"\n", " \"1. When a user starts a conversation, call get_preferences() to check for saved information.\\n\"\n", " \"2. Store any new preferences the user mentions using save_preference().\\n\"\n", " \"3. Use search_hotels() to find suitable options that match their preferences and budget.\\n\"\n", " \"4. Do NOT recommend hotels that exceed the user's budget.\\n\\n\"\n", " \"IMPORTANT: Always use user_id='sarah_johnson_123' for all memory operations.\"\n", " ),\n", ")\n", "\n", "session_1 = travel_agent.create_session()\n", "\n", "response = await travel_agent.run(\n", " \"Hi! I'm Sarah and I'm planning a trip for my 10th wedding anniversary. \"\n", " \"We love romantic destinations, fine dining, and spa experiences. \"\n", " \"My husband has mobility issues, so we need accessible accommodations. \"\n", " \"Our budget is around $700-800 per night.\",\n", " session=session_1,\n", ")\n", "print(\"🤖 Agent:\", response)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "response = await travel_agent.run(\n", " \"The Hotel Sacher sounds perfect! We're both vegetarian and I have a \"\n", " \"severe nut allergy. Can you note that for future trips?\",\n", " session=session_1,\n", ")\n", "print(\"🤖 Agent:\", response)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Verify what was stored\n", "print(\"📋 Preference store contents:\")\n", "for uid, prefs in preference_store.items():\n", " print(f\"\\n User: {uid}\")\n", " for p in prefs:\n", " print(f\" - {p}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Scenario 2 — Sarah returns weeks later\n", "\n", "Sarah starts a **brand-new thread** (simulating a new session). The working memory is empty, but the long-term preference store still has her information. The agent should retrieve it and use it to personalize recommendations." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "session_2 = travel_agent.create_session() # New session — no working memory\n", "\n", "response = await travel_agent.run(\n", " \"Hi, my husband and I are planning another trip. Can you recommend a good hotel?\",\n", " session=session_2,\n", ")\n", "print(\"🤖 Agent:\", response)\n", "print(\"\\n💡 The agent retrieved Sarah's saved preferences from long-term memory \"\n", " \"even though this is a completely new conversation thread.\")" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "response = await travel_agent.run(\n", " \"Great suggestions! For the Maui option, what activities would you recommend for the kids?\",\n", " session=session_2,\n", ")\n", "print(\"🤖 Agent:\", response)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Summary\n", "\n", "In this lesson you learned three types of agent memory and how to implement them with the Microsoft Agent Framework:\n", "\n", "| Memory Type | MAF Mechanism | Lifetime |\n", "|---|---|---|\n", "| **Working** | `agent.create_session()` | Single conversation |\n", "| **Short-term** | Accumulated context within a thread | Single task / session |\n", "| **Long-term** | External store accessed via `@tool` functions | Across sessions |\n", "\n", "### Key Takeaways\n", "1. **`agent.create_session()`** provides working memory — the agent sees the full conversation history within a session.\n", "2. **New sessions lose context** — without long-term memory the agent cannot recall past conversations.\n", "3. **`@tool` functions** bridge the gap — they let the agent save and retrieve information from a persistent store.\n", "4. **Personalization improves over time** — the more preferences are stored, the better the agent's recommendations.\n", "\n", "### Real-World Applications\n", "- **Customer Service**: Remember customer history and preferences\n", "- **Personal Assistants**: Maintain context across days or weeks\n", "- **Healthcare**: Track patient information and preferences\n", "- **E-commerce**: Personalized shopping based on history\n", "\n", "### Next Steps\n", "- Replace the in-memory dict with a database or vector store (e.g. Azure AI Search)\n", "- Add memory expiration for time-sensitive information\n", "- Build multi-agent systems with shared memory\n", "- Explore the Cognee notebook for knowledge-graph-backed memory" ] } ], "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": 4 }