MemoryOS 0.2.1__py3-none-any.whl → 1.0.0__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 (92) hide show
  1. {memoryos-0.2.1.dist-info → memoryos-1.0.0.dist-info}/METADATA +7 -1
  2. {memoryos-0.2.1.dist-info → memoryos-1.0.0.dist-info}/RECORD +87 -64
  3. memos/__init__.py +1 -1
  4. memos/api/config.py +158 -69
  5. memos/api/context/context.py +147 -0
  6. memos/api/context/dependencies.py +101 -0
  7. memos/api/product_models.py +5 -1
  8. memos/api/routers/product_router.py +54 -26
  9. memos/configs/graph_db.py +49 -1
  10. memos/configs/internet_retriever.py +19 -0
  11. memos/configs/mem_os.py +5 -0
  12. memos/configs/mem_reader.py +9 -0
  13. memos/configs/mem_scheduler.py +54 -18
  14. memos/configs/mem_user.py +58 -0
  15. memos/graph_dbs/base.py +38 -3
  16. memos/graph_dbs/factory.py +2 -0
  17. memos/graph_dbs/nebular.py +1612 -0
  18. memos/graph_dbs/neo4j.py +18 -9
  19. memos/log.py +6 -1
  20. memos/mem_cube/utils.py +13 -6
  21. memos/mem_os/core.py +157 -37
  22. memos/mem_os/main.py +2 -2
  23. memos/mem_os/product.py +252 -201
  24. memos/mem_os/utils/default_config.py +1 -1
  25. memos/mem_os/utils/format_utils.py +281 -70
  26. memos/mem_os/utils/reference_utils.py +133 -0
  27. memos/mem_reader/simple_struct.py +13 -5
  28. memos/mem_scheduler/base_scheduler.py +239 -266
  29. memos/mem_scheduler/{modules → general_modules}/base.py +4 -5
  30. memos/mem_scheduler/{modules → general_modules}/dispatcher.py +57 -21
  31. memos/mem_scheduler/general_modules/misc.py +104 -0
  32. memos/mem_scheduler/{modules → general_modules}/rabbitmq_service.py +12 -10
  33. memos/mem_scheduler/{modules → general_modules}/redis_service.py +1 -1
  34. memos/mem_scheduler/general_modules/retriever.py +199 -0
  35. memos/mem_scheduler/general_modules/scheduler_logger.py +261 -0
  36. memos/mem_scheduler/general_scheduler.py +243 -80
  37. memos/mem_scheduler/monitors/__init__.py +0 -0
  38. memos/mem_scheduler/monitors/dispatcher_monitor.py +305 -0
  39. memos/mem_scheduler/{modules/monitor.py → monitors/general_monitor.py} +106 -57
  40. memos/mem_scheduler/mos_for_test_scheduler.py +23 -20
  41. memos/mem_scheduler/schemas/__init__.py +0 -0
  42. memos/mem_scheduler/schemas/general_schemas.py +44 -0
  43. memos/mem_scheduler/schemas/message_schemas.py +149 -0
  44. memos/mem_scheduler/schemas/monitor_schemas.py +337 -0
  45. memos/mem_scheduler/utils/__init__.py +0 -0
  46. memos/mem_scheduler/utils/filter_utils.py +176 -0
  47. memos/mem_scheduler/utils/misc_utils.py +102 -0
  48. memos/mem_user/factory.py +94 -0
  49. memos/mem_user/mysql_persistent_user_manager.py +271 -0
  50. memos/mem_user/mysql_user_manager.py +500 -0
  51. memos/mem_user/persistent_factory.py +96 -0
  52. memos/mem_user/user_manager.py +4 -4
  53. memos/memories/activation/item.py +5 -1
  54. memos/memories/activation/kv.py +20 -8
  55. memos/memories/textual/base.py +2 -2
  56. memos/memories/textual/general.py +36 -92
  57. memos/memories/textual/item.py +5 -33
  58. memos/memories/textual/tree.py +13 -7
  59. memos/memories/textual/tree_text_memory/organize/{conflict.py → handler.py} +34 -50
  60. memos/memories/textual/tree_text_memory/organize/manager.py +8 -96
  61. memos/memories/textual/tree_text_memory/organize/relation_reason_detector.py +49 -43
  62. memos/memories/textual/tree_text_memory/organize/reorganizer.py +107 -142
  63. memos/memories/textual/tree_text_memory/retrieve/bochasearch.py +229 -0
  64. memos/memories/textual/tree_text_memory/retrieve/internet_retriever.py +6 -3
  65. memos/memories/textual/tree_text_memory/retrieve/internet_retriever_factory.py +11 -0
  66. memos/memories/textual/tree_text_memory/retrieve/recall.py +15 -8
  67. memos/memories/textual/tree_text_memory/retrieve/reranker.py +1 -1
  68. memos/memories/textual/tree_text_memory/retrieve/retrieval_mid_structs.py +2 -0
  69. memos/memories/textual/tree_text_memory/retrieve/searcher.py +191 -116
  70. memos/memories/textual/tree_text_memory/retrieve/task_goal_parser.py +47 -15
  71. memos/memories/textual/tree_text_memory/retrieve/utils.py +11 -7
  72. memos/memories/textual/tree_text_memory/retrieve/xinyusearch.py +62 -58
  73. memos/memos_tools/dinding_report_bot.py +422 -0
  74. memos/memos_tools/lockfree_dict.py +120 -0
  75. memos/memos_tools/notification_service.py +44 -0
  76. memos/memos_tools/notification_utils.py +96 -0
  77. memos/memos_tools/thread_safe_dict.py +288 -0
  78. memos/settings.py +3 -1
  79. memos/templates/mem_reader_prompts.py +4 -1
  80. memos/templates/mem_scheduler_prompts.py +62 -15
  81. memos/templates/mos_prompts.py +116 -0
  82. memos/templates/tree_reorganize_prompts.py +24 -17
  83. memos/utils.py +19 -0
  84. memos/mem_scheduler/modules/misc.py +0 -39
  85. memos/mem_scheduler/modules/retriever.py +0 -268
  86. memos/mem_scheduler/modules/schemas.py +0 -328
  87. memos/mem_scheduler/utils.py +0 -75
  88. memos/memories/textual/tree_text_memory/organize/redundancy.py +0 -193
  89. {memoryos-0.2.1.dist-info → memoryos-1.0.0.dist-info}/LICENSE +0 -0
  90. {memoryos-0.2.1.dist-info → memoryos-1.0.0.dist-info}/WHEEL +0 -0
  91. {memoryos-0.2.1.dist-info → memoryos-1.0.0.dist-info}/entry_points.txt +0 -0
  92. /memos/mem_scheduler/{modules → general_modules}/__init__.py +0 -0
