MemoryOS 0.2.2__py3-none-any.whl → 1.0.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of MemoryOS might be problematic. Click here for more details.
- {memoryos-0.2.2.dist-info → memoryos-1.0.1.dist-info}/METADATA +7 -1
- {memoryos-0.2.2.dist-info → memoryos-1.0.1.dist-info}/RECORD +81 -66
- memos/__init__.py +1 -1
- memos/api/config.py +31 -8
- memos/api/context/context.py +1 -1
- memos/api/context/context_thread.py +96 -0
- memos/api/middleware/request_context.py +94 -0
- memos/api/product_api.py +5 -1
- memos/api/product_models.py +16 -0
- memos/api/routers/product_router.py +39 -3
- memos/api/start_api.py +3 -0
- memos/configs/internet_retriever.py +13 -0
- memos/configs/mem_scheduler.py +38 -16
- memos/configs/memory.py +13 -0
- memos/configs/reranker.py +18 -0
- memos/graph_dbs/base.py +33 -4
- memos/graph_dbs/nebular.py +631 -236
- memos/graph_dbs/neo4j.py +18 -7
- memos/graph_dbs/neo4j_community.py +6 -3
- memos/llms/vllm.py +2 -0
- memos/log.py +125 -8
- memos/mem_os/core.py +49 -11
- memos/mem_os/main.py +1 -1
- memos/mem_os/product.py +392 -215
- memos/mem_os/utils/default_config.py +1 -1
- memos/mem_os/utils/format_utils.py +11 -47
- memos/mem_os/utils/reference_utils.py +153 -0
- memos/mem_reader/simple_struct.py +112 -43
- memos/mem_scheduler/base_scheduler.py +58 -55
- memos/mem_scheduler/{modules → general_modules}/base.py +1 -2
- memos/mem_scheduler/{modules → general_modules}/dispatcher.py +54 -15
- memos/mem_scheduler/{modules → general_modules}/rabbitmq_service.py +4 -4
- memos/mem_scheduler/{modules → general_modules}/redis_service.py +1 -1
- memos/mem_scheduler/{modules → general_modules}/retriever.py +19 -5
- memos/mem_scheduler/{modules → general_modules}/scheduler_logger.py +10 -4
- memos/mem_scheduler/general_scheduler.py +110 -67
- 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} +57 -19
- memos/mem_scheduler/mos_for_test_scheduler.py +7 -1
- memos/mem_scheduler/schemas/general_schemas.py +3 -2
- memos/mem_scheduler/schemas/message_schemas.py +2 -1
- memos/mem_scheduler/schemas/monitor_schemas.py +10 -2
- memos/mem_scheduler/utils/misc_utils.py +43 -2
- memos/mem_user/mysql_user_manager.py +4 -2
- memos/memories/activation/item.py +1 -1
- memos/memories/activation/kv.py +20 -8
- memos/memories/textual/base.py +1 -1
- memos/memories/textual/general.py +1 -1
- memos/memories/textual/item.py +1 -1
- memos/memories/textual/tree.py +31 -1
- memos/memories/textual/tree_text_memory/organize/{conflict.py → handler.py} +30 -48
- memos/memories/textual/tree_text_memory/organize/manager.py +8 -96
- memos/memories/textual/tree_text_memory/organize/relation_reason_detector.py +2 -0
- memos/memories/textual/tree_text_memory/organize/reorganizer.py +102 -140
- memos/memories/textual/tree_text_memory/retrieve/bochasearch.py +231 -0
- memos/memories/textual/tree_text_memory/retrieve/internet_retriever_factory.py +9 -0
- memos/memories/textual/tree_text_memory/retrieve/recall.py +67 -10
- memos/memories/textual/tree_text_memory/retrieve/reranker.py +1 -1
- memos/memories/textual/tree_text_memory/retrieve/searcher.py +246 -134
- memos/memories/textual/tree_text_memory/retrieve/task_goal_parser.py +7 -2
- memos/memories/textual/tree_text_memory/retrieve/utils.py +7 -5
- memos/memos_tools/lockfree_dict.py +120 -0
- memos/memos_tools/notification_utils.py +46 -0
- memos/memos_tools/thread_safe_dict.py +288 -0
- memos/reranker/__init__.py +4 -0
- memos/reranker/base.py +24 -0
- memos/reranker/cosine_local.py +95 -0
- memos/reranker/factory.py +43 -0
- memos/reranker/http_bge.py +99 -0
- memos/reranker/noop.py +16 -0
- memos/templates/mem_reader_prompts.py +290 -39
- memos/templates/mem_scheduler_prompts.py +23 -10
- memos/templates/mos_prompts.py +133 -31
- memos/templates/tree_reorganize_prompts.py +24 -17
- memos/utils.py +19 -0
- memos/memories/textual/tree_text_memory/organize/redundancy.py +0 -193
- {memoryos-0.2.2.dist-info → memoryos-1.0.1.dist-info}/LICENSE +0 -0
- {memoryos-0.2.2.dist-info → memoryos-1.0.1.dist-info}/WHEEL +0 -0
- {memoryos-0.2.2.dist-info → memoryos-1.0.1.dist-info}/entry_points.txt +0 -0
- /memos/mem_scheduler/{modules → general_modules}/__init__.py +0 -0
- /memos/mem_scheduler/{modules → general_modules}/misc.py +0 -0
memos/graph_dbs/neo4j.py
CHANGED
|
@@ -323,7 +323,7 @@ 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, **kwargs) -> dict[str, Any] | None:
|
|
327
327
|
"""
|
|
328
328
|
Retrieve the metadata and memory of a node.
|
|
329
329
|
Args:
|
|
@@ -331,6 +331,7 @@ class Neo4jGraphDB(BaseGraphDB):
|
|
|
331
331
|
Returns:
|
|
332
332
|
Dictionary of node fields, or None if not found.
|
|
333
333
|
"""
|
|
334
|
+
|
|
334
335
|
where_user = ""
|
|
335
336
|
params = {"id": id}
|
|
336
337
|
if not self.config.use_multi_db and self.config.user_name:
|
|
@@ -343,7 +344,7 @@ class Neo4jGraphDB(BaseGraphDB):
|
|
|
343
344
|
record = session.run(query, params).single()
|
|
344
345
|
return self._parse_node(dict(record["n"])) if record else None
|
|
345
346
|
|
|
346
|
-
def get_nodes(self, ids: list[str]) -> list[dict[str, Any]]:
|
|
347
|
+
def get_nodes(self, ids: list[str], **kwargs) -> list[dict[str, Any]]:
|
|
347
348
|
"""
|
|
348
349
|
Retrieve the metadata and memory of a list of nodes.
|
|
349
350
|
Args:
|
|
@@ -355,6 +356,7 @@ class Neo4jGraphDB(BaseGraphDB):
|
|
|
355
356
|
- Assumes all provided IDs are valid and exist.
|
|
356
357
|
- Returns empty list if input is empty.
|
|
357
358
|
"""
|
|
359
|
+
|
|
358
360
|
if not ids:
|
|
359
361
|
return []
|
|
360
362
|
|
|
@@ -363,7 +365,10 @@ class Neo4jGraphDB(BaseGraphDB):
|
|
|
363
365
|
|
|
364
366
|
if not self.config.use_multi_db and self.config.user_name:
|
|
365
367
|
where_user = " AND n.user_name = $user_name"
|
|
366
|
-
|
|
368
|
+
if kwargs.get("cube_name"):
|
|
369
|
+
params["user_name"] = kwargs["cube_name"]
|
|
370
|
+
else:
|
|
371
|
+
params["user_name"] = self.config.user_name
|
|
367
372
|
|
|
368
373
|
query = f"MATCH (n:Memory) WHERE n.id IN $ids{where_user} RETURN n"
|
|
369
374
|
|
|
@@ -601,6 +606,7 @@ class Neo4jGraphDB(BaseGraphDB):
|
|
|
601
606
|
scope: str | None = None,
|
|
602
607
|
status: str | None = None,
|
|
603
608
|
threshold: float | None = None,
|
|
609
|
+
**kwargs,
|
|
604
610
|
) -> list[dict]:
|
|
605
611
|
"""
|
|
606
612
|
Retrieve node IDs based on vector similarity.
|
|
@@ -650,7 +656,10 @@ class Neo4jGraphDB(BaseGraphDB):
|
|
|
650
656
|
if status:
|
|
651
657
|
parameters["status"] = status
|
|
652
658
|
if not self.config.use_multi_db and self.config.user_name:
|
|
653
|
-
|
|
659
|
+
if kwargs.get("cube_name"):
|
|
660
|
+
parameters["user_name"] = kwargs["cube_name"]
|
|
661
|
+
else:
|
|
662
|
+
parameters["user_name"] = self.config.user_name
|
|
654
663
|
|
|
655
664
|
with self.driver.session(database=self.db_name) as session:
|
|
656
665
|
result = session.run(query, parameters)
|
|
@@ -829,7 +838,7 @@ class Neo4jGraphDB(BaseGraphDB):
|
|
|
829
838
|
logger.error(f"[ERROR] Failed to clear database '{self.db_name}': {e}")
|
|
830
839
|
raise
|
|
831
840
|
|
|
832
|
-
def export_graph(self) -> dict[str, Any]:
|
|
841
|
+
def export_graph(self, **kwargs) -> dict[str, Any]:
|
|
833
842
|
"""
|
|
834
843
|
Export all graph nodes and edges in a structured form.
|
|
835
844
|
|
|
@@ -910,12 +919,13 @@ class Neo4jGraphDB(BaseGraphDB):
|
|
|
910
919
|
target_id=edge["target"],
|
|
911
920
|
)
|
|
912
921
|
|
|
913
|
-
def get_all_memory_items(self, scope: str) -> list[dict]:
|
|
922
|
+
def get_all_memory_items(self, scope: str, **kwargs) -> list[dict]:
|
|
914
923
|
"""
|
|
915
924
|
Retrieve all memory items of a specific memory_type.
|
|
916
925
|
|
|
917
926
|
Args:
|
|
918
927
|
scope (str): Must be one of 'WorkingMemory', 'LongTermMemory', or 'UserMemory'.
|
|
928
|
+
Returns:
|
|
919
929
|
|
|
920
930
|
Returns:
|
|
921
931
|
list[dict]: Full list of memory items under this scope.
|
|
@@ -940,12 +950,13 @@ class Neo4jGraphDB(BaseGraphDB):
|
|
|
940
950
|
results = session.run(query, params)
|
|
941
951
|
return [self._parse_node(dict(record["n"])) for record in results]
|
|
942
952
|
|
|
943
|
-
def get_structure_optimization_candidates(self, scope: str) -> list[dict]:
|
|
953
|
+
def get_structure_optimization_candidates(self, scope: str, **kwargs) -> list[dict]:
|
|
944
954
|
"""
|
|
945
955
|
Find nodes that are likely candidates for structure optimization:
|
|
946
956
|
- Isolated nodes, nodes with empty background, or nodes with exactly one child.
|
|
947
957
|
- Plus: the child of any parent node that has exactly one child.
|
|
948
958
|
"""
|
|
959
|
+
|
|
949
960
|
where_clause = """
|
|
950
961
|
WHERE n.memory_type = $scope
|
|
951
962
|
AND n.status = 'activated'
|
|
@@ -129,6 +129,7 @@ class Neo4jCommunityGraphDB(Neo4jGraphDB):
|
|
|
129
129
|
scope: str | None = None,
|
|
130
130
|
status: str | None = None,
|
|
131
131
|
threshold: float | None = None,
|
|
132
|
+
**kwargs,
|
|
132
133
|
) -> list[dict]:
|
|
133
134
|
"""
|
|
134
135
|
Retrieve node IDs based on vector similarity using external vector DB.
|
|
@@ -157,7 +158,10 @@ class Neo4jCommunityGraphDB(Neo4jGraphDB):
|
|
|
157
158
|
if status:
|
|
158
159
|
vec_filter["status"] = status
|
|
159
160
|
vec_filter["vector_sync"] = "success"
|
|
160
|
-
|
|
161
|
+
if kwargs.get("cube_name"):
|
|
162
|
+
vec_filter["user_name"] = kwargs["cube_name"]
|
|
163
|
+
else:
|
|
164
|
+
vec_filter["user_name"] = self.config.user_name
|
|
161
165
|
|
|
162
166
|
# Perform vector search
|
|
163
167
|
results = self.vec_db.search(query_vector=vector, top_k=top_k, filter=vec_filter)
|
|
@@ -169,13 +173,12 @@ class Neo4jCommunityGraphDB(Neo4jGraphDB):
|
|
|
169
173
|
# Return consistent format
|
|
170
174
|
return [{"id": r.id, "score": r.score} for r in results]
|
|
171
175
|
|
|
172
|
-
def get_all_memory_items(self, scope: str) -> list[dict]:
|
|
176
|
+
def get_all_memory_items(self, scope: str, **kwargs) -> list[dict]:
|
|
173
177
|
"""
|
|
174
178
|
Retrieve all memory items of a specific memory_type.
|
|
175
179
|
|
|
176
180
|
Args:
|
|
177
181
|
scope (str): Must be one of 'WorkingMemory', 'LongTermMemory', or 'UserMemory'.
|
|
178
|
-
|
|
179
182
|
Returns:
|
|
180
183
|
list[dict]: Full list of memory items under this scope.
|
|
181
184
|
"""
|
memos/llms/vllm.py
CHANGED
|
@@ -105,6 +105,7 @@ class VLLMLLM(BaseLLM):
|
|
|
105
105
|
"temperature": float(getattr(self.config, "temperature", 0.8)),
|
|
106
106
|
"max_tokens": int(getattr(self.config, "max_tokens", 1024)),
|
|
107
107
|
"top_p": float(getattr(self.config, "top_p", 0.9)),
|
|
108
|
+
"extra_body": {"chat_template_kwargs": {"enable_thinking": False}},
|
|
108
109
|
}
|
|
109
110
|
|
|
110
111
|
response = self.client.chat.completions.create(**completion_kwargs)
|
|
@@ -142,6 +143,7 @@ class VLLMLLM(BaseLLM):
|
|
|
142
143
|
"max_tokens": int(getattr(self.config, "max_tokens", 1024)),
|
|
143
144
|
"top_p": float(getattr(self.config, "top_p", 0.9)),
|
|
144
145
|
"stream": True, # Enable streaming
|
|
146
|
+
"extra_body": {"chat_template_kwargs": {"enable_thinking": False}},
|
|
145
147
|
}
|
|
146
148
|
|
|
147
149
|
stream = self.client.chat.completions.create(**completion_kwargs)
|
memos/log.py
CHANGED
|
@@ -1,11 +1,23 @@
|
|
|
1
|
+
import atexit
|
|
1
2
|
import logging
|
|
3
|
+
import os
|
|
4
|
+
import threading
|
|
2
5
|
|
|
3
6
|
from logging.config import dictConfig
|
|
4
7
|
from pathlib import Path
|
|
5
8
|
from sys import stdout
|
|
6
9
|
|
|
10
|
+
import requests
|
|
11
|
+
|
|
12
|
+
from dotenv import load_dotenv
|
|
13
|
+
|
|
7
14
|
from memos import settings
|
|
15
|
+
from memos.api.context.context import get_current_trace_id
|
|
16
|
+
from memos.api.context.context_thread import ContextThreadPoolExecutor
|
|
17
|
+
|
|
8
18
|
|
|
19
|
+
# Load environment variables
|
|
20
|
+
load_dotenv()
|
|
9
21
|
|
|
10
22
|
selected_log_level = logging.DEBUG if settings.DEBUG else logging.WARNING
|
|
11
23
|
|
|
@@ -21,27 +33,126 @@ def _setup_logfile() -> Path:
|
|
|
21
33
|
return logfile
|
|
22
34
|
|
|
23
35
|
|
|
36
|
+
class TraceIDFilter(logging.Filter):
|
|
37
|
+
"""add trace_id to the log record"""
|
|
38
|
+
|
|
39
|
+
def filter(self, record):
|
|
40
|
+
try:
|
|
41
|
+
trace_id = get_current_trace_id()
|
|
42
|
+
record.trace_id = trace_id if trace_id else "no-trace-id"
|
|
43
|
+
except Exception:
|
|
44
|
+
record.trace_id = "no-trace-id"
|
|
45
|
+
return True
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class CustomLoggerRequestHandler(logging.Handler):
|
|
49
|
+
_instance = None
|
|
50
|
+
_lock = threading.Lock()
|
|
51
|
+
|
|
52
|
+
def __new__(cls):
|
|
53
|
+
if cls._instance is None:
|
|
54
|
+
with cls._lock:
|
|
55
|
+
if cls._instance is None:
|
|
56
|
+
cls._instance = super().__new__(cls)
|
|
57
|
+
cls._instance._initialized = False
|
|
58
|
+
cls._instance._executor = None
|
|
59
|
+
cls._instance._session = None
|
|
60
|
+
cls._instance._is_shutting_down = None
|
|
61
|
+
return cls._instance
|
|
62
|
+
|
|
63
|
+
def __init__(self):
|
|
64
|
+
"""Initialize handler with minimal setup"""
|
|
65
|
+
if not self._initialized:
|
|
66
|
+
super().__init__()
|
|
67
|
+
workers = int(os.getenv("CUSTOM_LOGGER_WORKERS", "2"))
|
|
68
|
+
self._executor = ContextThreadPoolExecutor(
|
|
69
|
+
max_workers=workers, thread_name_prefix="log_sender"
|
|
70
|
+
)
|
|
71
|
+
self._is_shutting_down = threading.Event()
|
|
72
|
+
self._session = requests.Session()
|
|
73
|
+
self._initialized = True
|
|
74
|
+
atexit.register(self._cleanup)
|
|
75
|
+
|
|
76
|
+
def emit(self, record):
|
|
77
|
+
"""Process log records of INFO or ERROR level (non-blocking)"""
|
|
78
|
+
if os.getenv("CUSTOM_LOGGER_URL") is None or self._is_shutting_down.is_set():
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
trace_id = get_current_trace_id() or "no-trace-id"
|
|
83
|
+
self._executor.submit(self._send_log_sync, record.getMessage(), trace_id)
|
|
84
|
+
except Exception as e:
|
|
85
|
+
if not self._is_shutting_down.is_set():
|
|
86
|
+
print(f"Error sending log: {e}")
|
|
87
|
+
|
|
88
|
+
def _send_log_sync(self, message, trace_id):
|
|
89
|
+
"""Send log message synchronously in a separate thread"""
|
|
90
|
+
try:
|
|
91
|
+
logger_url = os.getenv("CUSTOM_LOGGER_URL")
|
|
92
|
+
token = os.getenv("CUSTOM_LOGGER_TOKEN")
|
|
93
|
+
|
|
94
|
+
headers = {"Content-Type": "application/json"}
|
|
95
|
+
post_content = {"message": message, "trace_id": trace_id}
|
|
96
|
+
|
|
97
|
+
# Add auth token if exists
|
|
98
|
+
if token:
|
|
99
|
+
headers["Authorization"] = f"Bearer {token}"
|
|
100
|
+
|
|
101
|
+
# Add traceId to headers for consistency
|
|
102
|
+
headers["traceId"] = trace_id
|
|
103
|
+
|
|
104
|
+
# Add custom attributes from env
|
|
105
|
+
for key, value in os.environ.items():
|
|
106
|
+
if key.startswith("CUSTOM_LOGGER_ATTRIBUTE_"):
|
|
107
|
+
attribute_key = key[len("CUSTOM_LOGGER_ATTRIBUTE_") :].lower()
|
|
108
|
+
post_content[attribute_key] = value
|
|
109
|
+
|
|
110
|
+
self._session.post(logger_url, headers=headers, json=post_content, timeout=5)
|
|
111
|
+
except Exception:
|
|
112
|
+
# Silently ignore errors to avoid affecting main application
|
|
113
|
+
pass
|
|
114
|
+
|
|
115
|
+
def _cleanup(self):
|
|
116
|
+
"""Clean up resources during program exit"""
|
|
117
|
+
if not self._initialized:
|
|
118
|
+
return
|
|
119
|
+
|
|
120
|
+
self._is_shutting_down.set()
|
|
121
|
+
try:
|
|
122
|
+
self._executor.shutdown(wait=False)
|
|
123
|
+
self._session.close()
|
|
124
|
+
except Exception as e:
|
|
125
|
+
print(f"Error during cleanup: {e}")
|
|
126
|
+
|
|
127
|
+
def close(self):
|
|
128
|
+
"""Override close to prevent premature shutdown"""
|
|
129
|
+
|
|
130
|
+
|
|
24
131
|
LOGGING_CONFIG = {
|
|
25
132
|
"version": 1,
|
|
26
133
|
"disable_existing_loggers": False,
|
|
27
134
|
"formatters": {
|
|
28
135
|
"standard": {
|
|
29
|
-
"format": "%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(funcName)s - %(message)s"
|
|
136
|
+
"format": "%(asctime)s [%(trace_id)s] - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(funcName)s - %(message)s"
|
|
30
137
|
},
|
|
31
138
|
"no_datetime": {
|
|
32
|
-
"format": "%(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(funcName)s - %(message)s"
|
|
139
|
+
"format": "[%(trace_id)s] - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(funcName)s - %(message)s"
|
|
140
|
+
},
|
|
141
|
+
"simplified": {
|
|
142
|
+
"format": "%(asctime)s | %(trace_id)s | %(levelname)s | %(filename)s | %(message)s"
|
|
33
143
|
},
|
|
34
144
|
},
|
|
35
145
|
"filters": {
|
|
36
|
-
"package_tree_filter": {"()": "logging.Filter", "name": settings.LOG_FILTER_TREE_PREFIX}
|
|
146
|
+
"package_tree_filter": {"()": "logging.Filter", "name": settings.LOG_FILTER_TREE_PREFIX},
|
|
147
|
+
"trace_id_filter": {"()": "memos.log.TraceIDFilter"},
|
|
37
148
|
},
|
|
38
149
|
"handlers": {
|
|
39
150
|
"console": {
|
|
40
151
|
"level": selected_log_level,
|
|
41
152
|
"class": "logging.StreamHandler",
|
|
42
153
|
"stream": stdout,
|
|
43
|
-
"formatter": "
|
|
44
|
-
"filters": ["package_tree_filter"],
|
|
154
|
+
"formatter": "simplified",
|
|
155
|
+
"filters": ["package_tree_filter", "trace_id_filter"],
|
|
45
156
|
},
|
|
46
157
|
"file": {
|
|
47
158
|
"level": "DEBUG",
|
|
@@ -49,12 +160,18 @@ LOGGING_CONFIG = {
|
|
|
49
160
|
"filename": _setup_logfile(),
|
|
50
161
|
"maxBytes": 1024**2 * 10,
|
|
51
162
|
"backupCount": 10,
|
|
52
|
-
"formatter": "
|
|
163
|
+
"formatter": "simplified",
|
|
164
|
+
"filters": ["trace_id_filter"],
|
|
165
|
+
},
|
|
166
|
+
"custom_logger": {
|
|
167
|
+
"level": selected_log_level,
|
|
168
|
+
"class": "memos.log.CustomLoggerRequestHandler",
|
|
169
|
+
"formatter": "simplified",
|
|
53
170
|
},
|
|
54
171
|
},
|
|
55
172
|
"root": { # Root logger handles all logs
|
|
56
|
-
"level":
|
|
57
|
-
"handlers": ["console", "file"],
|
|
173
|
+
"level": selected_log_level,
|
|
174
|
+
"handlers": ["console", "file", "custom_logger"],
|
|
58
175
|
},
|
|
59
176
|
"loggers": {
|
|
60
177
|
"memos": {
|
memos/mem_os/core.py
CHANGED
|
@@ -24,6 +24,7 @@ from memos.mem_user.user_manager import UserManager, UserRole
|
|
|
24
24
|
from memos.memories.activation.item import ActivationMemoryItem
|
|
25
25
|
from memos.memories.parametric.item import ParametricMemoryItem
|
|
26
26
|
from memos.memories.textual.item import TextualMemoryItem, TextualMemoryMetadata
|
|
27
|
+
from memos.memos_tools.thread_safe_dict import ThreadSafeDict
|
|
27
28
|
from memos.templates.mos_prompts import QUERY_REWRITING_PROMPT
|
|
28
29
|
from memos.types import ChatHistory, MessageList, MOSSearchResult
|
|
29
30
|
|
|
@@ -42,10 +43,13 @@ class MOSCore:
|
|
|
42
43
|
self.config = config
|
|
43
44
|
self.user_id = config.user_id
|
|
44
45
|
self.session_id = config.session_id
|
|
45
|
-
self.mem_cubes: dict[str, GeneralMemCube] = {}
|
|
46
46
|
self.chat_llm = LLMFactory.from_config(config.chat_model)
|
|
47
47
|
self.mem_reader = MemReaderFactory.from_config(config.mem_reader)
|
|
48
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
|
+
)
|
|
49
53
|
self._register_chat_history()
|
|
50
54
|
|
|
51
55
|
# Use provided user_manager or create a new one
|
|
@@ -124,7 +128,7 @@ class MOSCore:
|
|
|
124
128
|
chat_llm=self.chat_llm, process_llm=self.chat_llm
|
|
125
129
|
)
|
|
126
130
|
else:
|
|
127
|
-
# Configure scheduler
|
|
131
|
+
# Configure scheduler general_modules
|
|
128
132
|
self._mem_scheduler.initialize_modules(
|
|
129
133
|
chat_llm=self.chat_llm, process_llm=self.mem_reader.llm
|
|
130
134
|
)
|
|
@@ -185,7 +189,7 @@ class MOSCore:
|
|
|
185
189
|
self.chat_history_manager[user_id] = ChatHistory(
|
|
186
190
|
user_id=user_id,
|
|
187
191
|
session_id=self.session_id,
|
|
188
|
-
created_at=datetime.
|
|
192
|
+
created_at=datetime.utcnow(),
|
|
189
193
|
total_messages=0,
|
|
190
194
|
chat_history=[],
|
|
191
195
|
)
|
|
@@ -279,7 +283,7 @@ class MOSCore:
|
|
|
279
283
|
mem_cube=mem_cube,
|
|
280
284
|
label=QUERY_LABEL,
|
|
281
285
|
content=query,
|
|
282
|
-
timestamp=datetime.
|
|
286
|
+
timestamp=datetime.utcnow(),
|
|
283
287
|
)
|
|
284
288
|
self.mem_scheduler.submit_messages(messages=[message_item])
|
|
285
289
|
|
|
@@ -338,7 +342,7 @@ class MOSCore:
|
|
|
338
342
|
mem_cube=mem_cube,
|
|
339
343
|
label=ANSWER_LABEL,
|
|
340
344
|
content=response,
|
|
341
|
-
timestamp=datetime.
|
|
345
|
+
timestamp=datetime.utcnow(),
|
|
342
346
|
)
|
|
343
347
|
self.mem_scheduler.submit_messages(messages=[message_item])
|
|
344
348
|
|
|
@@ -348,6 +352,7 @@ class MOSCore:
|
|
|
348
352
|
self,
|
|
349
353
|
memories: list[TextualMemoryItem] | list[str] | None = None,
|
|
350
354
|
base_prompt: str | None = None,
|
|
355
|
+
**kwargs,
|
|
351
356
|
) -> str:
|
|
352
357
|
"""Build system prompt with optional memories context."""
|
|
353
358
|
if base_prompt is None:
|
|
@@ -541,6 +546,8 @@ class MOSCore:
|
|
|
541
546
|
top_k: int | None = None,
|
|
542
547
|
mode: Literal["fast", "fine"] = "fast",
|
|
543
548
|
internet_search: bool = False,
|
|
549
|
+
moscube: bool = False,
|
|
550
|
+
**kwargs,
|
|
544
551
|
) -> MOSSearchResult:
|
|
545
552
|
"""
|
|
546
553
|
Search for textual memories across all registered MemCubes.
|
|
@@ -575,7 +582,13 @@ class MOSCore:
|
|
|
575
582
|
}
|
|
576
583
|
if install_cube_ids is None:
|
|
577
584
|
install_cube_ids = user_cube_ids
|
|
578
|
-
|
|
585
|
+
# create exist dict in mem_cubes and avoid one search slow
|
|
586
|
+
tmp_mem_cubes = {}
|
|
587
|
+
for mem_cube_id in install_cube_ids:
|
|
588
|
+
if mem_cube_id in self.mem_cubes:
|
|
589
|
+
tmp_mem_cubes[mem_cube_id] = self.mem_cubes.get(mem_cube_id)
|
|
590
|
+
|
|
591
|
+
for mem_cube_id, mem_cube in tmp_mem_cubes.items():
|
|
579
592
|
if (
|
|
580
593
|
(mem_cube_id in install_cube_ids)
|
|
581
594
|
and (mem_cube.text_mem is not None)
|
|
@@ -592,6 +605,7 @@ class MOSCore:
|
|
|
592
605
|
"session_id": self.session_id,
|
|
593
606
|
"chat_history": chat_history.chat_history,
|
|
594
607
|
},
|
|
608
|
+
moscube=moscube,
|
|
595
609
|
)
|
|
596
610
|
result["text_mem"].append({"cube_id": mem_cube_id, "memories": memories})
|
|
597
611
|
logger.info(
|
|
@@ -649,7 +663,7 @@ class MOSCore:
|
|
|
649
663
|
if self.mem_cubes[mem_cube_id].config.text_mem.backend != "tree_text":
|
|
650
664
|
add_memory = []
|
|
651
665
|
metadata = TextualMemoryMetadata(
|
|
652
|
-
user_id=
|
|
666
|
+
user_id=target_user_id, session_id=self.session_id, source="conversation"
|
|
653
667
|
)
|
|
654
668
|
for message in messages:
|
|
655
669
|
add_memory.append(
|
|
@@ -681,7 +695,7 @@ class MOSCore:
|
|
|
681
695
|
mem_cube=mem_cube,
|
|
682
696
|
label=ADD_LABEL,
|
|
683
697
|
content=json.dumps(mem_ids),
|
|
684
|
-
timestamp=datetime.
|
|
698
|
+
timestamp=datetime.utcnow(),
|
|
685
699
|
)
|
|
686
700
|
self.mem_scheduler.submit_messages(messages=[message_item])
|
|
687
701
|
|
|
@@ -725,7 +739,7 @@ class MOSCore:
|
|
|
725
739
|
mem_cube=mem_cube,
|
|
726
740
|
label=ADD_LABEL,
|
|
727
741
|
content=json.dumps(mem_ids),
|
|
728
|
-
timestamp=datetime.
|
|
742
|
+
timestamp=datetime.utcnow(),
|
|
729
743
|
)
|
|
730
744
|
self.mem_scheduler.submit_messages(messages=[message_item])
|
|
731
745
|
|
|
@@ -756,7 +770,7 @@ class MOSCore:
|
|
|
756
770
|
mem_cube=mem_cube,
|
|
757
771
|
label=ADD_LABEL,
|
|
758
772
|
content=json.dumps(mem_ids),
|
|
759
|
-
timestamp=datetime.
|
|
773
|
+
timestamp=datetime.utcnow(),
|
|
760
774
|
)
|
|
761
775
|
self.mem_scheduler.submit_messages(messages=[message_item])
|
|
762
776
|
|
|
@@ -946,6 +960,30 @@ class MOSCore:
|
|
|
946
960
|
self.mem_cubes[mem_cube_id].dump(dump_dir)
|
|
947
961
|
logger.info(f"MemCube {mem_cube_id} dumped to {dump_dir}")
|
|
948
962
|
|
|
963
|
+
def load(
|
|
964
|
+
self,
|
|
965
|
+
load_dir: str,
|
|
966
|
+
user_id: str | None = None,
|
|
967
|
+
mem_cube_id: str | None = None,
|
|
968
|
+
memory_types: list[Literal["text_mem", "act_mem", "para_mem"]] | None = None,
|
|
969
|
+
) -> None:
|
|
970
|
+
"""Dump the MemCube to a dictionary.
|
|
971
|
+
Args:
|
|
972
|
+
load_dir (str): The directory to load the MemCube from.
|
|
973
|
+
user_id (str, optional): The identifier of the user to load the MemCube from.
|
|
974
|
+
If None, the default user is used.
|
|
975
|
+
mem_cube_id (str, optional): The identifier of the MemCube to load.
|
|
976
|
+
If None, the default MemCube for the user is used.
|
|
977
|
+
"""
|
|
978
|
+
target_user_id = user_id if user_id is not None else self.user_id
|
|
979
|
+
accessible_cubes = self.user_manager.get_user_cubes(target_user_id)
|
|
980
|
+
if not mem_cube_id:
|
|
981
|
+
mem_cube_id = accessible_cubes[0].cube_id
|
|
982
|
+
if mem_cube_id not in self.mem_cubes:
|
|
983
|
+
raise ValueError(f"MemCube with ID {mem_cube_id} does not exist. please regiester")
|
|
984
|
+
self.mem_cubes[mem_cube_id].load(load_dir, memory_types=memory_types)
|
|
985
|
+
logger.info(f"MemCube {mem_cube_id} loaded from {load_dir}")
|
|
986
|
+
|
|
949
987
|
def get_user_info(self) -> dict[str, Any]:
|
|
950
988
|
"""Get current user information including accessible cubes.
|
|
951
989
|
|
|
@@ -961,7 +999,7 @@ class MOSCore:
|
|
|
961
999
|
return {
|
|
962
1000
|
"user_id": user.user_id,
|
|
963
1001
|
"user_name": user.user_name,
|
|
964
|
-
"role": user.role.value,
|
|
1002
|
+
"role": user.role.value if hasattr(user.role, "value") else user.role,
|
|
965
1003
|
"created_at": user.created_at.isoformat(),
|
|
966
1004
|
"accessible_cubes": [
|
|
967
1005
|
{
|
memos/mem_os/main.py
CHANGED