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
|
@@ -1,27 +1,16 @@
|
|
|
1
1
|
import json
|
|
2
2
|
|
|
3
|
-
from datetime import datetime, timedelta
|
|
4
|
-
|
|
5
3
|
from memos.configs.mem_scheduler import GeneralSchedulerConfig
|
|
6
|
-
from memos.llms.base import BaseLLM
|
|
7
4
|
from memos.log import get_logger
|
|
8
5
|
from memos.mem_cube.general import GeneralMemCube
|
|
9
6
|
from memos.mem_scheduler.base_scheduler import BaseScheduler
|
|
10
|
-
from memos.mem_scheduler.modules.monitor import SchedulerMonitor
|
|
11
|
-
from memos.mem_scheduler.modules.retriever import SchedulerRetriever
|
|
12
7
|
from memos.mem_scheduler.modules.schemas import (
|
|
8
|
+
ADD_LABEL,
|
|
13
9
|
ANSWER_LABEL,
|
|
14
|
-
DEFAULT_ACT_MEM_DUMP_PATH,
|
|
15
|
-
DEFAULT_ACTIVATION_MEM_SIZE,
|
|
16
|
-
NOT_INITIALIZED,
|
|
17
10
|
QUERY_LABEL,
|
|
18
|
-
ScheduleLogForWebItem,
|
|
19
11
|
ScheduleMessageItem,
|
|
20
|
-
TextMemory_SEARCH_METHOD,
|
|
21
|
-
TreeTextMemory_SEARCH_METHOD,
|
|
22
12
|
)
|
|
23
13
|
from memos.memories.textual.tree import TextualMemoryItem, TreeTextMemory
|
|
24
|
-
from memos.templates.mem_scheduler_prompts import MEMORY_ASSEMBLY_TEMPLATE
|
|
25
14
|
|
|
26
15
|
|
|
27
16
|
logger = get_logger(__name__)
|
|
@@ -31,275 +20,167 @@ class GeneralScheduler(BaseScheduler):
|
|
|
31
20
|
def __init__(self, config: GeneralSchedulerConfig):
|
|
32
21
|
"""Initialize the scheduler with the given configuration."""
|
|
33
22
|
super().__init__(config)
|
|
34
|
-
self.top_k = self.config.get("top_k", 10)
|
|
35
|
-
self.top_n = self.config.get("top_n", 5)
|
|
36
|
-
self.act_mem_update_interval = self.config.get("act_mem_update_interval", 300)
|
|
37
|
-
self.context_window_size = self.config.get("context_window_size", 5)
|
|
38
|
-
self.activation_mem_size = self.config.get(
|
|
39
|
-
"activation_mem_size", DEFAULT_ACTIVATION_MEM_SIZE
|
|
40
|
-
)
|
|
41
|
-
self.act_mem_dump_path = self.config.get("act_mem_dump_path", DEFAULT_ACT_MEM_DUMP_PATH)
|
|
42
|
-
self.search_method = TextMemory_SEARCH_METHOD
|
|
43
|
-
self._last_activation_mem_update_time = 0.0
|
|
44
|
-
self.query_list = []
|
|
45
23
|
|
|
46
24
|
# register handlers
|
|
47
25
|
handlers = {
|
|
48
|
-
QUERY_LABEL: self.
|
|
49
|
-
ANSWER_LABEL: self.
|
|
26
|
+
QUERY_LABEL: self._query_message_consumer,
|
|
27
|
+
ANSWER_LABEL: self._answer_message_consumer,
|
|
28
|
+
ADD_LABEL: self._add_message_consumer,
|
|
50
29
|
}
|
|
51
30
|
self.dispatcher.register_handlers(handlers)
|
|
52
31
|
|
|
53
|
-
def
|
|
54
|
-
self.chat_llm = chat_llm
|
|
55
|
-
self.monitor = SchedulerMonitor(
|
|
56
|
-
chat_llm=self.chat_llm, activation_mem_size=self.activation_mem_size
|
|
57
|
-
)
|
|
58
|
-
self.retriever = SchedulerRetriever(chat_llm=self.chat_llm)
|
|
59
|
-
logger.debug("GeneralScheduler has been initialized")
|
|
60
|
-
|
|
61
|
-
def _answer_message_consume(self, messages: list[ScheduleMessageItem]) -> None:
|
|
32
|
+
def _query_message_consumer(self, messages: list[ScheduleMessageItem]) -> None:
|
|
62
33
|
"""
|
|
63
|
-
Process and handle
|
|
34
|
+
Process and handle query trigger messages from the queue.
|
|
64
35
|
|
|
65
36
|
Args:
|
|
66
|
-
|
|
37
|
+
messages: List of query messages to process
|
|
67
38
|
"""
|
|
68
|
-
|
|
69
|
-
logger.debug(f"Messages {messages} assigned to {ANSWER_LABEL} handler.")
|
|
70
|
-
for msg in messages:
|
|
71
|
-
if msg.label is not ANSWER_LABEL:
|
|
72
|
-
logger.error(f"_answer_message_consume is not designed for {msg.label}")
|
|
73
|
-
continue
|
|
74
|
-
answer = msg.content
|
|
75
|
-
self._current_user_id = msg.user_id
|
|
76
|
-
self._current_mem_cube_id = msg.mem_cube_id
|
|
77
|
-
self._current_mem_cube = msg.mem_cube
|
|
39
|
+
logger.debug(f"Messages {messages} assigned to {QUERY_LABEL} handler.")
|
|
78
40
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
item["memory"]
|
|
82
|
-
for item in self.monitor.activation_memory_freq_list
|
|
83
|
-
if item["memory"] is not None
|
|
84
|
-
]
|
|
41
|
+
# Process the query in a session turn
|
|
42
|
+
grouped_messages = self.dispatcher.group_messages_by_user_and_cube(messages=messages)
|
|
85
43
|
|
|
86
|
-
|
|
87
|
-
# TODO: not implemented
|
|
88
|
-
self.monitor.activation_memory_freq_list = self.monitor.update_freq(
|
|
89
|
-
answer=answer, activation_memory_freq_list=self.monitor.activation_memory_freq_list
|
|
90
|
-
)
|
|
44
|
+
self._validate_messages(messages=messages, label=QUERY_LABEL)
|
|
91
45
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
# TODO: not implemented
|
|
98
|
-
self.update_activation_memory(current_activation_mem)
|
|
99
|
-
self._last_activation_mem_update_time = now
|
|
46
|
+
for user_id in grouped_messages:
|
|
47
|
+
for mem_cube_id in grouped_messages[user_id]:
|
|
48
|
+
messages = grouped_messages[user_id][mem_cube_id]
|
|
49
|
+
if len(messages) == 0:
|
|
50
|
+
return
|
|
100
51
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
log_title="memos answer triggers scheduling...",
|
|
104
|
-
label=ANSWER_LABEL,
|
|
105
|
-
log_content="activation_memory has been updated",
|
|
106
|
-
)
|
|
107
|
-
self._submit_web_logs(messages=log_message)
|
|
52
|
+
# for status update
|
|
53
|
+
self._set_current_context_from_message(msg=messages[0])
|
|
108
54
|
|
|
109
|
-
|
|
55
|
+
self.process_session_turn(
|
|
56
|
+
queries=[msg.content for msg in messages],
|
|
57
|
+
user_id=user_id,
|
|
58
|
+
mem_cube_id=mem_cube_id,
|
|
59
|
+
mem_cube=messages[0].mem_cube,
|
|
60
|
+
top_k=self.top_k,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
def _answer_message_consumer(self, messages: list[ScheduleMessageItem]) -> None:
|
|
110
64
|
"""
|
|
111
|
-
Process and handle
|
|
65
|
+
Process and handle answer trigger messages from the queue.
|
|
112
66
|
|
|
113
67
|
Args:
|
|
114
|
-
|
|
68
|
+
messages: List of answer messages to process
|
|
115
69
|
"""
|
|
116
|
-
logger.debug(f"Messages {messages} assigned to {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
70
|
+
logger.debug(f"Messages {messages} assigned to {ANSWER_LABEL} handler.")
|
|
71
|
+
# Process the query in a session turn
|
|
72
|
+
grouped_messages = self.dispatcher.group_messages_by_user_and_cube(messages=messages)
|
|
73
|
+
|
|
74
|
+
self._validate_messages(messages=messages, label=ANSWER_LABEL)
|
|
75
|
+
|
|
76
|
+
for user_id in grouped_messages:
|
|
77
|
+
for mem_cube_id in grouped_messages[user_id]:
|
|
78
|
+
messages = grouped_messages[user_id][mem_cube_id]
|
|
79
|
+
if len(messages) == 0:
|
|
80
|
+
return
|
|
81
|
+
|
|
82
|
+
# for status update
|
|
83
|
+
self._set_current_context_from_message(msg=messages[0])
|
|
84
|
+
|
|
85
|
+
# update acivation memories
|
|
86
|
+
if self.enable_act_memory_update:
|
|
87
|
+
self.update_activation_memory_periodically(
|
|
88
|
+
interval_seconds=self.monitor.act_mem_update_interval,
|
|
89
|
+
label=ANSWER_LABEL,
|
|
90
|
+
user_id=user_id,
|
|
91
|
+
mem_cube_id=mem_cube_id,
|
|
92
|
+
mem_cube=messages[0].mem_cube,
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
def _add_message_consumer(self, messages: list[ScheduleMessageItem]) -> None:
|
|
96
|
+
logger.debug(f"Messages {messages} assigned to {ADD_LABEL} handler.")
|
|
97
|
+
# Process the query in a session turn
|
|
98
|
+
grouped_messages = self.dispatcher.group_messages_by_user_and_cube(messages=messages)
|
|
99
|
+
|
|
100
|
+
self._validate_messages(messages=messages, label=ADD_LABEL)
|
|
101
|
+
|
|
102
|
+
for user_id in grouped_messages:
|
|
103
|
+
for mem_cube_id in grouped_messages[user_id]:
|
|
104
|
+
messages = grouped_messages[user_id][mem_cube_id]
|
|
105
|
+
if len(messages) == 0:
|
|
106
|
+
return
|
|
107
|
+
|
|
108
|
+
# for status update
|
|
109
|
+
self._set_current_context_from_message(msg=messages[0])
|
|
110
|
+
|
|
111
|
+
# submit logs
|
|
112
|
+
for msg in messages:
|
|
113
|
+
user_inputs = json.loads(msg.content)
|
|
114
|
+
self.log_adding_user_inputs(
|
|
115
|
+
user_inputs=user_inputs,
|
|
116
|
+
user_id=msg.user_id,
|
|
117
|
+
mem_cube_id=msg.mem_cube_id,
|
|
118
|
+
mem_cube=msg.mem_cube,
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# update acivation memories
|
|
122
|
+
if self.enable_act_memory_update:
|
|
123
|
+
self.update_activation_memory_periodically(
|
|
124
|
+
interval_seconds=self.monitor.act_mem_update_interval,
|
|
125
|
+
label=ADD_LABEL,
|
|
126
|
+
user_id=user_id,
|
|
127
|
+
mem_cube_id=mem_cube_id,
|
|
128
|
+
mem_cube=messages[0].mem_cube,
|
|
129
|
+
)
|
|
126
130
|
|
|
127
131
|
def process_session_turn(
|
|
128
132
|
self,
|
|
129
|
-
|
|
133
|
+
queries: str | list[str],
|
|
134
|
+
user_id: str,
|
|
135
|
+
mem_cube_id: str,
|
|
136
|
+
mem_cube: GeneralMemCube,
|
|
130
137
|
top_k: int = 10,
|
|
131
|
-
|
|
138
|
+
query_history: list[str] | None = None,
|
|
132
139
|
) -> None:
|
|
133
140
|
"""
|
|
134
141
|
Process a dialog turn:
|
|
135
142
|
- If q_list reaches window size, trigger retrieval;
|
|
136
143
|
- Immediately switch to the new memory if retrieval is triggered.
|
|
137
144
|
"""
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
if
|
|
142
|
-
|
|
145
|
+
if isinstance(queries, str):
|
|
146
|
+
queries = [queries]
|
|
147
|
+
|
|
148
|
+
if query_history is None:
|
|
149
|
+
query_history = queries
|
|
143
150
|
else:
|
|
144
|
-
|
|
151
|
+
query_history.extend(queries)
|
|
152
|
+
|
|
153
|
+
text_mem_base = mem_cube.text_mem
|
|
154
|
+
if not isinstance(text_mem_base, TreeTextMemory):
|
|
155
|
+
logger.error("Not implemented!", exc_info=True)
|
|
145
156
|
return
|
|
157
|
+
|
|
158
|
+
working_memory: list[TextualMemoryItem] = text_mem_base.get_working_memory()
|
|
146
159
|
text_working_memory: list[str] = [w_m.memory for w_m in working_memory]
|
|
147
160
|
intent_result = self.monitor.detect_intent(
|
|
148
|
-
q_list=
|
|
161
|
+
q_list=query_history, text_working_memory=text_working_memory
|
|
149
162
|
)
|
|
163
|
+
|
|
150
164
|
if intent_result["trigger_retrieval"]:
|
|
151
|
-
|
|
152
|
-
num_evidence = len(
|
|
165
|
+
missing_evidences = intent_result["missing_evidences"]
|
|
166
|
+
num_evidence = len(missing_evidences)
|
|
153
167
|
k_per_evidence = max(1, top_k // max(1, num_evidence))
|
|
154
168
|
new_candidates = []
|
|
155
|
-
for item in
|
|
156
|
-
logger.debug(f"
|
|
157
|
-
results = self.search(
|
|
158
|
-
|
|
169
|
+
for item in missing_evidences:
|
|
170
|
+
logger.debug(f"missing_evidences: {item}")
|
|
171
|
+
results = self.retriever.search(
|
|
172
|
+
query=item, mem_cube=mem_cube, top_k=k_per_evidence, method=self.search_method
|
|
173
|
+
)
|
|
174
|
+
logger.debug(f"search results for {missing_evidences}: {results}")
|
|
159
175
|
new_candidates.extend(results)
|
|
160
176
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
original_memory=working_memory, new_memory=new_candidates, top_k=top_k, top_n=top_n
|
|
170
|
-
)
|
|
171
|
-
self.update_activation_memory(new_order_working_memory)
|
|
172
|
-
|
|
173
|
-
def create_autofilled_log_item(
|
|
174
|
-
self, log_title: str, log_content: str, label: str
|
|
175
|
-
) -> ScheduleLogForWebItem:
|
|
176
|
-
# TODO: create the log iterm with real stats
|
|
177
|
-
text_mem_base: TreeTextMemory = self.mem_cube.text_mem
|
|
178
|
-
current_memory_sizes = {
|
|
179
|
-
"long_term_memory_size": NOT_INITIALIZED,
|
|
180
|
-
"user_memory_size": NOT_INITIALIZED,
|
|
181
|
-
"working_memory_size": NOT_INITIALIZED,
|
|
182
|
-
"transformed_act_memory_size": NOT_INITIALIZED,
|
|
183
|
-
"parameter_memory_size": NOT_INITIALIZED,
|
|
184
|
-
}
|
|
185
|
-
memory_capacities = {
|
|
186
|
-
"long_term_memory_capacity": text_mem_base.memory_manager.memory_size["LongTermMemory"],
|
|
187
|
-
"user_memory_capacity": text_mem_base.memory_manager.memory_size["UserMemory"],
|
|
188
|
-
"working_memory_capacity": text_mem_base.memory_manager.memory_size["WorkingMemory"],
|
|
189
|
-
"transformed_act_memory_capacity": NOT_INITIALIZED,
|
|
190
|
-
"parameter_memory_capacity": NOT_INITIALIZED,
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
log_message = ScheduleLogForWebItem(
|
|
194
|
-
user_id=self._current_user_id,
|
|
195
|
-
mem_cube_id=self._current_mem_cube_id,
|
|
196
|
-
label=label,
|
|
197
|
-
log_title=log_title,
|
|
198
|
-
log_content=log_content,
|
|
199
|
-
current_memory_sizes=current_memory_sizes,
|
|
200
|
-
memory_capacities=memory_capacities,
|
|
201
|
-
)
|
|
202
|
-
return log_message
|
|
203
|
-
|
|
204
|
-
@property
|
|
205
|
-
def mem_cube(self) -> GeneralMemCube:
|
|
206
|
-
"""The memory cube associated with this MemChat."""
|
|
207
|
-
return self._current_mem_cube
|
|
208
|
-
|
|
209
|
-
@mem_cube.setter
|
|
210
|
-
def mem_cube(self, value: GeneralMemCube) -> None:
|
|
211
|
-
"""The memory cube associated with this MemChat."""
|
|
212
|
-
self._current_mem_cube = value
|
|
213
|
-
self.retriever.mem_cube = value
|
|
214
|
-
|
|
215
|
-
def replace_working_memory(
|
|
216
|
-
self,
|
|
217
|
-
original_memory: list[TextualMemoryItem],
|
|
218
|
-
new_memory: list[TextualMemoryItem],
|
|
219
|
-
top_k: int = 10,
|
|
220
|
-
top_n: int = 5,
|
|
221
|
-
) -> None | list[TextualMemoryItem]:
|
|
222
|
-
new_order_memory = None
|
|
223
|
-
text_mem_base = self.mem_cube.text_mem
|
|
224
|
-
if isinstance(text_mem_base, TreeTextMemory):
|
|
225
|
-
text_mem_base: TreeTextMemory = text_mem_base
|
|
226
|
-
combined_text_memory = [new_m.memory for new_m in original_memory] + [
|
|
227
|
-
new_m.memory for new_m in new_memory
|
|
228
|
-
]
|
|
229
|
-
combined_memory = original_memory + new_memory
|
|
230
|
-
memory_map = {mem_obj.memory: mem_obj for mem_obj in combined_memory}
|
|
231
|
-
|
|
232
|
-
unique_memory = list(dict.fromkeys(combined_text_memory))
|
|
233
|
-
prompt = self.build_prompt(
|
|
234
|
-
"memory_reranking", query="", current_order=unique_memory, staging_buffer=[]
|
|
235
|
-
)
|
|
236
|
-
response = self.chat_llm.generate([{"role": "user", "content": prompt}])
|
|
237
|
-
response = json.loads(response)
|
|
238
|
-
new_order_text_memory = response.get("new_order", [])[: top_n + top_k]
|
|
239
|
-
|
|
240
|
-
new_order_memory = []
|
|
241
|
-
for text in new_order_text_memory:
|
|
242
|
-
if text in memory_map:
|
|
243
|
-
new_order_memory.append(memory_map[text])
|
|
244
|
-
else:
|
|
245
|
-
logger.warning(
|
|
246
|
-
f"Memory text not found in memory map. text: {text}; memory_map: {memory_map}"
|
|
247
|
-
)
|
|
248
|
-
|
|
249
|
-
text_mem_base.replace_working_memory(new_order_memory[top_n:])
|
|
250
|
-
new_order_memory = new_order_memory[:top_n]
|
|
251
|
-
logger.info(
|
|
252
|
-
f"The working memory has been replaced with {len(new_order_memory)} new memories."
|
|
253
|
-
)
|
|
254
|
-
else:
|
|
255
|
-
logger.error("memory_base is not supported")
|
|
256
|
-
|
|
257
|
-
return new_order_memory
|
|
258
|
-
|
|
259
|
-
def search(self, query: str, top_k: int, method=TreeTextMemory_SEARCH_METHOD):
|
|
260
|
-
text_mem_base = self.mem_cube.text_mem
|
|
261
|
-
if isinstance(text_mem_base, TreeTextMemory) and method == TextMemory_SEARCH_METHOD:
|
|
262
|
-
results_long_term = text_mem_base.search(
|
|
263
|
-
query=query, top_k=top_k, memory_type="LongTermMemory"
|
|
264
|
-
)
|
|
265
|
-
results_user = text_mem_base.search(query=query, top_k=top_k, memory_type="UserMemory")
|
|
266
|
-
results = results_long_term + results_user
|
|
267
|
-
else:
|
|
268
|
-
logger.error("Not implemented.")
|
|
269
|
-
results = None
|
|
270
|
-
return results
|
|
271
|
-
|
|
272
|
-
def update_activation_memory(self, new_memory: list[str | TextualMemoryItem]) -> None:
|
|
273
|
-
"""
|
|
274
|
-
Update activation memory by extracting KVCacheItems from new_memory (list of str),
|
|
275
|
-
add them to a KVCacheMemory instance, and dump to disk.
|
|
276
|
-
"""
|
|
277
|
-
# TODO: The function of update activation memory is waiting to test
|
|
278
|
-
if len(new_memory) == 0:
|
|
279
|
-
logger.error("update_activation_memory: new_memory is empty.")
|
|
280
|
-
return
|
|
281
|
-
if isinstance(new_memory[0], TextualMemoryItem):
|
|
282
|
-
new_text_memory = [mem.memory for mem in new_memory]
|
|
283
|
-
elif isinstance(new_memory[0], str):
|
|
284
|
-
new_text_memory = new_memory
|
|
285
|
-
else:
|
|
286
|
-
logger.error("Not Implemented.")
|
|
287
|
-
|
|
288
|
-
try:
|
|
289
|
-
act_mem = self.mem_cube.act_mem
|
|
290
|
-
|
|
291
|
-
text_memory = MEMORY_ASSEMBLY_TEMPLATE.format(
|
|
292
|
-
memory_text="".join(
|
|
293
|
-
[
|
|
294
|
-
f"{i + 1}. {sentence.strip()}\n"
|
|
295
|
-
for i, sentence in enumerate(new_text_memory)
|
|
296
|
-
if sentence.strip() # Skip empty strings
|
|
297
|
-
]
|
|
298
|
-
)
|
|
177
|
+
new_order_working_memory = self.retriever.replace_working_memory(
|
|
178
|
+
queries=queries,
|
|
179
|
+
user_id=user_id,
|
|
180
|
+
mem_cube_id=mem_cube_id,
|
|
181
|
+
mem_cube=mem_cube,
|
|
182
|
+
original_memory=working_memory,
|
|
183
|
+
new_memory=new_candidates,
|
|
184
|
+
top_k=top_k,
|
|
299
185
|
)
|
|
300
|
-
|
|
301
|
-
cache_item = act_mem.extract(text_memory)
|
|
302
|
-
act_mem.add(cache_item)
|
|
303
|
-
act_mem.dump(self.act_mem_dump_path)
|
|
304
|
-
except Exception as e:
|
|
305
|
-
logger.warning(f"MOS-based activation memory update failed: {e}")
|
|
186
|
+
logger.debug(f"size of new_order_working_memory: {len(new_order_working_memory)}")
|
|
@@ -16,6 +16,7 @@ class BaseSchedulerModule:
|
|
|
16
16
|
self.base_dir = Path(BASE_DIR)
|
|
17
17
|
|
|
18
18
|
self._chat_llm = None
|
|
19
|
+
self._process_llm = None
|
|
19
20
|
self._current_mem_cube_id: str | None = None
|
|
20
21
|
self._current_mem_cube: GeneralMemCube | None = None
|
|
21
22
|
self.mem_cubes: dict[str, GeneralMemCube] = {}
|
|
@@ -63,6 +64,14 @@ class BaseSchedulerModule:
|
|
|
63
64
|
"""The memory cube associated with this MemChat."""
|
|
64
65
|
self._chat_llm = value
|
|
65
66
|
|
|
67
|
+
@property
|
|
68
|
+
def process_llm(self) -> BaseLLM:
|
|
69
|
+
return self._process_llm
|
|
70
|
+
|
|
71
|
+
@process_llm.setter
|
|
72
|
+
def process_llm(self, value: BaseLLM) -> None:
|
|
73
|
+
self._process_llm = value
|
|
74
|
+
|
|
66
75
|
@property
|
|
67
76
|
def mem_cube(self) -> GeneralMemCube:
|
|
68
77
|
"""The memory cube associated with this MemChat."""
|
|
@@ -73,6 +73,38 @@ class SchedulerDispatcher(BaseSchedulerModule):
|
|
|
73
73
|
def _default_message_handler(self, messages: list[ScheduleMessageItem]) -> None:
|
|
74
74
|
logger.debug(f"Using _default_message_handler to deal with messages: {messages}")
|
|
75
75
|
|
|
76
|
+
def group_messages_by_user_and_cube(
|
|
77
|
+
self, messages: list[ScheduleMessageItem]
|
|
78
|
+
) -> dict[str, dict[str, list[ScheduleMessageItem]]]:
|
|
79
|
+
"""
|
|
80
|
+
Groups messages into a nested dictionary structure first by user_id, then by mem_cube_id.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
messages: List of ScheduleMessageItem objects to be grouped
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
A nested dictionary with the structure:
|
|
87
|
+
{
|
|
88
|
+
"user_id_1": {
|
|
89
|
+
"mem_cube_id_1": [msg1, msg2, ...],
|
|
90
|
+
"mem_cube_id_2": [msg3, msg4, ...],
|
|
91
|
+
...
|
|
92
|
+
},
|
|
93
|
+
"user_id_2": {
|
|
94
|
+
...
|
|
95
|
+
},
|
|
96
|
+
...
|
|
97
|
+
}
|
|
98
|
+
Where each msg is the original ScheduleMessageItem object
|
|
99
|
+
"""
|
|
100
|
+
grouped_dict = defaultdict(lambda: defaultdict(list))
|
|
101
|
+
|
|
102
|
+
for msg in messages:
|
|
103
|
+
grouped_dict[msg.user_id][msg.mem_cube_id].append(msg)
|
|
104
|
+
|
|
105
|
+
# Convert defaultdict to regular dict for cleaner output
|
|
106
|
+
return {user_id: dict(cube_groups) for user_id, cube_groups in grouped_dict.items()}
|
|
107
|
+
|
|
76
108
|
def dispatch(self, msg_list: list[ScheduleMessageItem]):
|
|
77
109
|
"""
|
|
78
110
|
Dispatch a list of messages to their respective handlers.
|
|
@@ -98,6 +130,40 @@ class SchedulerDispatcher(BaseSchedulerModule):
|
|
|
98
130
|
# dispatch to different handler
|
|
99
131
|
logger.debug(f"Dispatch {len(msgs)} messages to {label} handler.")
|
|
100
132
|
if self.enable_parallel_dispatch and self.dispatcher_executor is not None:
|
|
101
|
-
|
|
133
|
+
# Capture variables in lambda to avoid loop variable issues
|
|
134
|
+
# TODO check this
|
|
135
|
+
future = self.dispatcher_executor.submit(handler, msgs)
|
|
136
|
+
logger.debug(f"Dispatched {len(msgs)} messages as future task")
|
|
137
|
+
return future
|
|
102
138
|
else:
|
|
103
|
-
handler(msgs)
|
|
139
|
+
handler(msgs)
|
|
140
|
+
return None
|
|
141
|
+
|
|
142
|
+
def join(self, timeout: float | None = None) -> bool:
|
|
143
|
+
"""Wait for all dispatched tasks to complete.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
timeout: Maximum time to wait in seconds. None means wait forever.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
bool: True if all tasks completed, False if timeout occurred.
|
|
150
|
+
"""
|
|
151
|
+
if not self.enable_parallel_dispatch or self.dispatcher_executor is None:
|
|
152
|
+
return True # 串行模式无需等待
|
|
153
|
+
|
|
154
|
+
self.dispatcher_executor.shutdown(wait=True, timeout=timeout)
|
|
155
|
+
return True
|
|
156
|
+
|
|
157
|
+
def shutdown(self) -> None:
|
|
158
|
+
"""Gracefully shutdown the dispatcher."""
|
|
159
|
+
if self.dispatcher_executor is not None:
|
|
160
|
+
self.dispatcher_executor.shutdown(wait=True)
|
|
161
|
+
self._running = False
|
|
162
|
+
logger.info("Dispatcher has been shutdown")
|
|
163
|
+
|
|
164
|
+
def __enter__(self):
|
|
165
|
+
self._running = True
|
|
166
|
+
return self
|
|
167
|
+
|
|
168
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
169
|
+
self.shutdown()
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import threading
|
|
2
|
+
|
|
3
|
+
from queue import Empty, Full, Queue
|
|
4
|
+
from typing import TypeVar
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
T = TypeVar("T")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AutoDroppingQueue(Queue[T]):
|
|
11
|
+
"""A thread-safe queue that automatically drops the oldest item when full."""
|
|
12
|
+
|
|
13
|
+
def __init__(self, maxsize: int = 0):
|
|
14
|
+
super().__init__(maxsize=maxsize)
|
|
15
|
+
self._lock = threading.Lock() # Additional lock to prevent race conditions
|
|
16
|
+
|
|
17
|
+
def put(self, item: T, block: bool = True, timeout: float | None = None) -> None:
|
|
18
|
+
"""Put an item into the queue.
|
|
19
|
+
|
|
20
|
+
If the queue is full, the oldest item will be automatically removed to make space.
|
|
21
|
+
This operation is thread-safe.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
item: The item to be put into the queue
|
|
25
|
+
block: Ignored (kept for compatibility with Queue interface)
|
|
26
|
+
timeout: Ignored (kept for compatibility with Queue interface)
|
|
27
|
+
"""
|
|
28
|
+
with self._lock: # Ensure atomic operation
|
|
29
|
+
try:
|
|
30
|
+
# First try non-blocking put
|
|
31
|
+
super().put(item, block=False)
|
|
32
|
+
except Full:
|
|
33
|
+
# If queue is full, remove the oldest item
|
|
34
|
+
from contextlib import suppress
|
|
35
|
+
|
|
36
|
+
with suppress(Empty):
|
|
37
|
+
self.get_nowait() # Remove oldest item
|
|
38
|
+
# Retry putting the new item
|
|
39
|
+
super().put(item, block=False)
|