memos/mem_os/product.py CHANGED
@@ -16,17 +16,27 @@ from memos.log import get_logger
16
16
  from memos.mem_cube.general import GeneralMemCube
17
17
  from memos.mem_os.core import MOSCore
18
18
  from memos.mem_os.utils.format_utils import (
19
+ clean_json_response,
19
20
  convert_graph_to_tree_forworkmem,
21
+ ensure_unique_tree_ids,
20
22
  filter_nodes_by_tree_ids,
21
23
  remove_embedding_recursive,
22
24
  sort_children_by_memory_type,
23
25
  )
24
- from memos.mem_scheduler.modules.schemas import ANSWER_LABEL, QUERY_LABEL, ScheduleMessageItem
25
- from memos.mem_user.persistent_user_manager import PersistentUserManager
26
+ from memos.mem_os.utils.reference_utils import (
27
+ process_streaming_references_complete,
28
+ )
29
+ from memos.mem_scheduler.schemas.general_schemas import (
30
+ ANSWER_LABEL,
31
+ QUERY_LABEL,
32
+ )
33
+ from memos.mem_scheduler.schemas.message_schemas import ScheduleMessageItem
34
+ from memos.mem_user.persistent_factory import PersistentUserManagerFactory
26
35
  from memos.mem_user.user_manager import UserRole
27
36
  from memos.memories.textual.item import (
28
37
  TextualMemoryItem,
29
38
  )
39
+ from memos.templates.mos_prompts import MEMOS_PRODUCT_BASE_PROMPT, MEMOS_PRODUCT_ENHANCE_PROMPT
30
40
  from memos.types import MessageList
31
41
 
32
42
 
@@ -46,8 +56,10 @@ class MOSProduct(MOSCore):
46
56
  def __init__(
47
57
  self,
48
58
  default_config: MOSConfig | None = None,
49
- max_user_instances: int = 100,
59
+ max_user_instances: int = 1,
50
60
  default_cube_config: GeneralMemCubeConfig | None = None,
61
+ online_bot=None,
62
+ error_bot=None,
51
63
  ):
52
64
  """
53
65
  Initialize MOSProduct with an optional default configuration.
@@ -56,6 +68,8 @@ class MOSProduct(MOSCore):
56
68
  default_config (MOSConfig | None): Default configuration for new users
57
69
  max_user_instances (int): Maximum number of user instances to keep in memory
58
70
  default_cube_config (GeneralMemCubeConfig | None): Default cube configuration for loading cubes
71
+ online_bot: DingDing online_bot function or None if disabled
72
+ error_bot: DingDing error_bot function or None if disabled
59
73
  """
60
74
  # Initialize with a root config for shared resources
61
75
  if default_config is None:
@@ -75,21 +89,28 @@ class MOSProduct(MOSCore):
75
89
  root_config.user_id = "root"
76
90
  root_config.session_id = "root_session"
77
91
 
78
- # Initialize parent MOSCore with root config
79
- super().__init__(root_config)
92
+ # Create persistent user manager BEFORE calling parent constructor
93
+ persistent_user_manager_client = PersistentUserManagerFactory.from_config(
94
+ config_factory=root_config.user_manager
95
+ )
96
+
97
+ # Initialize parent MOSCore with root config and persistent user manager
98
+ super().__init__(root_config, user_manager=persistent_user_manager_client)
80
99
 
81
100
  # Product-specific attributes
82
101
  self.default_config = default_config
83
102
  self.default_cube_config = default_cube_config
84
103
  self.max_user_instances = max_user_instances
104
+ self.online_bot = online_bot
105
+ self.error_bot = error_bot
85
106
 
86
107
  # User-specific data structures
87
108
  self.user_configs: dict[str, MOSConfig] = {}
88
109
  self.user_cube_access: dict[str, set[str]] = {} # user_id -> set of cube_ids
89
110
  self.user_chat_histories: dict[str, dict] = {}
90
111
 
91
- # Use PersistentUserManager for user management
92
- self.global_user_manager = PersistentUserManager(user_id="root")
112
+ # Note: self.user_manager is now the persistent user manager from parent class
113
+ # No need for separate global_user_manager as they are the same instance
93
114
 
94
115
  # Initialize tiktoken for streaming
95
116
  try:
@@ -116,10 +137,10 @@ class MOSProduct(MOSCore):
116
137
  """
117
138
  try:
118
139
  # Get all user configurations from persistent storage
119
- user_configs = self.global_user_manager.list_user_configs()
140
+ user_configs = self.user_manager.list_user_configs()
120
141
 
121
142
  # Get the raw database records for sorting by updated_at
122
- session = self.global_user_manager._get_session()
143
+ session = self.user_manager._get_session()
123
144
  try:
124
145
  from memos.mem_user.persistent_user_manager import UserConfig
125
146
 
@@ -165,7 +186,7 @@ class MOSProduct(MOSCore):
165
186
  """
