MemoryOS 0.1.13__py3-none-any.whl → 0.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of MemoryOS might be problematic. Click here for more details.
- {memoryos-0.1.13.dist-info → memoryos-0.2.1.dist-info}/METADATA +78 -49
- memoryos-0.2.1.dist-info/RECORD +152 -0
- memoryos-0.2.1.dist-info/entry_points.txt +3 -0
- memos/__init__.py +1 -1
- memos/api/config.py +471 -0
- memos/api/exceptions.py +28 -0
- memos/api/mcp_serve.py +502 -0
- memos/api/product_api.py +35 -0
- memos/api/product_models.py +159 -0
- memos/api/routers/__init__.py +1 -0
- memos/api/routers/product_router.py +358 -0
- memos/chunkers/sentence_chunker.py +8 -2
- memos/cli.py +113 -0
- memos/configs/embedder.py +27 -0
- memos/configs/graph_db.py +83 -2
- memos/configs/llm.py +48 -0
- memos/configs/mem_cube.py +1 -1
- memos/configs/mem_reader.py +4 -0
- memos/configs/mem_scheduler.py +91 -5
- memos/configs/memory.py +10 -4
- memos/dependency.py +52 -0
- memos/embedders/ark.py +92 -0
- memos/embedders/factory.py +4 -0
- memos/embedders/sentence_transformer.py +8 -2
- memos/embedders/universal_api.py +32 -0
- memos/graph_dbs/base.py +2 -2
- memos/graph_dbs/factory.py +2 -0
- memos/graph_dbs/item.py +46 -0
- memos/graph_dbs/neo4j.py +377 -101
- memos/graph_dbs/neo4j_community.py +300 -0
- memos/llms/base.py +9 -0
- memos/llms/deepseek.py +54 -0
- memos/llms/factory.py +10 -1
- memos/llms/hf.py +170 -13
- memos/llms/hf_singleton.py +114 -0
- memos/llms/ollama.py +4 -0
- memos/llms/openai.py +68 -1
- memos/llms/qwen.py +63 -0
- memos/llms/vllm.py +153 -0
- memos/mem_cube/general.py +77 -16
- memos/mem_cube/utils.py +102 -0
- memos/mem_os/core.py +131 -41
- memos/mem_os/main.py +93 -11
- memos/mem_os/product.py +1098 -35
- memos/mem_os/utils/default_config.py +352 -0
- memos/mem_os/utils/format_utils.py +1154 -0
- memos/mem_reader/simple_struct.py +13 -8
- memos/mem_scheduler/base_scheduler.py +467 -36
- memos/mem_scheduler/general_scheduler.py +125 -244
- memos/mem_scheduler/modules/base.py +9 -0
- memos/mem_scheduler/modules/dispatcher.py +68 -2
- memos/mem_scheduler/modules/misc.py +39 -0
- memos/mem_scheduler/modules/monitor.py +228 -49
- memos/mem_scheduler/modules/rabbitmq_service.py +317 -0
- memos/mem_scheduler/modules/redis_service.py +32 -22
- memos/mem_scheduler/modules/retriever.py +250 -23
- memos/mem_scheduler/modules/schemas.py +189 -7
- memos/mem_scheduler/mos_for_test_scheduler.py +143 -0
- memos/mem_scheduler/utils.py +51 -2
- memos/mem_user/persistent_user_manager.py +260 -0
- memos/memories/activation/item.py +25 -0
- memos/memories/activation/kv.py +10 -3
- memos/memories/activation/vllmkv.py +219 -0
- memos/memories/factory.py +2 -0
- memos/memories/textual/general.py +7 -5
- memos/memories/textual/item.py +3 -1
- memos/memories/textual/tree.py +14 -6
- memos/memories/textual/tree_text_memory/organize/conflict.py +198 -0
- memos/memories/textual/tree_text_memory/organize/manager.py +72 -23
- memos/memories/textual/tree_text_memory/organize/redundancy.py +193 -0
- memos/memories/textual/tree_text_memory/organize/relation_reason_detector.py +233 -0
- memos/memories/textual/tree_text_memory/organize/reorganizer.py +606 -0
- memos/memories/textual/tree_text_memory/retrieve/recall.py +0 -1
- memos/memories/textual/tree_text_memory/retrieve/reranker.py +2 -2
- memos/memories/textual/tree_text_memory/retrieve/searcher.py +6 -5
- memos/parsers/markitdown.py +8 -2
- memos/templates/mem_reader_prompts.py +105 -36
- memos/templates/mem_scheduler_prompts.py +96 -47
- memos/templates/tree_reorganize_prompts.py +223 -0
- memos/vec_dbs/base.py +12 -0
- memos/vec_dbs/qdrant.py +46 -20
- memoryos-0.1.13.dist-info/RECORD +0 -122
- {memoryos-0.1.13.dist-info → memoryos-0.2.1.dist-info}/LICENSE +0 -0
- {memoryos-0.1.13.dist-info → memoryos-0.2.1.dist-info}/WHEEL +0 -0
memos/mem_cube/utils.py
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
import logging
|
|
1
3
|
import subprocess
|
|
2
4
|
import tempfile
|
|
3
5
|
|
|
6
|
+
from memos.configs.mem_cube import GeneralMemCubeConfig
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger(__name__)
|
|
10
|
+
|
|
4
11
|
|
|
5
12
|
def download_repo(repo: str, base_url: str, dir: str | None = None) -> str:
|
|
6
13
|
"""Download a repository from a remote source.
|
|
@@ -22,3 +29,98 @@ def download_repo(repo: str, base_url: str, dir: str | None = None) -> str:
|
|
|
22
29
|
subprocess.run(["git", "clone", repo_url, dir], check=True)
|
|
23
30
|
|
|
24
31
|
return dir
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def merge_config_with_default(
|
|
35
|
+
existing_config: GeneralMemCubeConfig, default_config: GeneralMemCubeConfig
|
|
36
|
+
) -> GeneralMemCubeConfig:
|
|
37
|
+
"""
|
|
38
|
+
Merge existing cube config with default config, preserving critical fields.
|
|
39
|
+
|
|
40
|
+
This method updates general configuration fields (like API keys, model parameters)
|
|
41
|
+
while preserving critical user-specific fields (like user_id, cube_id, graph_db settings).
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
existing_config (GeneralMemCubeConfig): The existing cube configuration loaded from file
|
|
45
|
+
default_config (GeneralMemCubeConfig): The default configuration to merge from
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
GeneralMemCubeConfig: Merged configuration
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
# Convert configs to dictionaries
|
|
52
|
+
existing_dict = existing_config.model_dump(mode="json")
|
|
53
|
+
default_dict = default_config.model_dump(mode="json")
|
|
54
|
+
|
|
55
|
+
logger.info(
|
|
56
|
+
f"Starting config merge for user {existing_config.user_id}, cube {existing_config.cube_id}"
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
# Define fields that should be preserved from existing config
|
|
60
|
+
preserve_fields = {"user_id", "cube_id", "config_filename", "model_schema"}
|
|
61
|
+
|
|
62
|
+
# Preserve graph_db from existing config if it exists, but merge some fields
|
|
63
|
+
preserved_graph_db = None
|
|
64
|
+
if "text_mem" in existing_dict and "text_mem" in default_dict:
|
|
65
|
+
existing_text_config = existing_dict["text_mem"].get("config", {})
|
|
66
|
+
default_text_config = default_dict["text_mem"].get("config", {})
|
|
67
|
+
|
|
68
|
+
if "graph_db" in existing_text_config and "graph_db" in default_text_config:
|
|
69
|
+
existing_graph_config = existing_text_config["graph_db"]["config"]
|
|
70
|
+
default_graph_config = default_text_config["graph_db"]["config"]
|
|
71
|
+
|
|
72
|
+
# Define graph_db fields to preserve (user-specific)
|
|
73
|
+
preserve_graph_fields = {
|
|
74
|
+
"uri",
|
|
75
|
+
"user",
|
|
76
|
+
"password",
|
|
77
|
+
"db_name",
|
|
78
|
+
"auto_create",
|
|
79
|
+
"user_name",
|
|
80
|
+
"use_multi_db",
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
# Create merged graph_db config
|
|
84
|
+
merged_graph_config = copy.deepcopy(existing_graph_config)
|
|
85
|
+
for key, value in default_graph_config.items():
|
|
86
|
+
if key not in preserve_graph_fields:
|
|
87
|
+
merged_graph_config[key] = value
|
|
88
|
+
logger.debug(
|
|
89
|
+
f"Updated graph_db field '{key}': {existing_graph_config.get(key)} -> {value}"
|
|
90
|
+
)
|
|
91
|
+
if not default_graph_config.get("use_multi_db", True):
|
|
92
|
+
# set original use_multi_db to False if default_graph_config.use_multi_db is False
|
|
93
|
+
if merged_graph_config.get("use_multi_db", True):
|
|
94
|
+
merged_graph_config["use_multi_db"] = False
|
|
95
|
+
merged_graph_config["user_name"] = merged_graph_config.get("db_name")
|
|
96
|
+
merged_graph_config["db_name"] = default_graph_config.get("db_name")
|
|
97
|
+
else:
|
|
98
|
+
logger.info("use_multi_db is already False, no need to change")
|
|
99
|
+
|
|
100
|
+
preserved_graph_db = {
|
|
101
|
+
"backend": existing_text_config["graph_db"]["backend"],
|
|
102
|
+
"config": merged_graph_config,
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
# Use default config as base
|
|
106
|
+
merged_dict = copy.deepcopy(default_dict)
|
|
107
|
+
|
|
108
|
+
# Restore preserved fields from existing config
|
|
109
|
+
for field in preserve_fields:
|
|
110
|
+
if field in existing_dict:
|
|
111
|
+
merged_dict[field] = existing_dict[field]
|
|
112
|
+
logger.debug(f"Preserved field '{field}': {existing_dict[field]}")
|
|
113
|
+
|
|
114
|
+
# Restore graph_db if it was preserved
|
|
115
|
+
if preserved_graph_db and "text_mem" in merged_dict:
|
|
116
|
+
merged_dict["text_mem"]["config"]["graph_db"] = preserved_graph_db
|
|
117
|
+
logger.debug(f"Preserved graph_db with merged config: {preserved_graph_db}")
|
|
118
|
+
|
|
119
|
+
# Create new config from merged dictionary
|
|
120
|
+
merged_config = GeneralMemCubeConfig.model_validate(merged_dict)
|
|
121
|
+
|
|
122
|
+
logger.info(
|
|
123
|
+
f"Successfully merged cube config for user {merged_config.user_id}, cube {merged_config.cube_id}"
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
return merged_config
|
memos/mem_os/core.py
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import json
|
|
1
2
|
import os
|
|
3
|
+
import uuid
|
|
2
4
|
|
|
3
5
|
from datetime import datetime
|
|
4
6
|
from pathlib import Path
|
|
@@ -11,7 +13,11 @@ from memos.log import get_logger
|
|
|
11
13
|
from memos.mem_cube.general import GeneralMemCube
|
|
12
14
|
from memos.mem_reader.factory import MemReaderFactory
|
|
13
15
|
from memos.mem_scheduler.general_scheduler import GeneralScheduler
|
|
14
|
-
from memos.mem_scheduler.modules.schemas import
|
|
16
|
+
from memos.mem_scheduler.modules.schemas import (
|
|
17
|
+
ADD_LABEL,
|
|
18
|
+
ANSWER_LABEL,
|
|
19
|
+
ScheduleMessageItem,
|
|
20
|
+
)
|
|
15
21
|
from memos.mem_scheduler.scheduler_factory import SchedulerFactory
|
|
16
22
|
from memos.mem_user.user_manager import UserManager, UserRole
|
|
17
23
|
from memos.memories.activation.item import ActivationMemoryItem
|
|
@@ -30,7 +36,7 @@ class MOSCore:
|
|
|
30
36
|
MOSCore acts as an operating system layer for handling and orchestrating MemCube instances.
|
|
31
37
|
"""
|
|
32
38
|
|
|
33
|
-
def __init__(self, config: MOSConfig):
|
|
39
|
+
def __init__(self, config: MOSConfig, user_manager: UserManager | None = None):
|
|
34
40
|
self.config = config
|
|
35
41
|
self.user_id = config.user_id
|
|
36
42
|
self.session_id = config.session_id
|
|
@@ -39,7 +45,12 @@ class MOSCore:
|
|
|
39
45
|
self.mem_reader = MemReaderFactory.from_config(config.mem_reader)
|
|
40
46
|
self.chat_history_manager: dict[str, ChatHistory] = {}
|
|
41
47
|
self._register_chat_history()
|
|
42
|
-
|
|
48
|
+
|
|
49
|
+
# Use provided user_manager or create a new one
|
|
50
|
+
if user_manager is not None:
|
|
51
|
+
self.user_manager = user_manager
|
|
52
|
+
else:
|
|
53
|
+
self.user_manager = UserManager(user_id=self.user_id if self.user_id else "root")
|
|
43
54
|
|
|
44
55
|
# Validate user exists
|
|
45
56
|
if not self.user_manager.validate_user(self.user_id):
|
|
@@ -50,7 +61,7 @@ class MOSCore:
|
|
|
50
61
|
# Lazy initialization marker
|
|
51
62
|
self._mem_scheduler_lock = Lock()
|
|
52
63
|
self.enable_mem_scheduler = self.config.get("enable_mem_scheduler", False)
|
|
53
|
-
self._mem_scheduler = None
|
|
64
|
+
self._mem_scheduler: GeneralScheduler = None
|
|
54
65
|
logger.info(f"MOS initialized for user: {self.user_id}")
|
|
55
66
|
|
|
56
67
|
@property
|
|
@@ -58,6 +69,7 @@ class MOSCore:
|
|
|
58
69
|
"""Lazy-loaded property for memory scheduler."""
|
|
59
70
|
if self.enable_mem_scheduler and self._mem_scheduler is None:
|
|
60
71
|
self._initialize_mem_scheduler()
|
|
72
|
+
self._mem_scheduler.mem_cubes = self.mem_cubes
|
|
61
73
|
return self._mem_scheduler
|
|
62
74
|
|
|
63
75
|
@mem_scheduler.setter
|
|
@@ -74,6 +86,7 @@ class MOSCore:
|
|
|
74
86
|
raise TypeError(f"Expected GeneralScheduler or None, got {type(value)}")
|
|
75
87
|
|
|
76
88
|
self._mem_scheduler = value
|
|
89
|
+
self._mem_scheduler.mem_cubes = self.mem_cubes
|
|
77
90
|
|
|
78
91
|
if value:
|
|
79
92
|
logger.info("Memory scheduler manually set")
|
|
@@ -92,7 +105,18 @@ class MOSCore:
|
|
|
92
105
|
logger.info("Initializing memory scheduler...")
|
|
93
106
|
scheduler_config = self.config.mem_scheduler
|
|
94
107
|
self._mem_scheduler = SchedulerFactory.from_config(scheduler_config)
|
|
95
|
-
|
|
108
|
+
# Validate required components
|
|
109
|
+
if not hasattr(self.mem_reader, "llm"):
|
|
110
|
+
raise AttributeError(
|
|
111
|
+
f"Memory reader of type {type(self.mem_reader).__name__} "
|
|
112
|
+
"missing required 'llm' attribute"
|
|
113
|
+
)
|
|
114
|
+
self._mem_scheduler.initialize_modules(chat_llm=self.chat_llm)
|
|
115
|
+
else:
|
|
116
|
+
# Configure scheduler modules
|
|
117
|
+
self._mem_scheduler.initialize_modules(
|
|
118
|
+
chat_llm=self.chat_llm, process_llm=self.mem_reader.llm
|
|
119
|
+
)
|
|
96
120
|
self._mem_scheduler.start()
|
|
97
121
|
|
|
98
122
|
def mem_scheduler_on(self) -> bool:
|
|
@@ -123,6 +147,17 @@ class MOSCore:
|
|
|
123
147
|
logger.error(f"Failed to stop scheduler: {e!s}")
|
|
124
148
|
return False
|
|
125
149
|
|
|
150
|
+
def mem_reorganizer_on(self) -> bool:
|
|
151
|
+
pass
|
|
152
|
+
|
|
153
|
+
def mem_reorganizer_off(self) -> bool:
|
|
154
|
+
"""temporally implement"""
|
|
155
|
+
for mem_cube in self.mem_cubes.values():
|
|
156
|
+
logger.info(f"try to close reorganizer for {mem_cube.text_mem.config.cube_id}")
|
|
157
|
+
if mem_cube.text_mem and mem_cube.text_mem.is_reorganize:
|
|
158
|
+
logger.info(f"close reorganizer for {mem_cube.text_mem.config.cube_id}")
|
|
159
|
+
mem_cube.text_mem.memory_manager.close()
|
|
160
|
+
|
|
126
161
|
def _register_chat_history(self, user_id: str | None = None) -> None:
|
|
127
162
|
"""Initialize chat history with user ID."""
|
|
128
163
|
if user_id is None:
|
|
@@ -186,12 +221,16 @@ class MOSCore:
|
|
|
186
221
|
documents.append(str(file_path))
|
|
187
222
|
return documents
|
|
188
223
|
|
|
189
|
-
def chat(self, query: str, user_id: str | None = None) -> str:
|
|
224
|
+
def chat(self, query: str, user_id: str | None = None, base_prompt: str | None = None) -> str:
|
|
190
225
|
"""
|
|
191
226
|
Chat with the MOS.
|
|
192
227
|
|
|
193
228
|
Args:
|
|
194
229
|
query (str): The user's query.
|
|
230
|
+
user_id (str, optional): The user ID for the chat session. Defaults to the user ID from the config.
|
|
231
|
+
base_prompt (str, optional): A custom base prompt to use for the chat.
|
|
232
|
+
It can be a template string with a `{memories}` placeholder.
|
|
233
|
+
If not provided, a default prompt is used.
|
|
195
234
|
|
|
196
235
|
Returns:
|
|
197
236
|
str: The response from the MOS.
|
|
@@ -218,7 +257,7 @@ class MOSCore:
|
|
|
218
257
|
user_id=target_user_id,
|
|
219
258
|
mem_cube_id=mem_cube_id,
|
|
220
259
|
mem_cube=mem_cube,
|
|
221
|
-
label=
|
|
260
|
+
label=ADD_LABEL,
|
|
222
261
|
content=query,
|
|
223
262
|
timestamp=datetime.now(),
|
|
224
263
|
)
|
|
@@ -227,9 +266,9 @@ class MOSCore:
|
|
|
227
266
|
memories = mem_cube.text_mem.search(query, top_k=self.config.top_k)
|
|
228
267
|
memories_all.extend(memories)
|
|
229
268
|
logger.info(f"🧠 [Memory] Searched memories:\n{self._str_memories(memories_all)}\n")
|
|
230
|
-
system_prompt = self._build_system_prompt(memories_all)
|
|
269
|
+
system_prompt = self._build_system_prompt(memories_all, base_prompt=base_prompt)
|
|
231
270
|
else:
|
|
232
|
-
system_prompt = self._build_system_prompt()
|
|
271
|
+
system_prompt = self._build_system_prompt(base_prompt=base_prompt)
|
|
233
272
|
current_messages = [
|
|
234
273
|
{"role": "system", "content": system_prompt},
|
|
235
274
|
*chat_history.chat_history,
|
|
@@ -261,8 +300,8 @@ class MOSCore:
|
|
|
261
300
|
self.chat_history_manager[user_id] = chat_history
|
|
262
301
|
|
|
263
302
|
# submit message to scheduler
|
|
264
|
-
|
|
265
|
-
mem_cube_id =
|
|
303
|
+
for accessible_mem_cube in accessible_cubes:
|
|
304
|
+
mem_cube_id = accessible_mem_cube.cube_id
|
|
266
305
|
mem_cube = self.mem_cubes[mem_cube_id]
|
|
267
306
|
if self.enable_mem_scheduler and self.mem_scheduler is not None:
|
|
268
307
|
message_item = ScheduleMessageItem(
|
|
@@ -277,20 +316,39 @@ class MOSCore:
|
|
|
277
316
|
|
|
278
317
|
return response
|
|
279
318
|
|
|
280
|
-
def _build_system_prompt(
|
|
319
|
+
def _build_system_prompt(
|
|
320
|
+
self,
|
|
321
|
+
memories: list[TextualMemoryItem] | list[str] | None = None,
|
|
322
|
+
base_prompt: str | None = None,
|
|
323
|
+
) -> str:
|
|
281
324
|
"""Build system prompt with optional memories context."""
|
|
282
|
-
base_prompt
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
325
|
+
if base_prompt is None:
|
|
326
|
+
base_prompt = (
|
|
327
|
+
"You are a knowledgeable and helpful AI assistant. "
|
|
328
|
+
"You have access to conversation memories that help you provide more personalized responses. "
|
|
329
|
+
"Use the memories to understand the user's context, preferences, and past interactions. "
|
|
330
|
+
"If memories are provided, reference them naturally when relevant, but don't explicitly mention having memories."
|
|
331
|
+
)
|
|
288
332
|
|
|
333
|
+
memory_context = ""
|
|
289
334
|
if memories:
|
|
290
|
-
|
|
335
|
+
memory_list = []
|
|
291
336
|
for i, memory in enumerate(memories, 1):
|
|
292
|
-
|
|
293
|
-
|
|
337
|
+
if isinstance(memory, TextualMemoryItem):
|
|
338
|
+
text_memory = memory.memory
|
|
339
|
+
else:
|
|
340
|
+
if not isinstance(memory, str):
|
|
341
|
+
logger.error("Unexpected memory type.")
|
|
342
|
+
text_memory = memory
|
|
343
|
+
memory_list.append(f"{i}. {text_memory}")
|
|
344
|
+
memory_context = "\n".join(memory_list)
|
|
345
|
+
|
|
346
|
+
if "{memories}" in base_prompt:
|
|
347
|
+
return base_prompt.format(memories=memory_context)
|
|
348
|
+
elif memories:
|
|
349
|
+
# For backward compatibility, append memories if no placeholder is found
|
|
350
|
+
memory_context_with_header = "\n\n## Memories:\n" + memory_context
|
|
351
|
+
return base_prompt + memory_context_with_header
|
|
294
352
|
return base_prompt
|
|
295
353
|
|
|
296
354
|
def _str_memories(
|
|
@@ -364,7 +422,10 @@ class MOSCore:
|
|
|
364
422
|
return self.user_manager.create_cube(cube_name, owner_id, cube_path, cube_id)
|
|
365
423
|
|
|
366
424
|
def register_mem_cube(
|
|
367
|
-
self,
|
|
425
|
+
self,
|
|
426
|
+
mem_cube_name_or_path: str | GeneralMemCube,
|
|
427
|
+
mem_cube_id: str | None = None,
|
|
428
|
+
user_id: str | None = None,
|
|
368
429
|
) -> None:
|
|
369
430
|
"""
|
|
370
431
|
Register a MemCube with the MOS.
|
|
@@ -377,12 +438,18 @@ class MOSCore:
|
|
|
377
438
|
self._validate_user_exists(target_user_id)
|
|
378
439
|
|
|
379
440
|
if mem_cube_id is None:
|
|
380
|
-
|
|
441
|
+
if isinstance(mem_cube_name_or_path, GeneralMemCube):
|
|
442
|
+
mem_cube_id = f"cube_{target_user_id}"
|
|
443
|
+
else:
|
|
444
|
+
mem_cube_id = mem_cube_name_or_path
|
|
381
445
|
|
|
382
446
|
if mem_cube_id in self.mem_cubes:
|
|
383
447
|
logger.info(f"MemCube with ID {mem_cube_id} already in MOS, skip install.")
|
|
384
448
|
else:
|
|
385
|
-
if
|
|
449
|
+
if isinstance(mem_cube_name_or_path, GeneralMemCube):
|
|
450
|
+
self.mem_cubes[mem_cube_id] = mem_cube_name_or_path
|
|
451
|
+
logger.info(f"register new cube {mem_cube_id} for user {target_user_id}")
|
|
452
|
+
elif os.path.exists(mem_cube_name_or_path):
|
|
386
453
|
self.mem_cubes[mem_cube_id] = GeneralMemCube.init_from_dir(mem_cube_name_or_path)
|
|
387
454
|
else:
|
|
388
455
|
logger.warning(
|
|
@@ -394,6 +461,14 @@ class MOSCore:
|
|
|
394
461
|
# Check if cube already exists in database
|
|
395
462
|
existing_cube = self.user_manager.get_cube(mem_cube_id)
|
|
396
463
|
|
|
464
|
+
# check the embedder is it consistent with MOSConfig
|
|
465
|
+
if self.config.mem_reader.config.embedder != (
|
|
466
|
+
cube_embedder := self.mem_cubes[mem_cube_id].text_mem.config.embedder
|
|
467
|
+
):
|
|
468
|
+
logger.warning(
|
|
469
|
+
f"Cube Embedder is not consistent with MOSConfig for cube: {mem_cube_id}, will use Cube Embedder: {cube_embedder}"
|
|
470
|
+
)
|
|
471
|
+
|
|
397
472
|
if existing_cube:
|
|
398
473
|
# Cube exists, just add user to cube if not already associated
|
|
399
474
|
if not self.user_manager.validate_user_cube_access(target_user_id, mem_cube_id):
|
|
@@ -407,10 +482,14 @@ class MOSCore:
|
|
|
407
482
|
else:
|
|
408
483
|
# Cube doesn't exist, create it
|
|
409
484
|
self.create_cube_for_user(
|
|
410
|
-
cube_name=mem_cube_name_or_path
|
|
485
|
+
cube_name=mem_cube_name_or_path
|
|
486
|
+
if not isinstance(mem_cube_name_or_path, GeneralMemCube)
|
|
487
|
+
else mem_cube_id,
|
|
411
488
|
owner_id=target_user_id,
|
|
412
489
|
cube_id=mem_cube_id,
|
|
413
|
-
cube_path=mem_cube_name_or_path
|
|
490
|
+
cube_path=mem_cube_name_or_path
|
|
491
|
+
if not isinstance(mem_cube_name_or_path, GeneralMemCube)
|
|
492
|
+
else "init",
|
|
414
493
|
)
|
|
415
494
|
logger.info(f"register new cube {mem_cube_id} for user {target_user_id}")
|
|
416
495
|
|
|
@@ -427,7 +506,11 @@ class MOSCore:
|
|
|
427
506
|
raise ValueError(f"MemCube with ID {mem_cube_id} does not exist.")
|
|
428
507
|
|
|
429
508
|
def search(
|
|
430
|
-
self,
|
|
509
|
+
self,
|
|
510
|
+
query: str,
|
|
511
|
+
user_id: str | None = None,
|
|
512
|
+
install_cube_ids: list[str] | None = None,
|
|
513
|
+
top_k: int | None = None,
|
|
431
514
|
) -> MOSSearchResult:
|
|
432
515
|
"""
|
|
433
516
|
Search for textual memories across all registered MemCubes.
|
|
@@ -464,18 +547,10 @@ class MOSCore:
|
|
|
464
547
|
and (mem_cube.text_mem is not None)
|
|
465
548
|
and self.config.enable_textual_memory
|
|
466
549
|
):
|
|
467
|
-
memories = mem_cube.text_mem.search(
|
|
468
|
-
|
|
469
|
-
logger.info(
|
|
470
|
-
f"🧠 [Memory] Searched memories from {mem_cube_id}:\n{self._str_memories(memories)}\n"
|
|
550
|
+
memories = mem_cube.text_mem.search(
|
|
551
|
+
query, top_k=top_k if top_k else self.config.top_k
|
|
471
552
|
)
|
|
472
|
-
|
|
473
|
-
(mem_cube_id in install_cube_ids)
|
|
474
|
-
and (mem_cube.act_mem is not None)
|
|
475
|
-
and self.config.enable_activation_memory
|
|
476
|
-
):
|
|
477
|
-
memories = mem_cube.act_mem.extract(query)
|
|
478
|
-
result["act_mem"].append({"cube_id": mem_cube_id, "memories": [memories]})
|
|
553
|
+
result["text_mem"].append({"cube_id": mem_cube_id, "memories": memories})
|
|
479
554
|
logger.info(
|
|
480
555
|
f"🧠 [Memory] Searched memories from {mem_cube_id}:\n{self._str_memories(memories)}\n"
|
|
481
556
|
)
|
|
@@ -538,10 +613,25 @@ class MOSCore:
|
|
|
538
613
|
memories = self.mem_reader.get_memory(
|
|
539
614
|
messages_list,
|
|
540
615
|
type="chat",
|
|
541
|
-
info={"user_id": target_user_id, "session_id":
|
|
616
|
+
info={"user_id": target_user_id, "session_id": str(uuid.uuid4())},
|
|
542
617
|
)
|
|
543
618
|
for mem in memories:
|
|
544
619
|
self.mem_cubes[mem_cube_id].text_mem.add(mem)
|
|
620
|
+
|
|
621
|
+
# submit messages for scheduler
|
|
622
|
+
mem_cube = self.mem_cubes[mem_cube_id]
|
|
623
|
+
if self.enable_mem_scheduler and self.mem_scheduler is not None:
|
|
624
|
+
text_messages = [message["content"] for message in messages]
|
|
625
|
+
message_item = ScheduleMessageItem(
|
|
626
|
+
user_id=target_user_id,
|
|
627
|
+
mem_cube_id=mem_cube_id,
|
|
628
|
+
mem_cube=mem_cube,
|
|
629
|
+
label=ADD_LABEL,
|
|
630
|
+
content=json.dumps(text_messages),
|
|
631
|
+
timestamp=datetime.now(),
|
|
632
|
+
)
|
|
633
|
+
self.mem_scheduler.submit_messages(messages=[message_item])
|
|
634
|
+
|
|
545
635
|
if (
|
|
546
636
|
(memory_content is not None)
|
|
547
637
|
and self.config.enable_textual_memory
|
|
@@ -567,7 +657,7 @@ class MOSCore:
|
|
|
567
657
|
memories = self.mem_reader.get_memory(
|
|
568
658
|
messages_list,
|
|
569
659
|
type="chat",
|
|
570
|
-
info={"user_id": target_user_id, "session_id":
|
|
660
|
+
info={"user_id": target_user_id, "session_id": str(uuid.uuid4())},
|
|
571
661
|
)
|
|
572
662
|
for mem in memories:
|
|
573
663
|
self.mem_cubes[mem_cube_id].text_mem.add(mem)
|
|
@@ -580,7 +670,7 @@ class MOSCore:
|
|
|
580
670
|
doc_memory = self.mem_reader.get_memory(
|
|
581
671
|
documents,
|
|
582
672
|
type="doc",
|
|
583
|
-
info={"user_id": target_user_id, "session_id":
|
|
673
|
+
info={"user_id": target_user_id, "session_id": str(uuid.uuid4())},
|
|
584
674
|
)
|
|
585
675
|
for mem in doc_memory:
|
|
586
676
|
self.mem_cubes[mem_cube_id].text_mem.add(mem)
|
memos/mem_os/main.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import concurrent.futures
|
|
2
2
|
import json
|
|
3
|
+
import os
|
|
3
4
|
|
|
4
5
|
from typing import Any
|
|
5
6
|
|
|
@@ -7,6 +8,7 @@ from memos.configs.mem_os import MOSConfig
|
|
|
7
8
|
from memos.llms.factory import LLMFactory
|
|
8
9
|
from memos.log import get_logger
|
|
9
10
|
from memos.mem_os.core import MOSCore
|
|
11
|
+
from memos.mem_os.utils.default_config import get_default
|
|
10
12
|
from memos.memories.textual.base import BaseTextMemory
|
|
11
13
|
from memos.templates.mos_prompts import (
|
|
12
14
|
COT_DECOMPOSE_PROMPT,
|
|
@@ -24,20 +26,94 @@ class MOS(MOSCore):
|
|
|
24
26
|
This class maintains backward compatibility with the original MOS interface.
|
|
25
27
|
"""
|
|
26
28
|
|
|
27
|
-
def __init__(self, config: MOSConfig):
|
|
29
|
+
def __init__(self, config: MOSConfig | None = None):
|
|
30
|
+
"""
|
|
31
|
+
Initialize MOS with optional automatic configuration.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
config (MOSConfig, optional): MOS configuration. If None, will use automatic configuration from environment variables.
|
|
35
|
+
"""
|
|
36
|
+
if config is None:
|
|
37
|
+
# Auto-configure if no config provided
|
|
38
|
+
config, default_cube = self._auto_configure()
|
|
39
|
+
self._auto_registered_cube = default_cube
|
|
40
|
+
else:
|
|
41
|
+
self._auto_registered_cube = None
|
|
42
|
+
|
|
28
43
|
self.enable_cot = config.PRO_MODE
|
|
29
44
|
if config.PRO_MODE:
|
|
30
45
|
print(PRO_MODE_WELCOME_MESSAGE)
|
|
31
46
|
logger.info(PRO_MODE_WELCOME_MESSAGE)
|
|
32
47
|
super().__init__(config)
|
|
33
48
|
|
|
34
|
-
|
|
49
|
+
# Auto-register cube if one was created
|
|
50
|
+
if self._auto_registered_cube is not None:
|
|
51
|
+
self.register_mem_cube(self._auto_registered_cube)
|
|
52
|
+
logger.info(
|
|
53
|
+
f"Auto-registered default cube: {self._auto_registered_cube.config.cube_id}"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
def _auto_configure(self, **kwargs) -> tuple[MOSConfig, Any]:
|
|
57
|
+
"""
|
|
58
|
+
Automatically configure MOS with default settings.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
tuple[MOSConfig, Any]: MOS configuration and default MemCube
|
|
62
|
+
"""
|
|
63
|
+
# Get configuration from environment variables
|
|
64
|
+
openai_api_key = os.getenv("OPENAI_API_KEY")
|
|
65
|
+
openai_api_base = os.getenv("OPENAI_API_BASE", "https://api.openai.com/v1")
|
|
66
|
+
text_mem_type = os.getenv("MOS_TEXT_MEM_TYPE", "general_text")
|
|
67
|
+
|
|
68
|
+
if not openai_api_key:
|
|
69
|
+
raise ValueError("OPENAI_API_KEY environment variable is required")
|
|
70
|
+
|
|
71
|
+
logger.info(f"Auto-configuring MOS with text_mem_type: {text_mem_type}")
|
|
72
|
+
return get_default(
|
|
73
|
+
openai_api_key=openai_api_key,
|
|
74
|
+
openai_api_base=openai_api_base,
|
|
75
|
+
text_mem_type=text_mem_type,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
@classmethod
|
|
79
|
+
def simple(cls) -> "MOS":
|
|
80
|
+
"""
|
|
81
|
+
Create a MOS instance with automatic configuration from environment variables.
|
|
82
|
+
|
|
83
|
+
This is the simplest way to get started with MemOS.
|
|
84
|
+
|
|
85
|
+
Environment variables needed:
|
|
86
|
+
- OPENAI_API_KEY: Your OpenAI API key
|
|
87
|
+
- OPENAI_API_BASE: OpenAI API base URL (optional, defaults to "https://api.openai.com/v1")
|
|
88
|
+
- MOS_TEXT_MEM_TYPE: Text memory type (optional, defaults to "general_text")
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
MOS: Configured MOS instance with auto-registered default cube
|
|
92
|
+
|
|
93
|
+
Example:
|
|
94
|
+
```python
|
|
95
|
+
# Set environment variables
|
|
96
|
+
export OPENAI_API_KEY="your-api-key"
|
|
97
|
+
export MOS_TEXT_MEM_TYPE="general_text"
|
|
98
|
+
|
|
99
|
+
# Then use
|
|
100
|
+
memory = MOS.simple()
|
|
101
|
+
memory.add_memory("Hello world!")
|
|
102
|
+
response = memory.chat("What did I just say?")
|
|
103
|
+
```
|
|
104
|
+
"""
|
|
105
|
+
return cls()
|
|
106
|
+
|
|
107
|
+
def chat(self, query: str, user_id: str | None = None, base_prompt: str | None = None) -> str:
|
|
35
108
|
"""
|
|
36
109
|
Enhanced chat method with optional CoT (Chain of Thought) enhancement.
|
|
37
110
|
|
|
38
111
|
Args:
|
|
39
112
|
query (str): The user's query.
|
|
40
113
|
user_id (str, optional): User ID for context.
|
|
114
|
+
base_prompt (str, optional): A custom base prompt to use for the chat.
|
|
115
|
+
It can be a template string with a `{memories}` placeholder.
|
|
116
|
+
If not provided, a default prompt is used.
|
|
41
117
|
|
|
42
118
|
Returns:
|
|
43
119
|
str: The response from the MOS.
|
|
@@ -46,12 +122,14 @@ class MOS(MOSCore):
|
|
|
46
122
|
|
|
47
123
|
if not self.enable_cot:
|
|
48
124
|
# Use the original chat method from core
|
|
49
|
-
return super().chat(query, user_id)
|
|
125
|
+
return super().chat(query, user_id, base_prompt=base_prompt)
|
|
50
126
|
|
|
51
127
|
# Enhanced chat with CoT decomposition
|
|
52
|
-
return self._chat_with_cot_enhancement(query, user_id)
|
|
128
|
+
return self._chat_with_cot_enhancement(query, user_id, base_prompt=base_prompt)
|
|
53
129
|
|
|
54
|
-
def _chat_with_cot_enhancement(
|
|
130
|
+
def _chat_with_cot_enhancement(
|
|
131
|
+
self, query: str, user_id: str | None = None, base_prompt: str | None = None
|
|
132
|
+
) -> str:
|
|
55
133
|
"""
|
|
56
134
|
Chat with CoT enhancement for complex query decomposition.
|
|
57
135
|
This method includes all the same validation and processing logic as the core chat method.
|
|
@@ -84,7 +162,7 @@ class MOS(MOSCore):
|
|
|
84
162
|
# Check if the query is complex and needs decomposition
|
|
85
163
|
if not decomposition_result.get("is_complex", False):
|
|
86
164
|
logger.info("🔍 [CoT] Query is not complex, using standard chat")
|
|
87
|
-
return super().chat(query, user_id)
|
|
165
|
+
return super().chat(query, user_id, base_prompt=base_prompt)
|
|
88
166
|
|
|
89
167
|
sub_questions = decomposition_result.get("sub_questions", [])
|
|
90
168
|
logger.info(f"🔍 [CoT] Decomposed into {len(sub_questions)} sub-questions")
|
|
@@ -93,7 +171,7 @@ class MOS(MOSCore):
|
|
|
93
171
|
search_engine = self._get_search_engine_for_cot_with_validation(user_cube_ids)
|
|
94
172
|
if not search_engine:
|
|
95
173
|
logger.warning("🔍 [CoT] No search engine available, using standard chat")
|
|
96
|
-
return super().chat(query, user_id)
|
|
174
|
+
return super().chat(query, user_id, base_prompt=base_prompt)
|
|
97
175
|
|
|
98
176
|
# Step 4: Get answers for sub-questions
|
|
99
177
|
logger.info("🔍 [CoT] Getting answers for sub-questions...")
|
|
@@ -115,6 +193,7 @@ class MOS(MOSCore):
|
|
|
115
193
|
chat_history=chat_history,
|
|
116
194
|
user_id=target_user_id,
|
|
117
195
|
search_engine=search_engine,
|
|
196
|
+
base_prompt=base_prompt,
|
|
118
197
|
)
|
|
119
198
|
|
|
120
199
|
# Step 6: Update chat history (same as core method)
|
|
@@ -149,7 +228,7 @@ class MOS(MOSCore):
|
|
|
149
228
|
except Exception as e:
|
|
150
229
|
logger.error(f"🔍 [CoT] Error in CoT enhancement: {e}")
|
|
151
230
|
logger.info("🔍 [CoT] Falling back to standard chat")
|
|
152
|
-
return super().chat(query, user_id)
|
|
231
|
+
return super().chat(query, user_id, base_prompt=base_prompt)
|
|
153
232
|
|
|
154
233
|
def _get_search_engine_for_cot_with_validation(
|
|
155
234
|
self, user_cube_ids: list[str]
|
|
@@ -183,6 +262,7 @@ class MOS(MOSCore):
|
|
|
183
262
|
chat_history: Any,
|
|
184
263
|
user_id: str | None = None,
|
|
185
264
|
search_engine: BaseTextMemory | None = None,
|
|
265
|
+
base_prompt: str | None = None,
|
|
186
266
|
) -> str:
|
|
187
267
|
"""
|
|
188
268
|
Generate an enhanced response using sub-questions and their answers, with chat context.
|
|
@@ -193,6 +273,8 @@ class MOS(MOSCore):
|
|
|
193
273
|
sub_answers (list[str]): List of answers to sub-questions.
|
|
194
274
|
chat_history: The user's chat history.
|
|
195
275
|
user_id (str, optional): User ID for context.
|
|
276
|
+
search_engine (BaseTextMemory, optional): Search engine for context retrieval.
|
|
277
|
+
base_prompt (str, optional): A custom base prompt for the chat.
|
|
196
278
|
|
|
197
279
|
Returns:
|
|
198
280
|
str: The enhanced response.
|
|
@@ -213,10 +295,10 @@ class MOS(MOSCore):
|
|
|
213
295
|
original_query, top_k=self.config.top_k, mode="fast"
|
|
214
296
|
)
|
|
215
297
|
system_prompt = self._build_system_prompt(
|
|
216
|
-
search_memories
|
|
298
|
+
search_memories, base_prompt=base_prompt
|
|
217
299
|
) # Use the same system prompt builder
|
|
218
300
|
else:
|
|
219
|
-
system_prompt = self._build_system_prompt()
|
|
301
|
+
system_prompt = self._build_system_prompt(base_prompt=base_prompt)
|
|
220
302
|
current_messages = [
|
|
221
303
|
{"role": "system", "content": system_prompt + SYNTHESIS_PROMPT.format(qa_text=qa_text)},
|
|
222
304
|
*chat_history.chat_history,
|
|
@@ -261,7 +343,7 @@ class MOS(MOSCore):
|
|
|
261
343
|
except Exception as e:
|
|
262
344
|
logger.error(f"🔍 [CoT] Error generating enhanced response: {e}")
|
|
263
345
|
# Fallback to standard chat
|
|
264
|
-
return super().chat(original_query, user_id)
|
|
346
|
+
return super().chat(original_query, user_id, base_prompt=base_prompt)
|
|
265
347
|
|
|
266
348
|
@classmethod
|
|
267
349
|
def cot_decompose(
|