diff --git a/python/samples/02-agents/context_providers/mem0/README.md b/python/samples/02-agents/context_providers/mem0/README.md index 4c12bb67d8..985ad60e75 100644 --- a/python/samples/02-agents/context_providers/mem0/README.md +++ b/python/samples/02-agents/context_providers/mem0/README.md @@ -9,7 +9,7 @@ This folder contains examples demonstrating how to use the Mem0 context provider | File | Description | |------|-------------| | [`mem0_basic.py`](mem0_basic.py) | Basic example of using Mem0 context provider to store and retrieve user preferences across different conversation threads. | -| [`mem0_sessions.py`](mem0_sessions.py) | Advanced example demonstrating different thread scoping strategies with Mem0. Covers global thread scope (memories shared across all operations), per-operation thread scope (memories isolated per thread), and multiple agents with different memory configurations for personal vs. work contexts. | +| [`mem0_sessions.py`](mem0_sessions.py) | Example demonstrating different memory scoping strategies with Mem0. Covers user-scoped memory (memories shared across all sessions for the same user), agent-scoped memory (memories isolated per agent), and multiple agents with different memory configurations for personal vs. work contexts. | | [`mem0_oss.py`](mem0_oss.py) | Example of using the Mem0 Open Source self-hosted version as the context provider. Demonstrates setup and configuration for local deployment. | ## Prerequisites @@ -40,16 +40,8 @@ Set the following environment variables: ### Memory Scoping -The Mem0 context provider supports different scoping strategies: +The Mem0 context provider supports scoping via identifiers: -- **Global Scope** (`scope_to_per_operation_thread_id=False`): Memories are shared across all conversation threads -- **Thread Scope** (`scope_to_per_operation_thread_id=True`): Memories are isolated per conversation thread - -### Memory Association - -Mem0 records can be associated with different identifiers: - -- `user_id`: Associate memories with a specific user -- `agent_id`: Associate memories with a specific agent -- `thread_id`: Associate memories with a specific conversation thread -- `application_id`: Associate memories with an application context +- **User scope** (`user_id`): Associate memories with a specific user, shared across all sessions +- **Agent scope** (`agent_id`): Isolate memories per agent persona +- **Application scope** (`application_id`): Associate memories with an application context diff --git a/python/samples/02-agents/context_providers/mem0/mem0_sessions.py b/python/samples/02-agents/context_providers/mem0/mem0_sessions.py index 8bda4a575e..94d6f4a041 100644 --- a/python/samples/02-agents/context_providers/mem0/mem0_sessions.py +++ b/python/samples/02-agents/context_providers/mem0/mem0_sessions.py @@ -1,7 +1,6 @@ # Copyright (c) Microsoft. All rights reserved. import asyncio -import uuid from agent_framework import tool from agent_framework.azure import AzureAIAgentClient @@ -27,51 +26,48 @@ def get_user_preferences(user_id: str) -> str: return preferences.get(user_id, "No specific preferences found") -async def example_global_thread_scope() -> None: - """Example 1: Global thread_id scope (memories shared across all operations).""" - print("1. Global Thread Scope Example:") +async def example_user_scoped_memory() -> None: + """Example 1: User-scoped memory (memories shared across all sessions for the same user).""" + print("1. User-Scoped Memory Example:") print("-" * 40) - global_thread_id = str(uuid.uuid4()) user_id = "user123" async with ( AzureCliCredential() as credential, AzureAIAgentClient(credential=credential).as_agent( - name="GlobalMemoryAssistant", + name="UserMemoryAssistant", instructions="You are an assistant that remembers user preferences across conversations.", tools=get_user_preferences, context_providers=[ Mem0ContextProvider( source_id="mem0", user_id=user_id, - thread_id=global_thread_id, - scope_to_per_operation_thread_id=False, # Share memories across all sessions ) ], - ) as global_agent, + ) as user_agent, ): - # Store some preferences in the global scope + # Store some preferences query = "Remember that I prefer technical responses with code examples when discussing programming." print(f"User: {query}") - result = await global_agent.run(query) + result = await user_agent.run(query) print(f"Agent: {result}\n") - # Create a new session - but memories should still be accessible due to global scope - new_session = global_agent.create_session() + # Create a new session - memories should still be accessible via user_id scoping + new_session = user_agent.create_session() query = "What do you know about my preferences?" print(f"User (new session): {query}") - result = await global_agent.run(query, session=new_session) + result = await user_agent.run(query, session=new_session) print(f"Agent: {result}\n") -async def example_per_operation_thread_scope() -> None: - """Example 2: Per-operation thread scope (memories isolated per session). +async def example_agent_scoped_memory() -> None: + """Example 2: Agent-scoped memory (memories isolated per agent_id). - Note: When scope_to_per_operation_thread_id=True, the provider is bound to a single session - throughout its lifetime. Use the same session object for all operations with that provider. + Note: Use different agent_id values to isolate memories between different + agent personas, even when the user_id is the same. """ - print("2. Per-Operation Thread Scope Example:") + print("2. Agent-Scoped Memory Example:") print("-" * 40) user_id = "user123" @@ -80,48 +76,45 @@ async def example_per_operation_thread_scope() -> None: AzureCliCredential() as credential, AzureAIAgentClient(credential=credential).as_agent( name="ScopedMemoryAssistant", - instructions="You are an assistant with thread-scoped memory.", + instructions="You are an assistant with agent-scoped memory.", tools=get_user_preferences, context_providers=[ Mem0ContextProvider( source_id="mem0", user_id=user_id, - scope_to_per_operation_thread_id=True, # Isolate memories per session + agent_id="scoped_assistant", ) ], ) as scoped_agent, ): - # Create a specific session for this scoped provider - dedicated_session = scoped_agent.create_session() - - # Store some information in the dedicated session + # Store some information query = "Remember that for this conversation, I'm working on a Python project about data analysis." - print(f"User (dedicated session): {query}") - result = await scoped_agent.run(query, session=dedicated_session) + print(f"User: {query}") + result = await scoped_agent.run(query) print(f"Agent: {result}\n") - # Test memory retrieval in the same dedicated session + # Test memory retrieval query = "What project am I working on?" - print(f"User (same dedicated session): {query}") - result = await scoped_agent.run(query, session=dedicated_session) + print(f"User: {query}") + result = await scoped_agent.run(query) print(f"Agent: {result}\n") - # Store more information in the same session + # Store more information query = "Also remember that I prefer using pandas and matplotlib for this project." - print(f"User (same dedicated session): {query}") - result = await scoped_agent.run(query, session=dedicated_session) + print(f"User: {query}") + result = await scoped_agent.run(query) print(f"Agent: {result}\n") # Test comprehensive memory retrieval query = "What do you know about my current project and preferences?" - print(f"User (same dedicated session): {query}") - result = await scoped_agent.run(query, session=dedicated_session) + print(f"User: {query}") + result = await scoped_agent.run(query) print(f"Agent: {result}\n") async def example_multiple_agents() -> None: - """Example 3: Multiple agents with different thread configurations.""" - print("3. Multiple Agents with Different Thread Configurations:") + """Example 3: Multiple agents with different memory configurations.""" + print("3. Multiple Agents with Different Memory Configurations:") print("-" * 40) agent_id_1 = "agent_personal" @@ -174,11 +167,11 @@ async def example_multiple_agents() -> None: async def main() -> None: - """Run all Mem0 thread management examples.""" - print("=== Mem0 Thread Management Example ===\n") + """Run all Mem0 memory management examples.""" + print("=== Mem0 Memory Management Example ===\n") - await example_global_thread_scope() - await example_per_operation_thread_scope() + await example_user_scoped_memory() + await example_agent_scoped_memory() await example_multiple_agents() diff --git a/python/samples/02-agents/context_providers/redis/README.md b/python/samples/02-agents/context_providers/redis/README.md index 060061c908..d424ad9781 100644 --- a/python/samples/02-agents/context_providers/redis/README.md +++ b/python/samples/02-agents/context_providers/redis/README.md @@ -11,7 +11,7 @@ This folder contains an example demonstrating how to use the Redis context provi | [`azure_redis_conversation.py`](azure_redis_conversation.py) | Demonstrates conversation persistence with RedisHistoryProvider and Azure Redis with Azure AD (Entra ID) authentication using credential provider. | | [`redis_basics.py`](redis_basics.py) | Shows standalone provider usage and agent integration. Demonstrates writing messages to Redis, retrieving context via full‑text or hybrid vector search, and persisting preferences across threads. Also includes a simple tool example whose outputs are remembered. | | [`redis_conversation.py`](redis_conversation.py) | Simple example showing conversation persistence with RedisContextProvider using traditional connection string authentication. | -| [`redis_sessions.py`](redis_sessions.py) | Demonstrates thread scoping. Includes: (1) global thread scope with a fixed `thread_id` shared across operations; (2) per‑operation thread scope where `scope_to_per_operation_thread_id=True` binds memory to a single thread for the provider's lifetime; and (3) multiple agents with isolated memory via different `agent_id` values. | +| [`redis_sessions.py`](redis_sessions.py) | Demonstrates memory scoping strategies. Includes: (1) global memory scope with `application_id`, `agent_id`, and `user_id` shared across operations; (2) agent-scoped memory with a custom vectorizer for hybrid search; and (3) multiple agents with isolated memory via different `agent_id` values. | ## Prerequisites @@ -61,8 +61,7 @@ The provider supports both full‑text only and hybrid vector search: - Set `vectorizer_choice` to `"openai"` or `"hf"` to enable embeddings and hybrid search. - When using a vectorizer, also set `vector_field_name` (e.g., `"vector"`). -- Partition fields for scoping memory: `application_id`, `agent_id`, `user_id`, `thread_id`. -- Thread scoping: `scope_to_per_operation_thread_id=True` isolates memory per operation thread. +- Partition fields for scoping memory: `application_id`, `agent_id`, `user_id`. - Index management: `index_name`, `overwrite_redis_index`, `drop_redis_index`. ## What the example does @@ -104,8 +103,8 @@ You should see the agent responses and, when using embeddings, context retrieved ### Memory scoping -- Global scope: set `application_id`, `agent_id`, `user_id`, or `thread_id` on the provider to filter memory. -- Per‑operation thread scope: set `scope_to_per_operation_thread_id=True` to isolate memory to the current thread created by the framework. +- Global scope: set `application_id`, `agent_id`, or `user_id` on the provider to filter memory. +- Agent isolation: use different `agent_id` values to keep memories separated for different agent personas. ### Hybrid vector search (optional) @@ -118,7 +117,7 @@ You should see the agent responses and, when using embeddings, context retrieved ## Troubleshooting -- Ensure at least one of `application_id`, `agent_id`, `user_id`, or `thread_id` is set; the provider requires a scope. +- Ensure at least one of `application_id`, `agent_id`, or `user_id` is set; the provider requires a scope. - Verify `AZURE_AI_PROJECT_ENDPOINT` and `AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME` are set for the chat client. - If using embeddings, verify `OPENAI_API_KEY` is set and reachable. - Make sure Redis exposes RediSearch (Redis Stack image or managed service with search enabled). diff --git a/python/samples/02-agents/context_providers/redis/redis_sessions.py b/python/samples/02-agents/context_providers/redis/redis_sessions.py index 48cf0e596b..00c541cdaa 100644 --- a/python/samples/02-agents/context_providers/redis/redis_sessions.py +++ b/python/samples/02-agents/context_providers/redis/redis_sessions.py @@ -1,17 +1,17 @@ # Copyright (c) Microsoft. All rights reserved. -"""Redis Context Provider: Thread scoping examples +"""Redis Context Provider: Memory scoping examples This sample demonstrates how conversational memory can be scoped when using the Redis context provider. It covers three scenarios: -1) Global thread scope - - Provide a fixed thread_id to share memories across operations/threads. +1) Global memory scope + - Use application_id, agent_id, and user_id to share memories across + all operations/sessions. -2) Per-operation thread scope - - Enable scope_to_per_operation_thread_id to bind the provider to a single - thread for the lifetime of that provider instance. Use the same thread - object for reads/writes with that provider. +2) Agent-scoped memory with custom vectorizer + - Use a custom vectorizer with the provider for hybrid vector search. + Memories are scoped by application_id, user_id, and agent_id. 3) Multiple agents with isolated memory - Use different agent_id values to keep memories separated for different @@ -23,7 +23,7 @@ - Optionally an OpenAI API key for the chat client in this demo Run: - python redis_threads.py + python redis_sessions.py """ import asyncio @@ -55,9 +55,9 @@ def create_chat_client() -> AzureOpenAIResponsesClient: ) -async def example_global_thread_scope() -> None: - """Example 1: Global thread_id scope (memories shared across all operations).""" - print("1. Global Thread Scope Example:") +async def example_global_memory_scope() -> None: + """Example 1: Global memory scope (memories shared across all operations).""" + print("1. Global Memory Scope Example:") print("-" * 40) client = create_chat_client() @@ -98,13 +98,13 @@ async def example_global_thread_scope() -> None: await provider.redis_index.delete() -async def example_per_operation_thread_scope() -> None: - """Example 2: Per-operation thread scope (memories isolated per session). +async def example_agent_scoped_memory() -> None: + """Example 2: Agent-scoped memory with custom vectorizer. - Note: When scope_to_per_operation_thread_id=True, the provider is bound to a single session - throughout its lifetime. Use the same session object for all operations with that provider. + Demonstrates using a custom OpenAI vectorizer for hybrid vector search. + Memories are scoped by application_id, user_id, and agent_id. """ - print("2. Per-Operation Thread Scope Example:") + print("2. Agent-Scoped Memory with Vectorizer Example:") print("-" * 40) client = create_chat_client() @@ -130,7 +130,7 @@ async def example_per_operation_thread_scope() -> None: agent = client.as_agent( name="ScopedMemoryAssistant", - instructions="You are an assistant with thread-scoped memory.", + instructions="You are an assistant with agent-scoped memory.", context_providers=[provider], ) @@ -166,8 +166,8 @@ async def example_per_operation_thread_scope() -> None: async def example_multiple_agents() -> None: - """Example 3: Multiple agents with different thread configurations (isolated via agent_id) but within 1 index.""" - print("3. Multiple Agents with Different Thread Configurations:") + """Example 3: Multiple agents with different memory configurations (isolated via agent_id) but within 1 index.""" + print("3. Multiple Agents with Different Memory Configurations:") print("-" * 40) client = create_chat_client() @@ -243,9 +243,9 @@ async def example_multiple_agents() -> None: async def main() -> None: - print("=== Redis Thread Scoping Examples ===\n") - await example_global_thread_scope() - await example_per_operation_thread_scope() + print("=== Redis Memory Scoping Examples ===\n") + await example_global_memory_scope() + await example_agent_scoped_memory() await example_multiple_agents()