diff --git a/src/memos/api/config.py b/src/memos/api/config.py index d0352d641..90acb054a 100644 --- a/src/memos/api/config.py +++ b/src/memos/api/config.py @@ -130,6 +130,11 @@ def is_scheduler_enabled() -> bool: """Check if scheduler is enabled via environment variable.""" return os.getenv("MOS_ENABLE_SCHEDULER", "false").lower() == "true" + @staticmethod + def is_default_cube_config_enabled() -> bool: + """Check if default cube config is enabled via environment variable.""" + return os.getenv("MOS_ENABLE_DEFAULT_CUBE_CONFIG", "false").lower() == "true" + @staticmethod def get_product_default_config() -> dict[str, Any]: """Get default configuration for Product API.""" @@ -321,3 +326,46 @@ def create_user_config(user_name: str, user_id: str) -> tuple[MOSConfig, General default_mem_cube = GeneralMemCube(default_cube_config) return default_config, default_mem_cube + + @staticmethod + def get_default_cube_config() -> GeneralMemCubeConfig | None: + """Get default cube configuration for product initialization. + + Returns: + GeneralMemCubeConfig | None: Default cube configuration if enabled, None otherwise. + """ + if not APIConfig.is_default_cube_config_enabled(): + return None + + openai_config = APIConfig.get_openai_config() + neo4j_config = APIConfig.get_neo4j_config() + + return GeneralMemCubeConfig.model_validate( + { + "user_id": "default", + "cube_id": "default_cube", + "text_mem": { + "backend": "tree_text", + "config": { + "extractor_llm": {"backend": "openai", "config": openai_config}, + "dispatcher_llm": {"backend": "openai", "config": openai_config}, + "graph_db": { + "backend": "neo4j", + "config": neo4j_config, + }, + "embedder": { + "backend": "ollama", + "config": { + "model_name_or_path": "nomic-embed-text:latest", + "api_base": os.getenv("OLLAMA_API_BASE", "http://localhost:11434"), + }, + }, + "reorganize": os.getenv("MOS_ENABLE_REORGANIZE", "false").lower() == "true", + }, + }, + "act_mem": {} + if os.getenv("ENABLE_ACTIVATION_MEMORY", "false").lower() == "false" + else APIConfig.get_activation_vllm_config(), + "para_mem": {}, + } + ) diff --git a/src/memos/api/routers/product_router.py b/src/memos/api/routers/product_router.py index 3642db9b8..96feba668 100644 --- a/src/memos/api/routers/product_router.py +++ b/src/memos/api/routers/product_router.py @@ -41,7 +41,13 @@ def get_mos_product_instance(): from memos.configs.mem_os import MOSConfig mos_config = MOSConfig(**default_config) - MOS_PRODUCT_INSTANCE = MOSProduct(default_config=mos_config) + + # Get default cube config from APIConfig (may be None if disabled) + default_cube_config = APIConfig.get_default_cube_config() + print("*********default_cube_config*********", default_cube_config) + MOS_PRODUCT_INSTANCE = MOSProduct( + default_config=mos_config, default_cube_config=default_cube_config + ) logger.info("MOSProduct instance created successfully with inheritance architecture") return MOS_PRODUCT_INSTANCE @@ -68,6 +74,7 @@ async def register_user(user_req: UserRegisterRequest): logger.info(f"user_config: {user_config.model_dump(mode='json')}") logger.info(f"default_mem_cube: {default_mem_cube.config.model_dump(mode='json')}") mos_product = get_mos_product_instance() + # Register user with default config and mem cube result = mos_product.user_register( user_id=user_req.user_id, diff --git a/src/memos/mem_cube/general.py b/src/memos/mem_cube/general.py index 220ae9149..7217c354b 100644 --- a/src/memos/mem_cube/general.py +++ b/src/memos/mem_cube/general.py @@ -7,7 +7,7 @@ from memos.exceptions import ConfigurationError, MemCubeError from memos.log import get_logger from memos.mem_cube.base import BaseMemCube -from memos.mem_cube.utils import download_repo +from memos.mem_cube.utils import download_repo, merge_config_with_default from memos.memories.activation.base import BaseActMemory from memos.memories.factory import MemoryFactory from memos.memories.parametric.base import BaseParaMemory @@ -114,7 +114,9 @@ def dump( @staticmethod def init_from_dir( - dir: str, memory_types: list[Literal["text_mem", "act_mem", "para_mem"]] | None = None + dir: str, + memory_types: list[Literal["text_mem", "act_mem", "para_mem"]] | None = None, + default_config: GeneralMemCubeConfig | None = None, ) -> "GeneralMemCube": """Create a MemCube instance from a MemCube directory. @@ -122,12 +124,20 @@ def init_from_dir( dir (str): The directory containing the memory files. memory_types (list[str], optional): List of memory types to load. If None, loads all available memory types. + default_config (GeneralMemCubeConfig, optional): Default configuration to merge with existing config. + If provided, will merge general settings while preserving critical user-specific fields. Returns: MemCube: An instance of MemCube loaded with memories from the specified directory. """ config_path = os.path.join(dir, "config.json") config = GeneralMemCubeConfig.from_json_file(config_path) + + # Merge with default config if provided + if default_config is not None: + config = merge_config_with_default(config, default_config) + logger.info(f"Applied default config to cube {config.cube_id}") + mem_cube = GeneralMemCube(config) mem_cube.load(dir, memory_types) return mem_cube @@ -137,6 +147,7 @@ def init_from_remote_repo( cube_id: str, base_url: str = "https://huggingface.co/datasets", memory_types: list[Literal["text_mem", "act_mem", "para_mem"]] | None = None, + default_config: GeneralMemCubeConfig | None = None, ) -> "GeneralMemCube": """Create a MemCube instance from a remote repository. @@ -145,12 +156,13 @@ def init_from_remote_repo( base_url (str): The base URL of the remote repository. memory_types (list[str], optional): List of memory types to load. If None, loads all available memory types. + default_config (GeneralMemCubeConfig, optional): Default configuration to merge with existing config. Returns: MemCube: An instance of MemCube loaded with memories from the specified remote repository. """ dir = download_repo(cube_id, base_url) - return GeneralMemCube.init_from_dir(dir, memory_types) + return GeneralMemCube.init_from_dir(dir, memory_types, default_config) @property def text_mem(self) -> "BaseTextMemory | None": diff --git a/src/memos/mem_cube/utils.py b/src/memos/mem_cube/utils.py index c6820b771..d0c3c5bd7 100644 --- a/src/memos/mem_cube/utils.py +++ b/src/memos/mem_cube/utils.py @@ -1,6 +1,15 @@ +import copy +import logging import subprocess import tempfile +from typing import Any + +from memos.configs.mem_cube import GeneralMemCubeConfig + + +logger = logging.getLogger(__name__) + def download_repo(repo: str, base_url: str, dir: str | None = None) -> str: """Download a repository from a remote source. @@ -22,3 +31,96 @@ def download_repo(repo: str, base_url: str, dir: str | None = None) -> str: subprocess.run(["git", "clone", repo_url, dir], check=True) return dir + + +def merge_config_with_default( + existing_config: GeneralMemCubeConfig, default_config: GeneralMemCubeConfig +) -> GeneralMemCubeConfig: + """ + Merge existing cube config with default config, preserving critical fields. + + This method updates general configuration fields (like API keys, model parameters) + while preserving critical user-specific fields (like user_id, cube_id, graph_db settings). + + Args: + existing_config (GeneralMemCubeConfig): The existing cube configuration loaded from file + default_config (GeneralMemCubeConfig): The default configuration to merge from + + Returns: + GeneralMemCubeConfig: Merged configuration + """ + + def deep_merge_dicts( + existing: dict[str, Any], default: dict[str, Any], preserve_keys: set[str] | None = None + ) -> dict[str, Any]: + """Recursively merge dictionaries, preserving specified keys from existing dict.""" + if preserve_keys is None: + preserve_keys = set() + + result = copy.deepcopy(existing) + + for key, default_value in default.items(): + if key in preserve_keys: + # Preserve existing value for critical keys + continue + + if key in result and isinstance(result[key], dict) and isinstance(default_value, dict): + # Recursively merge nested dictionaries + result[key] = deep_merge_dicts(result[key], default_value, preserve_keys) + elif key not in result or result[key] is None: + # Use default value if key doesn't exist or is None + result[key] = copy.deepcopy(default_value) + # For non-dict values, keep existing value unless it's None + + return result + + # Convert configs to dictionaries + existing_dict = existing_config.model_dump(mode="json") + default_dict = default_config.model_dump(mode="json") + + # Merge text_mem config + if "text_mem" in existing_dict and "text_mem" in default_dict: + existing_text_config = existing_dict["text_mem"].get("config", {}) + default_text_config = default_dict["text_mem"].get("config", {}) + + # Handle nested graph_db config specially + if "graph_db" in existing_text_config and "graph_db" in default_text_config: + existing_graph_config = existing_text_config["graph_db"].get("config", {}) + default_graph_config = default_text_config["graph_db"].get("config", {}) + + # Merge graph_db config, preserving critical keys + merged_graph_config = deep_merge_dicts( + existing_graph_config, + default_graph_config, + preserve_keys={"uri", "user", "password", "db_name", "auto_create"}, + ) + + # Update the configs + existing_text_config["graph_db"]["config"] = merged_graph_config + default_text_config["graph_db"]["config"] = merged_graph_config + + # Merge other text_mem config fields + merged_text_config = deep_merge_dicts(existing_text_config, default_text_config) + existing_dict["text_mem"]["config"] = merged_text_config + + # Merge act_mem config + if "act_mem" in existing_dict and "act_mem" in default_dict: + existing_act_config = existing_dict["act_mem"].get("config", {}) + default_act_config = default_dict["act_mem"].get("config", {}) + merged_act_config = deep_merge_dicts(existing_act_config, default_act_config) + existing_dict["act_mem"]["config"] = merged_act_config + + # Merge para_mem config + if "para_mem" in existing_dict and "para_mem" in default_dict: + existing_para_config = existing_dict["para_mem"].get("config", {}) + default_para_config = default_dict["para_mem"].get("config", {}) + merged_para_config = deep_merge_dicts(existing_para_config, default_para_config) + existing_dict["para_mem"]["config"] = merged_para_config + + # Create new config from merged dictionary + merged_config = GeneralMemCubeConfig.model_validate(existing_dict) + logger.info( + f"Merged cube config for user {merged_config.user_id}, cube {merged_config.cube_id}" + ) + + return merged_config diff --git a/src/memos/mem_os/product.py b/src/memos/mem_os/product.py index 6401d2b72..220b7deb8 100644 --- a/src/memos/mem_os/product.py +++ b/src/memos/mem_os/product.py @@ -1,5 +1,6 @@ import json import os +import random import time from collections.abc import Generator @@ -9,6 +10,7 @@ from dotenv import load_dotenv from transformers import AutoTokenizer +from memos.configs.mem_cube import GeneralMemCubeConfig from memos.configs.mem_os import MOSConfig from memos.log import get_logger from memos.mem_cube.general import GeneralMemCube @@ -43,13 +45,19 @@ class MOSProduct(MOSCore): Each user has their own configuration and cube access, but shares the same model instances. """ - def __init__(self, default_config: MOSConfig | None = None, max_user_instances: int = 100): + def __init__( + self, + default_config: MOSConfig | None = None, + max_user_instances: int = 100, + default_cube_config: GeneralMemCubeConfig | None = None, + ): """ Initialize MOSProduct with an optional default configuration. Args: default_config (MOSConfig | None): Default configuration for new users max_user_instances (int): Maximum number of user instances to keep in memory + default_cube_config (GeneralMemCubeConfig | None): Default cube configuration for loading cubes """ # Initialize with a root config for shared resources if default_config is None: @@ -74,6 +82,7 @@ def __init__(self, default_config: MOSConfig | None = None, max_user_instances: # Product-specific attributes self.default_config = default_config + self.default_cube_config = default_cube_config self.max_user_instances = max_user_instances # User-specific data structures @@ -96,11 +105,17 @@ def __init__(self, default_config: MOSConfig | None = None, max_user_instances: self.tokenizer = None # Restore user instances from persistent storage - self._restore_user_instances() + self._restore_user_instances(default_cube_config=default_cube_config) logger.info(f"User instances restored successfully, now user is {self.mem_cubes.keys()}") - def _restore_user_instances(self) -> None: - """Restore user instances from persistent storage after service restart.""" + def _restore_user_instances( + self, default_cube_config: GeneralMemCubeConfig | None = None + ) -> None: + """Restore user instances from persistent storage after service restart. + + Args: + default_cube_config (GeneralMemCubeConfig | None, optional): Default cube configuration. Defaults to None. + """ try: # Get all user configurations from persistent storage user_configs = self.global_user_manager.list_user_configs() @@ -128,8 +143,8 @@ def _restore_user_instances(self) -> None: self.user_configs[user_id] = config self._load_user_cube_access(user_id) - # Pre-load all cubes for this user - self._preload_user_cubes(user_id) + # Pre-load all cubes for this user with default config + self._preload_user_cubes(user_id, default_cube_config) logger.info( f"Restored user configuration and pre-loaded cubes for {user_id}" @@ -141,11 +156,14 @@ def _restore_user_instances(self) -> None: except Exception as e: logger.error(f"Error during user instance restoration: {e}") - def _preload_user_cubes(self, user_id: str) -> None: + def _preload_user_cubes( + self, user_id: str, default_cube_config: GeneralMemCubeConfig | None = None + ) -> None: """Pre-load all cubes for a user into memory. Args: user_id (str): The user ID to pre-load cubes for. + default_cube_config (GeneralMemCubeConfig | None, optional): Default cube configuration. Defaults to None. """ try: # Get user's accessible cubes from persistent storage @@ -155,7 +173,7 @@ def _preload_user_cubes(self, user_id: str) -> None: if cube.cube_id not in self.mem_cubes: try: if cube.cube_path and os.path.exists(cube.cube_path): - # Pre-load cube with all memory types + # Pre-load cube with all memory types and default config self.register_mem_cube( cube.cube_path, cube.cube_id, @@ -163,6 +181,7 @@ def _preload_user_cubes(self, user_id: str) -> None: memory_types=["act_mem"] if self.config.enable_activation_memory else [], + default_config=default_cube_config, ) logger.info(f"Pre-loaded cube {cube.cube_id} for user {user_id}") else: @@ -177,6 +196,38 @@ def _preload_user_cubes(self, user_id: str) -> None: except Exception as e: logger.error(f"Error pre-loading cubes for user {user_id}: {e}") + def _load_user_cubes( + self, user_id: str, default_cube_config: GeneralMemCubeConfig | None = None + ) -> None: + """Load all cubes for a user into memory. + + Args: + user_id (str): The user ID to load cubes for. + default_cube_config (GeneralMemCubeConfig | None, optional): Default cube configuration. Defaults to None. + """ + # Get user's accessible cubes from persistent storage + accessible_cubes = self.global_user_manager.get_user_cubes(user_id) + + for cube in accessible_cubes[:1]: + if cube.cube_id not in self.mem_cubes: + try: + if cube.cube_path and os.path.exists(cube.cube_path): + # Use MOSCore's register_mem_cube method directly with default config + # Only load act_mem since text_mem is stored in database + self.register_mem_cube( + cube.cube_path, + cube.cube_id, + user_id, + memory_types=["act_mem"], + default_config=default_cube_config, + ) + else: + logger.warning( + f"Cube path {cube.cube_path} does not exist for cube {cube.cube_id}" + ) + except Exception as e: + logger.error(f"Failed to load cube {cube.cube_id} for user {user_id}: {e}") + def _ensure_user_instance(self, user_id: str, max_instances: int | None = None) -> None: """ Ensure user configuration exists, creating it if necessary. @@ -278,27 +329,6 @@ def _get_or_create_user_config( return self._create_user_config(user_id, user_config) - def _load_user_cubes(self, user_id: str) -> None: - """Load all cubes for a user into memory.""" - # Get user's accessible cubes from persistent storage - accessible_cubes = self.global_user_manager.get_user_cubes(user_id) - - for cube in accessible_cubes[:1]: - if cube.cube_id not in self.mem_cubes: - try: - if cube.cube_path and os.path.exists(cube.cube_path): - # Use MOSCore's register_mem_cube method directly - # Only load act_mem since text_mem is stored in database - self.register_mem_cube( - cube.cube_path, cube.cube_id, user_id, memory_types=["act_mem"] - ) - else: - logger.warning( - f"Cube path {cube.cube_path} does not exist for cube {cube.cube_id}" - ) - except Exception as e: - logger.error(f"Failed to load cube {cube.cube_id} for user {user_id}: {e}") - def _build_system_prompt(self, user_id: str, memories_all: list[TextualMemoryItem]) -> str: """ Build custom system prompt for the user with memory references. @@ -470,6 +500,7 @@ def register_mem_cube( mem_cube_id: str | None = None, user_id: str | None = None, memory_types: list[Literal["text_mem", "act_mem", "para_mem"]] | None = None, + default_config: GeneralMemCubeConfig | None = None, ) -> None: """ Register a MemCube with the MOS. @@ -481,6 +512,7 @@ def register_mem_cube( memory_types (list[str], optional): List of memory types to load. If None, loads all available memory types. Options: ["text_mem", "act_mem", "para_mem"] + default_config (GeneralMemCubeConfig, optional): Default configuration for the cube. """ # Handle different input types if isinstance(mem_cube_name_or_path_or_object, GeneralMemCube): @@ -500,13 +532,15 @@ def register_mem_cube( # Create MemCube from path if os.path.exists(mem_cube_name_or_path): - mem_cube = GeneralMemCube.init_from_dir(mem_cube_name_or_path, memory_types) + mem_cube = GeneralMemCube.init_from_dir( + mem_cube_name_or_path, memory_types, default_config + ) else: logger.warning( f"MemCube {mem_cube_name_or_path} does not exist, try to init from remote repo." ) mem_cube = GeneralMemCube.init_from_remote_repo( - mem_cube_name_or_path, memory_types=memory_types + mem_cube_name_or_path, memory_types=memory_types, default_config=default_config ) # Register the MemCube @@ -519,6 +553,7 @@ def user_register( config: MOSConfig | None = None, interests: str | None = None, default_mem_cube: GeneralMemCube | None = None, + default_cube_config: GeneralMemCubeConfig | None = None, ) -> dict[str, str]: """Register a new user with configuration and default cube. @@ -527,6 +562,8 @@ def user_register( user_name (str): The user name for registration. config (MOSConfig | None, optional): User-specific configuration. Defaults to None. interests (str | None, optional): User interests as string. Defaults to None. + default_mem_cube (GeneralMemCube | None, optional): Default memory cube. Defaults to None. + default_cube_config (GeneralMemCubeConfig | None, optional): Default cube configuration. Defaults to None. Returns: dict[str, str]: Registration result with status and message. @@ -563,12 +600,13 @@ def user_register( except Exception as e: print(e) - # Register the default cube with MOS TODO overide + # Register the default cube with MOS self.register_mem_cube( mem_cube_name_or_path_or_object=default_mem_cube, mem_cube_id=default_cube_id, user_id=user_id, memory_types=["act_mem"] if self.config.enable_activation_memory else [], + default_config=default_cube_config, # use default cube config ) # Add interests to the default cube if provided @@ -662,7 +700,7 @@ def chat( self._validate_user_exists(user_id) # Load user cubes if not already loaded - self._load_user_cubes(user_id) + self._load_user_cubes(user_id, self.default_cube_config) time_start = time.time() memories_list = super().search(query, user_id)["text_mem"] # Get response from parent MOSCore (returns string, not generator) @@ -709,7 +747,7 @@ def chat_with_references( Generator[str, None, None]: The response string generator with reference processing. """ - self._load_user_cubes(user_id) + self._load_user_cubes(user_id, self.default_cube_config) time_start = time.time() memories_list = [] @@ -865,7 +903,7 @@ def get_all( """ # Load user cubes if not already loaded - self._load_user_cubes(user_id) + self._load_user_cubes(user_id, self.default_cube_config) memory_list = super().get_all( mem_cube_id=mem_cube_ids[0] if mem_cube_ids else None, user_id=user_id )[memory_type] @@ -894,10 +932,25 @@ def get_all( } ) elif memory_type == "act_mem": + memories_list = [] + act_mem_params = self.mem_cubes[mem_cube_ids[0]].act_mem.get_all() + if act_mem_params: + memories_data = act_mem_params[0].model_dump() + records = memories_data.get("records", []) + for record in records["text_memories"]: + memories_list.append( + { + "id": memories_data["id"], + "text": record, + "create_time": records["timestamp"], + "size": random.randint(1, 20), + "modify_times": 1, + } + ) reformat_memory_list.append( { "cube_id": "xxxxxxxxxxxxxxxx" if not mem_cube_ids else mem_cube_ids[0], - "memories": MOCK_DATA, + "memories": memories_list, } ) elif memory_type == "para_mem": @@ -944,7 +997,7 @@ def get_subgraph( """ # Load user cubes if not already loaded - self._load_user_cubes(user_id) + self._load_user_cubes(user_id, self.default_cube_config) memory_list = self._get_subgraph( query=query, mem_cube_id=mem_cube_ids[0], user_id=user_id, top_k=20 )["text_mem"] @@ -978,7 +1031,7 @@ def search( self._validate_user_access(user_id) # Load user cubes if not already loaded - self._load_user_cubes(user_id) + self._load_user_cubes(user_id, self.default_cube_config) search_result = super().search(query, user_id, install_cube_ids, top_k) text_memory_list = search_result["text_mem"] reformat_memory_list = [] @@ -1014,7 +1067,7 @@ def add( self._validate_user_exists(user_id) # Load user cubes if not already loaded - self._load_user_cubes(user_id) + self._load_user_cubes(user_id, self.default_cube_config) result = super().add(messages, memory_content, doc_path, mem_cube_id, user_id) diff --git a/src/memos/memories/textual/general.py b/src/memos/memories/textual/general.py index d7098dbe0..9e1ee1e04 100644 --- a/src/memos/memories/textual/general.py +++ b/src/memos/memories/textual/general.py @@ -8,7 +8,7 @@ from memos.configs.memory import GeneralTextMemoryConfig from memos.embedders.factory import EmbedderFactory, OllamaEmbedder -from memos.llms.factory import LLMFactory, OllamaLLM, OpenAILLM, AzureLLM +from memos.llms.factory import AzureLLM, LLMFactory, OllamaLLM, OpenAILLM from memos.log import get_logger from memos.memories.textual.base import BaseTextMemory from memos.memories.textual.item import TextualMemoryItem diff --git a/src/memos/memories/textual/tree.py b/src/memos/memories/textual/tree.py index edd1a662f..a3f87337d 100644 --- a/src/memos/memories/textual/tree.py +++ b/src/memos/memories/textual/tree.py @@ -10,7 +10,7 @@ from memos.configs.memory import TreeTextMemoryConfig from memos.embedders.factory import EmbedderFactory, OllamaEmbedder from memos.graph_dbs.factory import GraphStoreFactory, Neo4jGraphDB -from memos.llms.factory import LLMFactory, OllamaLLM, OpenAILLM, AzureLLM +from memos.llms.factory import AzureLLM, LLMFactory, OllamaLLM, OpenAILLM from memos.log import get_logger from memos.memories.textual.base import BaseTextMemory from memos.memories.textual.item import TextualMemoryItem, TreeNodeTextualMemoryMetadata diff --git a/src/memos/memories/textual/tree_text_memory/organize/manager.py b/src/memos/memories/textual/tree_text_memory/organize/manager.py index 10395485c..f3796b6bb 100644 --- a/src/memos/memories/textual/tree_text_memory/organize/manager.py +++ b/src/memos/memories/textual/tree_text_memory/organize/manager.py @@ -5,7 +5,7 @@ from memos.embedders.factory import OllamaEmbedder from memos.graph_dbs.neo4j import Neo4jGraphDB -from memos.llms.factory import OllamaLLM, OpenAILLM, AzureLLM +from memos.llms.factory import AzureLLM, OllamaLLM, OpenAILLM from memos.log import get_logger from memos.memories.textual.item import TextualMemoryItem, TreeNodeTextualMemoryMetadata from memos.memories.textual.tree_text_memory.organize.reorganizer import ( diff --git a/src/memos/memories/textual/tree_text_memory/retrieve/reranker.py b/src/memos/memories/textual/tree_text_memory/retrieve/reranker.py index 9c51a1b41..2a09ff752 100644 --- a/src/memos/memories/textual/tree_text_memory/retrieve/reranker.py +++ b/src/memos/memories/textual/tree_text_memory/retrieve/reranker.py @@ -1,7 +1,7 @@ import numpy as np from memos.embedders.factory import OllamaEmbedder -from memos.llms.factory import OllamaLLM, OpenAILLM, AzureLLM +from memos.llms.factory import AzureLLM, OllamaLLM, OpenAILLM from memos.memories.textual.item import TextualMemoryItem from memos.memories.textual.tree_text_memory.retrieve.retrieval_mid_structs import ParsedTaskGoal diff --git a/src/memos/memories/textual/tree_text_memory/retrieve/searcher.py b/src/memos/memories/textual/tree_text_memory/retrieve/searcher.py index 0f016ee0b..40bd01a4d 100644 --- a/src/memos/memories/textual/tree_text_memory/retrieve/searcher.py +++ b/src/memos/memories/textual/tree_text_memory/retrieve/searcher.py @@ -5,7 +5,7 @@ from memos.embedders.factory import OllamaEmbedder from memos.graph_dbs.factory import Neo4jGraphDB -from memos.llms.factory import OllamaLLM, OpenAILLM, AzureLLM +from memos.llms.factory import AzureLLM, OllamaLLM, OpenAILLM from memos.memories.textual.item import SearchedTreeNodeTextualMemoryMetadata, TextualMemoryItem from .internet_retriever_factory import InternetRetrieverFactory