diff --git a/python/samples/02-agents/context_providers/README.md b/python/samples/02-agents/context_providers/README.md index 03746864b81..df0a3a3b1cc 100644 --- a/python/samples/02-agents/context_providers/README.md +++ b/python/samples/02-agents/context_providers/README.md @@ -11,6 +11,7 @@ These samples demonstrate how to use context providers to enrich agent conversat | [`agent_mode_provider.py`](agent_mode_provider.py) | Use the built-in `AgentModeProvider` to track and switch an agent's operating mode at runtime. An interactive loop with a `/mode` slash command demonstrating the built-in `plan`/`execute` modes and custom modes. | | [`cross_session_observer.py`](cross_session_observer.py) | Detect injected context messages whose origins differ from the current session, via the `Message.additional_properties["_attribution"]["origin_session_ids"]` field. Self-contained — no LLM credentials required. | | [`azure_ai_foundry_memory.py`](azure_ai_foundry_memory.py) | Use `FoundryMemoryProvider` to add semantic memory — automatically retrieves, searches, and stores memories via Microsoft Foundry. | +| [`file_memory_provider.py`](file_memory_provider.py) | Use the built-in `FileMemoryProvider` with `FileSystemAgentFileStore` to give an agent tools for storing and recalling memories as files, and configure the `scope` so memories persist and are recalled across separate sessions. | | [`file_access_data_processing/`](file_access_data_processing/) | Use `FileAccessProvider` with `FileSystemAgentFileStore` to give an agent read/write/search access to a folder of CSV data files. See its own [README](file_access_data_processing/README.md). | | [`azure_ai_search/`](azure_ai_search/) | Retrieval Augmented Generation (RAG) with Azure AI Search in semantic and agentic modes. See its own [README](azure_ai_search/README.md). | | [`azure_content_understanding/`](azure_content_understanding/) | Analyze documents, images, audio, and video with Azure Content Understanding and inject the extracted content into agent context. | @@ -45,6 +46,11 @@ These samples demonstrate how to use context providers to enrich agent conversat - `AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME`: Embedding model deployment name (e.g., `text-embedding-ada-002`) - Azure CLI authentication (`az login`) +**For `file_memory_provider.py`:** +- `FOUNDRY_PROJECT_ENDPOINT`: Your Microsoft Foundry project endpoint +- `FOUNDRY_MODEL`: Chat model deployment name +- Azure CLI authentication (`az login`) + **For `file_access_data_processing/`:** - `FOUNDRY_PROJECT_ENDPOINT`: Your Microsoft Foundry project endpoint - `FOUNDRY_MODEL`: Chat model deployment name diff --git a/python/samples/02-agents/context_providers/file_memory_provider.py b/python/samples/02-agents/context_providers/file_memory_provider.py new file mode 100644 index 00000000000..776dd5f24aa --- /dev/null +++ b/python/samples/02-agents/context_providers/file_memory_provider.py @@ -0,0 +1,145 @@ +# Copyright (c) Microsoft. All rights reserved. + +import asyncio +import os +from pathlib import Path + +from agent_framework import Agent, AgentSession, FileMemoryProvider, FileSystemAgentFileStore +from agent_framework.foundry import FoundryChatClient +from azure.identity import AzureCliCredential +from dotenv import load_dotenv + +# Load python/.env (python-dotenv walks up from this file by default). Pass +# override=True so values from .env take precedence over any pre-existing OS +# environment variables — without this, OS-level values silently win. +load_dotenv(override=True) + +""" +Sample: give an agent file-based memory with ``FileMemoryProvider``. + +This sample shows how to attach :class:`FileMemoryProvider` directly to a plain +``Agent`` via ``context_providers``, and how to control the *scope* of the +memories so they persist and can be recalled across separate sessions. + +``FileMemoryProvider`` is a :class:`~agent_framework.ContextProvider` that +exposes a set of ``file_memory_*`` tools (write, read, delete, ls, grep, +replace, replace_lines) to the agent, letting the model decide what to remember +and when to recall it. Each memory is stored as an individual file in an +:class:`~agent_framework.AgentFileStore`; here we use a +:class:`~agent_framework.FileSystemAgentFileStore` so the memories are written +to real files on disk. Because the files live outside the conversation, the +agent can recall them in a later session, even after the original chat history +is gone. + +Controlling scope: + The provider isolates memories per session by default (the working folder is + derived from the session id). Passing an explicit ``scope`` (for example a + user id) instead groups memories under a stable working folder that every + session for that user shares — which is what lets the second conversation + below recall what the user said in the first. + +Prerequisites: + - ``FOUNDRY_PROJECT_ENDPOINT``: Your Microsoft Foundry project endpoint. + - ``FOUNDRY_MODEL``: Chat model deployment name. + - Run ``az login`` before executing the sample. +""" + +# The id of the user we are storing memories for. It is used below as the +# provider's scope so that each user gets their own memory folder. +USER_ID = "UID1" + + +async def main() -> None: + client = FoundryChatClient( + project_endpoint=os.environ["FOUNDRY_PROJECT_ENDPOINT"], + model=os.environ["FOUNDRY_MODEL"], + credential=AzureCliCredential(), + ) + + # + # 1. Create the file store the provider will use to persist memory files. + # Here we use a file-system backed store rooted at a local + # ``agent-file-memory`` folder, but any AgentFileStore implementation can + # be used, e.g. InMemoryAgentFileStore or a custom blob-backed store. + memory_root = Path(__file__).parent / "agent-file-memory" + store = FileSystemAgentFileStore(memory_root) + + # 2. Create the FileMemoryProvider over that store. + # The ``scope`` determines the scope and lifetime of the memories: + # - A stable scope, like the per-user one below, gives durable memories + # shared by every session for that user. That is what allows the second + # conversation further down to recall what the user said in the first. + # - Omitting ``scope`` (the default) isolates memories to a single session + # (the working folder is derived from the session id). + file_memory_provider = FileMemoryProvider(store, scope=f"users/{USER_ID}") + + # 3. Attach the provider to the agent so it gets the file_memory_* tools. + agent = Agent( + client=client, + name="TravelAssistant", + instructions=( + "You are a helpful travel assistant. Remember what the user tells you about " + "themselves so that you can give better recommendations later." + ), + context_providers=[file_memory_provider], + ) + # + + working_folder = memory_root / "users" / USER_ID + print(f"Memory files will be written to: {working_folder}\n") + + # 4. First conversation: tell the agent something worth remembering. The + # agent should call file_memory_write to store it as a file in the scope. + first_session: AgentSession = agent.create_session() + print("=== First conversation ===") + print( + await agent.run( + "I'm vegetarian and I always travel with my dog. Please remember this for future trips.", + session=first_session, + ) + ) + print() + + # 5. Show the memory files the agent created on disk. + print("=== Memory files on disk ===") + if working_folder.exists(): + for file in sorted(working_folder.iterdir()): + if file.is_file(): + print(file.name) + print() + + # 6. Second conversation: a brand new session with no chat history from the + # first. The provider injects the memory index (memories.md) into the + # agent's context, and the agent calls file_memory_read to recall the + # user's preferences with no shared chat history. + second_session: AgentSession = agent.create_session() + print("=== Second conversation (new session) ===") + print( + await agent.run( + "Suggest a hotel and a restaurant for my trip to Paris next week.", + session=second_session, + ) + ) + + +if __name__ == "__main__": + asyncio.run(main()) + + +""" +Sample output (abridged; exact text varies by model): + +Memory files will be written to: .../context_providers/agent-file-memory/users/UID1 + +=== First conversation === +Got it — I'll remember that you're vegetarian and always travel with your dog. I've saved +this so I can tailor future recommendations for you. + +=== Memory files on disk === +userpreferences.md + +=== Second conversation (new session) === +Since you're vegetarian and travel with your dog, here are some dog-friendly options in Paris: +- Hotel: ... (pet-friendly, central) ... +- Restaurant: ... (excellent vegetarian menu, welcomes dogs) ... +"""