From 1e527a328cff67445035fbc77978991d88c1be1d Mon Sep 17 00:00:00 2001 From: "L. Elaine Dazzio" Date: Tue, 31 Mar 2026 17:57:23 -0400 Subject: [PATCH] Python: Remove unsupported memory scoping params from mem0/redis samples and docs (#4367) * Python: Remove unsupported memory scoping params from samples and docs Fixes #4353 The `Mem0ContextProvider` and `RedisContextProvider` no longer support `thread_id` or `scope_to_per_operation_thread_id` parameters. This commit updates the affected samples and READMEs to use only the currently supported API (`user_id`, `agent_id`, `application_id`). Changes: - mem0_sessions.py: Remove `thread_id` and `scope_to_per_operation_thread_id` from examples 1 and 2, rewrite to demonstrate user-scoped and agent-scoped memory patterns - redis_sessions.py: Update module docstring to remove references to removed thread scoping params - mem0/README.md: Update Memory Scoping docs to reflect current API - redis/README.md: Remove `thread_id` and `scope_to_per_operation_thread_id` references from docs * Address Copilot review: rename thread_scope functions, fix docstring - Rename `example_global_thread_scope` -> `example_global_memory_scope` - Rename `example_per_operation_thread_scope` -> `example_agent_scoped_memory` - Update example 2 docstring to mention `application_id` alongside `user_id` and `agent_id` since it's set in the provider config - Update module docstring scenario 2 to include `application_id` * fix: rebase onto main, address giles17 review feedback - Resolve merge conflicts by rebasing all 4 original files onto current main - Address giles17's agent review suggestions: - mem0_basic.py: update comment to remove thread_id from scoping list - mem0_oss.py: update comment to remove thread_id from scoping list - redis_sessions.py: rename Example 2 from "Agent-Scoped Memory" to "Hybrid Vector Search" to accurately describe what it demonstrates - redis/README.md: update Example 2 description to match renamed example --------- Co-authored-by: Tao Chen Co-authored-by: Giles Odigwe <79032838+giles17@users.noreply.github.com> --- .../context_providers/mem0/README.md | 18 ++--- .../context_providers/mem0/mem0_basic.py | 2 +- .../context_providers/mem0/mem0_oss.py | 2 +- .../context_providers/mem0/mem0_sessions.py | 75 +++++++++---------- .../context_providers/redis/README.md | 11 ++- .../context_providers/redis/redis_sessions.py | 72 +++++++++--------- 6 files changed, 81 insertions(+), 99 deletions(-) diff --git a/python/samples/02-agents/context_providers/mem0/README.md b/python/samples/02-agents/context_providers/mem0/README.md index 2a7e3416d6..c4a3cdbc61 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_basic.py b/python/samples/02-agents/context_providers/mem0/mem0_basic.py index 46b185573b..489e5bfcae 100644 --- a/python/samples/02-agents/context_providers/mem0/mem0_basic.py +++ b/python/samples/02-agents/context_providers/mem0/mem0_basic.py @@ -31,7 +31,7 @@ def retrieve_company_report(company_code: str, detailed: bool) -> str: async def main() -> None: """Example of memory usage with Mem0 context provider.""" print("=== Mem0 Context Provider Example ===") - # Each record in Mem0 should be associated with agent_id or user_id or application_id or thread_id. + # Each record in Mem0 should be associated with agent_id or user_id or application_id. # In this example, we associate Mem0 records with user_id. user_id = str(uuid.uuid4()) # For Azure authentication, run `az login` command in terminal or replace AzureCliCredential with preferred diff --git a/python/samples/02-agents/context_providers/mem0/mem0_oss.py b/python/samples/02-agents/context_providers/mem0/mem0_oss.py index 9126ab4bdc..a0c4bbb372 100644 --- a/python/samples/02-agents/context_providers/mem0/mem0_oss.py +++ b/python/samples/02-agents/context_providers/mem0/mem0_oss.py @@ -32,7 +32,7 @@ def retrieve_company_report(company_code: str, detailed: bool) -> str: async def main() -> None: """Example of memory usage with local Mem0 OSS context provider.""" print("=== Mem0 Context Provider Example ===") - # Each record in Mem0 should be associated with agent_id or user_id or application_id or thread_id. + # Each record in Mem0 should be associated with agent_id or user_id or application_id. # In this example, we associate Mem0 records with user_id. user_id = str(uuid.uuid4()) # For Azure authentication, run `az login` command in terminal or replace AzureCliCredential with preferred 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 80976fa8f3..d402b34baf 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 Agent, tool from agent_framework.foundry import FoundryChatClient @@ -27,52 +26,49 @@ 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, Agent( client=FoundryChatClient(credential=credential), - 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" @@ -82,48 +78,45 @@ async def example_per_operation_thread_scope() -> None: Agent( client=FoundryChatClient(credential=credential), 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" @@ -178,11 +171,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 097aa6b4eb..104ea3a5b8 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) hybrid vector search using a custom OpenAI vectorizer for richer context retrieval; 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 `FOUNDRY_PROJECT_ENDPOINT` and `FOUNDRY_MODEL` 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 92635a5326..7819894f7b 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,18 @@ # 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) Hybrid vector search + - Use a custom OpenAI vectorizer with the provider for hybrid vector search. + Demonstrates combining full-text and semantic search for richer context + retrieval. 3) Multiple agents with isolated memory - Use different agent_id values to keep memories separated for different @@ -23,7 +24,7 @@ Requirements: - 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 +56,9 @@ def create_chat_client() -> FoundryChatClient: ) -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() @@ -99,13 +100,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_hybrid_vector_search() -> None: + """Example 2: Hybrid vector search 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, + combining full-text and semantic search for richer context retrieval. """ - print("2. Per-Operation Thread Scope Example:") + print("2. Hybrid Vector Search Example:") print("-" * 40) client = create_chat_client() @@ -131,36 +132,33 @@ async def example_per_operation_thread_scope() -> None: agent = Agent( client=client, - name="ScopedMemoryAssistant", - instructions="You are an assistant with thread-scoped memory.", + name="HybridSearchAssistant", + instructions="You are an assistant with hybrid vector search for richer context retrieval.", context_providers=[provider], ) - # Create a specific session for this scoped provider - dedicated_session = 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 agent.run(query, session=dedicated_session) + print(f"User: {query}") + result = await agent.run(query) print(f"Agent: {result}\n") - # Test memory retrieval in the same dedicated session + # Test memory retrieval via hybrid search query = "What project am I working on?" - print(f"User (same dedicated session): {query}") - result = await agent.run(query, session=dedicated_session) + print(f"User: {query}") + result = await 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 agent.run(query, session=dedicated_session) + print(f"User: {query}") + result = await 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 agent.run(query, session=dedicated_session) + print(f"User: {query}") + result = await agent.run(query) print(f"Agent: {result}\n") # Clean up the Redis index @@ -168,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() @@ -247,9 +245,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_hybrid_vector_search() await example_multiple_agents()