MemoryOS 0.1.13__py3-none-any.whl → 0.2.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.1.13.dist-info → memoryos-0.2.1.dist-info}/METADATA +78 -49
- memoryos-0.2.1.dist-info/RECORD +152 -0
- memoryos-0.2.1.dist-info/entry_points.txt +3 -0
- memos/__init__.py +1 -1
- memos/api/config.py +471 -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 +159 -0
- memos/api/routers/__init__.py +1 -0
- memos/api/routers/product_router.py +358 -0
- memos/chunkers/sentence_chunker.py +8 -2
- memos/cli.py +113 -0
- memos/configs/embedder.py +27 -0
- memos/configs/graph_db.py +83 -2
- memos/configs/llm.py +48 -0
- memos/configs/mem_cube.py +1 -1
- memos/configs/mem_reader.py +4 -0
- memos/configs/mem_scheduler.py +91 -5
- memos/configs/memory.py +10 -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 +2 -2
- memos/graph_dbs/factory.py +2 -0
- memos/graph_dbs/item.py +46 -0
- memos/graph_dbs/neo4j.py +377 -101
- 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 +68 -1
- memos/llms/qwen.py +63 -0
- memos/llms/vllm.py +153 -0
- memos/mem_cube/general.py +77 -16
- memos/mem_cube/utils.py +102 -0
- memos/mem_os/core.py +131 -41
- memos/mem_os/main.py +93 -11
- memos/mem_os/product.py +1098 -35
- memos/mem_os/utils/default_config.py +352 -0
- memos/mem_os/utils/format_utils.py +1154 -0
- memos/mem_reader/simple_struct.py +13 -8
- memos/mem_scheduler/base_scheduler.py +467 -36
- memos/mem_scheduler/general_scheduler.py +125 -244
- memos/mem_scheduler/modules/base.py +9 -0
- memos/mem_scheduler/modules/dispatcher.py +68 -2
- memos/mem_scheduler/modules/misc.py +39 -0
- memos/mem_scheduler/modules/monitor.py +228 -49
- memos/mem_scheduler/modules/rabbitmq_service.py +317 -0
- memos/mem_scheduler/modules/redis_service.py +32 -22
- memos/mem_scheduler/modules/retriever.py +250 -23
- memos/mem_scheduler/modules/schemas.py +189 -7
- memos/mem_scheduler/mos_for_test_scheduler.py +143 -0
- memos/mem_scheduler/utils.py +51 -2
- memos/mem_user/persistent_user_manager.py +260 -0
- memos/memories/activation/item.py +25 -0
- memos/memories/activation/kv.py +10 -3
- memos/memories/activation/vllmkv.py +219 -0
- memos/memories/factory.py +2 -0
- memos/memories/textual/general.py +7 -5
- memos/memories/textual/item.py +3 -1
- memos/memories/textual/tree.py +14 -6
- memos/memories/textual/tree_text_memory/organize/conflict.py +198 -0
- memos/memories/textual/tree_text_memory/organize/manager.py +72 -23
- memos/memories/textual/tree_text_memory/organize/redundancy.py +193 -0
- memos/memories/textual/tree_text_memory/organize/relation_reason_detector.py +233 -0
- memos/memories/textual/tree_text_memory/organize/reorganizer.py +606 -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/searcher.py +6 -5
- memos/parsers/markitdown.py +8 -2
- memos/templates/mem_reader_prompts.py +105 -36
- memos/templates/mem_scheduler_prompts.py +96 -47
- memos/templates/tree_reorganize_prompts.py +223 -0
- memos/vec_dbs/base.py +12 -0
- memos/vec_dbs/qdrant.py +46 -20
- memoryos-0.1.13.dist-info/RECORD +0 -122
- {memoryos-0.1.13.dist-info → memoryos-0.2.1.dist-info}/LICENSE +0 -0
- {memoryos-0.1.13.dist-info → memoryos-0.2.1.dist-info}/WHEEL +0 -0
|
@@ -16,6 +16,7 @@ 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_EXAMPLE,
|
|
19
20
|
SIMPLE_STRUCT_MEM_READER_PROMPT,
|
|
20
21
|
)
|
|
21
22
|
|
|
@@ -39,11 +40,11 @@ class SimpleStructMemReader(BaseMemReader, ABC):
|
|
|
39
40
|
self.chunker = ChunkerFactory.from_config(config.chunker)
|
|
40
41
|
|
|
41
42
|
def _process_chat_data(self, scene_data_info, info):
|
|
42
|
-
prompt = (
|
|
43
|
-
|
|
44
|
-
.replace("${user_b}", "assistant")
|
|
45
|
-
.replace("${conversation}", "\n".join(scene_data_info))
|
|
43
|
+
prompt = SIMPLE_STRUCT_MEM_READER_PROMPT.replace(
|
|
44
|
+
"${conversation}", "\n".join(scene_data_info)
|
|
46
45
|
)
|
|
46
|
+
if self.config.remove_prompt_example:
|
|
47
|
+
prompt = prompt.replace(SIMPLE_STRUCT_MEM_READER_EXAMPLE, "")
|
|
47
48
|
|
|
48
49
|
messages = [{"role": "user", "content": prompt}]
|
|
49
50
|
|
|
@@ -207,15 +208,15 @@ class SimpleStructMemReader(BaseMemReader, ABC):
|
|
|
207
208
|
for i, chunk_res in enumerate(processed_chunks):
|
|
208
209
|
if chunk_res:
|
|
209
210
|
node_i = TextualMemoryItem(
|
|
210
|
-
memory=chunk_res["
|
|
211
|
+
memory=chunk_res["value"],
|
|
211
212
|
metadata=TreeNodeTextualMemoryMetadata(
|
|
212
213
|
user_id=info.get("user_id"),
|
|
213
214
|
session_id=info.get("session_id"),
|
|
214
215
|
memory_type="LongTermMemory",
|
|
215
216
|
status="activated",
|
|
216
217
|
tags=chunk_res["tags"],
|
|
217
|
-
key="",
|
|
218
|
-
embedding=self.embedder.embed([chunk_res["
|
|
218
|
+
key=chunk_res["key"],
|
|
219
|
+
embedding=self.embedder.embed([chunk_res["value"]])[0],
|
|
219
220
|
usage=[],
|
|
220
221
|
sources=[f"{scene_data_info['file']}_{i}"],
|
|
221
222
|
background="",
|
|
@@ -228,7 +229,11 @@ class SimpleStructMemReader(BaseMemReader, ABC):
|
|
|
228
229
|
|
|
229
230
|
def parse_json_result(self, response_text):
|
|
230
231
|
try:
|
|
231
|
-
|
|
232
|
+
json_start = response_text.find("{")
|
|
233
|
+
response_text = response_text[json_start:]
|
|
234
|
+
response_text = response_text.replace("```", "").strip()
|
|
235
|
+
if response_text[-1] != "}":
|
|
236
|
+
response_text += "}"
|
|
232
237
|
response_json = json.loads(response_text)
|
|
233
238
|
return response_json
|
|
234
239
|
except json.JSONDecodeError as e:
|
|
@@ -2,61 +2,283 @@ 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
|
|
17
|
+
from memos.mem_scheduler.modules.retriever import SchedulerRetriever
|
|
13
18
|
from memos.mem_scheduler.modules.schemas import (
|
|
19
|
+
ACTIVATION_MEMORY_TYPE,
|
|
20
|
+
ADD_LABEL,
|
|
21
|
+
DEFAULT_ACT_MEM_DUMP_PATH,
|
|
14
22
|
DEFAULT_CONSUME_INTERVAL_SECONDS,
|
|
15
23
|
DEFAULT_THREAD__POOL_MAX_WORKERS,
|
|
24
|
+
LONG_TERM_MEMORY_TYPE,
|
|
25
|
+
NOT_INITIALIZED,
|
|
26
|
+
PARAMETER_MEMORY_TYPE,
|
|
27
|
+
QUERY_LABEL,
|
|
28
|
+
TEXT_MEMORY_TYPE,
|
|
29
|
+
USER_INPUT_TYPE,
|
|
30
|
+
WORKING_MEMORY_TYPE,
|
|
16
31
|
ScheduleLogForWebItem,
|
|
17
32
|
ScheduleMessageItem,
|
|
33
|
+
TreeTextMemory_SEARCH_METHOD,
|
|
18
34
|
)
|
|
35
|
+
from memos.mem_scheduler.utils import transform_name_to_key
|
|
36
|
+
from memos.memories.activation.kv import KVCacheMemory
|
|
37
|
+
from memos.memories.activation.vllmkv import VLLMKVCacheItem, VLLMKVCacheMemory
|
|
38
|
+
from memos.memories.textual.tree import TextualMemoryItem, TreeTextMemory
|
|
39
|
+
from memos.templates.mem_scheduler_prompts import MEMORY_ASSEMBLY_TEMPLATE
|
|
19
40
|
|
|
20
41
|
|
|
21
42
|
logger = get_logger(__name__)
|
|
22
43
|
|
|
23
44
|
|
|
24
|
-
class BaseScheduler(RedisSchedulerModule):
|
|
45
|
+
class BaseScheduler(RabbitMQSchedulerModule, RedisSchedulerModule):
|
|
25
46
|
"""Base class for all mem_scheduler."""
|
|
26
47
|
|
|
27
48
|
def __init__(self, config: BaseSchedulerConfig):
|
|
28
49
|
"""Initialize the scheduler with the given configuration."""
|
|
29
50
|
super().__init__()
|
|
30
51
|
self.config = config
|
|
52
|
+
|
|
53
|
+
# hyper-parameters
|
|
54
|
+
self.top_k = self.config.get("top_k", 5)
|
|
55
|
+
self.context_window_size = self.config.get("context_window_size", 5)
|
|
56
|
+
self.enable_act_memory_update = self.config.get("enable_act_memory_update", False)
|
|
57
|
+
self.act_mem_dump_path = self.config.get("act_mem_dump_path", DEFAULT_ACT_MEM_DUMP_PATH)
|
|
58
|
+
self.search_method = TreeTextMemory_SEARCH_METHOD
|
|
59
|
+
|
|
60
|
+
self.enable_parallel_dispatch = self.config.get("enable_parallel_dispatch", False)
|
|
31
61
|
self.max_workers = self.config.get(
|
|
32
62
|
"thread_pool_max_workers", DEFAULT_THREAD__POOL_MAX_WORKERS
|
|
33
63
|
)
|
|
34
|
-
|
|
35
|
-
self.
|
|
36
|
-
self.
|
|
64
|
+
|
|
65
|
+
self.retriever: SchedulerRetriever | None = None
|
|
66
|
+
self.monitor: SchedulerMonitor | None = None
|
|
67
|
+
|
|
37
68
|
self.dispatcher = SchedulerDispatcher(
|
|
38
69
|
max_workers=self.max_workers, enable_parallel_dispatch=self.enable_parallel_dispatch
|
|
39
70
|
)
|
|
40
71
|
|
|
41
|
-
# message queue
|
|
42
|
-
self.
|
|
43
|
-
self.
|
|
72
|
+
# internal message queue
|
|
73
|
+
self.max_internal_messae_queue_size = 100
|
|
74
|
+
self.memos_message_queue: Queue[ScheduleMessageItem] = Queue(
|
|
75
|
+
maxsize=self.max_internal_messae_queue_size
|
|
76
|
+
)
|
|
77
|
+
self._web_log_message_queue: Queue[ScheduleLogForWebItem] = Queue(
|
|
78
|
+
maxsize=self.max_internal_messae_queue_size
|
|
79
|
+
)
|
|
44
80
|
self._consumer_thread = None # Reference to our consumer thread
|
|
45
81
|
self._running = False
|
|
46
82
|
self._consume_interval = self.config.get(
|
|
47
83
|
"consume_interval_seconds", DEFAULT_CONSUME_INTERVAL_SECONDS
|
|
48
84
|
)
|
|
49
85
|
|
|
50
|
-
#
|
|
86
|
+
# other attributes
|
|
87
|
+
self._context_lock = threading.Lock()
|
|
51
88
|
self._current_user_id: str | None = None
|
|
89
|
+
self.auth_config_path: str | Path | None = self.config.get("auth_config_path", None)
|
|
90
|
+
self.auth_config = None
|
|
91
|
+
self.rabbitmq_config = None
|
|
92
|
+
|
|
93
|
+
def initialize_modules(self, chat_llm: BaseLLM, process_llm: BaseLLM | None = None):
|
|
94
|
+
if process_llm is None:
|
|
95
|
+
process_llm = chat_llm
|
|
96
|
+
|
|
97
|
+
# initialize submodules
|
|
98
|
+
self.chat_llm = chat_llm
|
|
99
|
+
self.process_llm = process_llm
|
|
100
|
+
self.monitor = SchedulerMonitor(process_llm=self.process_llm, config=self.config)
|
|
101
|
+
self.retriever = SchedulerRetriever(process_llm=self.process_llm, config=self.config)
|
|
102
|
+
self.retriever.log_working_memory_replacement = self.log_working_memory_replacement
|
|
103
|
+
|
|
104
|
+
# initialize with auth_cofig
|
|
105
|
+
if self.auth_config_path is not None and Path(self.auth_config_path).exists():
|
|
106
|
+
self.auth_config = AuthConfig.from_local_yaml(config_path=self.auth_config_path)
|
|
107
|
+
elif AuthConfig.default_config_exists():
|
|
108
|
+
self.auth_config = AuthConfig.from_local_yaml()
|
|
109
|
+
else:
|
|
110
|
+
self.auth_config = None
|
|
111
|
+
|
|
112
|
+
if self.auth_config is not None:
|
|
113
|
+
self.rabbitmq_config = self.auth_config.rabbitmq
|
|
114
|
+
self.initialize_rabbitmq(config=self.rabbitmq_config)
|
|
52
115
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
116
|
+
logger.debug("GeneralScheduler has been initialized")
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def mem_cube(self) -> GeneralMemCube:
|
|
120
|
+
"""The memory cube associated with this MemChat."""
|
|
121
|
+
return self._current_mem_cube
|
|
122
|
+
|
|
123
|
+
@mem_cube.setter
|
|
124
|
+
def mem_cube(self, value: GeneralMemCube) -> None:
|
|
125
|
+
"""The memory cube associated with this MemChat."""
|
|
126
|
+
self._current_mem_cube = value
|
|
127
|
+
self.retriever.mem_cube = value
|
|
128
|
+
|
|
129
|
+
def _set_current_context_from_message(self, msg: ScheduleMessageItem) -> None:
|
|
130
|
+
"""Update current user/cube context from the incoming message (thread-safe)."""
|
|
131
|
+
with self._context_lock:
|
|
132
|
+
self._current_user_id = msg.user_id
|
|
133
|
+
self._current_mem_cube_id = msg.mem_cube_id
|
|
134
|
+
self._current_mem_cube = msg.mem_cube
|
|
135
|
+
|
|
136
|
+
def _validate_messages(self, messages: list[ScheduleMessageItem], label: str):
|
|
137
|
+
"""Validate if all messages match the expected label.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
messages: List of message items to validate.
|
|
141
|
+
label: Expected message label (e.g., QUERY_LABEL/ANSWER_LABEL).
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
bool: True if all messages passed validation, False if any failed.
|
|
145
|
+
"""
|
|
146
|
+
for message in messages:
|
|
147
|
+
if not self._validate_message(message, label):
|
|
148
|
+
return False
|
|
149
|
+
logger.error("Message batch contains invalid labels, aborting processing")
|
|
150
|
+
return True
|
|
151
|
+
|
|
152
|
+
def _validate_message(self, message: ScheduleMessageItem, label: str):
|
|
153
|
+
"""Validate if the message matches the expected label.
|
|
56
154
|
|
|
57
155
|
Args:
|
|
58
|
-
|
|
156
|
+
message: Incoming message item to validate.
|
|
157
|
+
label: Expected message label (e.g., QUERY_LABEL/ANSWER_LABEL).
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
bool: True if validation passed, False otherwise.
|
|
161
|
+
"""
|
|
162
|
+
if message.label != label:
|
|
163
|
+
logger.error(f"Handler validation failed: expected={label}, actual={message.label}")
|
|
164
|
+
return False
|
|
165
|
+
return True
|
|
166
|
+
|
|
167
|
+
def update_activation_memory(
|
|
168
|
+
self,
|
|
169
|
+
new_memories: list[str | TextualMemoryItem],
|
|
170
|
+
label: str,
|
|
171
|
+
user_id: str,
|
|
172
|
+
mem_cube_id: str,
|
|
173
|
+
mem_cube: GeneralMemCube,
|
|
174
|
+
) -> None:
|
|
59
175
|
"""
|
|
176
|
+
Update activation memory by extracting KVCacheItems from new_memory (list of str),
|
|
177
|
+
add them to a KVCacheMemory instance, and dump to disk.
|
|
178
|
+
"""
|
|
179
|
+
if len(new_memories) == 0:
|
|
180
|
+
logger.error("update_activation_memory: new_memory is empty.")
|
|
181
|
+
return
|
|
182
|
+
if isinstance(new_memories[0], TextualMemoryItem):
|
|
183
|
+
new_text_memories = [mem.memory for mem in new_memories]
|
|
184
|
+
elif isinstance(new_memories[0], str):
|
|
185
|
+
new_text_memories = new_memories
|
|
186
|
+
else:
|
|
187
|
+
logger.error("Not Implemented.")
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
if isinstance(mem_cube.act_mem, VLLMKVCacheMemory):
|
|
191
|
+
act_mem: VLLMKVCacheMemory = mem_cube.act_mem
|
|
192
|
+
elif isinstance(mem_cube.act_mem, KVCacheMemory):
|
|
193
|
+
act_mem: KVCacheMemory = mem_cube.act_mem
|
|
194
|
+
else:
|
|
195
|
+
logger.error("Not Implemented.")
|
|
196
|
+
return
|
|
197
|
+
|
|
198
|
+
text_memory = MEMORY_ASSEMBLY_TEMPLATE.format(
|
|
199
|
+
memory_text="".join(
|
|
200
|
+
[
|
|
201
|
+
f"{i + 1}. {sentence.strip()}\n"
|
|
202
|
+
for i, sentence in enumerate(new_text_memories)
|
|
203
|
+
if sentence.strip() # Skip empty strings
|
|
204
|
+
]
|
|
205
|
+
)
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
# huggingface or vllm kv cache
|
|
209
|
+
original_cache_items: list[VLLMKVCacheItem] = act_mem.get_all()
|
|
210
|
+
original_text_memories = []
|
|
211
|
+
if len(original_cache_items) > 0:
|
|
212
|
+
pre_cache_item: VLLMKVCacheItem = original_cache_items[-1]
|
|
213
|
+
original_text_memories = pre_cache_item.records.text_memories
|
|
214
|
+
act_mem.delete_all()
|
|
215
|
+
|
|
216
|
+
cache_item = act_mem.extract(text_memory)
|
|
217
|
+
cache_item.records.text_memories = new_text_memories
|
|
218
|
+
|
|
219
|
+
act_mem.add([cache_item])
|
|
220
|
+
act_mem.dump(self.act_mem_dump_path)
|
|
221
|
+
|
|
222
|
+
self.log_activation_memory_update(
|
|
223
|
+
original_text_memories=original_text_memories,
|
|
224
|
+
new_text_memories=new_text_memories,
|
|
225
|
+
label=label,
|
|
226
|
+
user_id=user_id,
|
|
227
|
+
mem_cube_id=mem_cube_id,
|
|
228
|
+
mem_cube=mem_cube,
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
except Exception as e:
|
|
232
|
+
logger.warning(f"MOS-based activation memory update failed: {e}", exc_info=True)
|
|
233
|
+
|
|
234
|
+
def update_activation_memory_periodically(
|
|
235
|
+
self,
|
|
236
|
+
interval_seconds: int,
|
|
237
|
+
label: str,
|
|
238
|
+
user_id: str,
|
|
239
|
+
mem_cube_id: str,
|
|
240
|
+
mem_cube: GeneralMemCube,
|
|
241
|
+
):
|
|
242
|
+
new_activation_memories = []
|
|
243
|
+
|
|
244
|
+
if self.monitor.timed_trigger(
|
|
245
|
+
last_time=self.monitor._last_activation_mem_update_time,
|
|
246
|
+
interval_seconds=interval_seconds,
|
|
247
|
+
):
|
|
248
|
+
logger.info(f"Updating activation memory for user {user_id} and mem_cube {mem_cube_id}")
|
|
249
|
+
|
|
250
|
+
self.monitor.update_memory_monitors(
|
|
251
|
+
user_id=user_id, mem_cube_id=mem_cube_id, mem_cube=mem_cube
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
new_activation_memories = [
|
|
255
|
+
m.memory_text
|
|
256
|
+
for m in self.monitor.activation_memory_monitors[user_id][mem_cube_id].memories
|
|
257
|
+
]
|
|
258
|
+
|
|
259
|
+
logger.info(
|
|
260
|
+
f"Collected {len(new_activation_memories)} new memory entries for processing"
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
self.update_activation_memory(
|
|
264
|
+
new_memories=new_activation_memories,
|
|
265
|
+
label=label,
|
|
266
|
+
user_id=user_id,
|
|
267
|
+
mem_cube_id=mem_cube_id,
|
|
268
|
+
mem_cube=mem_cube,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
self.monitor._last_activation_mem_update_time = datetime.now()
|
|
272
|
+
|
|
273
|
+
logger.debug(
|
|
274
|
+
f"Activation memory update completed at {self.monitor._last_activation_mem_update_time}"
|
|
275
|
+
)
|
|
276
|
+
else:
|
|
277
|
+
logger.info(
|
|
278
|
+
f"Skipping update - {interval_seconds} second interval not yet reached. "
|
|
279
|
+
f"Last update time is {self.monitor._last_activation_mem_update_time} and now is"
|
|
280
|
+
f"{datetime.now()}"
|
|
281
|
+
)
|
|
60
282
|
|
|
61
283
|
def submit_messages(self, messages: ScheduleMessageItem | list[ScheduleMessageItem]):
|
|
62
284
|
"""Submit multiple messages to the message queue."""
|
|
@@ -68,15 +290,185 @@ class BaseScheduler(RedisSchedulerModule):
|
|
|
68
290
|
logger.info(f"Submitted message: {message.label} - {message.content}")
|
|
69
291
|
|
|
70
292
|
def _submit_web_logs(self, messages: ScheduleLogForWebItem | list[ScheduleLogForWebItem]):
|
|
293
|
+
"""Submit log messages to the web log queue and optionally to RabbitMQ.
|
|
294
|
+
|
|
295
|
+
Args:
|
|
296
|
+
messages: Single log message or list of log messages
|
|
297
|
+
"""
|
|
71
298
|
if isinstance(messages, ScheduleLogForWebItem):
|
|
72
299
|
messages = [messages] # transform single message to list
|
|
73
300
|
|
|
74
301
|
for message in messages:
|
|
75
302
|
self._web_log_message_queue.put(message)
|
|
303
|
+
logger.info(f"Submitted Scheduling log for web: {message.log_content}")
|
|
304
|
+
|
|
305
|
+
if self.is_rabbitmq_connected():
|
|
306
|
+
logger.info("Submitted Scheduling log to rabbitmq")
|
|
307
|
+
self.rabbitmq_publish_message(message=message.to_dict())
|
|
308
|
+
logger.debug(f"{len(messages)} submitted. {self._web_log_message_queue.qsize()} in queue.")
|
|
309
|
+
|
|
310
|
+
def log_activation_memory_update(
|
|
311
|
+
self,
|
|
312
|
+
original_text_memories: list[str],
|
|
313
|
+
new_text_memories: list[str],
|
|
314
|
+
label: str,
|
|
315
|
+
user_id: str,
|
|
316
|
+
mem_cube_id: str,
|
|
317
|
+
mem_cube: GeneralMemCube,
|
|
318
|
+
):
|
|
319
|
+
"""Log changes when activation memory is updated.
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
original_text_memories: List of original memory texts
|
|
323
|
+
new_text_memories: List of new memory texts
|
|
324
|
+
"""
|
|
325
|
+
original_set = set(original_text_memories)
|
|
326
|
+
new_set = set(new_text_memories)
|
|
327
|
+
|
|
328
|
+
# Identify changes
|
|
329
|
+
added_memories = list(new_set - original_set) # Present in new but not original
|
|
330
|
+
|
|
331
|
+
# recording messages
|
|
332
|
+
for mem in added_memories:
|
|
333
|
+
log_message_a = self.create_autofilled_log_item(
|
|
334
|
+
log_content=mem,
|
|
335
|
+
label=label,
|
|
336
|
+
from_memory_type=TEXT_MEMORY_TYPE,
|
|
337
|
+
to_memory_type=ACTIVATION_MEMORY_TYPE,
|
|
338
|
+
user_id=user_id,
|
|
339
|
+
mem_cube_id=mem_cube_id,
|
|
340
|
+
mem_cube=mem_cube,
|
|
341
|
+
)
|
|
342
|
+
log_message_b = self.create_autofilled_log_item(
|
|
343
|
+
log_content=mem,
|
|
344
|
+
label=label,
|
|
345
|
+
from_memory_type=ACTIVATION_MEMORY_TYPE,
|
|
346
|
+
to_memory_type=PARAMETER_MEMORY_TYPE,
|
|
347
|
+
user_id=user_id,
|
|
348
|
+
mem_cube_id=mem_cube_id,
|
|
349
|
+
mem_cube=mem_cube,
|
|
350
|
+
)
|
|
351
|
+
self._submit_web_logs(messages=[log_message_a, log_message_b])
|
|
76
352
|
logger.info(
|
|
77
|
-
f"
|
|
353
|
+
f"{len(added_memories)} {LONG_TERM_MEMORY_TYPE} memorie(s) "
|
|
354
|
+
f"transformed to {WORKING_MEMORY_TYPE} memories."
|
|
78
355
|
)
|
|
79
|
-
|
|
356
|
+
|
|
357
|
+
def log_working_memory_replacement(
|
|
358
|
+
self,
|
|
359
|
+
original_memory: list[TextualMemoryItem],
|
|
360
|
+
new_memory: list[TextualMemoryItem],
|
|
361
|
+
user_id: str,
|
|
362
|
+
mem_cube_id: str,
|
|
363
|
+
mem_cube: GeneralMemCube,
|
|
364
|
+
):
|
|
365
|
+
"""Log changes when working memory is replaced."""
|
|
366
|
+
memory_type_map = {
|
|
367
|
+
transform_name_to_key(name=m.memory): m.metadata.memory_type
|
|
368
|
+
for m in original_memory + new_memory
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
original_text_memories = [m.memory for m in original_memory]
|
|
372
|
+
new_text_memories = [m.memory for m in new_memory]
|
|
373
|
+
|
|
374
|
+
# Convert to sets for efficient difference operations
|
|
375
|
+
original_set = set(original_text_memories)
|
|
376
|
+
new_set = set(new_text_memories)
|
|
377
|
+
|
|
378
|
+
# Identify changes
|
|
379
|
+
added_memories = list(new_set - original_set) # Present in new but not original
|
|
380
|
+
|
|
381
|
+
# recording messages
|
|
382
|
+
for mem in added_memories:
|
|
383
|
+
normalized_mem = transform_name_to_key(name=mem)
|
|
384
|
+
if normalized_mem not in memory_type_map:
|
|
385
|
+
logger.error(f"Memory text not found in type mapping: {mem[:50]}...")
|
|
386
|
+
# Get the memory type from the map, default to LONG_TERM_MEMORY_TYPE if not found
|
|
387
|
+
mem_type = memory_type_map.get(normalized_mem, LONG_TERM_MEMORY_TYPE)
|
|
388
|
+
|
|
389
|
+
if mem_type == WORKING_MEMORY_TYPE:
|
|
390
|
+
logger.warning(f"Memory already in working memory: {mem[:50]}...")
|
|
391
|
+
continue
|
|
392
|
+
|
|
393
|
+
log_message = self.create_autofilled_log_item(
|
|
394
|
+
log_content=mem,
|
|
395
|
+
label=QUERY_LABEL,
|
|
396
|
+
from_memory_type=mem_type,
|
|
397
|
+
to_memory_type=WORKING_MEMORY_TYPE,
|
|
398
|
+
user_id=user_id,
|
|
399
|
+
mem_cube_id=mem_cube_id,
|
|
400
|
+
mem_cube=mem_cube,
|
|
401
|
+
)
|
|
402
|
+
self._submit_web_logs(messages=log_message)
|
|
403
|
+
logger.info(
|
|
404
|
+
f"{len(added_memories)} {LONG_TERM_MEMORY_TYPE} memorie(s) "
|
|
405
|
+
f"transformed to {WORKING_MEMORY_TYPE} memories."
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
def log_adding_user_inputs(
|
|
409
|
+
self,
|
|
410
|
+
user_inputs: list[str],
|
|
411
|
+
user_id: str,
|
|
412
|
+
mem_cube_id: str,
|
|
413
|
+
mem_cube: GeneralMemCube,
|
|
414
|
+
):
|
|
415
|
+
"""Log changes when working memory is replaced."""
|
|
416
|
+
|
|
417
|
+
# recording messages
|
|
418
|
+
for input_str in user_inputs:
|
|
419
|
+
log_message = self.create_autofilled_log_item(
|
|
420
|
+
log_content=input_str,
|
|
421
|
+
label=ADD_LABEL,
|
|
422
|
+
from_memory_type=USER_INPUT_TYPE,
|
|
423
|
+
to_memory_type=TEXT_MEMORY_TYPE,
|
|
424
|
+
user_id=user_id,
|
|
425
|
+
mem_cube_id=mem_cube_id,
|
|
426
|
+
mem_cube=mem_cube,
|
|
427
|
+
)
|
|
428
|
+
self._submit_web_logs(messages=log_message)
|
|
429
|
+
logger.info(
|
|
430
|
+
f"{len(user_inputs)} {USER_INPUT_TYPE} memorie(s) "
|
|
431
|
+
f"transformed to {TEXT_MEMORY_TYPE} memories."
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
def create_autofilled_log_item(
|
|
435
|
+
self,
|
|
436
|
+
log_content: str,
|
|
437
|
+
label: str,
|
|
438
|
+
from_memory_type: str,
|
|
439
|
+
to_memory_type: str,
|
|
440
|
+
user_id: str,
|
|
441
|
+
mem_cube_id: str,
|
|
442
|
+
mem_cube: GeneralMemCube,
|
|
443
|
+
) -> ScheduleLogForWebItem:
|
|
444
|
+
text_mem_base: TreeTextMemory = mem_cube.text_mem
|
|
445
|
+
current_memory_sizes = text_mem_base.get_current_memory_size()
|
|
446
|
+
current_memory_sizes = {
|
|
447
|
+
"long_term_memory_size": current_memory_sizes["LongTermMemory"],
|
|
448
|
+
"user_memory_size": current_memory_sizes["UserMemory"],
|
|
449
|
+
"working_memory_size": current_memory_sizes["WorkingMemory"],
|
|
450
|
+
"transformed_act_memory_size": NOT_INITIALIZED,
|
|
451
|
+
"parameter_memory_size": NOT_INITIALIZED,
|
|
452
|
+
}
|
|
453
|
+
memory_capacities = {
|
|
454
|
+
"long_term_memory_capacity": text_mem_base.memory_manager.memory_size["LongTermMemory"],
|
|
455
|
+
"user_memory_capacity": text_mem_base.memory_manager.memory_size["UserMemory"],
|
|
456
|
+
"working_memory_capacity": text_mem_base.memory_manager.memory_size["WorkingMemory"],
|
|
457
|
+
"transformed_act_memory_capacity": NOT_INITIALIZED,
|
|
458
|
+
"parameter_memory_capacity": NOT_INITIALIZED,
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
log_message = ScheduleLogForWebItem(
|
|
462
|
+
user_id=user_id,
|
|
463
|
+
mem_cube_id=mem_cube_id,
|
|
464
|
+
label=label,
|
|
465
|
+
from_memory_type=from_memory_type,
|
|
466
|
+
to_memory_type=to_memory_type,
|
|
467
|
+
log_content=log_content,
|
|
468
|
+
current_memory_sizes=current_memory_sizes,
|
|
469
|
+
memory_capacities=memory_capacities,
|
|
470
|
+
)
|
|
471
|
+
return log_message
|
|
80
472
|
|
|
81
473
|
def get_web_log_messages(self) -> list[dict]:
|
|
82
474
|
"""
|
|
@@ -87,13 +479,12 @@ class BaseScheduler(RedisSchedulerModule):
|
|
|
87
479
|
ready for JSON serialization. The list is ordered from oldest to newest.
|
|
88
480
|
"""
|
|
89
481
|
messages = []
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
messages.append(item_dict)
|
|
482
|
+
while True:
|
|
483
|
+
try:
|
|
484
|
+
item = self._web_log_message_queue.get_nowait() # 线程安全的 get
|
|
485
|
+
messages.append(item.to_dict())
|
|
486
|
+
except queue.Empty:
|
|
487
|
+
break
|
|
97
488
|
return messages
|
|
98
489
|
|
|
99
490
|
def _message_consumer(self) -> None:
|
|
@@ -133,32 +524,72 @@ class BaseScheduler(RedisSchedulerModule):
|
|
|
133
524
|
|
|
134
525
|
def start(self) -> None:
|
|
135
526
|
"""
|
|
136
|
-
Start the message consumer thread.
|
|
527
|
+
Start the message consumer thread and initialize dispatcher resources.
|
|
137
528
|
|
|
138
|
-
Initializes and starts
|
|
139
|
-
|
|
529
|
+
Initializes and starts:
|
|
530
|
+
1. Message consumer thread
|
|
531
|
+
2. Dispatcher thread pool (if parallel dispatch enabled)
|
|
140
532
|
"""
|
|
141
|
-
if self.
|
|
142
|
-
logger.warning("
|
|
533
|
+
if self._running:
|
|
534
|
+
logger.warning("Memory Scheduler is already running")
|
|
143
535
|
return
|
|
144
536
|
|
|
537
|
+
# Initialize dispatcher resources
|
|
538
|
+
if self.enable_parallel_dispatch:
|
|
539
|
+
logger.info(f"Initializing dispatcher thread pool with {self.max_workers} workers")
|
|
540
|
+
|
|
541
|
+
# Start consumer thread
|
|
145
542
|
self._running = True
|
|
146
543
|
self._consumer_thread = threading.Thread(
|
|
147
544
|
target=self._message_consumer,
|
|
148
|
-
daemon=True,
|
|
545
|
+
daemon=True,
|
|
149
546
|
name="MessageConsumerThread",
|
|
150
547
|
)
|
|
151
548
|
self._consumer_thread.start()
|
|
152
549
|
logger.info("Message consumer thread started")
|
|
153
550
|
|
|
154
551
|
def stop(self) -> None:
|
|
155
|
-
"""Stop
|
|
156
|
-
|
|
157
|
-
|
|
552
|
+
"""Stop all scheduler components gracefully.
|
|
553
|
+
|
|
554
|
+
1. Stops message consumer thread
|
|
555
|
+
2. Shuts down dispatcher thread pool
|
|
556
|
+
3. Cleans up resources
|
|
557
|
+
"""
|
|
558
|
+
if not self._running:
|
|
559
|
+
logger.warning("Memory Scheduler is not running")
|
|
158
560
|
return
|
|
561
|
+
|
|
562
|
+
# Signal consumer thread to stop
|
|
159
563
|
self._running = False
|
|
160
|
-
|
|
161
|
-
|
|
564
|
+
|
|
565
|
+
# Wait for consumer thread
|
|
566
|
+
if self._consumer_thread and self._consumer_thread.is_alive():
|
|
567
|
+
self._consumer_thread.join(timeout=5.0)
|
|
162
568
|
if self._consumer_thread.is_alive():
|
|
163
569
|
logger.warning("Consumer thread did not stop gracefully")
|
|
164
|
-
|
|
570
|
+
else:
|
|
571
|
+
logger.info("Consumer thread stopped")
|
|
572
|
+
|
|
573
|
+
# Shutdown dispatcher
|
|
574
|
+
if hasattr(self, "dispatcher") and self.dispatcher:
|
|
575
|
+
logger.info("Shutting down dispatcher...")
|
|
576
|
+
self.dispatcher.shutdown()
|
|
577
|
+
|
|
578
|
+
# Clean up queues
|
|
579
|
+
self._cleanup_queues()
|
|
580
|
+
logger.info("Memory Scheduler stopped completely")
|
|
581
|
+
|
|
582
|
+
def _cleanup_queues(self) -> None:
|
|
583
|
+
"""Ensure all queues are emptied and marked as closed."""
|
|
584
|
+
try:
|
|
585
|
+
while not self.memos_message_queue.empty():
|
|
586
|
+
self.memos_message_queue.get_nowait()
|
|
587
|
+
self.memos_message_queue.task_done()
|
|
588
|
+
except queue.Empty:
|
|
589
|
+
pass
|
|
590
|
+
|
|
591
|
+
try:
|
|
592
|
+
while not self._web_log_message_queue.empty():
|
|
593
|
+
self._web_log_message_queue.get_nowait()
|
|
594
|
+
except queue.Empty:
|
|
595
|
+
pass
|