MemoryOS 0.2.0__py3-none-any.whl → 0.2.2__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 (114) hide show
  1. {memoryos-0.2.0.dist-info → memoryos-0.2.2.dist-info}/METADATA +67 -26
  2. memoryos-0.2.2.dist-info/RECORD +169 -0
  3. memoryos-0.2.2.dist-info/entry_points.txt +3 -0
  4. memos/__init__.py +1 -1
  5. memos/api/config.py +562 -0
  6. memos/api/context/context.py +147 -0
  7. memos/api/context/dependencies.py +90 -0
  8. memos/api/exceptions.py +28 -0
  9. memos/api/mcp_serve.py +502 -0
  10. memos/api/product_api.py +35 -0
  11. memos/api/product_models.py +163 -0
  12. memos/api/routers/__init__.py +1 -0
  13. memos/api/routers/product_router.py +386 -0
  14. memos/chunkers/sentence_chunker.py +8 -2
  15. memos/cli.py +113 -0
  16. memos/configs/embedder.py +27 -0
  17. memos/configs/graph_db.py +132 -3
  18. memos/configs/internet_retriever.py +6 -0
  19. memos/configs/llm.py +47 -0
  20. memos/configs/mem_cube.py +1 -1
  21. memos/configs/mem_os.py +5 -0
  22. memos/configs/mem_reader.py +9 -0
  23. memos/configs/mem_scheduler.py +107 -7
  24. memos/configs/mem_user.py +58 -0
  25. memos/configs/memory.py +5 -4
  26. memos/dependency.py +52 -0
  27. memos/embedders/ark.py +92 -0
  28. memos/embedders/factory.py +4 -0
  29. memos/embedders/sentence_transformer.py +8 -2
  30. memos/embedders/universal_api.py +32 -0
  31. memos/graph_dbs/base.py +11 -3
  32. memos/graph_dbs/factory.py +4 -0
  33. memos/graph_dbs/nebular.py +1364 -0
  34. memos/graph_dbs/neo4j.py +333 -124
  35. memos/graph_dbs/neo4j_community.py +300 -0
  36. memos/llms/base.py +9 -0
  37. memos/llms/deepseek.py +54 -0
  38. memos/llms/factory.py +10 -1
  39. memos/llms/hf.py +170 -13
  40. memos/llms/hf_singleton.py +114 -0
  41. memos/llms/ollama.py +4 -0
  42. memos/llms/openai.py +67 -1
  43. memos/llms/qwen.py +63 -0
  44. memos/llms/vllm.py +153 -0
  45. memos/log.py +1 -1
  46. memos/mem_cube/general.py +77 -16
  47. memos/mem_cube/utils.py +109 -0
  48. memos/mem_os/core.py +251 -51
  49. memos/mem_os/main.py +94 -12
  50. memos/mem_os/product.py +1220 -43
  51. memos/mem_os/utils/default_config.py +352 -0
  52. memos/mem_os/utils/format_utils.py +1401 -0
  53. memos/mem_reader/simple_struct.py +18 -10
  54. memos/mem_scheduler/base_scheduler.py +441 -40
  55. memos/mem_scheduler/general_scheduler.py +249 -248
  56. memos/mem_scheduler/modules/base.py +14 -5
  57. memos/mem_scheduler/modules/dispatcher.py +67 -4
  58. memos/mem_scheduler/modules/misc.py +104 -0
  59. memos/mem_scheduler/modules/monitor.py +240 -50
  60. memos/mem_scheduler/modules/rabbitmq_service.py +319 -0
  61. memos/mem_scheduler/modules/redis_service.py +32 -22
  62. memos/mem_scheduler/modules/retriever.py +167 -23
  63. memos/mem_scheduler/modules/scheduler_logger.py +255 -0
  64. memos/mem_scheduler/mos_for_test_scheduler.py +140 -0
  65. memos/mem_scheduler/schemas/__init__.py +0 -0
  66. memos/mem_scheduler/schemas/general_schemas.py +43 -0
  67. memos/mem_scheduler/{modules/schemas.py → schemas/message_schemas.py} +63 -61
  68. memos/mem_scheduler/schemas/monitor_schemas.py +329 -0
  69. memos/mem_scheduler/utils/__init__.py +0 -0
  70. memos/mem_scheduler/utils/filter_utils.py +176 -0
  71. memos/mem_scheduler/utils/misc_utils.py +61 -0
  72. memos/mem_user/factory.py +94 -0
  73. memos/mem_user/mysql_persistent_user_manager.py +271 -0
  74. memos/mem_user/mysql_user_manager.py +500 -0
  75. memos/mem_user/persistent_factory.py +96 -0
  76. memos/mem_user/persistent_user_manager.py +260 -0
  77. memos/mem_user/user_manager.py +4 -4
  78. memos/memories/activation/item.py +29 -0
  79. memos/memories/activation/kv.py +10 -3
  80. memos/memories/activation/vllmkv.py +219 -0
  81. memos/memories/factory.py +2 -0
  82. memos/memories/textual/base.py +1 -1
  83. memos/memories/textual/general.py +43 -97
  84. memos/memories/textual/item.py +5 -33
  85. memos/memories/textual/tree.py +22 -12
  86. memos/memories/textual/tree_text_memory/organize/conflict.py +9 -5
  87. memos/memories/textual/tree_text_memory/organize/manager.py +26 -18
  88. memos/memories/textual/tree_text_memory/organize/redundancy.py +25 -44
  89. memos/memories/textual/tree_text_memory/organize/relation_reason_detector.py +50 -48
  90. memos/memories/textual/tree_text_memory/organize/reorganizer.py +81 -56
  91. memos/memories/textual/tree_text_memory/retrieve/internet_retriever.py +6 -3
  92. memos/memories/textual/tree_text_memory/retrieve/internet_retriever_factory.py +2 -0
  93. memos/memories/textual/tree_text_memory/retrieve/recall.py +0 -1
  94. memos/memories/textual/tree_text_memory/retrieve/reranker.py +2 -2
  95. memos/memories/textual/tree_text_memory/retrieve/retrieval_mid_structs.py +2 -0
  96. memos/memories/textual/tree_text_memory/retrieve/searcher.py +52 -28
  97. memos/memories/textual/tree_text_memory/retrieve/task_goal_parser.py +42 -15
  98. memos/memories/textual/tree_text_memory/retrieve/utils.py +11 -7
  99. memos/memories/textual/tree_text_memory/retrieve/xinyusearch.py +62 -58
  100. memos/memos_tools/dinding_report_bot.py +422 -0
  101. memos/memos_tools/notification_service.py +44 -0
  102. memos/memos_tools/notification_utils.py +96 -0
  103. memos/parsers/markitdown.py +8 -2
  104. memos/settings.py +3 -1
  105. memos/templates/mem_reader_prompts.py +66 -23
  106. memos/templates/mem_scheduler_prompts.py +126 -43
  107. memos/templates/mos_prompts.py +87 -0
  108. memos/templates/tree_reorganize_prompts.py +85 -30
  109. memos/vec_dbs/base.py +12 -0
  110. memos/vec_dbs/qdrant.py +46 -20
  111. memoryos-0.2.0.dist-info/RECORD +0 -128
  112. memos/mem_scheduler/utils.py +0 -26
  113. {memoryos-0.2.0.dist-info → memoryos-0.2.2.dist-info}/LICENSE +0 -0
  114. {memoryos-0.2.0.dist-info → memoryos-0.2.2.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,105 @@ 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
+ "auto_create",
75
+ "user_name",
76
+ "use_multi_db",
77
+ }
78
+
79
+ # Create merged graph_db config
80
+ merged_graph_config = copy.deepcopy(existing_graph_config)
81
+ for key, value in default_graph_config.items():
82
+ if key not in preserve_graph_fields:
83
+ merged_graph_config[key] = value
84
+ logger.debug(
85
+ f"Updated graph_db field '{key}': {existing_graph_config.get(key)} -> {value}"
86
+ )
87
+ if not default_graph_config.get("use_multi_db", True):
88
+ # set original use_multi_db to False if default_graph_config.use_multi_db is False
89
+ if merged_graph_config.get("use_multi_db", True):
90
+ merged_graph_config["use_multi_db"] = False
91
+ merged_graph_config["user_name"] = merged_graph_config.get("db_name")
92
+ merged_graph_config["db_name"] = default_graph_config.get("db_name")
93
+ else:
94
+ logger.info("use_multi_db is already False, no need to change")
95
+ if "neo4j" not in default_text_config["graph_db"]["backend"]:
96
+ if "db_name" in merged_graph_config:
97
+ merged_graph_config.pop("db_name")
98
+ logger.info("neo4j is not supported, remove db_name")
99
+ else:
100
+ logger.info("db_name is not in merged_graph_config, no need to remove")
101
+ else:
102
+ if "space" in merged_graph_config:
103
+ merged_graph_config.pop("space")
104
+ logger.info("neo4j is not supported, remove db_name")
105
+ else:
106
+ logger.info("space is not in merged_graph_config, no need to remove")
107
+ preserved_graph_db = {
108
+ "backend": default_text_config["graph_db"]["backend"],
109
+ "config": merged_graph_config,
110
+ }
111
+
112
+ # Use default config as base
113
+ merged_dict = copy.deepcopy(default_dict)
114
+
115
+ # Restore preserved fields from existing config
116
+ for field in preserve_fields:
117
+ if field in existing_dict:
118
+ merged_dict[field] = existing_dict[field]
119
+ logger.debug(f"Preserved field '{field}': {existing_dict[field]}")
120
+
121
+ # Restore graph_db if it was preserved
122
+ if preserved_graph_db and "text_mem" in merged_dict:
123
+ merged_dict["text_mem"]["config"]["graph_db"] = preserved_graph_db
124
+ logger.debug(f"Preserved graph_db with merged config: {preserved_graph_db}")
125
+
126
+ # Create new config from merged dictionary
127
+ merged_config = GeneralMemCubeConfig.model_validate(merged_dict)
128
+
129
+ logger.info(
130
+ f"Successfully merged cube config for user {merged_config.user_id}, cube {merged_config.cube_id}"
131
+ )
132
+
133
+ return merged_config
memos/mem_os/core.py CHANGED
@@ -1,4 +1,6 @@
1
+ import json
1
2
  import os
