MemoryOS 0.2.1__py3-none-any.whl → 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of MemoryOS might be problematic. Click here for more details.
- {memoryos-0.2.1.dist-info → memoryos-1.0.0.dist-info}/METADATA +7 -1
- {memoryos-0.2.1.dist-info → memoryos-1.0.0.dist-info}/RECORD +87 -64
- memos/__init__.py +1 -1
- memos/api/config.py +158 -69
- memos/api/context/context.py +147 -0
- memos/api/context/dependencies.py +101 -0
- memos/api/product_models.py +5 -1
- memos/api/routers/product_router.py +54 -26
- memos/configs/graph_db.py +49 -1
- memos/configs/internet_retriever.py +19 -0
- memos/configs/mem_os.py +5 -0
- memos/configs/mem_reader.py +9 -0
- memos/configs/mem_scheduler.py +54 -18
- memos/configs/mem_user.py +58 -0
- memos/graph_dbs/base.py +38 -3
- memos/graph_dbs/factory.py +2 -0
- memos/graph_dbs/nebular.py +1612 -0
- memos/graph_dbs/neo4j.py +18 -9
- memos/log.py +6 -1
- memos/mem_cube/utils.py +13 -6
- memos/mem_os/core.py +157 -37
- memos/mem_os/main.py +2 -2
- memos/mem_os/product.py +252 -201
- memos/mem_os/utils/default_config.py +1 -1
- memos/mem_os/utils/format_utils.py +281 -70
- memos/mem_os/utils/reference_utils.py +133 -0
- memos/mem_reader/simple_struct.py +13 -5
- memos/mem_scheduler/base_scheduler.py +239 -266
- memos/mem_scheduler/{modules → general_modules}/base.py +4 -5
- memos/mem_scheduler/{modules → general_modules}/dispatcher.py +57 -21
- memos/mem_scheduler/general_modules/misc.py +104 -0
- memos/mem_scheduler/{modules → general_modules}/rabbitmq_service.py +12 -10
- memos/mem_scheduler/{modules → general_modules}/redis_service.py +1 -1
- memos/mem_scheduler/general_modules/retriever.py +199 -0
- memos/mem_scheduler/general_modules/scheduler_logger.py +261 -0
- memos/mem_scheduler/general_scheduler.py +243 -80
- memos/mem_scheduler/monitors/__init__.py +0 -0
- memos/mem_scheduler/monitors/dispatcher_monitor.py +305 -0
- memos/mem_scheduler/{modules/monitor.py → monitors/general_monitor.py} +106 -57
- memos/mem_scheduler/mos_for_test_scheduler.py +23 -20
- memos/mem_scheduler/schemas/__init__.py +0 -0
- memos/mem_scheduler/schemas/general_schemas.py +44 -0
- memos/mem_scheduler/schemas/message_schemas.py +149 -0
- memos/mem_scheduler/schemas/monitor_schemas.py +337 -0
- memos/mem_scheduler/utils/__init__.py +0 -0
- memos/mem_scheduler/utils/filter_utils.py +176 -0
- memos/mem_scheduler/utils/misc_utils.py +102 -0
- memos/mem_user/factory.py +94 -0
- memos/mem_user/mysql_persistent_user_manager.py +271 -0
- memos/mem_user/mysql_user_manager.py +500 -0
- memos/mem_user/persistent_factory.py +96 -0
- memos/mem_user/user_manager.py +4 -4
- memos/memories/activation/item.py +5 -1
- memos/memories/activation/kv.py +20 -8
- memos/memories/textual/base.py +2 -2
- memos/memories/textual/general.py +36 -92
- memos/memories/textual/item.py +5 -33
- memos/memories/textual/tree.py +13 -7
- memos/memories/textual/tree_text_memory/organize/{conflict.py → handler.py} +34 -50
- memos/memories/textual/tree_text_memory/organize/manager.py +8 -96
- memos/memories/textual/tree_text_memory/organize/relation_reason_detector.py +49 -43
- memos/memories/textual/tree_text_memory/organize/reorganizer.py +107 -142
- memos/memories/textual/tree_text_memory/retrieve/bochasearch.py +229 -0
- memos/memories/textual/tree_text_memory/retrieve/internet_retriever.py +6 -3
- memos/memories/textual/tree_text_memory/retrieve/internet_retriever_factory.py +11 -0
- memos/memories/textual/tree_text_memory/retrieve/recall.py +15 -8
- memos/memories/textual/tree_text_memory/retrieve/reranker.py +1 -1
- memos/memories/textual/tree_text_memory/retrieve/retrieval_mid_structs.py +2 -0
- memos/memories/textual/tree_text_memory/retrieve/searcher.py +191 -116
- memos/memories/textual/tree_text_memory/retrieve/task_goal_parser.py +47 -15
- memos/memories/textual/tree_text_memory/retrieve/utils.py +11 -7
- memos/memories/textual/tree_text_memory/retrieve/xinyusearch.py +62 -58
- memos/memos_tools/dinding_report_bot.py +422 -0
- memos/memos_tools/lockfree_dict.py +120 -0
- memos/memos_tools/notification_service.py +44 -0
- memos/memos_tools/notification_utils.py +96 -0
- memos/memos_tools/thread_safe_dict.py +288 -0
- memos/settings.py +3 -1
- memos/templates/mem_reader_prompts.py +4 -1
- memos/templates/mem_scheduler_prompts.py +62 -15
- memos/templates/mos_prompts.py +116 -0
- memos/templates/tree_reorganize_prompts.py +24 -17
- memos/utils.py +19 -0
- memos/mem_scheduler/modules/misc.py +0 -39
- memos/mem_scheduler/modules/retriever.py +0 -268
- memos/mem_scheduler/modules/schemas.py +0 -328
- memos/mem_scheduler/utils.py +0 -75
- memos/memories/textual/tree_text_memory/organize/redundancy.py +0 -193
- {memoryos-0.2.1.dist-info → memoryos-1.0.0.dist-info}/LICENSE +0 -0
- {memoryos-0.2.1.dist-info → memoryos-1.0.0.dist-info}/WHEEL +0 -0
- {memoryos-0.2.1.dist-info → memoryos-1.0.0.dist-info}/entry_points.txt +0 -0
- /memos/mem_scheduler/{modules → general_modules}/__init__.py +0 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
|
|
3
|
+
from memos.log import get_logger
|
|
4
|
+
from memos.mem_cube.general import GeneralMemCube
|
|
5
|
+
from memos.mem_scheduler.general_modules.base import BaseSchedulerModule
|
|
6
|
+
from memos.mem_scheduler.schemas.general_schemas import (
|
|
7
|
+
ACTIVATION_MEMORY_TYPE,
|
|
8
|
+
ADD_LABEL,
|
|
9
|
+
LONG_TERM_MEMORY_TYPE,
|
|
10
|
+
NOT_INITIALIZED,
|
|
11
|
+
PARAMETER_MEMORY_TYPE,
|
|
12
|
+
QUERY_LABEL,
|
|
13
|
+
TEXT_MEMORY_TYPE,
|
|
14
|
+
USER_INPUT_TYPE,
|
|
15
|
+
WORKING_MEMORY_TYPE,
|
|
16
|
+
)
|
|
17
|
+
from memos.mem_scheduler.schemas.message_schemas import (
|
|
18
|
+
ScheduleLogForWebItem,
|
|
19
|
+
ScheduleMessageItem,
|
|
20
|
+
)
|
|
21
|
+
from memos.mem_scheduler.utils.filter_utils import (
|
|
22
|
+
transform_name_to_key,
|
|
23
|
+
)
|
|
24
|
+
from memos.mem_scheduler.utils.misc_utils import log_exceptions
|
|
25
|
+
from memos.memories.textual.tree import TextualMemoryItem, TreeTextMemory
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
logger = get_logger(__name__)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class SchedulerLoggerModule(BaseSchedulerModule):
|
|
32
|
+
def __init__(self):
|
|
33
|
+
"""
|
|
34
|
+
Initialize RabbitMQ connection settings.
|
|
35
|
+
"""
|
|
36
|
+
super().__init__()
|
|
37
|
+
|
|
38
|
+
@log_exceptions(logger=logger)
|
|
39
|
+
def create_autofilled_log_item(
|
|
40
|
+
self,
|
|
41
|
+
log_content: str,
|
|
42
|
+
label: str,
|
|
43
|
+
from_memory_type: str,
|
|
44
|
+
to_memory_type: str,
|
|
45
|
+
user_id: str,
|
|
46
|
+
mem_cube_id: str,
|
|
47
|
+
mem_cube: GeneralMemCube,
|
|
48
|
+
) -> ScheduleLogForWebItem:
|
|
49
|
+
text_mem_base: TreeTextMemory = mem_cube.text_mem
|
|
50
|
+
current_memory_sizes = text_mem_base.get_current_memory_size()
|
|
51
|
+
current_memory_sizes = {
|
|
52
|
+
"long_term_memory_size": current_memory_sizes.get("LongTermMemory", 0),
|
|
53
|
+
"user_memory_size": current_memory_sizes.get("UserMemory", 0),
|
|
54
|
+
"working_memory_size": current_memory_sizes.get("WorkingMemory", 0),
|
|
55
|
+
"transformed_act_memory_size": NOT_INITIALIZED,
|
|
56
|
+
"parameter_memory_size": NOT_INITIALIZED,
|
|
57
|
+
}
|
|
58
|
+
memory_capacities = {
|
|
59
|
+
"long_term_memory_capacity": text_mem_base.memory_manager.memory_size["LongTermMemory"],
|
|
60
|
+
"user_memory_capacity": text_mem_base.memory_manager.memory_size["UserMemory"],
|
|
61
|
+
"working_memory_capacity": text_mem_base.memory_manager.memory_size["WorkingMemory"],
|
|
62
|
+
"transformed_act_memory_capacity": NOT_INITIALIZED,
|
|
63
|
+
"parameter_memory_capacity": NOT_INITIALIZED,
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if hasattr(self, "monitor"):
|
|
67
|
+
if (
|
|
68
|
+
user_id in self.monitor.activation_memory_monitors
|
|
69
|
+
and mem_cube_id in self.monitor.activation_memory_monitors[user_id]
|
|
70
|
+
):
|
|
71
|
+
activation_monitor = self.monitor.activation_memory_monitors[user_id][mem_cube_id]
|
|
72
|
+
transformed_act_memory_size = len(activation_monitor.memories)
|
|
73
|
+
logger.info(
|
|
74
|
+
f'activation_memory_monitors currently has "{transformed_act_memory_size}" transformed memory size'
|
|
75
|
+
)
|
|
76
|
+
else:
|
|
77
|
+
transformed_act_memory_size = 0
|
|
78
|
+
logger.info(
|
|
79
|
+
f'activation_memory_monitors is not initialized for user "{user_id}" and mem_cube "{mem_cube_id}'
|
|
80
|
+
)
|
|
81
|
+
current_memory_sizes["transformed_act_memory_size"] = transformed_act_memory_size
|
|
82
|
+
current_memory_sizes["parameter_memory_size"] = 1
|
|
83
|
+
|
|
84
|
+
memory_capacities["transformed_act_memory_capacity"] = (
|
|
85
|
+
self.monitor.activation_mem_monitor_capacity
|
|
86
|
+
)
|
|
87
|
+
memory_capacities["parameter_memory_capacity"] = 1
|
|
88
|
+
|
|
89
|
+
log_message = ScheduleLogForWebItem(
|
|
90
|
+
user_id=user_id,
|
|
91
|
+
mem_cube_id=mem_cube_id,
|
|
92
|
+
label=label,
|
|
93
|
+
from_memory_type=from_memory_type,
|
|
94
|
+
to_memory_type=to_memory_type,
|
|
95
|
+
log_content=log_content,
|
|
96
|
+
current_memory_sizes=current_memory_sizes,
|
|
97
|
+
memory_capacities=memory_capacities,
|
|
98
|
+
)
|
|
99
|
+
return log_message
|
|
100
|
+
|
|
101
|
+
@log_exceptions(logger=logger)
|
|
102
|
+
def log_working_memory_replacement(
|
|
103
|
+
self,
|
|
104
|
+
original_memory: list[TextualMemoryItem],
|
|
105
|
+
new_memory: list[TextualMemoryItem],
|
|
106
|
+
user_id: str,
|
|
107
|
+
mem_cube_id: str,
|
|
108
|
+
mem_cube: GeneralMemCube,
|
|
109
|
+
log_func_callback: Callable[[list[ScheduleLogForWebItem]], None],
|
|
110
|
+
):
|
|
111
|
+
"""Log changes when working memory is replaced."""
|
|
112
|
+
memory_type_map = {
|
|
113
|
+
transform_name_to_key(name=m.memory): m.metadata.memory_type
|
|
114
|
+
for m in original_memory + new_memory
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
original_text_memories = [m.memory for m in original_memory]
|
|
118
|
+
new_text_memories = [m.memory for m in new_memory]
|
|
119
|
+
|
|
120
|
+
# Convert to sets for efficient difference operations
|
|
121
|
+
original_set = set(original_text_memories)
|
|
122
|
+
new_set = set(new_text_memories)
|
|
123
|
+
|
|
124
|
+
# Identify changes
|
|
125
|
+
added_memories = list(new_set - original_set) # Present in new but not original
|
|
126
|
+
|
|
127
|
+
# recording messages
|
|
128
|
+
for memory in added_memories:
|
|
129
|
+
normalized_mem = transform_name_to_key(name=memory)
|
|
130
|
+
if normalized_mem not in memory_type_map:
|
|
131
|
+
logger.error(f"Memory text not found in type mapping: {memory[:50]}...")
|
|
132
|
+
# Get the memory type from the map, default to LONG_TERM_MEMORY_TYPE if not found
|
|
133
|
+
mem_type = memory_type_map.get(normalized_mem, LONG_TERM_MEMORY_TYPE)
|
|
134
|
+
|
|
135
|
+
if mem_type == WORKING_MEMORY_TYPE:
|
|
136
|
+
logger.warning(f"Memory already in working memory: {memory[:50]}...")
|
|
137
|
+
continue
|
|
138
|
+
|
|
139
|
+
log_message = self.create_autofilled_log_item(
|
|
140
|
+
log_content=memory,
|
|
141
|
+
label=QUERY_LABEL,
|
|
142
|
+
from_memory_type=mem_type,
|
|
143
|
+
to_memory_type=WORKING_MEMORY_TYPE,
|
|
144
|
+
user_id=user_id,
|
|
145
|
+
mem_cube_id=mem_cube_id,
|
|
146
|
+
mem_cube=mem_cube,
|
|
147
|
+
)
|
|
148
|
+
log_func_callback([log_message])
|
|
149
|
+
logger.info(
|
|
150
|
+
f"{len(added_memories)} {LONG_TERM_MEMORY_TYPE} memorie(s) "
|
|
151
|
+
f"transformed to {WORKING_MEMORY_TYPE} memories."
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
@log_exceptions(logger=logger)
|
|
155
|
+
def log_activation_memory_update(
|
|
156
|
+
self,
|
|
157
|
+
original_text_memories: list[str],
|
|
158
|
+
new_text_memories: list[str],
|
|
159
|
+
label: str,
|
|
160
|
+
user_id: str,
|
|
161
|
+
mem_cube_id: str,
|
|
162
|
+
mem_cube: GeneralMemCube,
|
|
163
|
+
log_func_callback: Callable[[list[ScheduleLogForWebItem]], None],
|
|
164
|
+
):
|
|
165
|
+
"""Log changes when activation memory is updated."""
|
|
166
|
+
original_set = set(original_text_memories)
|
|
167
|
+
new_set = set(new_text_memories)
|
|
168
|
+
|
|
169
|
+
# Identify changes
|
|
170
|
+
added_memories = list(new_set - original_set) # Present in new but not original
|
|
171
|
+
|
|
172
|
+
# recording messages
|
|
173
|
+
for mem in added_memories:
|
|
174
|
+
log_message_a = self.create_autofilled_log_item(
|
|
175
|
+
log_content=mem,
|
|
176
|
+
label=label,
|
|
177
|
+
from_memory_type=TEXT_MEMORY_TYPE,
|
|
178
|
+
to_memory_type=ACTIVATION_MEMORY_TYPE,
|
|
179
|
+
user_id=user_id,
|
|
180
|
+
mem_cube_id=mem_cube_id,
|
|
181
|
+
mem_cube=mem_cube,
|
|
182
|
+
)
|
|
183
|
+
logger.info(
|
|
184
|
+
f"{len(added_memories)} {TEXT_MEMORY_TYPE} memorie(s) "
|
|
185
|
+
f"transformed to {ACTIVATION_MEMORY_TYPE} memories."
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
log_message_b = self.create_autofilled_log_item(
|
|
189
|
+
log_content=mem,
|
|
190
|
+
label=label,
|
|
191
|
+
from_memory_type=ACTIVATION_MEMORY_TYPE,
|
|
192
|
+
to_memory_type=PARAMETER_MEMORY_TYPE,
|
|
193
|
+
user_id=user_id,
|
|
194
|
+
mem_cube_id=mem_cube_id,
|
|
195
|
+
mem_cube=mem_cube,
|
|
196
|
+
)
|
|
197
|
+
logger.info(
|
|
198
|
+
f"{len(added_memories)} {ACTIVATION_MEMORY_TYPE} memorie(s) "
|
|
199
|
+
f"transformed to {PARAMETER_MEMORY_TYPE} memories."
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
log_func_callback([log_message_a, log_message_b])
|
|
203
|
+
|
|
204
|
+
@log_exceptions(logger=logger)
|
|
205
|
+
def log_adding_memory(
|
|
206
|
+
self,
|
|
207
|
+
memory: str,
|
|
208
|
+
memory_type: str,
|
|
209
|
+
user_id: str,
|
|
210
|
+
mem_cube_id: str,
|
|
211
|
+
mem_cube: GeneralMemCube,
|
|
212
|
+
log_func_callback: Callable[[list[ScheduleLogForWebItem]], None],
|
|
213
|
+
):
|
|
214
|
+
"""Log changes when working memory is replaced."""
|
|
215
|
+
log_message = self.create_autofilled_log_item(
|
|
216
|
+
log_content=memory,
|
|
217
|
+
label=ADD_LABEL,
|
|
218
|
+
from_memory_type=USER_INPUT_TYPE,
|
|
219
|
+
to_memory_type=memory_type,
|
|
220
|
+
user_id=user_id,
|
|
221
|
+
mem_cube_id=mem_cube_id,
|
|
222
|
+
mem_cube=mem_cube,
|
|
223
|
+
)
|
|
224
|
+
log_func_callback([log_message])
|
|
225
|
+
logger.info(
|
|
226
|
+
f"{USER_INPUT_TYPE} memory for user {user_id} "
|
|
227
|
+
f"converted to {memory_type} memory in mem_cube {mem_cube_id}: {memory}"
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
@log_exceptions(logger=logger)
|
|
231
|
+
def validate_schedule_message(self, message: ScheduleMessageItem, label: str):
|
|
232
|
+
"""Validate if the message matches the expected label.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
message: Incoming message item to validate.
|
|
236
|
+
label: Expected message label (e.g., QUERY_LABEL/ANSWER_LABEL).
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
bool: True if validation passed, False otherwise.
|
|
240
|
+
"""
|
|
241
|
+
if message.label != label:
|
|
242
|
+
logger.error(f"Handler validation failed: expected={label}, actual={message.label}")
|
|
243
|
+
return False
|
|
244
|
+
return True
|
|
245
|
+
|
|
246
|
+
@log_exceptions(logger=logger)
|
|
247
|
+
def validate_schedule_messages(self, messages: list[ScheduleMessageItem], label: str):
|
|
248
|
+
"""Validate if all messages match the expected label.
|
|
249
|
+
|
|
250
|
+
Args:
|
|
251
|
+
messages: List of message items to validate.
|
|
252
|
+
label: Expected message label (e.g., QUERY_LABEL/ANSWER_LABEL).
|
|
253
|
+
|
|
254
|
+
Returns:
|
|
255
|
+
bool: True if all messages passed validation, False if any failed.
|
|
256
|
+
"""
|
|
257
|
+
for message in messages:
|
|
258
|
+
if not self.validate_schedule_message(message, label):
|
|
259
|
+
logger.error("Message batch contains invalid labels, aborting processing")
|
|
260
|
+
return False
|
|
261
|
+
return True
|
|
@@ -4,12 +4,18 @@ from memos.configs.mem_scheduler import GeneralSchedulerConfig
|
|
|
4
4
|
from memos.log import get_logger
|
|
5
5
|
from memos.mem_cube.general import GeneralMemCube
|
|
6
6
|
from memos.mem_scheduler.base_scheduler import BaseScheduler
|
|
7
|
-
from memos.mem_scheduler.
|
|
7
|
+
from memos.mem_scheduler.schemas.general_schemas import (
|
|
8
8
|
ADD_LABEL,
|
|
9
9
|
ANSWER_LABEL,
|
|
10
|
+
DEFAULT_MAX_QUERY_KEY_WORDS,
|
|
10
11
|
QUERY_LABEL,
|
|
11
|
-
|
|
12
|
+
WORKING_MEMORY_TYPE,
|
|
13
|
+
MemCubeID,
|
|
14
|
+
UserID,
|
|
12
15
|
)
|
|
16
|
+
from memos.mem_scheduler.schemas.message_schemas import ScheduleMessageItem
|
|
17
|
+
from memos.mem_scheduler.schemas.monitor_schemas import QueryMonitorItem
|
|
18
|
+
from memos.mem_scheduler.utils.filter_utils import is_all_chinese, is_all_english
|
|
13
19
|
from memos.memories.textual.tree import TextualMemoryItem, TreeTextMemory
|
|
14
20
|
|
|
15
21
|
|
|
@@ -29,6 +35,78 @@ class GeneralScheduler(BaseScheduler):
|
|
|
29
35
|
}
|
|
30
36
|
self.dispatcher.register_handlers(handlers)
|
|
31
37
|
|
|
38
|
+
# for evaluation
|
|
39
|
+
def search_for_eval(
|
|
40
|
+
self, query: str, user_id: UserID | str, top_k: int, scheduler_flag: bool = True
|
|
41
|
+
) -> (list[str], bool):
|
|
42
|
+
self.monitor.register_query_monitor_if_not_exists(
|
|
43
|
+
user_id=user_id, mem_cube_id=self.current_mem_cube_id
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
query_keywords = self.monitor.extract_query_keywords(query=query)
|
|
47
|
+
logger.info(f'Extract keywords "{query_keywords}" from query "{query}"')
|
|
48
|
+
|
|
49
|
+
item = QueryMonitorItem(
|
|
50
|
+
query_text=query,
|
|
51
|
+
keywords=query_keywords,
|
|
52
|
+
max_keywords=DEFAULT_MAX_QUERY_KEY_WORDS,
|
|
53
|
+
)
|
|
54
|
+
query_monitor = self.monitor.query_monitors[user_id][self.current_mem_cube_id]
|
|
55
|
+
query_monitor.put(item=item)
|
|
56
|
+
logger.debug(f"Queries in monitor are {query_monitor.get_queries_with_timesort()}.")
|
|
57
|
+
|
|
58
|
+
queries = [query]
|
|
59
|
+
|
|
60
|
+
# recall
|
|
61
|
+
mem_cube = self.current_mem_cube
|
|
62
|
+
text_mem_base = mem_cube.text_mem
|
|
63
|
+
|
|
64
|
+
cur_working_memory: list[TextualMemoryItem] = text_mem_base.get_working_memory()
|
|
65
|
+
text_working_memory: list[str] = [w_m.memory for w_m in cur_working_memory]
|
|
66
|
+
intent_result = self.monitor.detect_intent(
|
|
67
|
+
q_list=queries, text_working_memory=text_working_memory
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
if not scheduler_flag:
|
|
71
|
+
return text_working_memory, intent_result["trigger_retrieval"]
|
|
72
|
+
else:
|
|
73
|
+
if intent_result["trigger_retrieval"]:
|
|
74
|
+
missing_evidences = intent_result["missing_evidences"]
|
|
75
|
+
num_evidence = len(missing_evidences)
|
|
76
|
+
k_per_evidence = max(1, top_k // max(1, num_evidence))
|
|
77
|
+
new_candidates = []
|
|
78
|
+
for item in missing_evidences:
|
|
79
|
+
logger.info(f"missing_evidences: {item}")
|
|
80
|
+
results: list[TextualMemoryItem] = self.retriever.search(
|
|
81
|
+
query=item,
|
|
82
|
+
mem_cube=mem_cube,
|
|
83
|
+
top_k=k_per_evidence,
|
|
84
|
+
method=self.search_method,
|
|
85
|
+
)
|
|
86
|
+
logger.info(
|
|
87
|
+
f"search results for {missing_evidences}: {[one.memory for one in results]}"
|
|
88
|
+
)
|
|
89
|
+
new_candidates.extend(results)
|
|
90
|
+
print(
|
|
91
|
+
f"missing_evidences: {missing_evidences} and get {len(new_candidates)} new candidate memories."
|
|
92
|
+
)
|
|
93
|
+
else:
|
|
94
|
+
new_candidates = []
|
|
95
|
+
print(f"intent_result: {intent_result}. not triggered")
|
|
96
|
+
|
|
97
|
+
# rerank
|
|
98
|
+
new_order_working_memory = self.replace_working_memory(
|
|
99
|
+
user_id=user_id,
|
|
100
|
+
mem_cube_id=self.current_mem_cube_id,
|
|
101
|
+
mem_cube=self.current_mem_cube,
|
|
102
|
+
original_memory=cur_working_memory,
|
|
103
|
+
new_memory=new_candidates,
|
|
104
|
+
)
|
|
105
|
+
new_order_working_memory = new_order_working_memory[:top_k]
|
|
106
|
+
logger.info(f"size of new_order_working_memory: {len(new_order_working_memory)}")
|
|
107
|
+
|
|
108
|
+
return [m.memory for m in new_order_working_memory], intent_result["trigger_retrieval"]
|
|
109
|
+
|
|
32
110
|
def _query_message_consumer(self, messages: list[ScheduleMessageItem]) -> None:
|
|
33
111
|
"""
|
|
34
112
|
Process and handle query trigger messages from the queue.
|
|
@@ -36,12 +114,12 @@ class GeneralScheduler(BaseScheduler):
|
|
|
36
114
|
Args:
|
|
37
115
|
messages: List of query messages to process
|
|
38
116
|
"""
|
|
39
|
-
logger.
|
|
117
|
+
logger.info(f"Messages {messages} assigned to {QUERY_LABEL} handler.")
|
|
40
118
|
|
|
41
119
|
# Process the query in a session turn
|
|
42
120
|
grouped_messages = self.dispatcher.group_messages_by_user_and_cube(messages=messages)
|
|
43
121
|
|
|
44
|
-
self.
|
|
122
|
+
self.validate_schedule_messages(messages=messages, label=QUERY_LABEL)
|
|
45
123
|
|
|
46
124
|
for user_id in grouped_messages:
|
|
47
125
|
for mem_cube_id in grouped_messages[user_id]:
|
|
@@ -49,16 +127,88 @@ class GeneralScheduler(BaseScheduler):
|
|
|
49
127
|
if len(messages) == 0:
|
|
50
128
|
return
|
|
51
129
|
|
|
130
|
+
mem_cube = messages[0].mem_cube
|
|
131
|
+
|
|
52
132
|
# for status update
|
|
53
133
|
self._set_current_context_from_message(msg=messages[0])
|
|
54
134
|
|
|
55
|
-
|
|
56
|
-
|
|
135
|
+
# update query monitors
|
|
136
|
+
for msg in messages:
|
|
137
|
+
self.monitor.register_query_monitor_if_not_exists(
|
|
138
|
+
user_id=user_id, mem_cube_id=mem_cube_id
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
query = msg.content
|
|
142
|
+
query_keywords = self.monitor.extract_query_keywords(query=query)
|
|
143
|
+
logger.info(f'Extract keywords "{query_keywords}" from query "{query}"')
|
|
144
|
+
|
|
145
|
+
if len(query_keywords) == 0:
|
|
146
|
+
stripped_query = query.strip()
|
|
147
|
+
# Determine measurement method based on language
|
|
148
|
+
if is_all_english(stripped_query):
|
|
149
|
+
words = stripped_query.split() # Word count for English
|
|
150
|
+
elif is_all_chinese(stripped_query):
|
|
151
|
+
words = stripped_query # Character count for Chinese
|
|
152
|
+
else:
|
|
153
|
+
logger.debug(
|
|
154
|
+
f"Mixed-language memory, using character count: {stripped_query[:50]}..."
|
|
155
|
+
)
|
|
156
|
+
words = stripped_query # Default to character count
|
|
157
|
+
|
|
158
|
+
query_keywords = list(set(words[:20]))
|
|
159
|
+
logger.error(
|
|
160
|
+
f"Keyword extraction failed for query. Using fallback keywords: {query_keywords[:10]}... (truncated)"
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
item = QueryMonitorItem(
|
|
164
|
+
query_text=query,
|
|
165
|
+
keywords=query_keywords,
|
|
166
|
+
max_keywords=DEFAULT_MAX_QUERY_KEY_WORDS,
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
self.monitor.query_monitors[user_id][mem_cube_id].put(item=item)
|
|
170
|
+
logger.debug(
|
|
171
|
+
f"Queries in monitor are "
|
|
172
|
+
f"{self.monitor.query_monitors[user_id][mem_cube_id].get_queries_with_timesort()}."
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
queries = [msg.content for msg in messages]
|
|
176
|
+
|
|
177
|
+
# recall
|
|
178
|
+
cur_working_memory, new_candidates = self.process_session_turn(
|
|
179
|
+
queries=queries,
|
|
57
180
|
user_id=user_id,
|
|
58
181
|
mem_cube_id=mem_cube_id,
|
|
59
|
-
mem_cube=
|
|
182
|
+
mem_cube=mem_cube,
|
|
60
183
|
top_k=self.top_k,
|
|
61
184
|
)
|
|
185
|
+
logger.info(
|
|
186
|
+
f"Processed {queries} and get {len(new_candidates)} new candidate memories."
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
# rerank
|
|
190
|
+
new_order_working_memory = self.replace_working_memory(
|
|
191
|
+
user_id=user_id,
|
|
192
|
+
mem_cube_id=mem_cube_id,
|
|
193
|
+
mem_cube=mem_cube,
|
|
194
|
+
original_memory=cur_working_memory,
|
|
195
|
+
new_memory=new_candidates,
|
|
196
|
+
)
|
|
197
|
+
logger.info(f"size of new_order_working_memory: {len(new_order_working_memory)}")
|
|
198
|
+
|
|
199
|
+
# update activation memories
|
|
200
|
+
logger.info(
|
|
201
|
+
f"Activation memory update {'enabled' if self.enable_activation_memory else 'disabled'} "
|
|
202
|
+
f"(interval: {self.monitor.act_mem_update_interval}s)"
|
|
203
|
+
)
|
|
204
|
+
if self.enable_activation_memory:
|
|
205
|
+
self.update_activation_memory_periodically(
|
|
206
|
+
interval_seconds=self.monitor.act_mem_update_interval,
|
|
207
|
+
label=QUERY_LABEL,
|
|
208
|
+
user_id=user_id,
|
|
209
|
+
mem_cube_id=mem_cube_id,
|
|
210
|
+
mem_cube=messages[0].mem_cube,
|
|
211
|
+
)
|
|
62
212
|
|
|
63
213
|
def _answer_message_consumer(self, messages: list[ScheduleMessageItem]) -> None:
|
|
64
214
|
"""
|
|
@@ -67,11 +217,11 @@ class GeneralScheduler(BaseScheduler):
|
|
|
67
217
|
Args:
|
|
68
218
|
messages: List of answer messages to process
|
|
69
219
|
"""
|
|
70
|
-
logger.
|
|
220
|
+
logger.info(f"Messages {messages} assigned to {ANSWER_LABEL} handler.")
|
|
71
221
|
# Process the query in a session turn
|
|
72
222
|
grouped_messages = self.dispatcher.group_messages_by_user_and_cube(messages=messages)
|
|
73
223
|
|
|
74
|
-
self.
|
|
224
|
+
self.validate_schedule_messages(messages=messages, label=ANSWER_LABEL)
|
|
75
225
|
|
|
76
226
|
for user_id in grouped_messages:
|
|
77
227
|
for mem_cube_id in grouped_messages[user_id]:
|
|
@@ -82,105 +232,118 @@ class GeneralScheduler(BaseScheduler):
|
|
|
82
232
|
# for status update
|
|
83
233
|
self._set_current_context_from_message(msg=messages[0])
|
|
84
234
|
|
|
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
235
|
def _add_message_consumer(self, messages: list[ScheduleMessageItem]) -> None:
|
|
96
|
-
logger.
|
|
236
|
+
logger.info(f"Messages {messages} assigned to {ADD_LABEL} handler.")
|
|
97
237
|
# Process the query in a session turn
|
|
98
238
|
grouped_messages = self.dispatcher.group_messages_by_user_and_cube(messages=messages)
|
|
99
239
|
|
|
100
|
-
self.
|
|
240
|
+
self.validate_schedule_messages(messages=messages, label=ADD_LABEL)
|
|
241
|
+
try:
|
|
242
|
+
for user_id in grouped_messages:
|
|
243
|
+
for mem_cube_id in grouped_messages[user_id]:
|
|
244
|
+
messages = grouped_messages[user_id][mem_cube_id]
|
|
245
|
+
if len(messages) == 0:
|
|
246
|
+
return
|
|
101
247
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
messages = grouped_messages[user_id][mem_cube_id]
|
|
105
|
-
if len(messages) == 0:
|
|
106
|
-
return
|
|
248
|
+
# for status update
|
|
249
|
+
self._set_current_context_from_message(msg=messages[0])
|
|
107
250
|
|
|
108
|
-
|
|
109
|
-
|
|
251
|
+
# submit logs
|
|
252
|
+
for msg in messages:
|
|
253
|
+
try:
|
|
254
|
+
userinput_memory_ids = json.loads(msg.content)
|
|
255
|
+
except Exception as e:
|
|
256
|
+
logger.error(f"Error: {e}. Content: {msg.content}", exc_info=True)
|
|
257
|
+
userinput_memory_ids = []
|
|
110
258
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
user_id=msg.user_id,
|
|
117
|
-
mem_cube_id=msg.mem_cube_id,
|
|
118
|
-
mem_cube=msg.mem_cube,
|
|
119
|
-
)
|
|
259
|
+
mem_cube = msg.mem_cube
|
|
260
|
+
for memory_id in userinput_memory_ids:
|
|
261
|
+
mem_item: TextualMemoryItem = mem_cube.text_mem.get(memory_id=memory_id)
|
|
262
|
+
mem_type = mem_item.metadata.memory_type
|
|
263
|
+
mem_content = mem_item.memory
|
|
120
264
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
265
|
+
if mem_type == WORKING_MEMORY_TYPE:
|
|
266
|
+
continue
|
|
267
|
+
|
|
268
|
+
self.log_adding_memory(
|
|
269
|
+
memory=mem_content,
|
|
270
|
+
memory_type=mem_type,
|
|
271
|
+
user_id=msg.user_id,
|
|
272
|
+
mem_cube_id=msg.mem_cube_id,
|
|
273
|
+
mem_cube=msg.mem_cube,
|
|
274
|
+
log_func_callback=self._submit_web_logs,
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
except Exception as e:
|
|
278
|
+
logger.error(f"Error: {e}", exc_info=True)
|
|
130
279
|
|
|
131
280
|
def process_session_turn(
|
|
132
281
|
self,
|
|
133
282
|
queries: str | list[str],
|
|
134
|
-
user_id: str,
|
|
135
|
-
mem_cube_id: str,
|
|
283
|
+
user_id: UserID | str,
|
|
284
|
+
mem_cube_id: MemCubeID | str,
|
|
136
285
|
mem_cube: GeneralMemCube,
|
|
137
286
|
top_k: int = 10,
|
|
138
|
-
|
|
139
|
-
) -> None:
|
|
287
|
+
) -> tuple[list[TextualMemoryItem], list[TextualMemoryItem]] | None:
|
|
140
288
|
"""
|
|
141
289
|
Process a dialog turn:
|
|
142
290
|
- If q_list reaches window size, trigger retrieval;
|
|
143
291
|
- Immediately switch to the new memory if retrieval is triggered.
|
|
144
292
|
"""
|
|
145
|
-
if isinstance(queries, str):
|
|
146
|
-
queries = [queries]
|
|
147
|
-
|
|
148
|
-
if query_history is None:
|
|
149
|
-
query_history = queries
|
|
150
|
-
else:
|
|
151
|
-
query_history.extend(queries)
|
|
152
293
|
|
|
153
294
|
text_mem_base = mem_cube.text_mem
|
|
154
295
|
if not isinstance(text_mem_base, TreeTextMemory):
|
|
155
296
|
logger.error("Not implemented!", exc_info=True)
|
|
156
297
|
return
|
|
157
298
|
|
|
158
|
-
|
|
159
|
-
|
|
299
|
+
logger.info(f"Processing {len(queries)} queries.")
|
|
300
|
+
|
|
301
|
+
cur_working_memory: list[TextualMemoryItem] = text_mem_base.get_working_memory()
|
|
302
|
+
text_working_memory: list[str] = [w_m.memory for w_m in cur_working_memory]
|
|
160
303
|
intent_result = self.monitor.detect_intent(
|
|
161
|
-
q_list=
|
|
304
|
+
q_list=queries, text_working_memory=text_working_memory
|
|
162
305
|
)
|
|
163
306
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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}")
|
|
175
|
-
new_candidates.extend(results)
|
|
307
|
+
time_trigger_flag = False
|
|
308
|
+
if self.monitor.timed_trigger(
|
|
309
|
+
last_time=self.monitor.last_query_consume_time,
|
|
310
|
+
interval_seconds=self.monitor.query_trigger_interval,
|
|
311
|
+
):
|
|
312
|
+
time_trigger_flag = True
|
|
176
313
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
314
|
+
if (not intent_result["trigger_retrieval"]) and (not time_trigger_flag):
|
|
315
|
+
logger.info(f"Query schedule not triggered. Intent_result: {intent_result}")
|
|
316
|
+
return
|
|
317
|
+
elif (not intent_result["trigger_retrieval"]) and time_trigger_flag:
|
|
318
|
+
logger.info("Query schedule is forced to trigger due to time ticker")
|
|
319
|
+
intent_result["trigger_retrieval"] = True
|
|
320
|
+
intent_result["missing_evidences"] = queries
|
|
321
|
+
else:
|
|
322
|
+
logger.info(
|
|
323
|
+
f'Query schedule triggered for user "{user_id}" and mem_cube "{mem_cube_id}".'
|
|
324
|
+
f" Missing evidences: {intent_result['missing_evidences']}"
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
missing_evidences = intent_result["missing_evidences"]
|
|
328
|
+
num_evidence = len(missing_evidences)
|
|
329
|
+
k_per_evidence = max(1, top_k // max(1, num_evidence))
|
|
330
|
+
new_candidates = []
|
|
331
|
+
for item in missing_evidences:
|
|
332
|
+
logger.info(f"missing_evidences: {item}")
|
|
333
|
+
info = {
|
|
334
|
+
"user_id": user_id,
|
|
335
|
+
"session_id": "",
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
results: list[TextualMemoryItem] = self.retriever.search(
|
|
339
|
+
query=item,
|
|
181
340
|
mem_cube=mem_cube,
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
341
|
+
top_k=k_per_evidence,
|
|
342
|
+
method=self.search_method,
|
|
343
|
+
info=info,
|
|
344
|
+
)
|
|
345
|
+
logger.info(
|
|
346
|
+
f"search results for {missing_evidences}: {[one.memory for one in results]}"
|
|
185
347
|
)
|
|
186
|
-
|
|
348
|
+
new_candidates.extend(results)
|
|
349
|
+
return cur_working_memory, new_candidates
|
|
File without changes
|