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.

Files changed (92) hide show
  1. {memoryos-0.2.1.dist-info → memoryos-1.0.0.dist-info}/METADATA +7 -1
  2. {memoryos-0.2.1.dist-info → memoryos-1.0.0.dist-info}/RECORD +87 -64
  3. memos/__init__.py +1 -1
  4. memos/api/config.py +158 -69
  5. memos/api/context/context.py +147 -0
  6. memos/api/context/dependencies.py +101 -0
  7. memos/api/product_models.py +5 -1
  8. memos/api/routers/product_router.py +54 -26
  9. memos/configs/graph_db.py +49 -1
  10. memos/configs/internet_retriever.py +19 -0
  11. memos/configs/mem_os.py +5 -0
  12. memos/configs/mem_reader.py +9 -0
  13. memos/configs/mem_scheduler.py +54 -18
  14. memos/configs/mem_user.py +58 -0
  15. memos/graph_dbs/base.py +38 -3
  16. memos/graph_dbs/factory.py +2 -0
  17. memos/graph_dbs/nebular.py +1612 -0
  18. memos/graph_dbs/neo4j.py +18 -9
  19. memos/log.py +6 -1
  20. memos/mem_cube/utils.py +13 -6
  21. memos/mem_os/core.py +157 -37
  22. memos/mem_os/main.py +2 -2
  23. memos/mem_os/product.py +252 -201
  24. memos/mem_os/utils/default_config.py +1 -1
  25. memos/mem_os/utils/format_utils.py +281 -70
  26. memos/mem_os/utils/reference_utils.py +133 -0
  27. memos/mem_reader/simple_struct.py +13 -5
  28. memos/mem_scheduler/base_scheduler.py +239 -266
  29. memos/mem_scheduler/{modules → general_modules}/base.py +4 -5
  30. memos/mem_scheduler/{modules → general_modules}/dispatcher.py +57 -21
  31. memos/mem_scheduler/general_modules/misc.py +104 -0
  32. memos/mem_scheduler/{modules → general_modules}/rabbitmq_service.py +12 -10
  33. memos/mem_scheduler/{modules → general_modules}/redis_service.py +1 -1
  34. memos/mem_scheduler/general_modules/retriever.py +199 -0
  35. memos/mem_scheduler/general_modules/scheduler_logger.py +261 -0
  36. memos/mem_scheduler/general_scheduler.py +243 -80
  37. memos/mem_scheduler/monitors/__init__.py +0 -0
  38. memos/mem_scheduler/monitors/dispatcher_monitor.py +305 -0
  39. memos/mem_scheduler/{modules/monitor.py → monitors/general_monitor.py} +106 -57
  40. memos/mem_scheduler/mos_for_test_scheduler.py +23 -20
  41. memos/mem_scheduler/schemas/__init__.py +0 -0
  42. memos/mem_scheduler/schemas/general_schemas.py +44 -0
  43. memos/mem_scheduler/schemas/message_schemas.py +149 -0
  44. memos/mem_scheduler/schemas/monitor_schemas.py +337 -0
  45. memos/mem_scheduler/utils/__init__.py +0 -0
  46. memos/mem_scheduler/utils/filter_utils.py +176 -0
  47. memos/mem_scheduler/utils/misc_utils.py +102 -0
  48. memos/mem_user/factory.py +94 -0
  49. memos/mem_user/mysql_persistent_user_manager.py +271 -0
  50. memos/mem_user/mysql_user_manager.py +500 -0
  51. memos/mem_user/persistent_factory.py +96 -0
  52. memos/mem_user/user_manager.py +4 -4
  53. memos/memories/activation/item.py +5 -1
  54. memos/memories/activation/kv.py +20 -8
  55. memos/memories/textual/base.py +2 -2
  56. memos/memories/textual/general.py +36 -92
  57. memos/memories/textual/item.py +5 -33
  58. memos/memories/textual/tree.py +13 -7
  59. memos/memories/textual/tree_text_memory/organize/{conflict.py → handler.py} +34 -50
  60. memos/memories/textual/tree_text_memory/organize/manager.py +8 -96
  61. memos/memories/textual/tree_text_memory/organize/relation_reason_detector.py +49 -43
  62. memos/memories/textual/tree_text_memory/organize/reorganizer.py +107 -142
  63. memos/memories/textual/tree_text_memory/retrieve/bochasearch.py +229 -0
  64. memos/memories/textual/tree_text_memory/retrieve/internet_retriever.py +6 -3
  65. memos/memories/textual/tree_text_memory/retrieve/internet_retriever_factory.py +11 -0
  66. memos/memories/textual/tree_text_memory/retrieve/recall.py +15 -8
  67. memos/memories/textual/tree_text_memory/retrieve/reranker.py +1 -1
  68. memos/memories/textual/tree_text_memory/retrieve/retrieval_mid_structs.py +2 -0
  69. memos/memories/textual/tree_text_memory/retrieve/searcher.py +191 -116
  70. memos/memories/textual/tree_text_memory/retrieve/task_goal_parser.py +47 -15
  71. memos/memories/textual/tree_text_memory/retrieve/utils.py +11 -7
  72. memos/memories/textual/tree_text_memory/retrieve/xinyusearch.py +62 -58
  73. memos/memos_tools/dinding_report_bot.py +422 -0
  74. memos/memos_tools/lockfree_dict.py +120 -0
  75. memos/memos_tools/notification_service.py +44 -0
  76. memos/memos_tools/notification_utils.py +96 -0
  77. memos/memos_tools/thread_safe_dict.py +288 -0
  78. memos/settings.py +3 -1
  79. memos/templates/mem_reader_prompts.py +4 -1
  80. memos/templates/mem_scheduler_prompts.py +62 -15
  81. memos/templates/mos_prompts.py +116 -0
  82. memos/templates/tree_reorganize_prompts.py +24 -17
  83. memos/utils.py +19 -0
  84. memos/mem_scheduler/modules/misc.py +0 -39
  85. memos/mem_scheduler/modules/retriever.py +0 -268
  86. memos/mem_scheduler/modules/schemas.py +0 -328
  87. memos/mem_scheduler/utils.py +0 -75
  88. memos/memories/textual/tree_text_memory/organize/redundancy.py +0 -193
  89. {memoryos-0.2.1.dist-info → memoryos-1.0.0.dist-info}/LICENSE +0 -0
  90. {memoryos-0.2.1.dist-info → memoryos-1.0.0.dist-info}/WHEEL +0 -0
  91. {memoryos-0.2.1.dist-info → memoryos-1.0.0.dist-info}/entry_points.txt +0 -0
  92. /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.modules.schemas import (
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
- ScheduleMessageItem,
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.debug(f"Messages {messages} assigned to {QUERY_LABEL} handler.")
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._validate_messages(messages=messages, label=QUERY_LABEL)
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
- self.process_session_turn(
56
- queries=[msg.content for msg in messages],
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=messages[0].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.debug(f"Messages {messages} assigned to {ANSWER_LABEL} handler.")
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._validate_messages(messages=messages, label=ANSWER_LABEL)
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.debug(f"Messages {messages} assigned to {ADD_LABEL} handler.")
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._validate_messages(messages=messages, label=ADD_LABEL)
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
- 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
248
+ # for status update
249
+ self._set_current_context_from_message(msg=messages[0])
107
250
 
108
- # for status update
109
- self._set_current_context_from_message(msg=messages[0])
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
- # 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
- )
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
- # 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
- )
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
- query_history: list[str] | None = None,
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
- working_memory: list[TextualMemoryItem] = text_mem_base.get_working_memory()
159
- text_working_memory: list[str] = [w_m.memory for w_m in working_memory]
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=query_history, text_working_memory=text_working_memory
304
+ q_list=queries, text_working_memory=text_working_memory
162
305
  )
163
306
 
164
- if intent_result["trigger_retrieval"]:
165
- missing_evidences = intent_result["missing_evidences"]
166
- num_evidence = len(missing_evidences)
167
- k_per_evidence = max(1, top_k // max(1, num_evidence))
168
- new_candidates = []
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}")
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
- new_order_working_memory = self.retriever.replace_working_memory(
178
- queries=queries,
179
- user_id=user_id,
180
- mem_cube_id=mem_cube_id,
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
- original_memory=working_memory,
183
- new_memory=new_candidates,
184
- top_k=top_k,
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
- logger.debug(f"size of new_order_working_memory: {len(new_order_working_memory)}")
348
+ new_candidates.extend(results)
349
+ return cur_working_memory, new_candidates
File without changes