3
+ import time
2
4
 
3
5
  from datetime import datetime
4
6
  from pathlib import Path
@@ -11,12 +13,18 @@ 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
15
16
  from memos.mem_scheduler.scheduler_factory import SchedulerFactory
17
+ from memos.mem_scheduler.schemas.general_schemas import (
18
+ ADD_LABEL,
19
+ ANSWER_LABEL,
20
+ QUERY_LABEL,
21
+ )
22
+ from memos.mem_scheduler.schemas.message_schemas import ScheduleMessageItem
16
23
  from memos.mem_user.user_manager import UserManager, UserRole
17
24
  from memos.memories.activation.item import ActivationMemoryItem
18
25
  from memos.memories.parametric.item import ParametricMemoryItem
19
26
  from memos.memories.textual.item import TextualMemoryItem, TextualMemoryMetadata
27
+ from memos.templates.mos_prompts import QUERY_REWRITING_PROMPT
20
28
  from memos.types import ChatHistory, MessageList, MOSSearchResult
21
29
 
22
30
 
@@ -30,7 +38,7 @@ class MOSCore:
30
38
  MOSCore acts as an operating system layer for handling and orchestrating MemCube instances.
31
39
  """
32
40
 
33
- def __init__(self, config: MOSConfig):
41
+ def __init__(self, config: MOSConfig, user_manager: UserManager | None = None):
34
42
  self.config = config
35
43
  self.user_id = config.user_id
36
44
  self.session_id = config.session_id
@@ -39,7 +47,12 @@ class MOSCore:
39
47
  self.mem_reader = MemReaderFactory.from_config(config.mem_reader)
40
48
  self.chat_history_manager: dict[str, ChatHistory] = {}
41
49
  self._register_chat_history()
42
- self.user_manager = UserManager(user_id=self.user_id if self.user_id else "root")
50
+
51
+ # Use provided user_manager or create a new one
52
+ if user_manager is not None:
53
+ self.user_manager = user_manager
54
+ else:
55
+ self.user_manager = UserManager(user_id=self.user_id if self.user_id else "root")
43
56
 
44
57
  # Validate user exists
45
58
  if not self.user_manager.validate_user(self.user_id):
@@ -47,10 +60,15 @@ class MOSCore:
47
60
  f"User '{self.user_id}' does not exist or is inactive. Please create user first."
48
61
  )
49
62
 
50
- # Lazy initialization marker
63
+ # Initialize mem_scheduler
51
64
  self._mem_scheduler_lock = Lock()
52
65
  self.enable_mem_scheduler = self.config.get("enable_mem_scheduler", False)
53
- self._mem_scheduler = None
66
+ if self.enable_mem_scheduler:
67
+ self._mem_scheduler = self._initialize_mem_scheduler()
68
+ self._mem_scheduler.mem_cubes = self.mem_cubes
69
+ else:
70
+ self._mem_scheduler: GeneralScheduler = None
71
+
54
72
  logger.info(f"MOS initialized for user: {self.user_id}")
55
73
 
56
74
  @property
@@ -58,6 +76,7 @@ class MOSCore:
58
76
  """Lazy-loaded property for memory scheduler."""
59
77
  if self.enable_mem_scheduler and self._mem_scheduler is None:
60
78
  self._initialize_mem_scheduler()
79
+ self._mem_scheduler.mem_cubes = self.mem_cubes
61
80
  return self._mem_scheduler
62
81
 
63
82
  @mem_scheduler.setter
@@ -74,26 +93,43 @@ class MOSCore:
74
93
  raise TypeError(f"Expected GeneralScheduler or None, got {type(value)}")
75
94
 
76
95
  self._mem_scheduler = value
96
+ self._mem_scheduler.mem_cubes = self.mem_cubes
77
97
 
78
98
  if value:
79
99
  logger.info("Memory scheduler manually set")
80
100
  else:
81
101
  logger.debug("Memory scheduler cleared")
82
102
 
83
- def _initialize_mem_scheduler(self):
103
+ def _initialize_mem_scheduler(self) -> GeneralScheduler:
84
104
  """Initialize the memory scheduler on first access."""
85
105
  if not self.config.enable_mem_scheduler:
86
106
  logger.debug("Memory scheduler is disabled in config")
87
107
  self._mem_scheduler = None
108
+ return self._mem_scheduler
88
109
  elif not hasattr(self.config, "mem_scheduler"):
89
110
  logger.error("Config of Memory scheduler is not available")
90
111
  self._mem_scheduler = None
112
+ return self._mem_scheduler
91
113
  else:
92
114
  logger.info("Initializing memory scheduler...")
93
115
  scheduler_config = self.config.mem_scheduler
94
116
  self._mem_scheduler = SchedulerFactory.from_config(scheduler_config)
95
- self._mem_scheduler.initialize_modules(chat_llm=self.chat_llm)
117
+ # Validate required components
118
+ if not hasattr(self.mem_reader, "llm"):
119
+ raise AttributeError(
120
+ f"Memory reader of type {type(self.mem_reader).__name__} "
121
+ "missing required 'llm' attribute"
122
+ )
123
+ self._mem_scheduler.initialize_modules(
124
+ chat_llm=self.chat_llm, process_llm=self.chat_llm
125
+ )
126
+ else:
127
+ # Configure scheduler modules
128
+ self._mem_scheduler.initialize_modules(
129
+ chat_llm=self.chat_llm, process_llm=self.mem_reader.llm
130
+ )
96
131
  self._mem_scheduler.start()
132
+ return self._mem_scheduler
97
133
 
98
134
  def mem_scheduler_on(self) -> bool:
99
135
  if not self.config.enable_mem_scheduler or self._mem_scheduler is None:
@@ -123,6 +159,25 @@ class MOSCore:
123
159
  logger.error(f"Failed to stop scheduler: {e!s}")
124
160
  return False
125
161
 
162
+ def mem_reorganizer_on(self) -> bool:
163
+ pass
164
+
165
+ def mem_reorganizer_off(self) -> bool:
166
+ """temporally implement"""
167
+ for mem_cube in self.mem_cubes.values():
168
+ logger.info(f"try to close reorganizer for {mem_cube.text_mem.config.cube_id}")
169
+ if mem_cube.text_mem and mem_cube.text_mem.is_reorganize:
170
+ logger.info(f"close reorganizer for {mem_cube.text_mem.config.cube_id}")
171
+ mem_cube.text_mem.memory_manager.close()
172
+ mem_cube.text_mem.memory_manager.wait_reorganizer()
173
+
174
+ def mem_reorganizer_wait(self) -> bool:
175
+ for mem_cube in self.mem_cubes.values():
176
+ logger.info(f"try to close reorganizer for {mem_cube.text_mem.config.cube_id}")
177
+ if mem_cube.text_mem and mem_cube.text_mem.is_reorganize:
178
+ logger.info(f"close reorganizer for {mem_cube.text_mem.config.cube_id}")
179
+ mem_cube.text_mem.memory_manager.wait_reorganizer()
180
+
126
181
  def _register_chat_history(self, user_id: str | None = None) -> None:
127
182
  """Initialize chat history with user ID."""
128
183
  if user_id is None:
@@ -186,12 +241,16 @@ class MOSCore:
186
241
  documents.append(str(file_path))
187
242
  return documents
188
243
 
189
- def chat(self, query: str, user_id: str | None = None) -> str:
244
+ def chat(self, query: str, user_id: str | None = None, base_prompt: str | None = None) -> str:
190
245
  """
