MemoryOS 0.2.0__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.

Files changed (80) hide show
  1. {memoryos-0.2.0.dist-info → memoryos-0.2.1.dist-info}/METADATA +66 -26
  2. {memoryos-0.2.0.dist-info → memoryos-0.2.1.dist-info}/RECORD +80 -56
  3. memoryos-0.2.1.dist-info/entry_points.txt +3 -0
  4. memos/__init__.py +1 -1
  5. memos/api/config.py +471 -0
  6. memos/api/exceptions.py +28 -0
  7. memos/api/mcp_serve.py +502 -0
  8. memos/api/product_api.py +35 -0
  9. memos/api/product_models.py +159 -0
  10. memos/api/routers/__init__.py +1 -0
  11. memos/api/routers/product_router.py +358 -0
  12. memos/chunkers/sentence_chunker.py +8 -2
  13. memos/cli.py +113 -0
  14. memos/configs/embedder.py +27 -0
  15. memos/configs/graph_db.py +83 -2
  16. memos/configs/llm.py +47 -0
  17. memos/configs/mem_cube.py +1 -1
  18. memos/configs/mem_scheduler.py +91 -5
  19. memos/configs/memory.py +5 -4
  20. memos/dependency.py +52 -0
  21. memos/embedders/ark.py +92 -0
  22. memos/embedders/factory.py +4 -0
  23. memos/embedders/sentence_transformer.py +8 -2
  24. memos/embedders/universal_api.py +32 -0
  25. memos/graph_dbs/base.py +2 -2
  26. memos/graph_dbs/factory.py +2 -0
  27. memos/graph_dbs/neo4j.py +331 -122
  28. memos/graph_dbs/neo4j_community.py +300 -0
  29. memos/llms/base.py +9 -0
  30. memos/llms/deepseek.py +54 -0
  31. memos/llms/factory.py +10 -1
  32. memos/llms/hf.py +170 -13
  33. memos/llms/hf_singleton.py +114 -0
  34. memos/llms/ollama.py +4 -0
  35. memos/llms/openai.py +67 -1
  36. memos/llms/qwen.py +63 -0
  37. memos/llms/vllm.py +153 -0
  38. memos/mem_cube/general.py +77 -16
  39. memos/mem_cube/utils.py +102 -0
  40. memos/mem_os/core.py +131 -41
  41. memos/mem_os/main.py +93 -11
  42. memos/mem_os/product.py +1098 -35
  43. memos/mem_os/utils/default_config.py +352 -0
  44. memos/mem_os/utils/format_utils.py +1154 -0
  45. memos/mem_reader/simple_struct.py +5 -5
  46. memos/mem_scheduler/base_scheduler.py +467 -36
  47. memos/mem_scheduler/general_scheduler.py +125 -244
  48. memos/mem_scheduler/modules/base.py +9 -0
  49. memos/mem_scheduler/modules/dispatcher.py +68 -2
  50. memos/mem_scheduler/modules/misc.py +39 -0
  51. memos/mem_scheduler/modules/monitor.py +228 -49
  52. memos/mem_scheduler/modules/rabbitmq_service.py +317 -0
  53. memos/mem_scheduler/modules/redis_service.py +32 -22
  54. memos/mem_scheduler/modules/retriever.py +250 -23
  55. memos/mem_scheduler/modules/schemas.py +189 -7
  56. memos/mem_scheduler/mos_for_test_scheduler.py +143 -0
  57. memos/mem_scheduler/utils.py +51 -2
  58. memos/mem_user/persistent_user_manager.py +260 -0
  59. memos/memories/activation/item.py +25 -0
  60. memos/memories/activation/kv.py +10 -3
  61. memos/memories/activation/vllmkv.py +219 -0
  62. memos/memories/factory.py +2 -0
  63. memos/memories/textual/general.py +7 -5
  64. memos/memories/textual/tree.py +9 -5
  65. memos/memories/textual/tree_text_memory/organize/conflict.py +5 -3
  66. memos/memories/textual/tree_text_memory/organize/manager.py +26 -18
  67. memos/memories/textual/tree_text_memory/organize/redundancy.py +25 -44
  68. memos/memories/textual/tree_text_memory/organize/relation_reason_detector.py +11 -13
  69. memos/memories/textual/tree_text_memory/organize/reorganizer.py +73 -51
  70. memos/memories/textual/tree_text_memory/retrieve/recall.py +0 -1
  71. memos/memories/textual/tree_text_memory/retrieve/reranker.py +2 -2
  72. memos/memories/textual/tree_text_memory/retrieve/searcher.py +6 -5
  73. memos/parsers/markitdown.py +8 -2
  74. memos/templates/mem_reader_prompts.py +65 -23
  75. memos/templates/mem_scheduler_prompts.py +96 -47
  76. memos/templates/tree_reorganize_prompts.py +85 -30
  77. memos/vec_dbs/base.py +12 -0
  78. memos/vec_dbs/qdrant.py +46 -20
  79. {memoryos-0.2.0.dist-info → memoryos-0.2.1.dist-info}/LICENSE +0 -0
  80. {memoryos-0.2.0.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 ANSWER_LABEL, QUERY_LABEL, ScheduleMessageItem
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
- self.user_manager = UserManager(user_id=self.user_id if self.user_id else "root")
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
- self._mem_scheduler.initialize_modules(chat_llm=self.chat_llm)
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=QUERY_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
- if len(accessible_cubes) == 1:
265
- mem_cube_id = accessible_cubes[0].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(self, memories: list | None = None) -> str:
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
- "You are a knowledgeable and helpful AI assistant. "
284
- "You have access to conversation memories that help you provide more personalized responses. "
285
- "Use the memories to understand the user's context, preferences, and past interactions. "
286
- "If memories are provided, reference them naturally when relevant, but don't explicitly mention having memories."
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
- memory_context = "\n\n## Memories:\n"
335
+ memory_list = []
291
336
  for i, memory in enumerate(memories, 1):
292
- memory_context += f"{i}. {memory.memory}\n"
293
- return base_prompt + memory_context
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, mem_cube_name_or_path: str, mem_cube_id: str | None = None, user_id: str | None = None
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
- mem_cube_id = mem_cube_name_or_path
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 os.path.exists(mem_cube_name_or_path):
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, query: str, user_id: str | None = None, install_cube_ids: list[str] | None = None
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(query, top_k=self.config.top_k)
468
- result["text_mem"].append({"cube_id": mem_cube_id, "memories": memories})
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
- if (
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": self.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": self.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": self.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
- def chat(self, query: str, user_id: str | None = None) -> str:
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(self, query: str, user_id: str | None = None) -> str:
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(