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.
- {memoryos-0.2.1.dist-info → memoryos-1.0.0.dist-info}/METADATA +7 -1
- {memoryos-0.2.1.dist-info → memoryos-1.0.0.dist-info}/RECORD +87 -64
- memos/__init__.py +1 -1
- memos/api/config.py +158 -69
- memos/api/context/context.py +147 -0
- memos/api/context/dependencies.py +101 -0
- memos/api/product_models.py +5 -1
- memos/api/routers/product_router.py +54 -26
- memos/configs/graph_db.py +49 -1
- memos/configs/internet_retriever.py +19 -0
- memos/configs/mem_os.py +5 -0
- memos/configs/mem_reader.py +9 -0
- memos/configs/mem_scheduler.py +54 -18
- memos/configs/mem_user.py +58 -0
- memos/graph_dbs/base.py +38 -3
- memos/graph_dbs/factory.py +2 -0
- memos/graph_dbs/nebular.py +1612 -0
- memos/graph_dbs/neo4j.py +18 -9
- memos/log.py +6 -1
- memos/mem_cube/utils.py +13 -6
- memos/mem_os/core.py +157 -37
- memos/mem_os/main.py +2 -2
- memos/mem_os/product.py +252 -201
- memos/mem_os/utils/default_config.py +1 -1
- memos/mem_os/utils/format_utils.py +281 -70
- memos/mem_os/utils/reference_utils.py +133 -0
- memos/mem_reader/simple_struct.py +13 -5
- memos/mem_scheduler/base_scheduler.py +239 -266
- memos/mem_scheduler/{modules → general_modules}/base.py +4 -5
- memos/mem_scheduler/{modules → general_modules}/dispatcher.py +57 -21
- memos/mem_scheduler/general_modules/misc.py +104 -0
- memos/mem_scheduler/{modules → general_modules}/rabbitmq_service.py +12 -10
- memos/mem_scheduler/{modules → general_modules}/redis_service.py +1 -1
- memos/mem_scheduler/general_modules/retriever.py +199 -0
- memos/mem_scheduler/general_modules/scheduler_logger.py +261 -0
- memos/mem_scheduler/general_scheduler.py +243 -80
- memos/mem_scheduler/monitors/__init__.py +0 -0
- memos/mem_scheduler/monitors/dispatcher_monitor.py +305 -0
- memos/mem_scheduler/{modules/monitor.py → monitors/general_monitor.py} +106 -57
- memos/mem_scheduler/mos_for_test_scheduler.py +23 -20
- memos/mem_scheduler/schemas/__init__.py +0 -0
- memos/mem_scheduler/schemas/general_schemas.py +44 -0
- memos/mem_scheduler/schemas/message_schemas.py +149 -0
- memos/mem_scheduler/schemas/monitor_schemas.py +337 -0
- memos/mem_scheduler/utils/__init__.py +0 -0
- memos/mem_scheduler/utils/filter_utils.py +176 -0
- memos/mem_scheduler/utils/misc_utils.py +102 -0
- memos/mem_user/factory.py +94 -0
- memos/mem_user/mysql_persistent_user_manager.py +271 -0
- memos/mem_user/mysql_user_manager.py +500 -0
- memos/mem_user/persistent_factory.py +96 -0
- memos/mem_user/user_manager.py +4 -4
- memos/memories/activation/item.py +5 -1
- memos/memories/activation/kv.py +20 -8
- memos/memories/textual/base.py +2 -2
- memos/memories/textual/general.py +36 -92
- memos/memories/textual/item.py +5 -33
- memos/memories/textual/tree.py +13 -7
- memos/memories/textual/tree_text_memory/organize/{conflict.py → handler.py} +34 -50
- memos/memories/textual/tree_text_memory/organize/manager.py +8 -96
- memos/memories/textual/tree_text_memory/organize/relation_reason_detector.py +49 -43
- memos/memories/textual/tree_text_memory/organize/reorganizer.py +107 -142
- memos/memories/textual/tree_text_memory/retrieve/bochasearch.py +229 -0
- memos/memories/textual/tree_text_memory/retrieve/internet_retriever.py +6 -3
- memos/memories/textual/tree_text_memory/retrieve/internet_retriever_factory.py +11 -0
- memos/memories/textual/tree_text_memory/retrieve/recall.py +15 -8
- memos/memories/textual/tree_text_memory/retrieve/reranker.py +1 -1
- memos/memories/textual/tree_text_memory/retrieve/retrieval_mid_structs.py +2 -0
- memos/memories/textual/tree_text_memory/retrieve/searcher.py +191 -116
- memos/memories/textual/tree_text_memory/retrieve/task_goal_parser.py +47 -15
- memos/memories/textual/tree_text_memory/retrieve/utils.py +11 -7
- memos/memories/textual/tree_text_memory/retrieve/xinyusearch.py +62 -58
- memos/memos_tools/dinding_report_bot.py +422 -0
- memos/memos_tools/lockfree_dict.py +120 -0
- memos/memos_tools/notification_service.py +44 -0
- memos/memos_tools/notification_utils.py +96 -0
- memos/memos_tools/thread_safe_dict.py +288 -0
- memos/settings.py +3 -1
- memos/templates/mem_reader_prompts.py +4 -1
- memos/templates/mem_scheduler_prompts.py +62 -15
- memos/templates/mos_prompts.py +116 -0
- memos/templates/tree_reorganize_prompts.py +24 -17
- memos/utils.py +19 -0
- memos/mem_scheduler/modules/misc.py +0 -39
- memos/mem_scheduler/modules/retriever.py +0 -268
- memos/mem_scheduler/modules/schemas.py +0 -328
- memos/mem_scheduler/utils.py +0 -75
- memos/memories/textual/tree_text_memory/organize/redundancy.py +0 -193
- {memoryos-0.2.1.dist-info → memoryos-1.0.0.dist-info}/LICENSE +0 -0
- {memoryos-0.2.1.dist-info → memoryos-1.0.0.dist-info}/WHEEL +0 -0
- {memoryos-0.2.1.dist-info → memoryos-1.0.0.dist-info}/entry_points.txt +0 -0
- /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.
|
|
25
|
-
|
|
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 =
|
|
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
|
-
#
|
|
79
|
-
|
|
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
|
-
#
|
|
92
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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(
|
|
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
|
|
376
|
+
return MEMOS_PRODUCT_BASE_PROMPT + memory_context
|
|
363
377
|
|
|
364
|
-
return
|
|
378
|
+
return MEMOS_PRODUCT_BASE_PROMPT
|
|
365
379
|
|
|
366
|
-
def
|
|
380
|
+
def _build_enhance_system_prompt(
|
|
381
|
+
self, user_id: str, memories_all: list[TextualMemoryItem]
|
|
382
|
+
) -> str:
|
|
367
383
|
"""
|
|
368
|
-
|
|
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
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
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]:
|
|
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
|
-
|
|
433
|
-
|
|
434
|
-
|
|
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
|
-
|
|
437
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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=
|
|
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
|
-
|
|
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,
|
|
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.
|
|
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
|
-
|
|
766
|
-
|
|
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[
|
|
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 =
|
|
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 =
|
|
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":
|
|
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=
|
|
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=
|
|
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,
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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."""
|