191
246
  Chat with the MOS.
192
247
 
193
248
  Args:
194
249
  query (str): The user's query.
250
+ user_id (str, optional): The user ID for the chat session. Defaults to the user ID from the config.
251
+ base_prompt (str, optional): A custom base prompt to use for the chat.
252
+ It can be a template string with a `{memories}` placeholder.
253
+ If not provided, a default prompt is used.
195
254
 
196
255
  Returns:
197
256
  str: The response from the MOS.
@@ -224,12 +283,20 @@ class MOSCore:
224
283
  )
225
284
  self.mem_scheduler.submit_messages(messages=[message_item])
226
285
 
227
- memories = mem_cube.text_mem.search(query, top_k=self.config.top_k)
286
+ memories = mem_cube.text_mem.search(
287
+ query,
288
+ top_k=self.config.top_k,
289
+ info={
290
+ "user_id": target_user_id,
291
+ "session_id": self.session_id,
292
+ "chat_history": chat_history.chat_history,
293
+ },
294
+ )
228
295
  memories_all.extend(memories)
229
296
  logger.info(f"🧠 [Memory] Searched memories:\n{self._str_memories(memories_all)}\n")
230
- system_prompt = self._build_system_prompt(memories_all)
297
+ system_prompt = self._build_system_prompt(memories_all, base_prompt=base_prompt)
231
298
  else:
