From 61fafca066d5094974ff359e52bc9a4934d7fdd9 Mon Sep 17 00:00:00 2001 From: saverio Date: Thu, 24 Jul 2025 16:56:11 +0200 Subject: [PATCH] feat: Add context injection modes to Mem0Memory chore: Export ContextInjectionMode enum fix: tests fix: format chore: remove mem0 deps to fix tests fix: patch of memory client fix: check we are dealing with a FunctionCall before accessing attributes fix: use public constructor fix: add empty line at end of file --- .../agentchat-user-guide/memory.ipynb | 67 ++++++- .../src/autogen_ext/memory/mem0/__init__.py | 3 +- .../src/autogen_ext/memory/mem0/_mem0.py | 75 +++++++- .../autogen-ext/tests/memory/test_mem0.py | 174 ++++++++++++++++-- 4 files changed, 288 insertions(+), 31 deletions(-) diff --git a/python/docs/src/user-guide/agentchat-user-guide/memory.ipynb b/python/docs/src/user-guide/agentchat-user-guide/memory.ipynb index e5e71b0fc03e..bd618fd0955f 100644 --- a/python/docs/src/user-guide/agentchat-user-guide/memory.ipynb +++ b/python/docs/src/user-guide/agentchat-user-guide/memory.ipynb @@ -542,9 +542,9 @@ "source": [ "## Mem0Memory Example\n", "\n", - "`autogen_ext.memory.mem0.Mem0Memory` provides integration with `Mem0.ai`'s memory system. It supports both cloud-based and local backends, offering advanced memory capabilities for agents. The implementation handles proper retrieval and context updating, making it suitable for production environments.\n", + "`autogen_ext.memory.mem0.Mem0Memory` provides integration with `Mem0.ai`'s memory system. It supports both cloud-based and local backends, offering advanced memory capabilities for agents. The implementation handles proper retrieval and context updating with configurable injection modes, making it suitable for production environments.\n", "\n", - "In the following example, we'll demonstrate how to use `Mem0Memory` to maintain persistent memories across conversations:" + "In the following example, we'll demonstrate how to use `Mem0Memory` to maintain persistent memories across conversations:" ] }, { @@ -556,7 +556,7 @@ "from autogen_agentchat.agents import AssistantAgent\n", "from autogen_agentchat.ui import Console\n", "from autogen_core.memory import MemoryContent, MemoryMimeType\n", - "from autogen_ext.memory.mem0 import Mem0Memory\n", + "from autogen_ext.memory.mem0 import Mem0Memory, ContextInjectionMode\n", "from autogen_ext.models.openai import OpenAIChatCompletionClient\n", "\n", "# Initialize Mem0 cloud memory (requires API key)\n", @@ -564,6 +564,7 @@ "mem0_memory = Mem0Memory(\n", " is_cloud=True,\n", " limit=5, # Maximum number of memories to retrieve\n", + " context_injection_mode=ContextInjectionMode.SYSTEM_MESSAGE, # Default mode\n", ")\n", "\n", "# Add user preferences to memory\n", @@ -595,12 +596,12 @@ "\n", "# Ask about the weather\n", "stream = assistant_agent.run_stream(task=\"What are my dietary preferences?\")\n", - "await Console(stream)" + "await Console(stream)\n" ] }, { - "cell_type": "markdown", "metadata": {}, + "cell_type": "markdown", "source": [ "The example above demonstrates how Mem0Memory can be used with an assistant agent. The memory integration ensures that:\n", "\n", @@ -618,10 +619,10 @@ ] }, { - "cell_type": "code", - "execution_count": null, "metadata": {}, + "cell_type": "code", "outputs": [], + "execution_count": null, "source": [ "# Serialize the memory configuration\n", "config_json = mem0_memory.dump_component().model_dump_json()\n", @@ -629,9 +630,59 @@ ] }, { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "### Context Injection Modes\n", + "\n", + "Mem0Memory supports two different modes for injecting retrieved memories into the model context:" + ] + }, + { + "metadata": {}, + "cell_type": "markdown", + "source": [ + "#### System Message Mode (Default)\n", + "\n", + "This is the traditional approach where memories are added as system messages:" + ] + }, + { + "metadata": {}, + "cell_type": "code", + "outputs": [], + "execution_count": null, + "source": [ + "# Default behavior - memories added as system messages\n", + "mem0_memory = Mem0Memory(\n", + " is_cloud=False,\n", + " config={\"path\": \":memory:\"},\n", + " context_injection_mode=ContextInjectionMode.SYSTEM_MESSAGE, # Default\n", + ")" + ] + }, + { + "metadata": {}, "cell_type": "markdown", + "source": [ + "#### Function Call Mode\n", + "\n", + "This mode injects memories as function call results, this is suitable for models that do not support multiple system messages:" + ] + }, + { "metadata": {}, - "source": [] + "cell_type": "code", + "outputs": [], + "execution_count": null, + "source": [ + "# Function call mode - memories added as function execution results\n", + "mem0_memory = Mem0Memory(\n", + " is_cloud=False,\n", + " config={\"path\": \":memory:\"},\n", + " context_injection_mode=ContextInjectionMode.FUNCTION_CALL,\n", + ")" + ] } ], "metadata": { diff --git a/python/packages/autogen-ext/src/autogen_ext/memory/mem0/__init__.py b/python/packages/autogen-ext/src/autogen_ext/memory/mem0/__init__.py index 2f1af25679c0..2151041a6bb7 100644 --- a/python/packages/autogen-ext/src/autogen_ext/memory/mem0/__init__.py +++ b/python/packages/autogen-ext/src/autogen_ext/memory/mem0/__init__.py @@ -1,6 +1,7 @@ -from ._mem0 import Mem0Memory, Mem0MemoryConfig +from ._mem0 import ContextInjectionMode, Mem0Memory, Mem0MemoryConfig __all__ = [ "Mem0Memory", "Mem0MemoryConfig", + "ContextInjectionMode", ] diff --git a/python/packages/autogen-ext/src/autogen_ext/memory/mem0/_mem0.py b/python/packages/autogen-ext/src/autogen_ext/memory/mem0/_mem0.py index 48dfaf0b109f..6cde14a3ce0d 100644 --- a/python/packages/autogen-ext/src/autogen_ext/memory/mem0/_mem0.py +++ b/python/packages/autogen-ext/src/autogen_ext/memory/mem0/_mem0.py @@ -3,12 +3,13 @@ import uuid from contextlib import redirect_stderr, redirect_stdout from datetime import datetime +from enum import Enum from typing import Any, Dict, List, Optional, TypedDict, cast -from autogen_core import CancellationToken, Component, ComponentBase +from autogen_core import CancellationToken, Component, ComponentBase, FunctionCall from autogen_core.memory import Memory, MemoryContent, MemoryQueryResult, UpdateContextResult from autogen_core.model_context import ChatCompletionContext -from autogen_core.models import SystemMessage +from autogen_core.models import AssistantMessage, FunctionExecutionResult, FunctionExecutionResultMessage, SystemMessage from mem0 import Memory as Memory0 from mem0 import MemoryClient from pydantic import BaseModel, Field @@ -18,6 +19,13 @@ logging.getLogger("chromadb").setLevel(logging.ERROR) +class ContextInjectionMode(Enum): + """Enum for context injection modes.""" + + SYSTEM_MESSAGE = "system_message" + FUNCTION_CALL = "function_call" + + class Mem0MemoryConfig(BaseModel): """Configuration for Mem0Memory component.""" @@ -32,6 +40,10 @@ class Mem0MemoryConfig(BaseModel): config: Optional[Dict[str, Any]] = Field( default=None, description="Configuration dictionary for local Mem0 client. Required if is_cloud=False." ) + context_injection_mode: ContextInjectionMode = Field( + default=ContextInjectionMode.SYSTEM_MESSAGE, + description="Mode for injecting memories into context: 'system_message' or 'function_call'.", + ) class MemoryResult(TypedDict, total=False): @@ -68,15 +80,16 @@ class Mem0Memory(Memory, Component[Mem0MemoryConfig], ComponentBase[Mem0MemoryCo .. code-block:: python import asyncio - from autogen_ext.memory.mem0 import Mem0Memory + from autogen_ext.memory.mem0 import Mem0Memory, ContextInjectionMode from autogen_core.memory import MemoryContent async def main() -> None: - # Create a local Mem0Memory (no API key required) + # Create a local Mem0Memory with function call injection mode memory = Mem0Memory( is_cloud=False, config={"path": ":memory:"}, # Use in-memory storage for testing + context_injection_mode=ContextInjectionMode.FUNCTION_CALL, ) print("Memory initialized successfully!") @@ -111,7 +124,7 @@ async def main() -> None: import asyncio from autogen_agentchat.agents import AssistantAgent from autogen_core.memory import MemoryContent - from autogen_ext.memory.mem0 import Mem0Memory + from autogen_ext.memory.mem0 import Mem0Memory, ContextInjectionMode from autogen_ext.models.openai import OpenAIChatCompletionClient @@ -119,11 +132,12 @@ async def main() -> None: # Create a model client model_client = OpenAIChatCompletionClient(model="gpt-4.1") - # Create a Mem0 memory instance + # Create a Mem0 memory instance with system message injection (default) memory = Mem0Memory( user_id="user123", is_cloud=False, config={"path": ":memory:"}, # Use in-memory storage for testing + context_injection_mode=ContextInjectionMode.SYSTEM_MESSAGE, ) # Add something to memory @@ -157,6 +171,7 @@ async def main() -> None: is_cloud: Whether to use cloud Mem0 client (True) or local client (False). api_key: API key for cloud Mem0 client. It will read from the environment MEM0_API_KEY if not provided. config: Configuration dictionary for local Mem0 client. Required if is_cloud=False. + context_injection_mode: Mode for injecting memories into context ('system_message' or 'function_call'). """ component_type = "memory" @@ -170,6 +185,7 @@ def __init__( is_cloud: bool = True, api_key: Optional[str] = None, config: Optional[Dict[str, Any]] = None, + context_injection_mode: ContextInjectionMode = ContextInjectionMode.SYSTEM_MESSAGE, ) -> None: # Validate parameters if not is_cloud and config is None: @@ -181,6 +197,7 @@ def __init__( self._is_cloud = is_cloud self._api_key = api_key self._config = config + self._context_injection_mode = context_injection_mode # Initialize client if self._is_cloud: @@ -210,6 +227,11 @@ def config(self) -> Optional[Dict[str, Any]]: """Get the configuration for the Mem0 client.""" return self._config + @property + def context_injection_mode(self) -> ContextInjectionMode: + """Get the context injection mode.""" + return self._context_injection_mode + async def add( self, content: MemoryContent, @@ -366,7 +388,8 @@ async def update_context( This method retrieves the conversation history from the model context, uses the last message as a query to find relevant memories, and then - adds those memories to the context as a system message. + adds those memories to the context either as a system message or as + function call messages based on the configured injection mode. Args: model_context: The model context to update. @@ -392,8 +415,40 @@ async def update_context( memory_strings = [f"{i}. {str(memory.content)}" for i, memory in enumerate(query_results.results, 1)] memory_context = "\nRelevant memories:\n" + "\n".join(memory_strings) - # Add as system message - await model_context.add_message(SystemMessage(content=memory_context)) + if self._context_injection_mode == ContextInjectionMode.SYSTEM_MESSAGE: + # Add as system message (original behavior) + await model_context.add_message(SystemMessage(content=memory_context)) + + elif self._context_injection_mode == ContextInjectionMode.FUNCTION_CALL: + # Add as function call result messages + # Generate a unique call ID + call_id = f"call_{uuid.uuid4().hex[:20]}" + + # Create the function call + function_call = FunctionCall( + id=call_id, + name="retrieve_mem0memory", + arguments="{}", # No parameters as specified + ) + + # Create AssistantMessage with the function call + assistant_message = AssistantMessage( + content=[function_call], source="memory_system", type="AssistantMessage" + ) + + # Create the function execution result + function_result = FunctionExecutionResult( + content=memory_context, name="retrieve_mem0memory", call_id=call_id, is_error=False + ) + + # Create FunctionExecutionResultMessage + result_message = FunctionExecutionResultMessage( + content=[function_result], type="FunctionExecutionResultMessage" + ) + + # Add both messages to the context + await model_context.add_message(assistant_message) + await model_context.add_message(result_message) return UpdateContextResult(memories=query_results) @@ -432,6 +487,7 @@ def _from_config(cls, config: Mem0MemoryConfig) -> Self: is_cloud=config.is_cloud, api_key=config.api_key, config=config.config, + context_injection_mode=config.context_injection_mode, ) def _to_config(self) -> Mem0MemoryConfig: @@ -446,4 +502,5 @@ def _to_config(self) -> Mem0MemoryConfig: is_cloud=self._is_cloud, api_key=self._api_key, config=self._config, + context_injection_mode=self._context_injection_mode, ) diff --git a/python/packages/autogen-ext/tests/memory/test_mem0.py b/python/packages/autogen-ext/tests/memory/test_mem0.py index 27235e1305b9..e7d652382575 100644 --- a/python/packages/autogen-ext/tests/memory/test_mem0.py +++ b/python/packages/autogen-ext/tests/memory/test_mem0.py @@ -7,8 +7,9 @@ import pytest from autogen_core.memory import MemoryContent, MemoryMimeType from autogen_core.model_context import BufferedChatCompletionContext -from autogen_core.models import SystemMessage, UserMessage -from autogen_ext.memory.mem0 import Mem0Memory, Mem0MemoryConfig +from autogen_core.models import AssistantMessage, FunctionExecutionResultMessage, SystemMessage, UserMessage +from autogen_core import FunctionCall +from autogen_ext.memory.mem0 import ContextInjectionMode, Mem0Memory, Mem0MemoryConfig from dotenv import load_dotenv # Load environment variables from .env file @@ -66,6 +67,18 @@ def local_config() -> Mem0MemoryConfig: return Mem0MemoryConfig(user_id="test-user", limit=3, is_cloud=False, config={"path": ":memory:"}) +@pytest.fixture +def local_config_with_function_call_mode() -> Mem0MemoryConfig: + """Create local configuration with function call injection mode.""" + return Mem0MemoryConfig( + user_id="test-user", + limit=3, + is_cloud=False, + config={"path": ":memory:"}, + context_injection_mode=ContextInjectionMode.FUNCTION_CALL, + ) + + @pytest.mark.asyncio @patch("autogen_ext.memory.mem0._mem0.Memory0") async def test_basic_workflow(mock_mem0_class: MagicMock, local_config: Mem0MemoryConfig) -> None: @@ -89,6 +102,7 @@ async def test_basic_workflow(mock_mem0_class: MagicMock, local_config: Mem0Memo is_cloud=local_config.is_cloud, api_key=local_config.api_key, config=local_config.config, + context_injection_mode=local_config.context_injection_mode, ) # Add content to memory @@ -160,6 +174,7 @@ async def test_basic_workflow_with_cloud(mock_memory_client_class: MagicMock, cl is_cloud=cloud_config.is_cloud, api_key=cloud_config.api_key, config=cloud_config.config, + context_injection_mode=cloud_config.context_injection_mode, ) # Generate a unique test content string @@ -234,6 +249,7 @@ async def test_metadata_handling(mock_mem0_class: MagicMock, local_config: Mem0M is_cloud=local_config.is_cloud, api_key=local_config.api_key, config=local_config.config, + context_injection_mode=local_config.context_injection_mode, ) # Add content with metadata @@ -272,8 +288,8 @@ async def test_metadata_handling(mock_mem0_class: MagicMock, local_config: Mem0M @pytest.mark.asyncio @patch("autogen_ext.memory.mem0._mem0.Memory0") -async def test_update_context(mock_mem0_class: MagicMock, local_config: Mem0MemoryConfig) -> None: - """Test updating model context with retrieved memories.""" +async def test_update_context_system_message_mode(mock_mem0_class: MagicMock, local_config: Mem0MemoryConfig) -> None: + """Test updating model context with retrieved memories using system message mode (default).""" # Setup mock mock_mem0 = MagicMock() mock_mem0_class.from_config.return_value = mock_mem0 @@ -290,6 +306,7 @@ async def test_update_context(mock_mem0_class: MagicMock, local_config: Mem0Memo is_cloud=local_config.is_cloud, api_key=local_config.api_key, config=local_config.config, + context_injection_mode=ContextInjectionMode.SYSTEM_MESSAGE, # Explicit system message mode ) # Create a model context with a message @@ -323,19 +340,130 @@ async def test_update_context(mock_mem0_class: MagicMock, local_config: Mem0Memo @pytest.mark.asyncio -@patch("autogen_ext.memory.mem0._mem0.MemoryClient") # Patch for cloud mode -async def test_component_serialization(mock_memory_client_class: MagicMock) -> None: - """Test serialization and deserialization of the component.""" +@patch("autogen_ext.memory.mem0._mem0.Memory0") +async def test_update_context_function_call_mode( + mock_mem0_class: MagicMock, local_config_with_function_call_mode: Mem0MemoryConfig +) -> None: + """Test updating model context with retrieved memories using function call mode.""" + # Setup mock + mock_mem0 = MagicMock() + mock_mem0_class.from_config.return_value = mock_mem0 + + # Setup mock search results + mock_mem0.search.return_value = [ + {"memory": "Mars is known as the red planet.", "score": 0.9}, + {"memory": "Mars has two small moons: Phobos and Deimos.", "score": 0.8}, + ] + + memory = Mem0Memory( + user_id=local_config_with_function_call_mode.user_id, + limit=local_config_with_function_call_mode.limit, + is_cloud=local_config_with_function_call_mode.is_cloud, + api_key=local_config_with_function_call_mode.api_key, + config=local_config_with_function_call_mode.config, + context_injection_mode=local_config_with_function_call_mode.context_injection_mode, + ) + + # Verify the mode is set correctly + assert memory.context_injection_mode == ContextInjectionMode.FUNCTION_CALL + + # Create a model context with a message + context = BufferedChatCompletionContext(buffer_size=10) + await context.add_message(UserMessage(content="Tell me about Mars", source="user")) + + # Update context with memory + result = await memory.update_context(context) + + # Verify results + assert len(result.memories.results) == 2 + assert "Mars" in str(result.memories.results[0].content) + + # Verify search was called with correct query + mock_mem0.search.assert_called_once() + search_args = mock_mem0.search.call_args + assert "Mars" in search_args[0][0] + + # Verify context was updated with function call messages + messages = await context.get_messages() + assert len(messages) == 3 # Original message + assistant message + function result message + + # Verify assistant message with function call + assistant_message = messages[1] + assert isinstance(assistant_message, AssistantMessage) + assert len(assistant_message.content) == 1 + function_call = assistant_message.content[0] + # Add type check to ensure we have a FunctionCall object + assert isinstance(function_call, FunctionCall) + assert function_call.name == "retrieve_mem0memory" + assert function_call.arguments == "{}" + assert assistant_message.source == "memory_system" + + # Verify function execution result message + result_message = messages[2] + assert isinstance(result_message, FunctionExecutionResultMessage) + assert len(result_message.content) == 1 + function_result = result_message.content[0] + assert function_result.name == "retrieve_mem0memory" + assert function_result.call_id == function_call.id + assert function_result.is_error is False + assert "Mars is known as the red planet" in function_result.content + assert "Mars has two small moons" in function_result.content + + # Cleanup + await memory.close() + + +@pytest.mark.asyncio +@patch("autogen_ext.memory.mem0._mem0.Memory0") +async def test_context_injection_mode_property(mock_mem0_class: MagicMock) -> None: + """Test that context injection mode property works correctly.""" + # Setup mock + mock_mem0 = MagicMock() + mock_mem0_class.from_config.return_value = mock_mem0 + + # Test default mode (system message) + memory_default = Mem0Memory(user_id="test-user", is_cloud=False, config={"path": ":memory:"}) + assert memory_default.context_injection_mode == ContextInjectionMode.SYSTEM_MESSAGE + + # Test explicit system message mode + memory_system = Mem0Memory( + user_id="test-user", + is_cloud=False, + config={"path": ":memory:"}, + context_injection_mode=ContextInjectionMode.SYSTEM_MESSAGE, + ) + assert memory_system.context_injection_mode == ContextInjectionMode.SYSTEM_MESSAGE + + # Test function call mode + memory_function = Mem0Memory( + user_id="test-user", + is_cloud=False, + config={"path": ":memory:"}, + context_injection_mode=ContextInjectionMode.FUNCTION_CALL, + ) + assert memory_function.context_injection_mode == ContextInjectionMode.FUNCTION_CALL + + # Cleanup + await memory_default.close() + await memory_system.close() + await memory_function.close() + + +@pytest.mark.asyncio +@patch("autogen_ext.memory.mem0._mem0.MemoryClient") +async def test_component_serialization_with_context_injection_mode(mock_memory_client_class: MagicMock) -> None: + """Test serialization and deserialization of the component with context injection mode.""" # Setup mock mock_client = MagicMock() mock_memory_client_class.return_value = mock_client - # Create configuration + # Create configuration with function call mode user_id = str(uuid.uuid4()) config = Mem0MemoryConfig( user_id=user_id, limit=5, is_cloud=True, + context_injection_mode=ContextInjectionMode.FUNCTION_CALL, ) # Create memory instance @@ -345,23 +473,26 @@ async def test_component_serialization(mock_memory_client_class: MagicMock) -> N is_cloud=config.is_cloud, api_key=config.api_key, config=config.config, + context_injection_mode=config.context_injection_mode, ) # Dump config memory_config = memory.dump_component() - # Verify dumped config + # Verify dumped config includes context injection mode assert memory_config.config["user_id"] == user_id assert memory_config.config["limit"] == 5 assert memory_config.config["is_cloud"] is True + assert memory_config.config["context_injection_mode"] == ContextInjectionMode.FUNCTION_CALL - # Load from config + # Load from config using the public constructor loaded_memory = Mem0Memory( user_id=config.user_id, limit=config.limit, is_cloud=config.is_cloud, api_key=config.api_key, config=config.config, + context_injection_mode=config.context_injection_mode, ) # Verify loaded instance @@ -369,6 +500,7 @@ async def test_component_serialization(mock_memory_client_class: MagicMock) -> N assert loaded_memory.user_id == user_id assert loaded_memory.limit == 5 assert loaded_memory.is_cloud is True + assert loaded_memory.context_injection_mode == ContextInjectionMode.FUNCTION_CALL assert loaded_memory.config is None # Cleanup @@ -398,6 +530,7 @@ async def test_result_format_handling(mock_mem0_class: MagicMock, local_config: is_cloud=local_config.is_cloud, api_key=local_config.api_key, config=local_config.config, + context_injection_mode=local_config.context_injection_mode, ) # Query with dictionary format @@ -432,8 +565,14 @@ async def test_init_with_local_config(mock_mem0_class: MagicMock, full_local_con mock_mem0 = MagicMock() mock_mem0_class.from_config.return_value = mock_mem0 - # Initialize memory with local config - memory = Mem0Memory(user_id="test-local-config-user", limit=10, is_cloud=False, config=full_local_config) + # Initialize memory with local config and function call mode + memory = Mem0Memory( + user_id="test-local-config-user", + limit=10, + is_cloud=False, + config=full_local_config, + context_injection_mode=ContextInjectionMode.FUNCTION_CALL, + ) # Verify configuration was passed correctly mock_mem0_class.from_config.assert_called_once() @@ -443,6 +582,7 @@ async def test_init_with_local_config(mock_mem0_class: MagicMock, full_local_con assert memory._limit == 10 # type: ignore assert memory._is_cloud is False # type: ignore assert memory._config == full_local_config # type: ignore + assert memory._context_injection_mode == ContextInjectionMode.FUNCTION_CALL # type: ignore # Test serialization with local config memory_config = memory.dump_component() @@ -450,6 +590,7 @@ async def test_init_with_local_config(mock_mem0_class: MagicMock, full_local_con # Verify serialized config assert memory_config.config["user_id"] == "test-local-config-user" assert memory_config.config["is_cloud"] is False + assert memory_config.config["context_injection_mode"] == ContextInjectionMode.FUNCTION_CALL # Cleanup await memory.close() @@ -476,7 +617,13 @@ async def test_local_config_with_memory_operations( ] # Initialize Mem0Memory with is_cloud=False and the full_local_config - memory = Mem0Memory(user_id="test-local-config-user", limit=10, is_cloud=False, config=full_local_config) + memory = Mem0Memory( + user_id="test-local-config-user", + limit=10, + is_cloud=False, + config=full_local_config, + context_injection_mode=ContextInjectionMode.SYSTEM_MESSAGE, # Test with explicit system message mode + ) # Verify that mem0.Memory.from_config was called with the provided config mock_mem0_class.from_config.assert_called_once_with(config_dict=full_local_config) @@ -515,6 +662,7 @@ async def test_local_config_with_memory_operations( # Verify serialized config assert memory_config.config["user_id"] == "test-local-config-user" assert memory_config.config["is_cloud"] is False + assert memory_config.config["context_injection_mode"] == ContextInjectionMode.SYSTEM_MESSAGE assert "config" in memory_config.config assert memory_config.config["config"]["history_db_path"] == ":memory:"