MemoryOS 0.2.0__py3-none-any.whl → 0.2.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of MemoryOS might be problematic. Click here for more details.
- {memoryos-0.2.0.dist-info → memoryos-0.2.2.dist-info}/METADATA +67 -26
- memoryos-0.2.2.dist-info/RECORD +169 -0
- memoryos-0.2.2.dist-info/entry_points.txt +3 -0
- memos/__init__.py +1 -1
- memos/api/config.py +562 -0
- memos/api/context/context.py +147 -0
- memos/api/context/dependencies.py +90 -0
- memos/api/exceptions.py +28 -0
- memos/api/mcp_serve.py +502 -0
- memos/api/product_api.py +35 -0
- memos/api/product_models.py +163 -0
- memos/api/routers/__init__.py +1 -0
- memos/api/routers/product_router.py +386 -0
- memos/chunkers/sentence_chunker.py +8 -2
- memos/cli.py +113 -0
- memos/configs/embedder.py +27 -0
- memos/configs/graph_db.py +132 -3
- memos/configs/internet_retriever.py +6 -0
- memos/configs/llm.py +47 -0
- memos/configs/mem_cube.py +1 -1
- memos/configs/mem_os.py +5 -0
- memos/configs/mem_reader.py +9 -0
- memos/configs/mem_scheduler.py +107 -7
- memos/configs/mem_user.py +58 -0
- memos/configs/memory.py +5 -4
- memos/dependency.py +52 -0
- memos/embedders/ark.py +92 -0
- memos/embedders/factory.py +4 -0
- memos/embedders/sentence_transformer.py +8 -2
- memos/embedders/universal_api.py +32 -0
- memos/graph_dbs/base.py +11 -3
- memos/graph_dbs/factory.py +4 -0
- memos/graph_dbs/nebular.py +1364 -0
- memos/graph_dbs/neo4j.py +333 -124
- memos/graph_dbs/neo4j_community.py +300 -0
- memos/llms/base.py +9 -0
- memos/llms/deepseek.py +54 -0
- memos/llms/factory.py +10 -1
- memos/llms/hf.py +170 -13
- memos/llms/hf_singleton.py +114 -0
- memos/llms/ollama.py +4 -0
- memos/llms/openai.py +67 -1
- memos/llms/qwen.py +63 -0
- memos/llms/vllm.py +153 -0
- memos/log.py +1 -1
- memos/mem_cube/general.py +77 -16
- memos/mem_cube/utils.py +109 -0
- memos/mem_os/core.py +251 -51
- memos/mem_os/main.py +94 -12
- memos/mem_os/product.py +1220 -43
- memos/mem_os/utils/default_config.py +352 -0
- memos/mem_os/utils/format_utils.py +1401 -0
- memos/mem_reader/simple_struct.py +18 -10
- memos/mem_scheduler/base_scheduler.py +441 -40
- memos/mem_scheduler/general_scheduler.py +249 -248
- memos/mem_scheduler/modules/base.py +14 -5
- memos/mem_scheduler/modules/dispatcher.py +67 -4
- memos/mem_scheduler/modules/misc.py +104 -0
- memos/mem_scheduler/modules/monitor.py +240 -50
- memos/mem_scheduler/modules/rabbitmq_service.py +319 -0
- memos/mem_scheduler/modules/redis_service.py +32 -22
- memos/mem_scheduler/modules/retriever.py +167 -23
- memos/mem_scheduler/modules/scheduler_logger.py +255 -0
- memos/mem_scheduler/mos_for_test_scheduler.py +140 -0
- memos/mem_scheduler/schemas/__init__.py +0 -0
- memos/mem_scheduler/schemas/general_schemas.py +43 -0
- memos/mem_scheduler/{modules/schemas.py → schemas/message_schemas.py} +63 -61
- memos/mem_scheduler/schemas/monitor_schemas.py +329 -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 +61 -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/persistent_user_manager.py +260 -0
- memos/mem_user/user_manager.py +4 -4
- memos/memories/activation/item.py +29 -0
- memos/memories/activation/kv.py +10 -3
- memos/memories/activation/vllmkv.py +219 -0
- memos/memories/factory.py +2 -0
- memos/memories/textual/base.py +1 -1
- memos/memories/textual/general.py +43 -97
- memos/memories/textual/item.py +5 -33
- memos/memories/textual/tree.py +22 -12
- memos/memories/textual/tree_text_memory/organize/conflict.py +9 -5
- memos/memories/textual/tree_text_memory/organize/manager.py +26 -18
- memos/memories/textual/tree_text_memory/organize/redundancy.py +25 -44
- memos/memories/textual/tree_text_memory/organize/relation_reason_detector.py +50 -48
- memos/memories/textual/tree_text_memory/organize/reorganizer.py +81 -56
- memos/memories/textual/tree_text_memory/retrieve/internet_retriever.py +6 -3
- memos/memories/textual/tree_text_memory/retrieve/internet_retriever_factory.py +2 -0
- memos/memories/textual/tree_text_memory/retrieve/recall.py +0 -1
- memos/memories/textual/tree_text_memory/retrieve/reranker.py +2 -2
- memos/memories/textual/tree_text_memory/retrieve/retrieval_mid_structs.py +2 -0
- memos/memories/textual/tree_text_memory/retrieve/searcher.py +52 -28
- memos/memories/textual/tree_text_memory/retrieve/task_goal_parser.py +42 -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/notification_service.py +44 -0
- memos/memos_tools/notification_utils.py +96 -0
- memos/parsers/markitdown.py +8 -2
- memos/settings.py +3 -1
- memos/templates/mem_reader_prompts.py +66 -23
- memos/templates/mem_scheduler_prompts.py +126 -43
- memos/templates/mos_prompts.py +87 -0
- memos/templates/tree_reorganize_prompts.py +85 -30
- memos/vec_dbs/base.py +12 -0
- memos/vec_dbs/qdrant.py +46 -20
- memoryos-0.2.0.dist-info/RECORD +0 -128
- memos/mem_scheduler/utils.py +0 -26
- {memoryos-0.2.0.dist-info → memoryos-0.2.2.dist-info}/LICENSE +0 -0
- {memoryos-0.2.0.dist-info → memoryos-0.2.2.dist-info}/WHEEL +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import concurrent.futures
|
|
2
2
|
import copy
|
|
3
3
|
import json
|
|
4
|
-
|
|
4
|
+
|
|
5
5
|
from abc import ABC
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
@@ -16,8 +16,8 @@ from memos.memories.textual.item import TextualMemoryItem, TreeNodeTextualMemory
|
|
|
16
16
|
from memos.parsers.factory import ParserFactory
|
|
17
17
|
from memos.templates.mem_reader_prompts import (
|
|
18
18
|
SIMPLE_STRUCT_DOC_READER_PROMPT,
|
|
19
|
-
SIMPLE_STRUCT_MEM_READER_PROMPT,
|
|
20
19
|
SIMPLE_STRUCT_MEM_READER_EXAMPLE,
|
|
20
|
+
SIMPLE_STRUCT_MEM_READER_PROMPT,
|
|
21
21
|
)
|
|
22
22
|
|
|
23
23
|
|
|
@@ -58,9 +58,13 @@ class SimpleStructMemReader(BaseMemReader, ABC):
|
|
|
58
58
|
metadata=TreeNodeTextualMemoryMetadata(
|
|
59
59
|
user_id=info.get("user_id"),
|
|
60
60
|
session_id=info.get("session_id"),
|
|
61
|
-
memory_type=memory_i_raw.get("memory_type", "")
|
|
61
|
+
memory_type=memory_i_raw.get("memory_type", "")
|
|
62
|
+
.replace("长期记忆", "LongTermMemory")
|
|
63
|
+
.replace("用户记忆", "UserMemory"),
|
|
62
64
|
status="activated",
|
|
63
|
-
tags=memory_i_raw.get("tags",
|
|
65
|
+
tags=memory_i_raw.get("tags", [])
|
|
66
|
+
if type(memory_i_raw.get("tags", [])) is list
|
|
67
|
+
else [],
|
|
64
68
|
key=memory_i_raw.get("key", ""),
|
|
65
69
|
embedding=self.embedder.embed([memory_i_raw.get("value", "")])[0],
|
|
66
70
|
usage=[],
|
|
@@ -176,8 +180,12 @@ class SimpleStructMemReader(BaseMemReader, ABC):
|
|
|
176
180
|
elif type == "doc":
|
|
177
181
|
for item in scene_data:
|
|
178
182
|
try:
|
|
179
|
-
|
|
180
|
-
|
|
183
|
+
if not isinstance(item, str):
|
|
184
|
+
parsed_text = parser.parse(item)
|
|
185
|
+
results.append({"file": "pure_text", "text": parsed_text})
|
|
186
|
+
else:
|
|
187
|
+
parsed_text = item
|
|
188
|
+
results.append({"file": item, "text": parsed_text})
|
|
181
189
|
except Exception as e:
|
|
182
190
|
print(f"Error parsing file {item}: {e!s}")
|
|
183
191
|
|
|
@@ -208,15 +216,15 @@ class SimpleStructMemReader(BaseMemReader, ABC):
|
|
|
208
216
|
for i, chunk_res in enumerate(processed_chunks):
|
|
209
217
|
if chunk_res:
|
|
210
218
|
node_i = TextualMemoryItem(
|
|
211
|
-
memory=chunk_res["
|
|
219
|
+
memory=chunk_res["value"],
|
|
212
220
|
metadata=TreeNodeTextualMemoryMetadata(
|
|
213
221
|
user_id=info.get("user_id"),
|
|
214
222
|
session_id=info.get("session_id"),
|
|
215
223
|
memory_type="LongTermMemory",
|
|
216
224
|
status="activated",
|
|
217
|
-
tags=chunk_res["tags"],
|
|
218
|
-
key="",
|
|
219
|
-
embedding=self.embedder.embed([chunk_res["
|
|
225
|
+
tags=chunk_res["tags"] if type(chunk_res["tags"]) is list else [],
|
|
226
|
+
key=chunk_res["key"],
|
|
227
|
+
embedding=self.embedder.embed([chunk_res["value"]])[0],
|
|
220
228
|
usage=[],
|
|
221
229
|
sources=[f"{scene_data_info['file']}_{i}"],
|
|
222
230
|
background="",
|
|
@@ -2,61 +2,413 @@ import queue
|
|
|
2
2
|
import threading
|
|
3
3
|
import time
|
|
4
4
|
|
|
5
|
-
from
|
|
6
|
-
from
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from pathlib import Path
|
|
7
7
|
|
|
8
|
-
from memos.configs.mem_scheduler import BaseSchedulerConfig
|
|
8
|
+
from memos.configs.mem_scheduler import AuthConfig, BaseSchedulerConfig
|
|
9
9
|
from memos.llms.base import BaseLLM
|
|
10
10
|
from memos.log import get_logger
|
|
11
|
+
from memos.mem_cube.general import GeneralMemCube
|
|
11
12
|
from memos.mem_scheduler.modules.dispatcher import SchedulerDispatcher
|
|
13
|
+
from memos.mem_scheduler.modules.misc import AutoDroppingQueue as Queue
|
|
14
|
+
from memos.mem_scheduler.modules.monitor import SchedulerMonitor
|
|
15
|
+
from memos.mem_scheduler.modules.rabbitmq_service import RabbitMQSchedulerModule
|
|
12
16
|
from memos.mem_scheduler.modules.redis_service import RedisSchedulerModule
|
|
13
|
-
from memos.mem_scheduler.modules.
|
|
17
|
+
from memos.mem_scheduler.modules.retriever import SchedulerRetriever
|
|
18
|
+
from memos.mem_scheduler.modules.scheduler_logger import SchedulerLoggerModule
|
|
19
|
+
from memos.mem_scheduler.schemas.general_schemas import (
|
|
20
|
+
DEFAULT_ACT_MEM_DUMP_PATH,
|
|
14
21
|
DEFAULT_CONSUME_INTERVAL_SECONDS,
|
|
15
22
|
DEFAULT_THREAD__POOL_MAX_WORKERS,
|
|
23
|
+
MemCubeID,
|
|
24
|
+
TreeTextMemory_SEARCH_METHOD,
|
|
25
|
+
UserID,
|
|
26
|
+
)
|
|
27
|
+
from memos.mem_scheduler.schemas.message_schemas import (
|
|
16
28
|
ScheduleLogForWebItem,
|
|
17
29
|
ScheduleMessageItem,
|
|
18
30
|
)
|
|
31
|
+
from memos.mem_scheduler.schemas.monitor_schemas import MemoryMonitorItem
|
|
32
|
+
from memos.mem_scheduler.utils.filter_utils import (
|
|
33
|
+
transform_name_to_key,
|
|
34
|
+
)
|
|
35
|
+
from memos.memories.activation.kv import KVCacheMemory
|
|
36
|
+
from memos.memories.activation.vllmkv import VLLMKVCacheItem, VLLMKVCacheMemory
|
|
37
|
+
from memos.memories.textual.tree import TextualMemoryItem, TreeTextMemory
|
|
38
|
+
from memos.templates.mem_scheduler_prompts import MEMORY_ASSEMBLY_TEMPLATE
|
|
19
39
|
|
|
20
40
|
|
|
21
41
|
logger = get_logger(__name__)
|
|
22
42
|
|
|
23
43
|
|
|
24
|
-
class BaseScheduler(RedisSchedulerModule):
|
|
44
|
+
class BaseScheduler(RabbitMQSchedulerModule, RedisSchedulerModule, SchedulerLoggerModule):
|
|
25
45
|
"""Base class for all mem_scheduler."""
|
|
26
46
|
|
|
27
47
|
def __init__(self, config: BaseSchedulerConfig):
|
|
28
48
|
"""Initialize the scheduler with the given configuration."""
|
|
29
49
|
super().__init__()
|
|
30
50
|
self.config = config
|
|
51
|
+
|
|
52
|
+
# hyper-parameters
|
|
53
|
+
self.top_k = self.config.get("top_k", 10)
|
|
54
|
+
self.context_window_size = self.config.get("context_window_size", 5)
|
|
55
|
+
self.enable_act_memory_update = self.config.get("enable_act_memory_update", False)
|
|
56
|
+
self.act_mem_dump_path = self.config.get("act_mem_dump_path", DEFAULT_ACT_MEM_DUMP_PATH)
|
|
57
|
+
self.search_method = TreeTextMemory_SEARCH_METHOD
|
|
58
|
+
self.enable_parallel_dispatch = self.config.get("enable_parallel_dispatch", False)
|
|
31
59
|
self.max_workers = self.config.get(
|
|
32
60
|
"thread_pool_max_workers", DEFAULT_THREAD__POOL_MAX_WORKERS
|
|
33
61
|
)
|
|
34
|
-
|
|
35
|
-
self.
|
|
36
|
-
self.
|
|
62
|
+
|
|
63
|
+
self.retriever: SchedulerRetriever | None = None
|
|
64
|
+
self.monitor: SchedulerMonitor | None = None
|
|
65
|
+
|
|
37
66
|
self.dispatcher = SchedulerDispatcher(
|
|
38
67
|
max_workers=self.max_workers, enable_parallel_dispatch=self.enable_parallel_dispatch
|
|
39
68
|
)
|
|
40
69
|
|
|
41
|
-
# message queue
|
|
42
|
-
self.
|
|
43
|
-
self.
|
|
70
|
+
# internal message queue
|
|
71
|
+
self.max_internal_messae_queue_size = 100
|
|
72
|
+
self.memos_message_queue: Queue[ScheduleMessageItem] = Queue(
|
|
73
|
+
maxsize=self.max_internal_messae_queue_size
|
|
74
|
+
)
|
|
75
|
+
self._web_log_message_queue: Queue[ScheduleLogForWebItem] = Queue(
|
|
76
|
+
maxsize=self.max_internal_messae_queue_size
|
|
77
|
+
)
|
|
44
78
|
self._consumer_thread = None # Reference to our consumer thread
|
|
45
79
|
self._running = False
|
|
46
80
|
self._consume_interval = self.config.get(
|
|
47
81
|
"consume_interval_seconds", DEFAULT_CONSUME_INTERVAL_SECONDS
|
|
48
82
|
)
|
|
49
83
|
|
|
50
|
-
#
|
|
51
|
-
self.
|
|
84
|
+
# other attributes
|
|
85
|
+
self._context_lock = threading.Lock()
|
|
86
|
+
self.current_user_id: UserID | str | None = None
|
|
87
|
+
self.auth_config_path: str | Path | None = self.config.get("auth_config_path", None)
|
|
88
|
+
self.auth_config = None
|
|
89
|
+
self.rabbitmq_config = None
|
|
90
|
+
|
|
91
|
+
def initialize_modules(self, chat_llm: BaseLLM, process_llm: BaseLLM | None = None):
|
|
92
|
+
if process_llm is None:
|
|
93
|
+
process_llm = chat_llm
|
|
94
|
+
|
|
95
|
+
# initialize submodules
|
|
96
|
+
self.chat_llm = chat_llm
|
|
97
|
+
self.process_llm = process_llm
|
|
98
|
+
self.monitor = SchedulerMonitor(process_llm=self.process_llm, config=self.config)
|
|
99
|
+
self.retriever = SchedulerRetriever(process_llm=self.process_llm, config=self.config)
|
|
52
100
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
101
|
+
# initialize with auth_cofig
|
|
102
|
+
if self.auth_config_path is not None and Path(self.auth_config_path).exists():
|
|
103
|
+
self.auth_config = AuthConfig.from_local_yaml(config_path=self.auth_config_path)
|
|
104
|
+
elif AuthConfig.default_config_exists():
|
|
105
|
+
self.auth_config = AuthConfig.from_local_yaml()
|
|
106
|
+
else:
|
|
107
|
+
self.auth_config = None
|
|
108
|
+
|
|
109
|
+
if self.auth_config is not None:
|
|
110
|
+
self.rabbitmq_config = self.auth_config.rabbitmq
|
|
111
|
+
self.initialize_rabbitmq(config=self.rabbitmq_config)
|
|
112
|
+
|
|
113
|
+
logger.debug("GeneralScheduler has been initialized")
|
|
114
|
+
|
|
115
|
+
@property
|
|
116
|
+
def mem_cube(self) -> GeneralMemCube:
|
|
117
|
+
"""The memory cube associated with this MemChat."""
|
|
118
|
+
return self.current_mem_cube
|
|
119
|
+
|
|
120
|
+
@mem_cube.setter
|
|
121
|
+
def mem_cube(self, value: GeneralMemCube) -> None:
|
|
122
|
+
"""The memory cube associated with this MemChat."""
|
|
123
|
+
self.current_mem_cube = value
|
|
124
|
+
self.retriever.mem_cube = value
|
|
125
|
+
|
|
126
|
+
def _set_current_context_from_message(self, msg: ScheduleMessageItem) -> None:
|
|
127
|
+
"""Update current user/cube context from the incoming message (thread-safe)."""
|
|
128
|
+
with self._context_lock:
|
|
129
|
+
self.current_user_id = msg.user_id
|
|
130
|
+
self.current_mem_cube_id = msg.mem_cube_id
|
|
131
|
+
self.current_mem_cube = msg.mem_cube
|
|
132
|
+
|
|
133
|
+
def transform_memories_to_monitors(
|
|
134
|
+
self, memories: list[TextualMemoryItem]
|
|
135
|
+
) -> list[MemoryMonitorItem]:
|
|
136
|
+
"""
|
|
137
|
+
Convert a list of TextualMemoryItem objects into MemoryMonitorItem objects
|
|
138
|
+
with importance scores based on keyword matching.
|
|
56
139
|
|
|
57
140
|
Args:
|
|
58
|
-
|
|
141
|
+
memories: List of TextualMemoryItem objects to be transformed.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
List of MemoryMonitorItem objects with computed importance scores.
|
|
145
|
+
"""
|
|
146
|
+
query_keywords = self.monitor.query_monitors.get_keywords_collections()
|
|
147
|
+
logger.debug(
|
|
148
|
+
f"Processing {len(memories)} memories with {len(query_keywords)} query keywords"
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
result = []
|
|
152
|
+
mem_length = len(memories)
|
|
153
|
+
for idx, mem in enumerate(memories):
|
|
154
|
+
text_mem = mem.memory
|
|
155
|
+
mem_key = transform_name_to_key(name=text_mem)
|
|
156
|
+
|
|
157
|
+
# Calculate importance score based on keyword matches
|
|
158
|
+
keywords_score = 0
|
|
159
|
+
if query_keywords and text_mem:
|
|
160
|
+
for keyword, count in query_keywords.items():
|
|
161
|
+
keyword_count = text_mem.count(keyword)
|
|
162
|
+
if keyword_count > 0:
|
|
163
|
+
keywords_score += keyword_count * count
|
|
164
|
+
logger.debug(
|
|
165
|
+
f"Matched keyword '{keyword}' {keyword_count} times, added {keywords_score} to keywords_score"
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
# rank score
|
|
169
|
+
sorting_score = mem_length - idx
|
|
170
|
+
|
|
171
|
+
mem_monitor = MemoryMonitorItem(
|
|
172
|
+
memory_text=text_mem,
|
|
173
|
+
tree_memory_item=mem,
|
|
174
|
+
tree_memory_item_mapping_key=mem_key,
|
|
175
|
+
sorting_score=sorting_score,
|
|
176
|
+
keywords_score=keywords_score,
|
|
177
|
+
recording_count=1,
|
|
178
|
+
)
|
|
179
|
+
result.append(mem_monitor)
|
|
180
|
+
|
|
181
|
+
logger.debug(f"Transformed {len(result)} memories to monitors")
|
|
182
|
+
return result
|
|
183
|
+
|
|
184
|
+
def replace_working_memory(
|
|
185
|
+
self,
|
|
186
|
+
user_id: UserID | str,
|
|
187
|
+
mem_cube_id: MemCubeID | str,
|
|
188
|
+
mem_cube: GeneralMemCube,
|
|
189
|
+
original_memory: list[TextualMemoryItem],
|
|
190
|
+
new_memory: list[TextualMemoryItem],
|
|
191
|
+
) -> None | list[TextualMemoryItem]:
|
|
192
|
+
"""Replace working memory with new memories after reranking."""
|
|
193
|
+
text_mem_base = mem_cube.text_mem
|
|
194
|
+
if isinstance(text_mem_base, TreeTextMemory):
|
|
195
|
+
text_mem_base: TreeTextMemory = text_mem_base
|
|
196
|
+
|
|
197
|
+
# process rerank memories with llm
|
|
198
|
+
query_history = self.monitor.query_monitors.get_queries_with_timesort()
|
|
199
|
+
memories_with_new_order, rerank_success_flag = (
|
|
200
|
+
self.retriever.process_and_rerank_memories(
|
|
201
|
+
queries=query_history,
|
|
202
|
+
original_memory=original_memory,
|
|
203
|
+
new_memory=new_memory,
|
|
204
|
+
top_k=self.top_k,
|
|
205
|
+
)
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
# update working memory monitors
|
|
209
|
+
new_working_memory_monitors = self.transform_memories_to_monitors(
|
|
210
|
+
memories=memories_with_new_order
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
if not rerank_success_flag:
|
|
214
|
+
for one in new_working_memory_monitors:
|
|
215
|
+
one.sorting_score = 0
|
|
216
|
+
|
|
217
|
+
self.monitor.update_working_memory_monitors(
|
|
218
|
+
new_working_memory_monitors=new_working_memory_monitors,
|
|
219
|
+
user_id=user_id,
|
|
220
|
+
mem_cube_id=mem_cube_id,
|
|
221
|
+
mem_cube=mem_cube,
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
mem_monitors: list[MemoryMonitorItem] = self.monitor.working_memory_monitors[user_id][
|
|
225
|
+
mem_cube_id
|
|
226
|
+
].get_sorted_mem_monitors(reverse=True)
|
|
227
|
+
new_working_memories = [mem_monitor.tree_memory_item for mem_monitor in mem_monitors]
|
|
228
|
+
|
|
229
|
+
text_mem_base.replace_working_memory(memories=new_working_memories)
|
|
230
|
+
|
|
231
|
+
logger.info(
|
|
232
|
+
f"The working memory has been replaced with {len(memories_with_new_order)} new memories."
|
|
233
|
+
)
|
|
234
|
+
self.log_working_memory_replacement(
|
|
235
|
+
original_memory=original_memory,
|
|
236
|
+
new_memory=new_working_memories,
|
|
237
|
+
user_id=user_id,
|
|
238
|
+
mem_cube_id=mem_cube_id,
|
|
239
|
+
mem_cube=mem_cube,
|
|
240
|
+
log_func_callback=self._submit_web_logs,
|
|
241
|
+
)
|
|
242
|
+
else:
|
|
243
|
+
logger.error("memory_base is not supported")
|
|
244
|
+
memories_with_new_order = new_memory
|
|
245
|
+
|
|
246
|
+
return memories_with_new_order
|
|
247
|
+
|
|
248
|
+
def initialize_working_memory_monitors(
|
|
249
|
+
self,
|
|
250
|
+
user_id: UserID | str,
|
|
251
|
+
mem_cube_id: MemCubeID | str,
|
|
252
|
+
mem_cube: GeneralMemCube,
|
|
253
|
+
):
|
|
254
|
+
text_mem_base: TreeTextMemory = mem_cube.text_mem
|
|
255
|
+
working_memories = text_mem_base.get_working_memory()
|
|
256
|
+
|
|
257
|
+
working_memory_monitors = self.transform_memories_to_monitors(
|
|
258
|
+
memories=working_memories,
|
|
259
|
+
)
|
|
260
|
+
self.monitor.update_working_memory_monitors(
|
|
261
|
+
new_working_memory_monitors=working_memory_monitors,
|
|
262
|
+
user_id=user_id,
|
|
263
|
+
mem_cube_id=mem_cube_id,
|
|
264
|
+
mem_cube=mem_cube,
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
def update_activation_memory(
|
|
268
|
+
self,
|
|
269
|
+
new_memories: list[str | TextualMemoryItem],
|
|
270
|
+
label: str,
|
|
271
|
+
user_id: UserID | str,
|
|
272
|
+
mem_cube_id: MemCubeID | str,
|
|
273
|
+
mem_cube: GeneralMemCube,
|
|
274
|
+
) -> None:
|
|
59
275
|
"""
|
|
276
|
+
Update activation memory by extracting KVCacheItems from new_memory (list of str),
|
|
277
|
+
add them to a KVCacheMemory instance, and dump to disk.
|
|
278
|
+
"""
|
|
279
|
+
if len(new_memories) == 0:
|
|
280
|
+
logger.error("update_activation_memory: new_memory is empty.")
|
|
281
|
+
return
|
|
282
|
+
if isinstance(new_memories[0], TextualMemoryItem):
|
|
283
|
+
new_text_memories = [mem.memory for mem in new_memories]
|
|
284
|
+
elif isinstance(new_memories[0], str):
|
|
285
|
+
new_text_memories = new_memories
|
|
286
|
+
else:
|
|
287
|
+
logger.error("Not Implemented.")
|
|
288
|
+
|
|
289
|
+
try:
|
|
290
|
+
if isinstance(mem_cube.act_mem, VLLMKVCacheMemory):
|
|
291
|
+
act_mem: VLLMKVCacheMemory = mem_cube.act_mem
|
|
292
|
+
elif isinstance(mem_cube.act_mem, KVCacheMemory):
|
|
293
|
+
act_mem: KVCacheMemory = mem_cube.act_mem
|
|
294
|
+
else:
|
|
295
|
+
logger.error("Not Implemented.")
|
|
296
|
+
return
|
|
297
|
+
|
|
298
|
+
new_text_memory = MEMORY_ASSEMBLY_TEMPLATE.format(
|
|
299
|
+
memory_text="".join(
|
|
300
|
+
[
|
|
301
|
+
f"{i + 1}. {sentence.strip()}\n"
|
|
302
|
+
for i, sentence in enumerate(new_text_memories)
|
|
303
|
+
if sentence.strip() # Skip empty strings
|
|
304
|
+
]
|
|
305
|
+
)
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
# huggingface or vllm kv cache
|
|
309
|
+
original_cache_items: list[VLLMKVCacheItem] = act_mem.get_all()
|
|
310
|
+
original_text_memories = []
|
|
311
|
+
if len(original_cache_items) > 0:
|
|
312
|
+
pre_cache_item: VLLMKVCacheItem = original_cache_items[-1]
|
|
313
|
+
original_text_memories = pre_cache_item.records.text_memories
|
|
314
|
+
original_composed_text_memory = pre_cache_item.records.composed_text_memory
|
|
315
|
+
if original_composed_text_memory == new_text_memory:
|
|
316
|
+
logger.warning(
|
|
317
|
+
"Skipping memory update - new composition matches existing cache: %s",
|
|
318
|
+
new_text_memory[:50] + "..."
|
|
319
|
+
if len(new_text_memory) > 50
|
|
320
|
+
else new_text_memory,
|
|
321
|
+
)
|
|
322
|
+
return
|
|
323
|
+
act_mem.delete_all()
|
|
324
|
+
|
|
325
|
+
cache_item = act_mem.extract(new_text_memory)
|
|
326
|
+
cache_item.records.text_memories = new_text_memories
|
|
327
|
+
|
|
328
|
+
act_mem.add([cache_item])
|
|
329
|
+
act_mem.dump(self.act_mem_dump_path)
|
|
330
|
+
|
|
331
|
+
self.log_activation_memory_update(
|
|
332
|
+
original_text_memories=original_text_memories,
|
|
333
|
+
new_text_memories=new_text_memories,
|
|
334
|
+
label=label,
|
|
335
|
+
user_id=user_id,
|
|
336
|
+
mem_cube_id=mem_cube_id,
|
|
337
|
+
mem_cube=mem_cube,
|
|
338
|
+
log_func_callback=self._submit_web_logs,
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
except Exception as e:
|
|
342
|
+
logger.warning(f"MOS-based activation memory update failed: {e}", exc_info=True)
|
|
343
|
+
|
|
344
|
+
def update_activation_memory_periodically(
|
|
345
|
+
self,
|
|
346
|
+
interval_seconds: int,
|
|
347
|
+
label: str,
|
|
348
|
+
user_id: UserID | str,
|
|
349
|
+
mem_cube_id: MemCubeID | str,
|
|
350
|
+
mem_cube: GeneralMemCube,
|
|
351
|
+
):
|
|
352
|
+
try:
|
|
353
|
+
if (
|
|
354
|
+
self.monitor.last_activation_mem_update_time == datetime.min
|
|
355
|
+
or self.monitor.timed_trigger(
|
|
356
|
+
last_time=self.monitor.last_activation_mem_update_time,
|
|
357
|
+
interval_seconds=interval_seconds,
|
|
358
|
+
)
|
|
359
|
+
):
|
|
360
|
+
logger.info(
|
|
361
|
+
f"Updating activation memory for user {user_id} and mem_cube {mem_cube_id}"
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
if (
|
|
365
|
+
user_id not in self.monitor.working_memory_monitors
|
|
366
|
+
or mem_cube_id not in self.monitor.working_memory_monitors[user_id]
|
|
367
|
+
or len(self.monitor.working_memory_monitors[user_id][mem_cube_id].memories) == 0
|
|
368
|
+
):
|
|
369
|
+
logger.warning(
|
|
370
|
+
"No memories found in working_memory_monitors, initializing from current working_memories"
|
|
371
|
+
)
|
|
372
|
+
self.initialize_working_memory_monitors(
|
|
373
|
+
user_id=user_id,
|
|
374
|
+
mem_cube_id=mem_cube_id,
|
|
375
|
+
mem_cube=mem_cube,
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
self.monitor.update_activation_memory_monitors(
|
|
379
|
+
user_id=user_id, mem_cube_id=mem_cube_id, mem_cube=mem_cube
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
new_activation_memories = [
|
|
383
|
+
m.memory_text
|
|
384
|
+
for m in self.monitor.activation_memory_monitors[user_id][mem_cube_id].memories
|
|
385
|
+
]
|
|
386
|
+
|
|
387
|
+
logger.info(
|
|
388
|
+
f"Collected {len(new_activation_memories)} new memory entries for processing"
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
self.update_activation_memory(
|
|
392
|
+
new_memories=new_activation_memories,
|
|
393
|
+
label=label,
|
|
394
|
+
user_id=user_id,
|
|
395
|
+
mem_cube_id=mem_cube_id,
|
|
396
|
+
mem_cube=mem_cube,
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
self.monitor.last_activation_mem_update_time = datetime.now()
|
|
400
|
+
|
|
401
|
+
logger.debug(
|
|
402
|
+
f"Activation memory update completed at {self.monitor.last_activation_mem_update_time}"
|
|
403
|
+
)
|
|
404
|
+
else:
|
|
405
|
+
logger.info(
|
|
406
|
+
f"Skipping update - {interval_seconds} second interval not yet reached. "
|
|
407
|
+
f"Last update time is {self.monitor.last_activation_mem_update_time} and now is"
|
|
408
|
+
f"{datetime.now()}"
|
|
409
|
+
)
|
|
410
|
+
except Exception as e:
|
|
411
|
+
logger.error(f"Error: {e}", exc_info=True)
|
|
60
412
|
|
|
61
413
|
def submit_messages(self, messages: ScheduleMessageItem | list[ScheduleMessageItem]):
|
|
62
414
|
"""Submit multiple messages to the message queue."""
|
|
@@ -67,15 +419,25 @@ class BaseScheduler(RedisSchedulerModule):
|
|
|
67
419
|
self.memos_message_queue.put(message)
|
|
68
420
|
logger.info(f"Submitted message: {message.label} - {message.content}")
|
|
69
421
|
|
|
70
|
-
def _submit_web_logs(
|
|
422
|
+
def _submit_web_logs(
|
|
423
|
+
self, messages: ScheduleLogForWebItem | list[ScheduleLogForWebItem]
|
|
424
|
+
) -> None:
|
|
425
|
+
"""Submit log messages to the web log queue and optionally to RabbitMQ.
|
|
426
|
+
|
|
427
|
+
Args:
|
|
428
|
+
messages: Single log message or list of log messages
|
|
429
|
+
"""
|
|
71
430
|
if isinstance(messages, ScheduleLogForWebItem):
|
|
72
431
|
messages = [messages] # transform single message to list
|
|
73
432
|
|
|
74
433
|
for message in messages:
|
|
75
434
|
self._web_log_message_queue.put(message)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
435
|
+
message_info = message.debug_info()
|
|
436
|
+
logger.debug(f"Submitted Scheduling log for web: {message_info}")
|
|
437
|
+
|
|
438
|
+
if self.is_rabbitmq_connected():
|
|
439
|
+
logger.info(f"Submitted Scheduling log to rabbitmq: {message_info}")
|
|
440
|
+
self.rabbitmq_publish_message(message=message.to_dict())
|
|
79
441
|
logger.debug(f"{len(messages)} submitted. {self._web_log_message_queue.qsize()} in queue.")
|
|
80
442
|
|
|
81
443
|
def get_web_log_messages(self) -> list[dict]:
|
|
@@ -87,13 +449,12 @@ class BaseScheduler(RedisSchedulerModule):
|
|
|
87
449
|
ready for JSON serialization. The list is ordered from oldest to newest.
|
|
88
450
|
"""
|
|
89
451
|
messages = []
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
messages.append(item_dict)
|
|
452
|
+
while True:
|
|
453
|
+
try:
|
|
454
|
+
item = self._web_log_message_queue.get_nowait() # 线程安全的 get
|
|
455
|
+
messages.append(item.to_dict())
|
|
456
|
+
except queue.Empty:
|
|
457
|
+
break
|
|
97
458
|
return messages
|
|
98
459
|
|
|
99
460
|
def _message_consumer(self) -> None:
|
|
@@ -133,32 +494,72 @@ class BaseScheduler(RedisSchedulerModule):
|
|
|
133
494
|
|
|
134
495
|
def start(self) -> None:
|
|
135
496
|
"""
|
|
136
|
-
Start the message consumer thread.
|
|
497
|
+
Start the message consumer thread and initialize dispatcher resources.
|
|
137
498
|
|
|
138
|
-
Initializes and starts
|
|
139
|
-
|
|
499
|
+
Initializes and starts:
|
|
500
|
+
1. Message consumer thread
|
|
501
|
+
2. Dispatcher thread pool (if parallel dispatch enabled)
|
|
140
502
|
"""
|
|
141
|
-
if self.
|
|
142
|
-
logger.warning("
|
|
503
|
+
if self._running:
|
|
504
|
+
logger.warning("Memory Scheduler is already running")
|
|
143
505
|
return
|
|
144
506
|
|
|
507
|
+
# Initialize dispatcher resources
|
|
508
|
+
if self.enable_parallel_dispatch:
|
|
509
|
+
logger.info(f"Initializing dispatcher thread pool with {self.max_workers} workers")
|
|
510
|
+
|
|
511
|
+
# Start consumer thread
|
|
145
512
|
self._running = True
|
|
146
513
|
self._consumer_thread = threading.Thread(
|
|
147
514
|
target=self._message_consumer,
|
|
148
|
-
daemon=True,
|
|
515
|
+
daemon=True,
|
|
149
516
|
name="MessageConsumerThread",
|
|
150
517
|
)
|
|
151
518
|
self._consumer_thread.start()
|
|
152
519
|
logger.info("Message consumer thread started")
|
|
153
520
|
|
|
154
521
|
def stop(self) -> None:
|
|
155
|
-
"""Stop
|
|
156
|
-
|
|
157
|
-
|
|
522
|
+
"""Stop all scheduler components gracefully.
|
|
523
|
+
|
|
524
|
+
1. Stops message consumer thread
|
|
525
|
+
2. Shuts down dispatcher thread pool
|
|
526
|
+
3. Cleans up resources
|
|
527
|
+
"""
|
|
528
|
+
if not self._running:
|
|
529
|
+
logger.warning("Memory Scheduler is not running")
|
|
158
530
|
return
|
|
531
|
+
|
|
532
|
+
# Signal consumer thread to stop
|
|
159
533
|
self._running = False
|
|
160
|
-
|
|
161
|
-
|
|
534
|
+
|
|
535
|
+
# Wait for consumer thread
|
|
536
|
+
if self._consumer_thread and self._consumer_thread.is_alive():
|
|
537
|
+
self._consumer_thread.join(timeout=5.0)
|
|
162
538
|
if self._consumer_thread.is_alive():
|
|
163
539
|
logger.warning("Consumer thread did not stop gracefully")
|
|
164
|
-
|
|
540
|
+
else:
|
|
541
|
+
logger.info("Consumer thread stopped")
|
|
542
|
+
|
|
543
|
+
# Shutdown dispatcher
|
|
544
|
+
if hasattr(self, "dispatcher") and self.dispatcher:
|
|
545
|
+
logger.info("Shutting down dispatcher...")
|
|
546
|
+
self.dispatcher.shutdown()
|
|
547
|
+
|
|
548
|
+
# Clean up queues
|
|
549
|
+
self._cleanup_queues()
|
|
550
|
+
logger.info("Memory Scheduler stopped completely")
|
|
551
|
+
|
|
552
|
+
def _cleanup_queues(self) -> None:
|
|
553
|
+
"""Ensure all queues are emptied and marked as closed."""
|
|
554
|
+
try:
|
|
555
|
+
while not self.memos_message_queue.empty():
|
|
556
|
+
self.memos_message_queue.get_nowait()
|
|
557
|
+
self.memos_message_queue.task_done()
|
|
558
|
+
except queue.Empty:
|
|
559
|
+
pass
|
|
560
|
+
|
|
561
|
+
try:
|
|
562
|
+
while not self._web_log_message_queue.empty():
|
|
563
|
+
self._web_log_message_queue.get_nowait()
|
|
564
|
+
except queue.Empty:
|
|
565
|
+
pass
|