232
- system_prompt = self._build_system_prompt()
299
+ system_prompt = self._build_system_prompt(base_prompt=base_prompt)
233
300
  current_messages = [
234
301
  {"role": "system", "content": system_prompt},
235
302
  *chat_history.chat_history,
@@ -261,8 +328,8 @@ class MOSCore:
261
328
  self.chat_history_manager[user_id] = chat_history
262
329
 
263
330
  # submit message to scheduler
264
- if len(accessible_cubes) == 1:
265
- mem_cube_id = accessible_cubes[0].cube_id
331
+ for accessible_mem_cube in accessible_cubes:
332
+ mem_cube_id = accessible_mem_cube.cube_id
266
333
  mem_cube = self.mem_cubes[mem_cube_id]
267
334
  if self.enable_mem_scheduler and self.mem_scheduler is not None:
268
335
  message_item = ScheduleMessageItem(
@@ -277,20 +344,39 @@ class MOSCore:
277
344
 
278
345
  return response
279
346
 
280
- def _build_system_prompt(self, memories: list | None = None) -> str:
347
+ def _build_system_prompt(
348
+ self,
349
+ memories: list[TextualMemoryItem] | list[str] | None = None,
350
+ base_prompt: str | None = None,
351
+ ) -> str:
281
352
  """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
- )
353
+ if base_prompt is None:
354
+ base_prompt = (
355
+ "You are a knowledgeable and helpful AI assistant. "
356
+ "You have access to conversation memories that help you provide more personalized responses. "
357
+ "Use the memories to understand the user's context, preferences, and past interactions. "
358
+ "If memories are provided, reference them naturally when relevant, but don't explicitly mention having memories."
359
+ )
288
360
 
361
+ memory_context = ""
289
362
  if memories:
290
- memory_context = "\n\n## Memories:\n"
363
+ memory_list = []
291
364
  for i, memory in enumerate(memories, 1):
292
- memory_context += f"{i}. {memory.memory}\n"
293
- return base_prompt + memory_context
365
+ if isinstance(memory, TextualMemoryItem):
366
+ text_memory = memory.memory
367
+ else:
368
+ if not isinstance(memory, str):
369
+ logger.error("Unexpected memory type.")
370
+ text_memory = memory
371
+ memory_list.append(f"{i}. {text_memory}")
372
+ memory_context = "\n".join(memory_list)
373
+
374
+ if "{memories}" in base_prompt:
375
+ return base_prompt.format(memories=memory_context)
376
+ elif memories:
377
+ # For backward compatibility, append memories if no placeholder is found
378
+ memory_context_with_header = "\n\n## Memories:\n" + memory_context
379
+ return base_prompt + memory_context_with_header
294
380
  return base_prompt
295
381
 
296
382
  def _str_memories(
@@ -364,7 +450,10 @@ class MOSCore:
364
450
  return self.user_manager.create_cube(cube_name, owner_id, cube_path, cube_id)
365
451
 
366
452
  def register_mem_cube(
367
- self, mem_cube_name_or_path: str, mem_cube_id: str | None = None, user_id: str | None = None
453
+ self,
454
+ mem_cube_name_or_path: str | GeneralMemCube,
455
+ mem_cube_id: str | None = None,
456
+ user_id: str | None = None,
368
457
  ) -> None:
369
458
  """
370
459
  Register a MemCube with the MOS.
@@ -377,12 +466,18 @@ class MOSCore:
377
466
  self._validate_user_exists(target_user_id)
378
467
 
379
468
  if mem_cube_id is None:
380
- mem_cube_id = mem_cube_name_or_path
469
+ if isinstance(mem_cube_name_or_path, GeneralMemCube):
470
+ mem_cube_id = f"cube_{target_user_id}"
471
+ else:
472
+ mem_cube_id = mem_cube_name_or_path
381
473
 
382
474
  if mem_cube_id in self.mem_cubes:
383
475
  logger.info(f"MemCube with ID {mem_cube_id} already in MOS, skip install.")
384
476
  else:
385
- if os.path.exists(mem_cube_name_or_path):
477
+ if isinstance(mem_cube_name_or_path, GeneralMemCube):
478
+ self.mem_cubes[mem_cube_id] = mem_cube_name_or_path
479
+ logger.info(f"register new cube {mem_cube_id} for user {target_user_id}")
480
+ elif os.path.exists(mem_cube_name_or_path):
386
481
  self.mem_cubes[mem_cube_id] = GeneralMemCube.init_from_dir(mem_cube_name_or_path)
387
482
  else:
388
483
  logger.warning(
@@ -394,6 +489,14 @@ class MOSCore:
394
489
  # Check if cube already exists in database
395
490
  existing_cube = self.user_manager.get_cube(mem_cube_id)
396
491
 
492
+ # check the embedder is it consistent with MOSConfig
493
+ if self.config.mem_reader.config.embedder != (
494
+ cube_embedder := self.mem_cubes[mem_cube_id].text_mem.config.embedder
495
+ ):
496
+ logger.warning(
497
+ f"Cube Embedder is not consistent with MOSConfig for cube: {mem_cube_id}, will use Cube Embedder: {cube_embedder}"
498
+ )
499
+
397
500
  if existing_cube:
398
501
  # Cube exists, just add user to cube if not already associated
399
502
  if not self.user_manager.validate_user_cube_access(target_user_id, mem_cube_id):
@@ -407,10 +510,14 @@ class MOSCore:
407
510
  else:
408
511
  # Cube doesn't exist, create it
409
512
  self.create_cube_for_user(
410
- cube_name=mem_cube_name_or_path,
513
+ cube_name=mem_cube_name_or_path
514
+ if not isinstance(mem_cube_name_or_path, GeneralMemCube)
515
+ else mem_cube_id,
411
516
  owner_id=target_user_id,
412
517
  cube_id=mem_cube_id,
413
- cube_path=mem_cube_name_or_path,
518
+ cube_path=mem_cube_name_or_path
519
+ if not isinstance(mem_cube_name_or_path, GeneralMemCube)
520
+ else "init",
414
521
  )
415
522
  logger.info(f"register new cube {mem_cube_id} for user {target_user_id}")
416
523
 
@@ -427,7 +534,13 @@ class MOSCore:
427
534
  raise ValueError(f"MemCube with ID {mem_cube_id} does not exist.")
428
535
 
429
536
  def search(
430
- self, query: str, user_id: str | None = None, install_cube_ids: list[str] | None = None
537
+ self,
538
+ query: str,
539
+ user_id: str | None = None,
540
+ install_cube_ids: list[str] | None = None,
541
+ top_k: int | None = None,
542
+ mode: Literal["fast", "fine"] = "fast",
543
+ internet_search: bool = False,
431
544
  ) -> MOSSearchResult:
432
545
  """
433
546
  Search for textual memories across all registered MemCubes.
@@ -451,6 +564,10 @@ class MOSCore:
451
564
  logger.info(
452
565
  f"User {target_user_id} has access to {len(user_cube_ids)} cubes: {user_cube_ids}"
453
566
  )
567
+ if target_user_id not in self.chat_history_manager:
568
+ self._register_chat_history(target_user_id)
569
+ chat_history = self.chat_history_manager[target_user_id]
570
+
454
571
  result: MOSSearchResult = {
455
572
  "text_mem": [],
456
573
  "act_mem": [],
@@ -464,20 +581,25 @@ class MOSCore:
464
581
  and (mem_cube.text_mem is not None)
465
582
  and self.config.enable_textual_memory
466
583
  ):
467
- memories = mem_cube.text_mem.search(query, top_k=self.config.top_k)
584
+ time_start = time.time()
585
+ memories = mem_cube.text_mem.search(
586
+ query,
587
+ top_k=top_k if top_k else self.config.top_k,
588
+ mode=mode,
589
+ manual_close_internet=not internet_search,
590
+ info={
591
+ "user_id": target_user_id,
592
+ "session_id": self.session_id,
593
+ "chat_history": chat_history.chat_history,
594
+ },
595
+ )
468
596
  result["text_mem"].append({"cube_id": mem_cube_id, "memories": memories})
469
597
  logger.info(
470
598
  f"🧠 [Memory] Searched memories from {mem_cube_id}:\n{self._str_memories(memories)}\n"
471
599
  )
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]})
600
+ search_time_end = time.time()
479
601
  logger.info(
480
- f"🧠 [Memory] Searched memories from {mem_cube_id}:\n{self._str_memories(memories)}\n"
602
+ f"time search graph: search graph time user_id: {target_user_id} time is: {search_time_end - time_start}"
481
603
  )
482
604
  return result
483
605
 
@@ -501,6 +623,7 @@ class MOSCore:
501
623
  user_id (str, optional): The identifier of the user to add the memories to.
502
624
  If None, the default user is used.
503
625
  """
626
+ # user input messages
504
627
  assert (messages is not None) or (memory_content is not None) or (doc_path is not None), (
505
628
  "messages_or_doc_path or memory_content or doc_path must be provided."
506
629
  )
@@ -540,8 +663,29 @@ class MOSCore:
540
663
  type="chat",
541
664
  info={"user_id": target_user_id, "session_id": self.session_id},
542
665
  )
666
+
667
+ mem_ids = []
543
668
  for mem in memories:
544
- self.mem_cubes[mem_cube_id].text_mem.add(mem)
669
+ mem_id_list: list[str] = self.mem_cubes[mem_cube_id].text_mem.add(mem)
670
+ mem_ids.extend(mem_id_list)
671
+ logger.info(
672
+ f"Added memory user {target_user_id} to memcube {mem_cube_id}: {mem_id_list}"
673
+ )
674
+
675
+ # submit messages for scheduler
676
+ if self.enable_mem_scheduler and self.mem_scheduler is not None:
677
+ mem_cube = self.mem_cubes[mem_cube_id]
678
+ message_item = ScheduleMessageItem(
679
+ user_id=target_user_id,
680
+ mem_cube_id=mem_cube_id,
681
+ mem_cube=mem_cube,
682
+ label=ADD_LABEL,
683
+ content=json.dumps(mem_ids),
684
+ timestamp=datetime.now(),
685
+ )
686
+ self.mem_scheduler.submit_messages(messages=[message_item])
687
+
688
+ # user profile
545
689
  if (
546
690
  (memory_content is not None)
547
691
  and self.config.enable_textual_memory
@@ -556,34 +700,66 @@ class MOSCore:
556
700
  )
557
701
  else:
558
702
  messages_list = [
559
- [
560
- {"role": "user", "content": memory_content},
561
- {
562
- "role": "assistant",
563
- "content": "",
564
- }, # add by str to keep the format,assistant role is empty
565
- ]
566
- ]
703
+ [{"role": "user", "content": memory_content}]
704
+ ] # for only user-str input and convert message
567
705
  memories = self.mem_reader.get_memory(
568
706
  messages_list,
569
707
  type="chat",
570
708
  info={"user_id": target_user_id, "session_id": self.session_id},
571
709
  )
710
+
711
+ mem_ids = []
572
712
  for mem in memories:
573
- self.mem_cubes[mem_cube_id].text_mem.add(mem)
713
+ mem_id_list: list[str] = self.mem_cubes[mem_cube_id].text_mem.add(mem)
714
+ logger.info(
715
+ f"Added memory user {target_user_id} to memcube {mem_cube_id}: {mem_id_list}"
716
+ )
717
+ mem_ids.extend(mem_id_list)
718
+
719
+ # submit messages for scheduler
720
+ if self.enable_mem_scheduler and self.mem_scheduler is not None:
721
+ mem_cube = self.mem_cubes[mem_cube_id]
722
+ message_item = ScheduleMessageItem(
723
+ user_id=target_user_id,
724
+ mem_cube_id=mem_cube_id,
725
+ mem_cube=mem_cube,
726
+ label=ADD_LABEL,
727
+ content=json.dumps(mem_ids),
728
+ timestamp=datetime.now(),
729
+ )
730
+ self.mem_scheduler.submit_messages(messages=[message_item])
731
+
732
+ # user doc input
574
733
  if (
575
734
  (doc_path is not None)
576
735
  and self.config.enable_textual_memory
577
736
  and self.mem_cubes[mem_cube_id].text_mem
578
737
  ):
579
738
  documents = self._get_all_documents(doc_path)
580
- doc_memory = self.mem_reader.get_memory(
739
+ doc_memories = self.mem_reader.get_memory(
581
740
  documents,
582
741
  type="doc",
583
742
  info={"user_id": target_user_id, "session_id": self.session_id},
584
743
  )
585
- for mem in doc_memory:
586
- self.mem_cubes[mem_cube_id].text_mem.add(mem)
744
+
745
+ mem_ids = []
746
+ for mem in doc_memories:
747
+ mem_id_list: list[str] = self.mem_cubes[mem_cube_id].text_mem.add(mem)
748
+ mem_ids.extend(mem_id_list)
749
+
750
+ # submit messages for scheduler
751
+ if self.enable_mem_scheduler and self.mem_scheduler is not None:
752
+ mem_cube = self.mem_cubes[mem_cube_id]
753
+ message_item = ScheduleMessageItem(
754
+ user_id=target_user_id,
755
+ mem_cube_id=mem_cube_id,
756
+ mem_cube=mem_cube,
757
+ label=ADD_LABEL,
758
+ content=json.dumps(mem_ids),
759
+ timestamp=datetime.now(),
760
+ )
761
+ self.mem_scheduler.submit_messages(messages=[message_item])
762
+
587
763
  logger.info(f"Add memory to {mem_cube_id} successfully")
588
764
 
589
765
  def get(
@@ -817,3 +993,27 @@ class MOSCore:
817
993
  raise ValueError(f"Target user '{target_user_id}' does not exist or is inactive.")
818
994
 
819
995
  return self.user_manager.add_user_to_cube(target_user_id, cube_id)
996
+
997
+ def get_query_rewrite(self, query: str, user_id: str | None = None):
998
+ """
999
+ Rewrite user's query according the context.
1000
+ Args:
1001
+ query (str): The search query that needs rewriting.
1002
+ user_id(str, optional): The identifier of the user that the query belongs to.
1003
+ If None, the default user is used.
1004
+
1005
+ Returns:
1006
+ str: query after rewriting process.
1007
+ """
1008
+ target_user_id = user_id if user_id is not None else self.user_id
1009
+ chat_history = self.chat_history_manager[target_user_id]
1010
+
1011
+ dialogue = "————{}".format("\n————".join(chat_history.chat_history))
1012
+ user_prompt = QUERY_REWRITING_PROMPT.format(dialogue=dialogue, query=query)
1013
+ messages = {"role": "user", "content": user_prompt}
1014
+ rewritten_result = self.chat_llm.generate(messages=messages)
1015
+ rewritten_result = json.loads(rewritten_result)
1016
+ if rewritten_result.get("former_dialogue_related", False):
1017
+ rewritten_query = rewritten_result.get("rewritten_question")
1018
+ return rewritten_query if len(rewritten_query) > 0 else query
1019
+ return query