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/graph_dbs/neo4j.py
CHANGED
|
@@ -114,14 +114,14 @@ class Neo4jGraphDB(BaseGraphDB):
|
|
|
114
114
|
)
|
|
115
115
|
return result.single()["count"]
|
|
116
116
|
|
|
117
|
-
def
|
|
117
|
+
def node_not_exist(self, scope: str) -> int:
|
|
118
118
|
query = """
|
|
119
119
|
MATCH (n:Memory)
|
|
120
120
|
WHERE n.memory_type = $scope
|
|
121
121
|
"""
|
|
122
122
|
if not self.config.use_multi_db and self.config.user_name:
|
|
123
123
|
query += "\nAND n.user_name = $user_name"
|
|
124
|
-
query += "\nRETURN
|
|
124
|
+
query += "\nRETURN n LIMIT 1"
|
|
125
125
|
|
|
126
126
|
with self.driver.session(database=self.db_name) as session:
|
|
127
127
|
result = session.run(
|
|
@@ -131,7 +131,7 @@ class Neo4jGraphDB(BaseGraphDB):
|
|
|
131
131
|
"user_name": self.config.user_name if self.config.user_name else None,
|
|
132
132
|
},
|
|
133
133
|
)
|
|
134
|
-
return result.single()
|
|
134
|
+
return result.single() is None
|
|
135
135
|
|
|
136
136
|
def remove_oldest_memory(self, memory_type: str, keep_latest: int) -> None:
|
|
137
137
|
"""
|
|
@@ -323,14 +323,16 @@ class Neo4jGraphDB(BaseGraphDB):
|
|
|
323
323
|
return result.single() is not None
|
|
324
324
|
|
|
325
325
|
# Graph Query & Reasoning
|
|
326
|
-
def get_node(self, id: str) -> dict[str, Any] | None:
|
|
326
|
+
def get_node(self, id: str, include_embedding: bool = True) -> dict[str, Any] | None:
|
|
327
327
|
"""
|
|
328
328
|
Retrieve the metadata and memory of a node.
|
|
329
329
|
Args:
|
|
330
330
|
id: Node identifier.
|
|
331
|
+
include_embedding (bool): Whether to include the large embedding field.
|
|
331
332
|
Returns:
|
|
332
333
|
Dictionary of node fields, or None if not found.
|
|
333
334
|
"""
|
|
335
|
+
|
|
334
336
|
where_user = ""
|
|
335
337
|
params = {"id": id}
|
|
336
338
|
if not self.config.use_multi_db and self.config.user_name:
|
|
@@ -343,11 +345,12 @@ class Neo4jGraphDB(BaseGraphDB):
|
|
|
343
345
|
record = session.run(query, params).single()
|
|
344
346
|
return self._parse_node(dict(record["n"])) if record else None
|
|
345
347
|
|
|
346
|
-
def get_nodes(self, ids: list[str]) -> list[dict[str, Any]]:
|
|
348
|
+
def get_nodes(self, ids: list[str], include_embedding: bool = True) -> list[dict[str, Any]]:
|
|
347
349
|
"""
|
|
348
350
|
Retrieve the metadata and memory of a list of nodes.
|
|
349
351
|
Args:
|
|
350
352
|
ids: List of Node identifier.
|
|
353
|
+
include_embedding (bool): Whether to include the large embedding field.
|
|
351
354
|
Returns:
|
|
352
355
|
list[dict]: Parsed node records containing 'id', 'memory', and 'metadata'.
|
|
353
356
|
|
|
@@ -355,6 +358,7 @@ class Neo4jGraphDB(BaseGraphDB):
|
|
|
355
358
|
- Assumes all provided IDs are valid and exist.
|
|
356
359
|
- Returns empty list if input is empty.
|
|
357
360
|
"""
|
|
361
|
+
|
|
358
362
|
if not ids:
|
|
359
363
|
return []
|
|
360
364
|
|
|
@@ -829,7 +833,7 @@ class Neo4jGraphDB(BaseGraphDB):
|
|
|
829
833
|
logger.error(f"[ERROR] Failed to clear database '{self.db_name}': {e}")
|
|
830
834
|
raise
|
|
831
835
|
|
|
832
|
-
def export_graph(self) -> dict[str, Any]:
|
|
836
|
+
def export_graph(self, include_embedding: bool = True) -> dict[str, Any]:
|
|
833
837
|
"""
|
|
834
838
|
Export all graph nodes and edges in a structured form.
|
|
835
839
|
|
|
@@ -910,17 +914,19 @@ class Neo4jGraphDB(BaseGraphDB):
|
|
|
910
914
|
target_id=edge["target"],
|
|
911
915
|
)
|
|
912
916
|
|
|
913
|
-
def get_all_memory_items(self, scope: str) -> list[dict]:
|
|
917
|
+
def get_all_memory_items(self, scope: str, include_embedding: bool = True) -> list[dict]:
|
|
914
918
|
"""
|
|
915
919
|
Retrieve all memory items of a specific memory_type.
|
|
916
920
|
|
|
917
921
|
Args:
|
|
918
922
|
scope (str): Must be one of 'WorkingMemory', 'LongTermMemory', or 'UserMemory'.
|
|
923
|
+
include_embedding (bool): Whether to include the large embedding field.
|
|
924
|
+
Returns:
|
|
919
925
|
|
|
920
926
|
Returns:
|
|
921
927
|
list[dict]: Full list of memory items under this scope.
|
|
922
928
|
"""
|
|
923
|
-
if scope not in {"WorkingMemory", "LongTermMemory", "UserMemory"}:
|
|
929
|
+
if scope not in {"WorkingMemory", "LongTermMemory", "UserMemory", "OuterMemory"}:
|
|
924
930
|
raise ValueError(f"Unsupported memory type scope: {scope}")
|
|
925
931
|
|
|
926
932
|
where_clause = "WHERE n.memory_type = $scope"
|
|
@@ -940,12 +946,15 @@ class Neo4jGraphDB(BaseGraphDB):
|
|
|
940
946
|
results = session.run(query, params)
|
|
941
947
|
return [self._parse_node(dict(record["n"])) for record in results]
|
|
942
948
|
|
|
943
|
-
def get_structure_optimization_candidates(
|
|
949
|
+
def get_structure_optimization_candidates(
|
|
950
|
+
self, scope: str, include_embedding: bool = True
|
|
951
|
+
) -> list[dict]:
|
|
944
952
|
"""
|
|
945
953
|
Find nodes that are likely candidates for structure optimization:
|
|
946
954
|
- Isolated nodes, nodes with empty background, or nodes with exactly one child.
|
|
947
955
|
- Plus: the child of any parent node that has exactly one child.
|
|
948
956
|
"""
|
|
957
|
+
|
|
949
958
|
where_clause = """
|
|
950
959
|
WHERE n.memory_type = $scope
|
|
951
960
|
AND n.status = 'activated'
|
memos/log.py
CHANGED
|
@@ -4,9 +4,14 @@ from logging.config import dictConfig
|
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
from sys import stdout
|
|
6
6
|
|
|
7
|
+
from dotenv import load_dotenv
|
|
8
|
+
|
|
7
9
|
from memos import settings
|
|
8
10
|
|
|
9
11
|
|
|
12
|
+
# Load environment variables
|
|
13
|
+
load_dotenv()
|
|
14
|
+
|
|
10
15
|
selected_log_level = logging.DEBUG if settings.DEBUG else logging.WARNING
|
|
11
16
|
|
|
12
17
|
|
|
@@ -48,7 +53,7 @@ LOGGING_CONFIG = {
|
|
|
48
53
|
"class": "logging.handlers.RotatingFileHandler",
|
|
49
54
|
"filename": _setup_logfile(),
|
|
50
55
|
"maxBytes": 1024**2 * 10,
|
|
51
|
-
"backupCount":
|
|
56
|
+
"backupCount": 10,
|
|
52
57
|
"formatter": "standard",
|
|
53
58
|
},
|
|
54
59
|
},
|
memos/mem_cube/utils.py
CHANGED
|
@@ -71,10 +71,6 @@ def merge_config_with_default(
|
|
|
71
71
|
|
|
72
72
|
# Define graph_db fields to preserve (user-specific)
|
|
73
73
|
preserve_graph_fields = {
|
|
74
|
-
"uri",
|
|
75
|
-
"user",
|
|
76
|
-
"password",
|
|
77
|
-
"db_name",
|
|
78
74
|
"auto_create",
|
|
79
75
|
"user_name",
|
|
80
76
|
"use_multi_db",
|
|
@@ -96,9 +92,20 @@ def merge_config_with_default(
|
|
|
96
92
|
merged_graph_config["db_name"] = default_graph_config.get("db_name")
|
|
97
93
|
else:
|
|
98
94
|
logger.info("use_multi_db is already False, no need to change")
|
|
99
|
-
|
|
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")
|
|
100
107
|
preserved_graph_db = {
|
|
101
|
-
"backend":
|
|
108
|
+
"backend": default_text_config["graph_db"]["backend"],
|
|
102
109
|
"config": merged_graph_config,
|
|
103
110
|
}
|
|
104
111
|
|
memos/mem_os/core.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import json
|
|
2
2
|
import os
|
|
3
|
-
import
|
|
3
|
+
import time
|
|
4
4
|
|
|
5
5
|
from datetime import datetime
|
|
6
6
|
from pathlib import Path
|
|
@@ -13,16 +13,19 @@ from memos.log import get_logger
|
|
|
13
13
|
from memos.mem_cube.general import GeneralMemCube
|
|
14
14
|
from memos.mem_reader.factory import MemReaderFactory
|
|
15
15
|
from memos.mem_scheduler.general_scheduler import GeneralScheduler
|
|
16
|
-
from memos.mem_scheduler.
|
|
16
|
+
from memos.mem_scheduler.scheduler_factory import SchedulerFactory
|
|
17
|
+
from memos.mem_scheduler.schemas.general_schemas import (
|
|
17
18
|
ADD_LABEL,
|
|
18
19
|
ANSWER_LABEL,
|
|
19
|
-
|
|
20
|
+
QUERY_LABEL,
|
|
20
21
|
)
|
|
21
|
-
from memos.mem_scheduler.
|
|
22
|
+
from memos.mem_scheduler.schemas.message_schemas import ScheduleMessageItem
|
|
22
23
|
from memos.mem_user.user_manager import UserManager, UserRole
|
|
23
24
|
from memos.memories.activation.item import ActivationMemoryItem
|
|
24
25
|
from memos.memories.parametric.item import ParametricMemoryItem
|
|
25
26
|
from memos.memories.textual.item import TextualMemoryItem, TextualMemoryMetadata
|
|
27
|
+
from memos.memos_tools.thread_safe_dict import ThreadSafeDict
|
|
28
|
+
from memos.templates.mos_prompts import QUERY_REWRITING_PROMPT
|
|
26
29
|
from memos.types import ChatHistory, MessageList, MOSSearchResult
|
|
27
30
|
|
|
28
31
|
|
|
@@ -40,10 +43,13 @@ class MOSCore:
|
|
|
40
43
|
self.config = config
|
|
41
44
|
self.user_id = config.user_id
|
|
42
45
|
self.session_id = config.session_id
|
|
43
|
-
self.mem_cubes: dict[str, GeneralMemCube] = {}
|
|
44
46
|
self.chat_llm = LLMFactory.from_config(config.chat_model)
|
|
45
47
|
self.mem_reader = MemReaderFactory.from_config(config.mem_reader)
|
|
46
48
|
self.chat_history_manager: dict[str, ChatHistory] = {}
|
|
49
|
+
# use thread safe dict for multi-user product-server scenario
|
|
50
|
+
self.mem_cubes: ThreadSafeDict[str, GeneralMemCube] = (
|
|
51
|
+
ThreadSafeDict() if user_manager is not None else {}
|
|
52
|
+
)
|
|
47
53
|
self._register_chat_history()
|
|
48
54
|
|
|
49
55
|
# Use provided user_manager or create a new one
|
|
@@ -58,10 +64,15 @@ class MOSCore:
|
|
|
58
64
|
f"User '{self.user_id}' does not exist or is inactive. Please create user first."
|
|
59
65
|
)
|
|
60
66
|
|
|
61
|
-
#
|
|
67
|
+
# Initialize mem_scheduler
|
|
62
68
|
self._mem_scheduler_lock = Lock()
|
|
63
69
|
self.enable_mem_scheduler = self.config.get("enable_mem_scheduler", False)
|
|
64
|
-
self.
|
|
70
|
+
if self.enable_mem_scheduler:
|
|
71
|
+
self._mem_scheduler = self._initialize_mem_scheduler()
|
|
72
|
+
self._mem_scheduler.mem_cubes = self.mem_cubes
|
|
73
|
+
else:
|
|
74
|
+
self._mem_scheduler: GeneralScheduler = None
|
|
75
|
+
|
|
65
76
|
logger.info(f"MOS initialized for user: {self.user_id}")
|
|
66
77
|
|
|
67
78
|
@property
|
|
@@ -93,14 +104,16 @@ class MOSCore:
|
|
|
93
104
|
else:
|
|
94
105
|
logger.debug("Memory scheduler cleared")
|
|
95
106
|
|
|
96
|
-
def _initialize_mem_scheduler(self):
|
|
107
|
+
def _initialize_mem_scheduler(self) -> GeneralScheduler:
|
|
97
108
|
"""Initialize the memory scheduler on first access."""
|
|
98
109
|
if not self.config.enable_mem_scheduler:
|
|
99
110
|
logger.debug("Memory scheduler is disabled in config")
|
|
100
111
|
self._mem_scheduler = None
|
|
112
|
+
return self._mem_scheduler
|
|
101
113
|
elif not hasattr(self.config, "mem_scheduler"):
|
|
102
114
|
logger.error("Config of Memory scheduler is not available")
|
|
103
115
|
self._mem_scheduler = None
|
|
116
|
+
return self._mem_scheduler
|
|
104
117
|
else:
|
|
105
118
|
logger.info("Initializing memory scheduler...")
|
|
106
119
|
scheduler_config = self.config.mem_scheduler
|
|
@@ -111,13 +124,16 @@ class MOSCore:
|
|
|
111
124
|
f"Memory reader of type {type(self.mem_reader).__name__} "
|
|
112
125
|
"missing required 'llm' attribute"
|
|
113
126
|
)
|
|
114
|
-
self._mem_scheduler.initialize_modules(
|
|
127
|
+
self._mem_scheduler.initialize_modules(
|
|
128
|
+
chat_llm=self.chat_llm, process_llm=self.chat_llm
|
|
129
|
+
)
|
|
115
130
|
else:
|
|
116
|
-
# Configure scheduler
|
|
131
|
+
# Configure scheduler general_modules
|
|
117
132
|
self._mem_scheduler.initialize_modules(
|
|
118
133
|
chat_llm=self.chat_llm, process_llm=self.mem_reader.llm
|
|
119
134
|
)
|
|
120
135
|
self._mem_scheduler.start()
|
|
136
|
+
return self._mem_scheduler
|
|
121
137
|
|
|
122
138
|
def mem_scheduler_on(self) -> bool:
|
|
123
139
|
if not self.config.enable_mem_scheduler or self._mem_scheduler is None:
|
|
@@ -157,6 +173,14 @@ class MOSCore:
|
|
|
157
173
|
if mem_cube.text_mem and mem_cube.text_mem.is_reorganize:
|
|
158
174
|
logger.info(f"close reorganizer for {mem_cube.text_mem.config.cube_id}")
|
|
159
175
|
mem_cube.text_mem.memory_manager.close()
|
|
176
|
+
mem_cube.text_mem.memory_manager.wait_reorganizer()
|
|
177
|
+
|
|
178
|
+
def mem_reorganizer_wait(self) -> bool:
|
|
179
|
+
for mem_cube in self.mem_cubes.values():
|
|
180
|
+
logger.info(f"try to close reorganizer for {mem_cube.text_mem.config.cube_id}")
|
|
181
|
+
if mem_cube.text_mem and mem_cube.text_mem.is_reorganize:
|
|
182
|
+
logger.info(f"close reorganizer for {mem_cube.text_mem.config.cube_id}")
|
|
183
|
+
mem_cube.text_mem.memory_manager.wait_reorganizer()
|
|
160
184
|
|
|
161
185
|
def _register_chat_history(self, user_id: str | None = None) -> None:
|
|
162
186
|
"""Initialize chat history with user ID."""
|
|
@@ -165,7 +189,7 @@ class MOSCore:
|
|
|
165
189
|
self.chat_history_manager[user_id] = ChatHistory(
|
|
166
190
|
user_id=user_id,
|
|
167
191
|
session_id=self.session_id,
|
|
168
|
-
created_at=datetime.
|
|
192
|
+
created_at=datetime.utcnow(),
|
|
169
193
|
total_messages=0,
|
|
170
194
|
chat_history=[],
|
|
171
195
|
)
|
|
@@ -257,13 +281,21 @@ class MOSCore:
|
|
|
257
281
|
user_id=target_user_id,
|
|
258
282
|
mem_cube_id=mem_cube_id,
|
|
259
283
|
mem_cube=mem_cube,
|
|
260
|
-
label=
|
|
284
|
+
label=QUERY_LABEL,
|
|
261
285
|
content=query,
|
|
262
|
-
timestamp=datetime.
|
|
286
|
+
timestamp=datetime.utcnow(),
|
|
263
287
|
)
|
|
264
288
|
self.mem_scheduler.submit_messages(messages=[message_item])
|
|
265
289
|
|
|
266
|
-
memories = mem_cube.text_mem.search(
|
|
290
|
+
memories = mem_cube.text_mem.search(
|
|
291
|
+
query,
|
|
292
|
+
top_k=self.config.top_k,
|
|
293
|
+
info={
|
|
294
|
+
"user_id": target_user_id,
|
|
295
|
+
"session_id": self.session_id,
|
|
296
|
+
"chat_history": chat_history.chat_history,
|
|
297
|
+
},
|
|
298
|
+
)
|
|
267
299
|
memories_all.extend(memories)
|
|
268
300
|
logger.info(f"🧠 [Memory] Searched memories:\n{self._str_memories(memories_all)}\n")
|
|
269
301
|
system_prompt = self._build_system_prompt(memories_all, base_prompt=base_prompt)
|
|
@@ -310,7 +342,7 @@ class MOSCore:
|
|
|
310
342
|
mem_cube=mem_cube,
|
|
311
343
|
label=ANSWER_LABEL,
|
|
312
344
|
content=response,
|
|
313
|
-
timestamp=datetime.
|
|
345
|
+
timestamp=datetime.utcnow(),
|
|
314
346
|
)
|
|
315
347
|
self.mem_scheduler.submit_messages(messages=[message_item])
|
|
316
348
|
|
|
@@ -511,6 +543,8 @@ class MOSCore:
|
|
|
511
543
|
user_id: str | None = None,
|
|
512
544
|
install_cube_ids: list[str] | None = None,
|
|
513
545
|
top_k: int | None = None,
|
|
546
|
+
mode: Literal["fast", "fine"] = "fast",
|
|
547
|
+
internet_search: bool = False,
|
|
514
548
|
) -> MOSSearchResult:
|
|
515
549
|
"""
|
|
516
550
|
Search for textual memories across all registered MemCubes.
|
|
@@ -534,6 +568,10 @@ class MOSCore:
|
|
|
534
568
|
logger.info(
|
|
535
569
|
f"User {target_user_id} has access to {len(user_cube_ids)} cubes: {user_cube_ids}"
|
|
536
570
|
)
|
|
571
|
+
if target_user_id not in self.chat_history_manager:
|
|
572
|
+
self._register_chat_history(target_user_id)
|
|
573
|
+
chat_history = self.chat_history_manager[target_user_id]
|
|
574
|
+
|
|
537
575
|
result: MOSSearchResult = {
|
|
538
576
|
"text_mem": [],
|
|
539
577
|
"act_mem": [],
|
|
@@ -541,19 +579,38 @@ class MOSCore:
|
|
|
541
579
|
}
|
|
542
580
|
if install_cube_ids is None:
|
|
543
581
|
install_cube_ids = user_cube_ids
|
|
544
|
-
|
|
582
|
+
# create exist dict in mem_cubes and avoid one search slow
|
|
583
|
+
tmp_mem_cubes = {}
|
|
584
|
+
for mem_cube_id in install_cube_ids:
|
|
585
|
+
if mem_cube_id in self.mem_cubes:
|
|
586
|
+
tmp_mem_cubes[mem_cube_id] = self.mem_cubes.get(mem_cube_id)
|
|
587
|
+
|
|
588
|
+
for mem_cube_id, mem_cube in tmp_mem_cubes.items():
|
|
545
589
|
if (
|
|
546
590
|
(mem_cube_id in install_cube_ids)
|
|
547
591
|
and (mem_cube.text_mem is not None)
|
|
548
592
|
and self.config.enable_textual_memory
|
|
549
593
|
):
|
|
594
|
+
time_start = time.time()
|
|
550
595
|
memories = mem_cube.text_mem.search(
|
|
551
|
-
query,
|
|
596
|
+
query,
|
|
597
|
+
top_k=top_k if top_k else self.config.top_k,
|
|
598
|
+
mode=mode,
|
|
599
|
+
manual_close_internet=not internet_search,
|
|
600
|
+
info={
|
|
601
|
+
"user_id": target_user_id,
|
|
602
|
+
"session_id": self.session_id,
|
|
603
|
+
"chat_history": chat_history.chat_history,
|
|
604
|
+
},
|
|
552
605
|
)
|
|
553
606
|
result["text_mem"].append({"cube_id": mem_cube_id, "memories": memories})
|
|
554
607
|
logger.info(
|
|
555
608
|
f"🧠 [Memory] Searched memories from {mem_cube_id}:\n{self._str_memories(memories)}\n"
|
|
556
609
|
)
|
|
610
|
+
search_time_end = time.time()
|
|
611
|
+
logger.info(
|
|
612
|
+
f"time search graph: search graph time user_id: {target_user_id} time is: {search_time_end - time_start}"
|
|
613
|
+
)
|
|
557
614
|
return result
|
|
558
615
|
|
|
559
616
|
def add(
|
|
@@ -576,6 +633,7 @@ class MOSCore:
|
|
|
576
633
|
user_id (str, optional): The identifier of the user to add the memories to.
|
|
577
634
|
If None, the default user is used.
|
|
578
635
|
"""
|
|
636
|
+
# user input messages
|
|
579
637
|
assert (messages is not None) or (memory_content is not None) or (doc_path is not None), (
|
|
580
638
|
"messages_or_doc_path or memory_content or doc_path must be provided."
|
|
581
639
|
)
|
|
@@ -613,25 +671,31 @@ class MOSCore:
|
|
|
613
671
|
memories = self.mem_reader.get_memory(
|
|
614
672
|
messages_list,
|
|
615
673
|
type="chat",
|
|
616
|
-
info={"user_id": target_user_id, "session_id":
|
|
674
|
+
info={"user_id": target_user_id, "session_id": self.session_id},
|
|
617
675
|
)
|
|
676
|
+
|
|
677
|
+
mem_ids = []
|
|
618
678
|
for mem in memories:
|
|
619
|
-
self.mem_cubes[mem_cube_id].text_mem.add(mem)
|
|
679
|
+
mem_id_list: list[str] = self.mem_cubes[mem_cube_id].text_mem.add(mem)
|
|
680
|
+
mem_ids.extend(mem_id_list)
|
|
681
|
+
logger.info(
|
|
682
|
+
f"Added memory user {target_user_id} to memcube {mem_cube_id}: {mem_id_list}"
|
|
683
|
+
)
|
|
620
684
|
|
|
621
685
|
# submit messages for scheduler
|
|
622
|
-
mem_cube = self.mem_cubes[mem_cube_id]
|
|
623
686
|
if self.enable_mem_scheduler and self.mem_scheduler is not None:
|
|
624
|
-
|
|
687
|
+
mem_cube = self.mem_cubes[mem_cube_id]
|
|
625
688
|
message_item = ScheduleMessageItem(
|
|
626
689
|
user_id=target_user_id,
|
|
627
690
|
mem_cube_id=mem_cube_id,
|
|
628
691
|
mem_cube=mem_cube,
|
|
629
692
|
label=ADD_LABEL,
|
|
630
|
-
content=json.dumps(
|
|
631
|
-
timestamp=datetime.
|
|
693
|
+
content=json.dumps(mem_ids),
|
|
694
|
+
timestamp=datetime.utcnow(),
|
|
632
695
|
)
|
|
633
696
|
self.mem_scheduler.submit_messages(messages=[message_item])
|
|
634
697
|
|
|
698
|
+
# user profile
|
|
635
699
|
if (
|
|
636
700
|
(memory_content is not None)
|
|
637
701
|
and self.config.enable_textual_memory
|
|
@@ -646,34 +710,66 @@ class MOSCore:
|
|
|
646
710
|
)
|
|
647
711
|
else:
|
|
648
712
|
messages_list = [
|
|
649
|
-
[
|
|
650
|
-
|
|
651
|
-
{
|
|
652
|
-
"role": "assistant",
|
|
653
|
-
"content": "",
|
|
654
|
-
}, # add by str to keep the format,assistant role is empty
|
|
655
|
-
]
|
|
656
|
-
]
|
|
713
|
+
[{"role": "user", "content": memory_content}]
|
|
714
|
+
] # for only user-str input and convert message
|
|
657
715
|
memories = self.mem_reader.get_memory(
|
|
658
716
|
messages_list,
|
|
659
717
|
type="chat",
|
|
660
|
-
info={"user_id": target_user_id, "session_id":
|
|
718
|
+
info={"user_id": target_user_id, "session_id": self.session_id},
|
|
661
719
|
)
|
|
720
|
+
|
|
721
|
+
mem_ids = []
|
|
662
722
|
for mem in memories:
|
|
663
|
-
self.mem_cubes[mem_cube_id].text_mem.add(mem)
|
|
723
|
+
mem_id_list: list[str] = self.mem_cubes[mem_cube_id].text_mem.add(mem)
|
|
724
|
+
logger.info(
|
|
725
|
+
f"Added memory user {target_user_id} to memcube {mem_cube_id}: {mem_id_list}"
|
|
726
|
+
)
|
|
727
|
+
mem_ids.extend(mem_id_list)
|
|
728
|
+
|
|
729
|
+
# submit messages for scheduler
|
|
730
|
+
if self.enable_mem_scheduler and self.mem_scheduler is not None:
|
|
731
|
+
mem_cube = self.mem_cubes[mem_cube_id]
|
|
732
|
+
message_item = ScheduleMessageItem(
|
|
733
|
+
user_id=target_user_id,
|
|
734
|
+
mem_cube_id=mem_cube_id,
|
|
735
|
+
mem_cube=mem_cube,
|
|
736
|
+
label=ADD_LABEL,
|
|
737
|
+
content=json.dumps(mem_ids),
|
|
738
|
+
timestamp=datetime.utcnow(),
|
|
739
|
+
)
|
|
740
|
+
self.mem_scheduler.submit_messages(messages=[message_item])
|
|
741
|
+
|
|
742
|
+
# user doc input
|
|
664
743
|
if (
|
|
665
744
|
(doc_path is not None)
|
|
666
745
|
and self.config.enable_textual_memory
|
|
667
746
|
and self.mem_cubes[mem_cube_id].text_mem
|
|
668
747
|
):
|
|
669
748
|
documents = self._get_all_documents(doc_path)
|
|
670
|
-
|
|
749
|
+
doc_memories = self.mem_reader.get_memory(
|
|
671
750
|
documents,
|
|
672
751
|
type="doc",
|
|
673
|
-
info={"user_id": target_user_id, "session_id":
|
|
752
|
+
info={"user_id": target_user_id, "session_id": self.session_id},
|
|
674
753
|
)
|
|
675
|
-
|
|
676
|
-
|
|
754
|
+
|
|
755
|
+
mem_ids = []
|
|
756
|
+
for mem in doc_memories:
|
|
757
|
+
mem_id_list: list[str] = self.mem_cubes[mem_cube_id].text_mem.add(mem)
|
|
758
|
+
mem_ids.extend(mem_id_list)
|
|
759
|
+
|
|
760
|
+
# submit messages for scheduler
|
|
761
|
+
if self.enable_mem_scheduler and self.mem_scheduler is not None:
|
|
762
|
+
mem_cube = self.mem_cubes[mem_cube_id]
|
|
763
|
+
message_item = ScheduleMessageItem(
|
|
764
|
+
user_id=target_user_id,
|
|
765
|
+
mem_cube_id=mem_cube_id,
|
|
766
|
+
mem_cube=mem_cube,
|
|
767
|
+
label=ADD_LABEL,
|
|
768
|
+
content=json.dumps(mem_ids),
|
|
769
|
+
timestamp=datetime.utcnow(),
|
|
770
|
+
)
|
|
771
|
+
self.mem_scheduler.submit_messages(messages=[message_item])
|
|
772
|
+
|
|
677
773
|
logger.info(f"Add memory to {mem_cube_id} successfully")
|
|
678
774
|
|
|
679
775
|
def get(
|
|
@@ -907,3 +1003,27 @@ class MOSCore:
|
|
|
907
1003
|
raise ValueError(f"Target user '{target_user_id}' does not exist or is inactive.")
|
|
908
1004
|
|
|
909
1005
|
return self.user_manager.add_user_to_cube(target_user_id, cube_id)
|
|
1006
|
+
|
|
1007
|
+
def get_query_rewrite(self, query: str, user_id: str | None = None):
|
|
1008
|
+
"""
|
|
1009
|
+
Rewrite user's query according the context.
|
|
1010
|
+
Args:
|
|
1011
|
+
query (str): The search query that needs rewriting.
|
|
1012
|
+
user_id(str, optional): The identifier of the user that the query belongs to.
|
|
1013
|
+
If None, the default user is used.
|
|
1014
|
+
|
|
1015
|
+
Returns:
|
|
1016
|
+
str: query after rewriting process.
|
|
1017
|
+
"""
|
|
1018
|
+
target_user_id = user_id if user_id is not None else self.user_id
|
|
1019
|
+
chat_history = self.chat_history_manager[target_user_id]
|
|
1020
|
+
|
|
1021
|
+
dialogue = "————{}".format("\n————".join(chat_history.chat_history))
|
|
1022
|
+
user_prompt = QUERY_REWRITING_PROMPT.format(dialogue=dialogue, query=query)
|
|
1023
|
+
messages = {"role": "user", "content": user_prompt}
|
|
1024
|
+
rewritten_result = self.chat_llm.generate(messages=messages)
|
|
1025
|
+
rewritten_result = json.loads(rewritten_result)
|
|
1026
|
+
if rewritten_result.get("former_dialogue_related", False):
|
|
1027
|
+
rewritten_query = rewritten_result.get("rewritten_question")
|
|
1028
|
+
return rewritten_query if len(rewritten_query) > 0 else query
|
|
1029
|
+
return query
|
memos/mem_os/main.py
CHANGED
|
@@ -208,7 +208,7 @@ class MOS(MOSCore):
|
|
|
208
208
|
if self.enable_mem_scheduler and self.mem_scheduler is not None:
|
|
209
209
|
from datetime import datetime
|
|
210
210
|
|
|
211
|
-
from memos.mem_scheduler.
|
|
211
|
+
from memos.mem_scheduler.schemas import (
|
|
212
212
|
ANSWER_LABEL,
|
|
213
213
|
ScheduleMessageItem,
|
|
214
214
|
)
|
|
@@ -219,7 +219,7 @@ class MOS(MOSCore):
|
|
|
219
219
|
mem_cube=mem_cube,
|
|
220
220
|
label=ANSWER_LABEL,
|
|
221
221
|
content=enhanced_response,
|
|
222
|
-
timestamp=datetime.now(),
|
|
222
|
+
timestamp=datetime.now().isoformat(),
|
|
223
223
|
)
|
|
224
224
|
self.mem_scheduler.submit_messages(messages=[message_item])
|
|
225
225
|
|