166
187
  try:
167
188
  # Get user's accessible cubes from persistent storage
168
- accessible_cubes = self.global_user_manager.get_user_cubes(user_id)
189
+ accessible_cubes = self.user_manager.get_user_cubes(user_id)
169
190
 
170
191
  for cube in accessible_cubes:
171
192
  if cube.cube_id not in self.mem_cubes:
@@ -188,11 +209,12 @@ class MOSProduct(MOSCore):
188
209
  )
189
210
  except Exception as e:
190
211
  logger.error(
191
- f"Failed to pre-load cube {cube.cube_id} for user {user_id}: {e}"
212
+ f"Failed to pre-load cube {cube.cube_id} for user {user_id}: {e}",
213
+ exc_info=True,
192
214
  )
193
215
 
194
216
  except Exception as e:
195
- logger.error(f"Error pre-loading cubes for user {user_id}: {e}")
217
+ logger.error(f"Error pre-loading cubes for user {user_id}: {e}", exc_info=True)
196
218
 
197
219
  def _load_user_cubes(
198
220
  self, user_id: str, default_cube_config: GeneralMemCubeConfig | None = None
@@ -204,7 +226,7 @@ class MOSProduct(MOSCore):
204
226
  default_cube_config (GeneralMemCubeConfig | None, optional): Default cube configuration. Defaults to None.
205
227
  """
206
228
  # Get user's accessible cubes from persistent storage
207
- accessible_cubes = self.global_user_manager.get_user_cubes(user_id)
229
+ accessible_cubes = self.user_manager.get_user_cubes(user_id)
208
230
 
209
231
  for cube in accessible_cubes[:1]:
210
232
  if cube.cube_id not in self.mem_cubes:
@@ -238,7 +260,7 @@ class MOSProduct(MOSCore):
238
260
  return
239
261
 
240
262
  # Try to get config from persistent storage first
241
- stored_config = self.global_user_manager.get_user_config(user_id)
263
+ stored_config = self.user_manager.get_user_config(user_id)
242
264
  if stored_config:
243
265
  self.user_configs[user_id] = stored_config
244
266
  self._load_user_cube_access(user_id)
@@ -268,7 +290,7 @@ class MOSProduct(MOSCore):
268
290
  """Load user's cube access permissions."""
269
291
  try:
270
292
  # Get user's accessible cubes from persistent storage
271
- accessible_cubes = self.global_user_manager.get_user_cube_access(user_id)
293
+ accessible_cubes = self.user_manager.get_user_cube_access(user_id)
272
294
  self.user_cube_access[user_id] = set(accessible_cubes)
273
295
  except Exception as e:
274
296
  logger.warning(f"Failed to load cube access for user {user_id}: {e}")
@@ -304,7 +326,7 @@ class MOSProduct(MOSCore):
304
326
  user_config.session_id = f"{user_id}_session"
305
327
 
306
328
  # Save configuration to persistent storage
307
- self.global_user_manager.save_user_config(user_id, user_config)
329
+ self.user_manager.save_user_config(user_id, user_config)
308
330
 
309
331
  return user_config
310
332
 
@@ -316,7 +338,7 @@ class MOSProduct(MOSCore):
316
338
  return self.user_configs[user_id]
317
339
 
318
340
  # Try to get config from persistent storage first
319
- stored_config = self.global_user_manager.get_user_config(user_id)
341
+ stored_config = self.user_manager.get_user_config(user_id)
320
342
  if stored_config:
321
343
  return self._create_user_config(user_id, stored_config)
322
344
 
@@ -327,7 +349,9 @@ class MOSProduct(MOSCore):
327
349
 
328
350
  return self._create_user_config(user_id, user_config)
329
351
 
330
- def _build_system_prompt(self, user_id: str, memories_all: list[TextualMemoryItem]) -> str:
352
+ def _build_system_prompt(
353
+ self, memories_all: list[TextualMemoryItem], base_prompt: str | None = None
354
+ ) -> str:
331
355
  """
332
356
  Build custom system prompt for the user with memory references.
333
357
 
@@ -340,104 +364,83 @@ class MOSProduct(MOSCore):
340
364
  """
341
365
 
342
366
  # Build base prompt
343
- base_prompt = (
344
- "You are a knowledgeable and helpful AI assistant with access to user memories. "
345
- "When responding to user queries, you should reference relevant memories using the provided memory IDs. "
346
- "Use the reference format: [1-n:memoriesID] "
347
- "where refid is a sequential number starting from 1 and increments for each reference in your response, "
348
- "and memoriesID is the specific memory ID provided in the available memories list. "
349
- "For example: [1:abc123], [2:def456], [3:ghi789], [4:jkl101], [5:mno112] "
350
- "Only reference memories that are directly relevant to the user's question. "
351
- "Make your responses natural and conversational while incorporating memory references when appropriate."
352
- )
353
-
354
367
  # Add memory context if available
355
368
  if memories_all:
356
369
  memory_context = "\n\n## Available ID Memories:\n"
357
370
  for i, memory in enumerate(memories_all, 1):
358
371
  # Format: [memory_id]: memory_content
359
372
  memory_id = f"{memory.id.split('-')[0]}" if hasattr(memory, "id") else f"mem_{i}"
360
- memory_content = memory.memory if hasattr(memory, "memory") else str(memory)
373
+ memory_content = memory.memory[:500] if hasattr(memory, "memory") else str(memory)
374
+ memory_content = memory_content.replace("\n", " ")
361
375
  memory_context += f"{memory_id}: {memory_content}\n"
362
- return base_prompt + memory_context
376
+ return MEMOS_PRODUCT_BASE_PROMPT + memory_context
363
377
 
364
- return base_prompt
378
+ return MEMOS_PRODUCT_BASE_PROMPT
365
379
 
366
- def _process_streaming_references_complete(self, text_buffer: str) -> tuple[str, str]:
380
+ def _build_enhance_system_prompt(
381
+ self, user_id: str, memories_all: list[TextualMemoryItem]
382
+ ) -> str:
367
383
  """
368
- Complete streaming reference processing to ensure reference tags are never split.
369
-
370
- Args:
371
- text_buffer (str): The accumulated text buffer.
372
-
373
- Returns:
374
- tuple[str, str]: (processed_text, remaining_buffer)
384
+ Build enhance prompt for the user with memory references.
375
385
  """
376
- import re
377
-
378
- # Pattern to match complete reference tags: [refid:memoriesID]
379
- complete_pattern = r"\[\d+:[^\]]+\]"
380
-
381
- # Find all complete reference tags
382
- complete_matches = list(re.finditer(complete_pattern, text_buffer))
383
-
384
- if complete_matches:
385
- # Find the last complete tag
386
- last_match = complete_matches[-1]
387
- end_pos = last_match.end()
388
-
389
- # Return text up to the end of the last complete tag
390
- processed_text = text_buffer[:end_pos]
391
- remaining_buffer = text_buffer[end_pos:]
392
- return processed_text, remaining_buffer
393
-
394
- # Check for incomplete reference tags
395
- # Look for opening bracket with number and colon
396
- opening_pattern = r"\[\d+:"
397
- opening_matches = list(re.finditer(opening_pattern, text_buffer))
398
-
399
- if opening_matches:
400
- # Find the last opening tag
401
- last_opening = opening_matches[-1]
402
- opening_start = last_opening.start()
403
-
404
- # Check if we have a complete opening pattern
405
- if last_opening.end() <= len(text_buffer):
406
- # We have a complete opening pattern, keep everything in buffer
407
- return "", text_buffer
408
- else:
409
- # Incomplete opening pattern, return text before it
410
- return text_buffer[:opening_start], text_buffer[opening_start:]
411
-
412
- # Check for partial opening pattern (starts with [ but not complete)
413
- if "[" in text_buffer:
414
- ref_start = text_buffer.find("[")
415
- return text_buffer[:ref_start], text_buffer[ref_start:]
416
-
417
- # No reference tags found, return all text
418
- return text_buffer, ""
386
+ if memories_all:
387
+ personal_memory_context = "\n\n## Available ID and PersonalMemory Memories:\n"
388
+ outer_memory_context = "\n\n## Available ID and OuterMemory Memories:\n"
389
+ for i, memory in enumerate(memories_all, 1):
390
+ # Format: [memory_id]: memory_content
391
+ if memory.metadata.memory_type != "OuterMemory":
392
+ memory_id = (
393
+ f"{memory.id.split('-')[0]}" if hasattr(memory, "id") else f"mem_{i}"
394
+ )
395
+ memory_content = (
396
+ memory.memory[:500] if hasattr(memory, "memory") else str(memory)
397
+ )
398
+ personal_memory_context += f"{memory_id}: {memory_content}\n"
399
+ else:
400
+ memory_id = (
401
+ f"{memory.id.split('-')[0]}" if hasattr(memory, "id") else f"mem_{i}"
402
+ )
403
+ memory_content = (
404
+ memory.memory[:500] if hasattr(memory, "memory") else str(memory)
405
+ )
406
+ memory_content = memory_content.replace("\n", " ")
407
+ outer_memory_context += f"{memory_id}: {memory_content}\n"
408
+ return MEMOS_PRODUCT_ENHANCE_PROMPT + personal_memory_context + outer_memory_context
409
+ return MEMOS_PRODUCT_ENHANCE_PROMPT
419
410
 
420
- def _extract_references_from_response(self, response: str) -> list[dict]:
411
+ def _extract_references_from_response(self, response: str) -> tuple[str, list[dict]]:
421
412
  """
422
- Extract reference information from the response.
413
+ Extract reference information from the response and return clean text.
423
414
 
424
415
  Args:
425
416
  response (str): The complete response text.
426
417
 
427
418
  Returns:
428
- list[dict]: List of reference information.
419
+ tuple[str, list[dict]]: A tuple containing:
420
+ - clean_text: Text with reference markers removed
421
+ - references: List of reference information
429
422
  """
430
423
  import re
431
424
 
432
- references = []
433
- # Pattern to match [refid:memoriesID]
434
- pattern = r"\[(\d+):([^\]]+)\]"
425
+ try:
426
+ references = []
427
+ # Pattern to match [refid:memoriesID]
428
+ pattern = r"\[(\d+):([^\]]+)\]"
429
+
430
+ matches = re.findall(pattern, response)
431
+ for ref_number, memory_id in matches:
432
+ references.append({"memory_id": memory_id, "reference_number": int(ref_number)})
433
+
434
+ # Remove all reference markers from the text to get clean text
435
+ clean_text = re.sub(pattern, "", response)
435
436
 
436
- matches = re.findall(pattern, response)
437
- for ref_number, memory_id in matches:
438
- references.append({"memory_id": memory_id, "reference_number": int(ref_number)})
437
+ # Clean up any extra whitespace that might be left after removing markers
438
+ clean_text = re.sub(r"\s+", " ", clean_text).strip()
439
439
 
440
- return references
440
+ return clean_text, references
441
+ except Exception as e:
442
+ logger.error(f"Error extracting references from response: {e}", exc_info=True)
443
+ return response, []
441
444
 
442
445
  def _chunk_response_with_tiktoken(
443
446
  self, response: str, chunk_size: int = 5
@@ -488,10 +491,18 @@ class MOSProduct(MOSCore):
488
491
  mem_cube=self.mem_cubes[mem_cube_id],
489
492
  label=label,
490
493
  content=query,
491
- timestamp=datetime.now(),
494
+ timestamp=datetime.utcnow(),
492
495
  )
493
496
  self.mem_scheduler.submit_messages(messages=[message_item])
494
497
 
498
+ def _filter_memories_by_threshold(
499
+ self, memories: list[TextualMemoryItem], threshold: float = 0.20
500
+ ) -> list[TextualMemoryItem]:
501
+ """
502
+ Filter memories by threshold.
503
+ """
504
+ return [memory for memory in memories if memory.metadata.relativity >= threshold]
505
+
495
506
  def register_mem_cube(
496
507
  self,
497
508
  mem_cube_name_or_path_or_object: str | GeneralMemCube,
@@ -581,9 +592,7 @@ class MOSProduct(MOSCore):
581
592
  user_name = user_id
582
593
 
583
594
  # Create user with configuration using persistent user manager
584
- self.global_user_manager.create_user_with_config(
585
- user_id, user_config, UserRole.USER, user_id
586
- )
595
+ self.user_manager.create_user_with_config(user_id, user_config, UserRole.USER, user_id)
587
596
 
588
597
  # Create user configuration
589
598
  user_config = self._create_user_config(user_id, user_config)
@@ -599,7 +608,7 @@ class MOSProduct(MOSCore):
599
608
  try:
600
609
  default_mem_cube.dump(mem_cube_name_or_path)
601
610
  except Exception as e:
602
- print(e)
611
+ logger.error(f"Failed to dump default cube: {e}")
603
612
 
604
613
  # Register the default cube with MOS
605
614
  self.register_mem_cube(
@@ -656,7 +665,7 @@ class MOSProduct(MOSCore):
656
665
  you should generate some suggestion query, the query should be user what to query,
657
666
  user recently memories is:
658
667
  {memories}
659
- please generate 3 suggestion query in English,
668
+ if the user recently memories is empty, please generate 3 suggestion query in English,
660
669
  output should be a json format, the key is "query", the value is a list of suggestion query.
661
670
 
662
671
  example:
@@ -664,76 +673,27 @@ class MOSProduct(MOSCore):
664
673
  "query": ["query1", "query2", "query3"]
665
674
  }}
666
675
  """
667
- text_mem_result = super().search("my recently memories", user_id=user_id, top_k=10)[
676
+ text_mem_result = super().search("my recently memories", user_id=user_id, top_k=3)[
668
677
  "text_mem"
669
678
  ]
670
679
  if text_mem_result:
671
- memories = "\n".join([m.memory for m in text_mem_result[0]["memories"]])
680
+ memories = "\n".join([m.memory[:200] for m in text_mem_result[0]["memories"]])
672
681
  else:
673
682
  memories = ""
674
683
  message_list = [{"role": "system", "content": suggestion_prompt.format(memories=memories)}]
675
684
  response = self.chat_llm.generate(message_list)
676
- response_json = json.loads(response)
677
-
685
+ clean_response = clean_json_response(response)
686
+ response_json = json.loads(clean_response)
678
687
  return response_json["query"]
679
688
 
680
- def chat(
681
- self,
682
- query: str,
683
- user_id: str,
684
- cube_id: str | None = None,
685
- history: MessageList | None = None,
686
- ) -> Generator[str, None, None]:
687
- """Chat with LLM SSE Type.
688
- Args:
689
- query (str): Query string.
690
- user_id (str): User ID.
691
- cube_id (str, optional): Custom cube ID for user.
692
- history (list[dict], optional): Chat history.
693
-
694
- Returns:
695
- Generator[str, None, None]: The response string generator.
696
- """
697
- # Use MOSCore's built-in validation
698
- if cube_id:
699
- self._validate_cube_access(user_id, cube_id)
700
- else:
701
- self._validate_user_exists(user_id)
702
-
703
- # Load user cubes if not already loaded
704
- self._load_user_cubes(user_id, self.default_cube_config)
705
- time_start = time.time()
706
- memories_list = super().search(query, user_id)["text_mem"]
707
- # Get response from parent MOSCore (returns string, not generator)
708
- response = super().chat(query, user_id)
709
- time_end = time.time()
710
-
711
- # Use tiktoken for proper token-based chunking
712
- for chunk in self._chunk_response_with_tiktoken(response, chunk_size=5):
713
- chunk_data = f"data: {json.dumps({'type': 'text', 'content': chunk})}\n\n"
714
- yield chunk_data
715
-
716
- # Prepare reference data
717
- reference = []
718
- for memories in memories_list:
719
- memories_json = memories.model_dump()
720
- memories_json["metadata"]["ref_id"] = f"[{memories.id.split('-')[0]}]"
721
- memories_json["metadata"]["embedding"] = []
722
- memories_json["metadata"]["sources"] = []
723
- reference.append(memories_json)
724
-
725
- yield f"data: {json.dumps({'type': 'reference', 'content': reference})}\n\n"
726
- total_time = round(float(time_end - time_start), 1)
727
-
728
- yield f"data: {json.dumps({'type': 'time', 'content': {'total_time': total_time, 'speed_improvement': '23%'}})}\n\n"
729
- yield f"data: {json.dumps({'type': 'end'})}\n\n"
730
-
731
689
  def chat_with_references(
732
690
  self,
733
691
  query: str,
734
692
  user_id: str,
735
693
  cube_id: str | None = None,
736
694
  history: MessageList | None = None,
695
+ top_k: int = 10,
696
+ internet_search: bool = False,
737
697
  ) -> Generator[str, None, None]:
738
698
  """
739
699
  Chat with LLM with memory references and streaming output.
@@ -749,30 +709,46 @@ class MOSProduct(MOSCore):
749
709
  """
750
710
 
751
711
  self._load_user_cubes(user_id, self.default_cube_config)
752
-
753
712
  time_start = time.time()
754
713
  memories_list = []
714
+ yield f"data: {json.dumps({'type': 'status', 'data': '0'})}\n\n"
755
715
  memories_result = super().search(
756
- query, user_id, install_cube_ids=[cube_id] if cube_id else None, top_k=10
716
+ query,
717
+ user_id,
718
+ install_cube_ids=[cube_id] if cube_id else None,
719
+ top_k=top_k,
720
+ mode="fine",
721
+ internet_search=internet_search,
757
722
  )["text_mem"]
723
+ yield f"data: {json.dumps({'type': 'status', 'data': '1'})}\n\n"
724
+ search_time_end = time.time()
725
+ logger.info(
726
+ f"time chat: search text_mem time user_id: {user_id} time is: {search_time_end - time_start}"
727
+ )
728
+ self._send_message_to_scheduler(
729
+ user_id=user_id, mem_cube_id=cube_id, query=query, label=QUERY_LABEL
730
+ )
758
731
  if memories_result:
759
732
  memories_list = memories_result[0]["memories"]
760
-
761
- # Build custom system prompt with relevant memories
762
- system_prompt = self._build_system_prompt(user_id, memories_list)
763
-
733
+ memories_list = self._filter_memories_by_threshold(memories_list)
734
+ # Build custom system prompt with relevant memories)
735
+ system_prompt = self._build_enhance_system_prompt(user_id, memories_list)
764
736
  # Get chat history
765
- target_user_id = user_id if user_id is not None else self.user_id
766
- if target_user_id not in self.chat_history_manager:
767
- self._register_chat_history(target_user_id)
737
+ if user_id not in self.chat_history_manager:
738
+ self._register_chat_history(user_id)
768
739
 
769
- chat_history = self.chat_history_manager[target_user_id]
740
+ chat_history = self.chat_history_manager[user_id]
741
+ if history:
742
+ chat_history.chat_history = history[-10:]
770
743
  current_messages = [
771
744
  {"role": "system", "content": system_prompt},
772
745
  *chat_history.chat_history,
773
746
  {"role": "user", "content": query},
774
747
  ]
775
-
748
+ logger.info(
749
+ f"user_id: {user_id}, cube_id: {cube_id}, current_system_prompt: {system_prompt}"
750
+ )
751
+ yield f"data: {json.dumps({'type': 'status', 'data': '2'})}\n\n"
776
752
  # Generate response with custom prompt
777
753
  past_key_values = None
778
754
  response_stream = None
@@ -802,13 +778,16 @@ class MOSProduct(MOSCore):
802
778
  response_stream = self.chat_llm.generate(current_messages)
803
779
 
804
780
  time_end = time.time()
805
-
781
+ chat_time_end = time.time()
782
+ logger.info(
783
+ f"time chat: chat time user_id: {user_id} time is: {chat_time_end - search_time_end}"
784
+ )
806
785
  # Simulate streaming output with proper reference handling using tiktoken
807
786
 
808
787
  # Initialize buffer for streaming
809
788
  buffer = ""
810
789
  full_response = ""
811
-
790
+ token_count = 0
812
791
  # Use tiktoken for proper token-based chunking
813
792
  if self.config.chat_model.backend not in ["huggingface", "vllm"]:
814
793
  # For non-huggingface backends, we need to collect the full response first
@@ -821,11 +800,12 @@ class MOSProduct(MOSCore):
821
800
  for chunk in response_stream:
822
801
  if chunk in ["<think>", "</think>"]:
823
802
  continue
803
+ token_count += 1
824
804
  buffer += chunk
825
805
  full_response += chunk
826
806
 
827
807
  # Process buffer to ensure complete reference tags
828
- processed_chunk, remaining_buffer = self._process_streaming_references_complete(buffer)
808
+ processed_chunk, remaining_buffer = process_streaming_references_complete(buffer)
829
809
 
830
810
  if processed_chunk:
831
811
  chunk_data = f"data: {json.dumps({'type': 'text', 'data': processed_chunk}, ensure_ascii=False)}\n\n"
@@ -834,7 +814,7 @@ class MOSProduct(MOSCore):
834
814
 
835
815
  # Process any remaining buffer
836
816
  if buffer:
837
- processed_chunk, remaining_buffer = self._process_streaming_references_complete(buffer)
817
+ processed_chunk, remaining_buffer = process_streaming_references_complete(buffer)
838
818
  if processed_chunk:
839
819
  chunk_data = f"data: {json.dumps({'type': 'text', 'data': processed_chunk}, ensure_ascii=False)}\n\n"
840
820
  yield chunk_data
@@ -847,22 +827,60 @@ class MOSProduct(MOSCore):
847
827
  memories_json["metadata"]["embedding"] = []
848
828
  memories_json["metadata"]["sources"] = []
849
829
  memories_json["metadata"]["memory"] = memories.memory
830
+ memories_json["metadata"]["id"] = memories.id
850
831
  reference.append({"metadata": memories_json["metadata"]})
851
832
 
852
833
  yield f"data: {json.dumps({'type': 'reference', 'data': reference})}\n\n"
834
+ # set kvcache improve speed
835
+ speed_improvement = round(float((len(system_prompt) / 2) * 0.0048 + 44.5), 1)
853
836
  total_time = round(float(time_end - time_start), 1)
854
- yield f"data: {json.dumps({'type': 'time', 'data': {'total_time': total_time, 'speed_improvement': '23%'}})}\n\n"
855
- chat_history.chat_history.append({"role": "user", "content": query})
856
- chat_history.chat_history.append({"role": "assistant", "content": full_response})
857
- self._send_message_to_scheduler(
858
- user_id=user_id, mem_cube_id=cube_id, query=query, label=QUERY_LABEL
859
- )
860
- self._send_message_to_scheduler(
861
- user_id=user_id, mem_cube_id=cube_id, query=full_response, label=ANSWER_LABEL
862
- )
863
- self.chat_history_manager[user_id] = chat_history
864
837
 
838
+ yield f"data: {json.dumps({'type': 'time', 'data': {'total_time': total_time, 'speed_improvement': f'{speed_improvement}%'}})}\n\n"
865
839
  yield f"data: {json.dumps({'type': 'end'})}\n\n"
840
+
841
+ logger.info(f"user_id: {user_id}, cube_id: {cube_id}, current_messages: {current_messages}")
842
+ logger.info(f"user_id: {user_id}, cube_id: {cube_id}, full_response: {full_response}")
843
+
844
+ clean_response, extracted_references = self._extract_references_from_response(full_response)
845
+ logger.info(f"Extracted {len(extracted_references)} references from response")
846
+
847
+ # Send chat report if online_bot is available
848
+ try:
849
+ from memos.memos_tools.notification_utils import send_online_bot_notification
850
+
851
+ # Prepare data for online_bot
852
+ chat_data = {
853
+ "query": query,
854
+ "user_id": user_id,
855
+ "cube_id": cube_id,
856
+ "system_prompt": system_prompt,
857
+ "full_response": full_response,
858
+ }
859
+
860
+ system_data = {
861
+ "references": extracted_references,
862
+ "time_start": time_start,
863
+ "time_end": time_end,
864
+ "speed_improvement": speed_improvement,
865
+ }
866
+
867
+ emoji_config = {"chat": "💬", "system_info": "📊"}
868
+
869
+ send_online_bot_notification(
870
+ online_bot=self.online_bot,
871
+ header_name="MemOS Chat Report",
872
+ sub_title_name="chat_with_references",
873
+ title_color="#00956D",
874
+ other_data1=chat_data,
875
+ other_data2=system_data,
876
+ emoji=emoji_config,
877
+ )
878
+ except Exception as e:
879
+ logger.warning(f"Failed to send chat notification: {e}")
880
+
881
+ self._send_message_to_scheduler(
882
+ user_id=user_id, mem_cube_id=cube_id, query=clean_response, label=ANSWER_LABEL
883
+ )
866
884
  self.add(
867
885
  user_id=user_id,
868
886
  messages=[
@@ -873,18 +891,12 @@ class MOSProduct(MOSCore):
873
891
  },
874
892
  {
875
893
  "role": "assistant",
876
- "content": full_response,
894
+ "content": clean_response, # Store clean text without reference markers
877
895
  "chat_time": str(datetime.now().strftime("%Y-%m-%d %H:%M:%S")),
878
896
  },
879
897
  ],
880
898
  mem_cube_id=cube_id,
881
899
  )
882
- # Keep chat history under 30 messages by removing oldest conversation pair
883
- if len(self.chat_history_manager[user_id].chat_history) > 10:
884
- self.chat_history_manager[user_id].chat_history.pop(0) # Remove oldest user message
885
- self.chat_history_manager[user_id].chat_history.pop(
886
- 0
887
- ) # Remove oldest assistant response
888
900
 
889
901
  def get_all(
890
902
  self,
@@ -905,9 +917,14 @@ class MOSProduct(MOSCore):
905
917
 
906
918
  # Load user cubes if not already loaded
907
919
  self._load_user_cubes(user_id, self.default_cube_config)
920
+ time_start = time.time()
908
921
  memory_list = super().get_all(
909
922
  mem_cube_id=mem_cube_ids[0] if mem_cube_ids else None, user_id=user_id
910
923
  )[memory_type]
924
+ get_all_time_end = time.time()
925
+ logger.info(
926
+ f"time get_all: get_all time user_id: {user_id} time is: {get_all_time_end - time_start}"
927
+ )
911
928
  reformat_memory_list = []
912
929
  if memory_type == "text_mem":
913
930
  for memory in memory_list:
@@ -918,8 +935,10 @@ class MOSProduct(MOSCore):
918
935
  "UserMemory": 0.40,
919
936
  }
920
937
  tree_result, node_type_count = convert_graph_to_tree_forworkmem(
921
- memories, target_node_count=150, type_ratios=custom_type_ratios
938
+ memories, target_node_count=200, type_ratios=custom_type_ratios
922
939
  )
940
+ # Ensure all node IDs are unique in the tree structure
941
+ tree_result = ensure_unique_tree_ids(tree_result)
923
942
  memories_filtered = filter_nodes_by_tree_ids(tree_result, memories)
924
943
  children = tree_result["children"]
925
944
  children_sort = sort_children_by_memory_type(children)
@@ -963,6 +982,10 @@ class MOSProduct(MOSCore):
963
982
  "memories": act_mem_params[0].model_dump(),
964
983
  }
965
984
  )
985
+ make_format_time_end = time.time()
986
+ logger.info(
987
+ f"time get_all: make_format time user_id: {user_id} time is: {make_format_time_end - get_all_time_end}"
988
+ )
966
989
  return reformat_memory_list
967
990
 
968
991
  def _get_subgraph(
@@ -985,6 +1008,7 @@ class MOSProduct(MOSCore):
985
1008
  user_id: str,
986
1009
  query: str,
987
1010
  mem_cube_ids: list[str] | None = None,
1011
+ top_k: int = 20,
988
1012
  ) -> list[dict[str, Any]]:
989
1013
  """Get all memory items for a user.
990
1014
 
@@ -1000,7 +1024,7 @@ class MOSProduct(MOSCore):
1000
1024
  # Load user cubes if not already loaded
1001
1025
  self._load_user_cubes(user_id, self.default_cube_config)
1002
1026
  memory_list = self._get_subgraph(
1003
- query=query, mem_cube_id=mem_cube_ids[0], user_id=user_id, top_k=20
1027
+ query=query, mem_cube_id=mem_cube_ids[0], user_id=user_id, top_k=top_k
1004
1028
  )["text_mem"]
1005
1029
  reformat_memory_list = []
1006
1030
  for memory in memory_list:
@@ -1009,6 +1033,8 @@ class MOSProduct(MOSCore):
1009
1033
  tree_result, node_type_count = convert_graph_to_tree_forworkmem(
1010
1034
  memories, target_node_count=150, type_ratios=custom_type_ratios
1011
1035
  )
1036
+ # Ensure all node IDs are unique in the tree structure
1037
+ tree_result = ensure_unique_tree_ids(tree_result)
1012
1038
  memories_filtered = filter_nodes_by_tree_ids(tree_result, memories)
1013
1039
  children = tree_result["children"]
1014
1040
  children_sort = sort_children_by_memory_type(children)
@@ -1025,15 +1051,27 @@ class MOSProduct(MOSCore):
1025
1051
  return reformat_memory_list
1026
1052
 
1027
1053
  def search(
1028
- self, query: str, user_id: str, install_cube_ids: list[str] | None = None, top_k: int = 20
1054
+ self,
1055
+ query: str,
1056
+ user_id: str,
1057
+ install_cube_ids: list[str] | None = None,
1058
+ top_k: int = 10,
1059
+ mode: Literal["fast", "fine"] = "fast",
1029
1060
  ):
1030
1061
  """Search memories for a specific user."""
1031
- # Validate user access
1032
- self._validate_user_access(user_id)
1033
1062
 
1034
1063
  # Load user cubes if not already loaded
1064
+ time_start = time.time()
1035
1065
  self._load_user_cubes(user_id, self.default_cube_config)
1036
- search_result = super().search(query, user_id, install_cube_ids, top_k)
1066
+ load_user_cubes_time_end = time.time()
1067
+ logger.info(
1068
+ f"time search: load_user_cubes time user_id: {user_id} time is: {load_user_cubes_time_end - time_start}"
1069
+ )
1070
+ search_result = super().search(query, user_id, install_cube_ids, top_k, mode=mode)
1071
+ search_time_end = time.time()
1072
+ logger.info(
1073
+ f"time search: search text_mem time user_id: {user_id} time is: {search_time_end - load_user_cubes_time_end}"
1074
+ )
1037
1075
  text_memory_list = search_result["text_mem"]
1038
1076
  reformat_memory_list = []
1039
1077
  for memory in text_memory_list:
@@ -1049,7 +1087,10 @@ class MOSProduct(MOSCore):
1049
1087
  memories_list.append(memories)
1050
1088
  reformat_memory_list.append({"cube_id": memory["cube_id"], "memories": memories_list})
1051
1089
  search_result["text_mem"] = reformat_memory_list
1052
-
1090
+ time_end = time.time()
1091
+ logger.info(
1092
+ f"time search: total time for user_id: {user_id} time is: {time_end - time_start}"
1093
+ )
1053
1094
  return search_result
1054
1095
 
1055
1096
  def add(
@@ -1059,24 +1100,34 @@ class MOSProduct(MOSCore):
1059
1100
  memory_content: str | None = None,
1060
1101
  doc_path: str | None = None,
1061
1102
  mem_cube_id: str | None = None,
1103
+ source: str | None = None,
1104
+ user_profile: bool = False,
1062
1105
  ):
1063
1106
  """Add memory for a specific user."""
1064
- # Use MOSCore's built-in user/cube validation
1065
- if mem_cube_id:
1066
- self._validate_cube_access(user_id, mem_cube_id)
1067
- else:
1068
- self._validate_user_exists(user_id)
1069
1107
 
1070
1108
  # Load user cubes if not already loaded
1071
1109
  self._load_user_cubes(user_id, self.default_cube_config)
1072
1110
 
1073
1111
  result = super().add(messages, memory_content, doc_path, mem_cube_id, user_id)
1112
+ if user_profile:
1113
+ try:
1114
+ user_interests = memory_content.split("'userInterests': '")[1].split("', '")[0]
1115
+ user_interests = user_interests.replace(",", " ")
1116
+ user_profile_memories = self.mem_cubes[
1117
+ mem_cube_id
1118
+ ].text_mem.internet_retriever.retrieve_from_internet(query=user_interests, top_k=5)
1119
+ for memory in user_profile_memories:
1120
+ self.mem_cubes[mem_cube_id].text_mem.add(memory)
1121
+ except Exception as e:
1122
+ logger.error(
1123
+ f"Failed to retrieve user profile: {e}, memory_content: {memory_content}"
1124
+ )
1074
1125
 
1075
1126
  return result
1076
1127
 
1077
1128
  def list_users(self) -> list:
1078
1129
  """List all registered users."""
1079
- return self.global_user_manager.list_users()
1130
+ return self.user_manager.list_users()
1080
1131
 
1081
1132
  def get_user_info(self, user_id: str) -> dict:
1082
1133
  """Get user information including accessible cubes."""
@@ -1116,7 +1167,7 @@ class MOSProduct(MOSCore):
1116
1167
  """
1117
1168
  try:
1118
1169
  # Save to persistent storage
1119
- success = self.global_user_manager.save_user_config(user_id, config)
1170
+ success = self.user_manager.save_user_config(user_id, config)
1120
1171
  if success:
1121
1172
  # Update in-memory config
1122
1173
  self.user_configs[user_id] = config
@@ -1136,7 +1187,7 @@ class MOSProduct(MOSCore):
1136
1187
  Returns:
1137
1188
  MOSConfig | None: The user's configuration or None if not found.
1138
1189
  """
1139
- return self.global_user_manager.get_user_config(user_id)
1190
+ return self.user_manager.get_user_config(user_id)
1140
1191
 
1141
1192
  def get_active_user_count(self) -> int:
1142
1193
  """Get the number of active user configurations in memory."""