MemoryOS 1.0.0__py3-none-any.whl → 1.1.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.

Files changed (94) hide show
  1. {memoryos-1.0.0.dist-info → memoryos-1.1.1.dist-info}/METADATA +8 -2
  2. {memoryos-1.0.0.dist-info → memoryos-1.1.1.dist-info}/RECORD +92 -69
  3. {memoryos-1.0.0.dist-info → memoryos-1.1.1.dist-info}/WHEEL +1 -1
  4. memos/__init__.py +1 -1
  5. memos/api/client.py +109 -0
  6. memos/api/config.py +35 -8
  7. memos/api/context/dependencies.py +15 -66
  8. memos/api/middleware/request_context.py +63 -0
  9. memos/api/product_api.py +5 -2
  10. memos/api/product_models.py +107 -16
  11. memos/api/routers/product_router.py +62 -19
  12. memos/api/start_api.py +13 -0
  13. memos/configs/graph_db.py +4 -0
  14. memos/configs/mem_scheduler.py +38 -3
  15. memos/configs/memory.py +13 -0
  16. memos/configs/reranker.py +18 -0
  17. memos/context/context.py +255 -0
  18. memos/embedders/factory.py +2 -0
  19. memos/graph_dbs/base.py +4 -2
  20. memos/graph_dbs/nebular.py +368 -223
  21. memos/graph_dbs/neo4j.py +49 -13
  22. memos/graph_dbs/neo4j_community.py +13 -3
  23. memos/llms/factory.py +2 -0
  24. memos/llms/openai.py +74 -2
  25. memos/llms/vllm.py +2 -0
  26. memos/log.py +128 -4
  27. memos/mem_cube/general.py +3 -1
  28. memos/mem_os/core.py +89 -23
  29. memos/mem_os/main.py +3 -6
  30. memos/mem_os/product.py +418 -154
  31. memos/mem_os/utils/reference_utils.py +20 -0
  32. memos/mem_reader/factory.py +2 -0
  33. memos/mem_reader/simple_struct.py +204 -82
  34. memos/mem_scheduler/analyzer/__init__.py +0 -0
  35. memos/mem_scheduler/analyzer/mos_for_test_scheduler.py +569 -0
  36. memos/mem_scheduler/analyzer/scheduler_for_eval.py +280 -0
  37. memos/mem_scheduler/base_scheduler.py +126 -56
  38. memos/mem_scheduler/general_modules/dispatcher.py +2 -2
  39. memos/mem_scheduler/general_modules/misc.py +99 -1
  40. memos/mem_scheduler/general_modules/scheduler_logger.py +17 -11
  41. memos/mem_scheduler/general_scheduler.py +40 -88
  42. memos/mem_scheduler/memory_manage_modules/__init__.py +5 -0
  43. memos/mem_scheduler/memory_manage_modules/memory_filter.py +308 -0
  44. memos/mem_scheduler/{general_modules → memory_manage_modules}/retriever.py +34 -7
  45. memos/mem_scheduler/monitors/dispatcher_monitor.py +9 -8
  46. memos/mem_scheduler/monitors/general_monitor.py +119 -39
  47. memos/mem_scheduler/optimized_scheduler.py +124 -0
  48. memos/mem_scheduler/orm_modules/__init__.py +0 -0
  49. memos/mem_scheduler/orm_modules/base_model.py +635 -0
  50. memos/mem_scheduler/orm_modules/monitor_models.py +261 -0
  51. memos/mem_scheduler/scheduler_factory.py +2 -0
  52. memos/mem_scheduler/schemas/monitor_schemas.py +96 -29
  53. memos/mem_scheduler/utils/config_utils.py +100 -0
  54. memos/mem_scheduler/utils/db_utils.py +33 -0
  55. memos/mem_scheduler/utils/filter_utils.py +1 -1
  56. memos/mem_scheduler/webservice_modules/__init__.py +0 -0
  57. memos/mem_user/mysql_user_manager.py +4 -2
  58. memos/memories/activation/kv.py +2 -1
  59. memos/memories/textual/item.py +96 -17
  60. memos/memories/textual/naive.py +1 -1
  61. memos/memories/textual/tree.py +57 -3
  62. memos/memories/textual/tree_text_memory/organize/handler.py +4 -2
  63. memos/memories/textual/tree_text_memory/organize/manager.py +28 -14
  64. memos/memories/textual/tree_text_memory/organize/relation_reason_detector.py +1 -2
  65. memos/memories/textual/tree_text_memory/organize/reorganizer.py +75 -23
  66. memos/memories/textual/tree_text_memory/retrieve/bochasearch.py +10 -6
  67. memos/memories/textual/tree_text_memory/retrieve/internet_retriever.py +6 -2
  68. memos/memories/textual/tree_text_memory/retrieve/internet_retriever_factory.py +2 -0
  69. memos/memories/textual/tree_text_memory/retrieve/recall.py +119 -21
  70. memos/memories/textual/tree_text_memory/retrieve/searcher.py +172 -44
  71. memos/memories/textual/tree_text_memory/retrieve/utils.py +6 -4
  72. memos/memories/textual/tree_text_memory/retrieve/xinyusearch.py +5 -4
  73. memos/memos_tools/notification_utils.py +46 -0
  74. memos/memos_tools/singleton.py +174 -0
  75. memos/memos_tools/thread_safe_dict.py +22 -0
  76. memos/memos_tools/thread_safe_dict_segment.py +382 -0
  77. memos/parsers/factory.py +2 -0
  78. memos/reranker/__init__.py +4 -0
  79. memos/reranker/base.py +24 -0
  80. memos/reranker/concat.py +59 -0
  81. memos/reranker/cosine_local.py +96 -0
  82. memos/reranker/factory.py +48 -0
  83. memos/reranker/http_bge.py +312 -0
  84. memos/reranker/noop.py +16 -0
  85. memos/templates/mem_reader_prompts.py +289 -40
  86. memos/templates/mem_scheduler_prompts.py +242 -0
  87. memos/templates/mos_prompts.py +133 -60
  88. memos/types.py +4 -1
  89. memos/api/context/context.py +0 -147
  90. memos/mem_scheduler/mos_for_test_scheduler.py +0 -146
  91. {memoryos-1.0.0.dist-info → memoryos-1.1.1.dist-info}/entry_points.txt +0 -0
  92. {memoryos-1.0.0.dist-info → memoryos-1.1.1.dist-info/licenses}/LICENSE +0 -0
  93. /memos/mem_scheduler/{general_modules → webservice_modules}/rabbitmq_service.py +0 -0
  94. /memos/mem_scheduler/{general_modules → webservice_modules}/redis_service.py +0 -0
@@ -0,0 +1,280 @@
1
+ from __future__ import annotations
2
+
3
+ import time
4
+
5
+ from functools import wraps
6
+ from typing import TYPE_CHECKING, Any, ClassVar
7
+
8
+ from memos.log import get_logger
9
+ from memos.mem_scheduler.general_scheduler import GeneralScheduler
10
+ from memos.mem_scheduler.schemas.general_schemas import (
11
+ DEFAULT_MAX_QUERY_KEY_WORDS,
12
+ UserID,
13
+ )
14
+ from memos.mem_scheduler.schemas.monitor_schemas import QueryMonitorItem
15
+
16
+
17
+ if TYPE_CHECKING:
18
+ from memos.memories.textual.tree import TextualMemoryItem
19
+
20
+
21
+ logger = get_logger(__name__)
22
+
23
+
24
+ class SchedulerForEval(GeneralScheduler):
25
+ """
26
+ A scheduler class that inherits from GeneralScheduler and provides evaluation-specific functionality.
27
+ This class extends GeneralScheduler with evaluation methods.
28
+ """
29
+
30
+ # Class variable to store timing information for all instances
31
+ timer_cache: ClassVar[dict[str, dict[str, Any]]] = {}
32
+
33
+ def __init__(self, config):
34
+ """
35
+ Initialize the SchedulerForEval with the same configuration as GeneralScheduler.
36
+
37
+ Args:
38
+ config: Configuration object for the scheduler
39
+ """
40
+ super().__init__(config)
41
+ # Initialize instance timer_cache
42
+ self.timer_cache = {}
43
+
44
+ @staticmethod
45
+ def time_it(func_name: str | None = None):
46
+ """
47
+ Static method decorator to measure function execution time and store in timer_cache.
48
+
49
+ Args:
50
+ func_name: Custom name for the function in timer_cache. If None, uses function.__name__
51
+ """
52
+
53
+ def decorator(func):
54
+ @wraps(func)
55
+ def wrapper(self, *args, **kwargs):
56
+ # Get function name
57
+ name = func_name or func.__name__
58
+
59
+ # Start timing
60
+ start_time = time.time()
61
+ result = func(self, *args, **kwargs)
62
+ end_time = time.time()
63
+
64
+ # Calculate execution time
65
+ exec_time = end_time - start_time
66
+
67
+ # Format time as HH:MM:SS.mmm
68
+ hours = int(exec_time // 3600)
69
+ minutes = int((exec_time % 3600) // 60)
70
+ seconds = exec_time % 60
71
+
72
+ if hours > 0:
73
+ time_str = f"{hours:02d}:{minutes:02d}:{seconds:06.3f}"
74
+ else:
75
+ time_str = f"{minutes:02d}:{seconds:06.3f}"
76
+
77
+ # Store in timer_cache
78
+ if not hasattr(self, "timer_cache"):
79
+ self.timer_cache = {}
80
+
81
+ self.timer_cache[name] = {
82
+ "time_str": time_str,
83
+ "seconds": exec_time,
84
+ }
85
+
86
+ logger.info(f"{name} executed in {time_str}")
87
+ return result
88
+
89
+ return wrapper
90
+
91
+ return decorator
92
+
93
+ def get_timer_summary(self) -> str:
94
+ """
95
+ Get a summary of all timed functions.
96
+
97
+ Returns:
98
+ Formatted string with timing information
99
+ """
100
+ if not self.timer_cache:
101
+ return "No timing data available."
102
+
103
+ summary = "=== Timing Summary ===\n"
104
+ for func_name, data in self.timer_cache.items():
105
+ summary += f"{func_name}: {data['time_str']} (at {data['timestamp']})\n"
106
+
107
+ return summary
108
+
109
+ def clear_timer_cache(self):
110
+ """Clear the timer cache."""
111
+ self.timer_cache.clear()
112
+
113
+ @time_it("update_working_memory")
114
+ def update_working_memory_for_eval(
115
+ self, query: str, user_id: UserID | str, top_k: int
116
+ ) -> list[str]:
117
+ """
118
+ Update working memory based on query and return the updated memory list.
119
+
120
+ Args:
121
+ query: The query string
122
+ user_id: User identifier
123
+ top_k: Number of top memories to return
124
+
125
+ Returns:
126
+ List of memory strings from updated working memory
127
+ """
128
+ self.monitor.register_query_monitor_if_not_exists(
129
+ user_id=user_id, mem_cube_id=self.current_mem_cube_id
130
+ )
131
+
132
+ query_keywords = self.monitor.extract_query_keywords(query=query)
133
+ logger.info(f'Extract keywords "{query_keywords}" from query "{query}"')
134
+
135
+ item = QueryMonitorItem(
136
+ user_id=user_id,
137
+ mem_cube_id=self.current_mem_cube_id,
138
+ query_text=query,
139
+ keywords=query_keywords,
140
+ max_keywords=DEFAULT_MAX_QUERY_KEY_WORDS,
141
+ )
142
+ query_db_manager = self.monitor.query_monitors[user_id][self.current_mem_cube_id]
143
+ query_db_manager.obj.put(item=item)
144
+ # Sync with database after adding new item
145
+ query_db_manager.sync_with_orm()
146
+ logger.debug(f"Queries in monitor are {query_db_manager.obj.get_queries_with_timesort()}.")
147
+
148
+ queries = [query]
149
+
150
+ # recall
151
+ mem_cube = self.current_mem_cube
152
+ text_mem_base = mem_cube.text_mem
153
+
154
+ cur_working_memory: list[TextualMemoryItem] = text_mem_base.get_working_memory()
155
+ text_working_memory: list[str] = [w_m.memory for w_m in cur_working_memory]
156
+ intent_result = self.monitor.detect_intent(
157
+ q_list=queries, text_working_memory=text_working_memory
158
+ )
159
+
160
+ if intent_result["trigger_retrieval"]:
161
+ missing_evidences = intent_result["missing_evidences"]
162
+ num_evidence = len(missing_evidences)
163
+ k_per_evidence = max(1, top_k // max(1, num_evidence))
164
+ new_candidates = []
165
+ for item in missing_evidences:
166
+ logger.info(f"missing_evidences: {item}")
167
+ results: list[TextualMemoryItem] = self.retriever.search(
168
+ query=item,
169
+ mem_cube=mem_cube,
170
+ top_k=k_per_evidence,
171
+ method=self.search_method,
172
+ )
173
+ logger.info(
174
+ f"search results for {missing_evidences}: {[one.memory for one in results]}"
175
+ )
176
+ new_candidates.extend(results)
177
+ logger.info(
178
+ f"missing_evidences: {missing_evidences} and get {len(new_candidates)} new candidate memories."
179
+ )
180
+ else:
181
+ new_candidates = []
182
+ logger.info(f"intent_result: {intent_result}. not triggered")
183
+
184
+ # rerank
185
+ new_order_working_memory = self.replace_working_memory(
186
+ user_id=user_id,
187
+ mem_cube_id=self.current_mem_cube_id,
188
+ mem_cube=self.current_mem_cube,
189
+ original_memory=cur_working_memory,
190
+ new_memory=new_candidates,
191
+ )
192
+ new_order_working_memory = new_order_working_memory[:top_k]
193
+ logger.info(f"size of new_order_working_memory: {len(new_order_working_memory)}")
194
+
195
+ return [m.memory for m in new_order_working_memory]
196
+
197
+ @time_it("memory_answer_ability")
198
+ def evaluate_memory_answer_ability(
199
+ self, query: str, memory_texts: list[str], top_k: int = 100
200
+ ) -> bool:
201
+ """
202
+ Use LLM to evaluate whether the given memories can answer the query.
203
+
204
+ Args:
205
+ query: The query string to evaluate
206
+ memory_texts: List of memory texts to check against
207
+ top_k: Maximum number of memories to consider for evaluation
208
+
209
+ Returns:
210
+ Boolean indicating whether the memories can answer the query
211
+ """
212
+ # Limit the number of memories to evaluate
213
+ limited_memories = memory_texts[:top_k] if memory_texts else []
214
+
215
+ # Build prompt using the template
216
+ prompt = self.monitor.build_prompt(
217
+ template_name="memory_answer_ability_evaluation",
218
+ query=query,
219
+ memory_list="\n".join([f"- {memory}" for memory in limited_memories])
220
+ if limited_memories
221
+ else "No memories available",
222
+ )
223
+
224
+ # Use the process LLM to generate response
225
+ response = self.monitor._process_llm.generate([{"role": "user", "content": prompt}])
226
+
227
+ try:
228
+ # Extract JSON response
229
+ from memos.mem_scheduler.utils.misc_utils import extract_json_dict
230
+
231
+ result = extract_json_dict(response)
232
+
233
+ # Validate response structure
234
+ if "result" in result:
235
+ logger.info(
236
+ f"Memory answer ability evaluation result: {result['result']}, reason: {result.get('reason', 'No reason provided')}"
237
+ )
238
+ return result["result"]
239
+ else:
240
+ logger.warning(f"Invalid response structure from LLM: {result}")
241
+ return False
242
+
243
+ except Exception as e:
244
+ logger.error(
245
+ f"Failed to parse LLM response for memory answer ability evaluation: {response}. Error: {e}"
246
+ )
247
+ # Fallback: return False if we can't determine answer ability
248
+ return False
249
+
250
+ @time_it("search_for_eval")
251
+ def search_for_eval(
252
+ self, query: str, user_id: UserID | str, top_k: int, scheduler_flag: bool = True
253
+ ) -> list[str]:
254
+ """
255
+ Original search_for_eval function refactored to use the new decomposed functions.
256
+
257
+ Args:
258
+ query: The query string
259
+ user_id: User identifier
260
+ top_k: Number of top memories to return
261
+ scheduler_flag: Whether to update working memory or just evaluate
262
+
263
+ Returns:
264
+ Tuple of (memory_list, can_answer_boolean)
265
+ """
266
+ if not scheduler_flag:
267
+ # Get current working memory without updating
268
+ mem_cube = self.current_mem_cube
269
+ text_mem_base = mem_cube.text_mem
270
+ cur_working_memory: list[TextualMemoryItem] = text_mem_base.get_working_memory()
271
+ text_working_memory: list[str] = [w_m.memory for w_m in cur_working_memory]
272
+
273
+ return text_working_memory
274
+ else:
275
+ # Update working memory and get the result
276
+ updated_memories = self.update_working_memory_for_eval(
277
+ query=query, user_id=user_id, top_k=top_k
278
+ )
279
+
280
+ return updated_memories
@@ -5,16 +5,16 @@ import time
5
5
  from datetime import datetime
6
6
  from pathlib import Path
7
7
 
8
+ from sqlalchemy.engine import Engine
9
+
8
10
  from memos.configs.mem_scheduler import AuthConfig, BaseSchedulerConfig
9
11
  from memos.llms.base import BaseLLM
10
12
  from memos.log import get_logger
11
13
  from memos.mem_cube.general import GeneralMemCube
12
14
  from memos.mem_scheduler.general_modules.dispatcher import SchedulerDispatcher
13
15
  from memos.mem_scheduler.general_modules.misc import AutoDroppingQueue as Queue
14
- from memos.mem_scheduler.general_modules.rabbitmq_service import RabbitMQSchedulerModule
15
- from memos.mem_scheduler.general_modules.redis_service import RedisSchedulerModule
16
- from memos.mem_scheduler.general_modules.retriever import SchedulerRetriever
17
16
  from memos.mem_scheduler.general_modules.scheduler_logger import SchedulerLoggerModule
17
+ from memos.mem_scheduler.memory_manage_modules.retriever import SchedulerRetriever
18
18
  from memos.mem_scheduler.monitors.dispatcher_monitor import SchedulerDispatcherMonitor
19
19
  from memos.mem_scheduler.monitors.general_monitor import SchedulerGeneralMonitor
20
20
  from memos.mem_scheduler.schemas.general_schemas import (
@@ -33,6 +33,8 @@ from memos.mem_scheduler.schemas.monitor_schemas import MemoryMonitorItem
33
33
  from memos.mem_scheduler.utils.filter_utils import (
34
34
  transform_name_to_key,
35
35
  )
36
+ from memos.mem_scheduler.webservice_modules.rabbitmq_service import RabbitMQSchedulerModule
37
+ from memos.mem_scheduler.webservice_modules.redis_service import RedisSchedulerModule
36
38
  from memos.memories.activation.kv import KVCacheMemory
37
39
  from memos.memories.activation.vllmkv import VLLMKVCacheItem, VLLMKVCacheMemory
38
40
  from memos.memories.textual.tree import TextualMemoryItem, TreeTextMemory
@@ -62,6 +64,7 @@ class BaseScheduler(RabbitMQSchedulerModule, RedisSchedulerModule, SchedulerLogg
62
64
  )
63
65
 
64
66
  self.retriever: SchedulerRetriever | None = None
67
+ self.db_engine: Engine | None = None
65
68
  self.monitor: SchedulerGeneralMonitor | None = None
66
69
  self.dispatcher_monitor: SchedulerDispatcherMonitor | None = None
67
70
  self.dispatcher = SchedulerDispatcher(
@@ -70,12 +73,15 @@ class BaseScheduler(RabbitMQSchedulerModule, RedisSchedulerModule, SchedulerLogg
70
73
  )
71
74
 
72
75
  # internal message queue
73
- self.max_internal_messae_queue_size = 100
76
+ self.max_internal_message_queue_size = self.config.get(
77
+ "max_internal_message_queue_size", 100
78
+ )
74
79
  self.memos_message_queue: Queue[ScheduleMessageItem] = Queue(
75
- maxsize=self.max_internal_messae_queue_size
80
+ maxsize=self.max_internal_message_queue_size
76
81
  )
82
+ self.max_web_log_queue_size = self.config.get("max_web_log_queue_size", 50)
77
83
  self._web_log_message_queue: Queue[ScheduleLogForWebItem] = Queue(
78
- maxsize=self.max_internal_messae_queue_size
84
+ maxsize=self.max_web_log_queue_size
79
85
  )
80
86
  self._consumer_thread = None # Reference to our consumer thread
81
87
  self._running = False
@@ -92,34 +98,57 @@ class BaseScheduler(RabbitMQSchedulerModule, RedisSchedulerModule, SchedulerLogg
92
98
  self.auth_config = None
93
99
  self.rabbitmq_config = None
94
100
 
95
- def initialize_modules(self, chat_llm: BaseLLM, process_llm: BaseLLM | None = None):
101
+ def initialize_modules(
102
+ self,
103
+ chat_llm: BaseLLM,
104
+ process_llm: BaseLLM | None = None,
105
+ db_engine: Engine | None = None,
106
+ ):
96
107
  if process_llm is None:
97
108
  process_llm = chat_llm
98
109
 
99
- # initialize submodules
100
- self.chat_llm = chat_llm
101
- self.process_llm = process_llm
102
- self.monitor = SchedulerGeneralMonitor(process_llm=self.process_llm, config=self.config)
103
- self.dispatcher_monitor = SchedulerDispatcherMonitor(config=self.config)
104
- self.retriever = SchedulerRetriever(process_llm=self.process_llm, config=self.config)
110
+ try:
111
+ # initialize submodules
112
+ self.chat_llm = chat_llm
113
+ self.process_llm = process_llm
114
+ self.db_engine = db_engine
115
+ self.monitor = SchedulerGeneralMonitor(
116
+ process_llm=self.process_llm, config=self.config, db_engine=self.db_engine
117
+ )
118
+ self.db_engine = self.monitor.db_engine
119
+ self.dispatcher_monitor = SchedulerDispatcherMonitor(config=self.config)
120
+ self.retriever = SchedulerRetriever(process_llm=self.process_llm, config=self.config)
121
+
122
+ if self.enable_parallel_dispatch:
123
+ self.dispatcher_monitor.initialize(dispatcher=self.dispatcher)
124
+ self.dispatcher_monitor.start()
125
+
126
+ # initialize with auth_config
127
+ if self.auth_config_path is not None and Path(self.auth_config_path).exists():
128
+ self.auth_config = AuthConfig.from_local_config(config_path=self.auth_config_path)
129
+ elif AuthConfig.default_config_exists():
130
+ self.auth_config = AuthConfig.from_local_config()
131
+ else:
132
+ self.auth_config = AuthConfig.from_local_env()
105
133
 
106
- if self.enable_parallel_dispatch:
107
- self.dispatcher_monitor.initialize(dispatcher=self.dispatcher)
108
- self.dispatcher_monitor.start()
109
-
110
- # initialize with auth_cofig
111
- if self.auth_config_path is not None and Path(self.auth_config_path).exists():
112
- self.auth_config = AuthConfig.from_local_config(config_path=self.auth_config_path)
113
- elif AuthConfig.default_config_exists():
114
- self.auth_config = AuthConfig.from_local_config()
115
- else:
116
- self.auth_config = None
134
+ if self.auth_config is not None:
135
+ self.rabbitmq_config = self.auth_config.rabbitmq
136
+ self.initialize_rabbitmq(config=self.rabbitmq_config)
117
137
 
118
- if self.auth_config is not None:
119
- self.rabbitmq_config = self.auth_config.rabbitmq
120
- self.initialize_rabbitmq(config=self.rabbitmq_config)
138
+ logger.debug("GeneralScheduler has been initialized")
139
+ except Exception as e:
140
+ logger.error(f"Failed to initialize scheduler modules: {e}", exc_info=True)
141
+ # Clean up any partially initialized resources
142
+ self._cleanup_on_init_failure()
143
+ raise
121
144
 
122
- logger.debug("GeneralScheduler has been initialized")
145
+ def _cleanup_on_init_failure(self):
146
+ """Clean up resources if initialization fails."""
147
+ try:
148
+ if hasattr(self, "dispatcher_monitor") and self.dispatcher_monitor is not None:
149
+ self.dispatcher_monitor.stop()
150
+ except Exception as e:
151
+ logger.warning(f"Error during cleanup: {e}")
123
152
 
124
153
  @property
125
154
  def mem_cube(self) -> GeneralMemCube:
@@ -200,8 +229,11 @@ class BaseScheduler(RabbitMQSchedulerModule, RedisSchedulerModule, SchedulerLogg
200
229
  text_mem_base: TreeTextMemory = text_mem_base
201
230
 
202
231
  # process rerank memories with llm
203
- query_monitor = self.monitor.query_monitors[user_id][mem_cube_id]
204
- query_history = query_monitor.get_queries_with_timesort()
232
+ query_db_manager = self.monitor.query_monitors[user_id][mem_cube_id]
233
+ # Sync with database to get latest query history
234
+ query_db_manager.sync_with_orm()
235
+
236
+ query_history = query_db_manager.obj.get_queries_with_timesort()
205
237
  memories_with_new_order, rerank_success_flag = (
206
238
  self.retriever.process_and_rerank_memories(
207
239
  queries=query_history,
@@ -211,8 +243,27 @@ class BaseScheduler(RabbitMQSchedulerModule, RedisSchedulerModule, SchedulerLogg
211
243
  )
212
244
  )
213
245
 
214
- # update working memory monitors
215
- query_keywords = query_monitor.get_keywords_collections()
246
+ # Filter completely unrelated memories according to query_history
247
+ logger.info(f"Filtering memories based on query history: {len(query_history)} queries")
248
+ filtered_memories, filter_success_flag = self.retriever.filter_unrelated_memories(
249
+ query_history=query_history,
250
+ memories=memories_with_new_order,
251
+ )
252
+
253
+ if filter_success_flag:
254
+ logger.info(
255
+ f"Memory filtering completed successfully. "
256
+ f"Filtered from {len(memories_with_new_order)} to {len(filtered_memories)} memories"
257
+ )
258
+ memories_with_new_order = filtered_memories
259
+ else:
260
+ logger.warning(
261
+ "Memory filtering failed - keeping all memories as fallback. "
262
+ f"Original count: {len(memories_with_new_order)}"
263
+ )
264
+
265
+ # Update working memory monitors
266
+ query_keywords = query_db_manager.obj.get_keywords_collections()
216
267
  logger.info(
217
268
  f"Processing {len(memories_with_new_order)} memories with {len(query_keywords)} query keywords"
218
269
  )
@@ -235,7 +286,7 @@ class BaseScheduler(RabbitMQSchedulerModule, RedisSchedulerModule, SchedulerLogg
235
286
 
236
287
  mem_monitors: list[MemoryMonitorItem] = self.monitor.working_memory_monitors[user_id][
237
288
  mem_cube_id
238
- ].get_sorted_mem_monitors(reverse=True)
289
+ ].obj.get_sorted_mem_monitors(reverse=True)
239
290
  new_working_memories = [mem_monitor.tree_memory_item for mem_monitor in mem_monitors]
240
291
 
241
292
  text_mem_base.replace_working_memory(memories=new_working_memories)
@@ -278,6 +329,7 @@ class BaseScheduler(RabbitMQSchedulerModule, RedisSchedulerModule, SchedulerLogg
278
329
  new_text_memories = new_memories
279
330
  else:
280
331
  logger.error("Not Implemented.")
332
+ return
281
333
 
282
334
  try:
283
335
  if isinstance(mem_cube.act_mem, VLLMKVCacheMemory):
@@ -333,7 +385,9 @@ class BaseScheduler(RabbitMQSchedulerModule, RedisSchedulerModule, SchedulerLogg
333
385
  )
334
386
 
335
387
  except Exception as e:
336
- logger.warning(f"MOS-based activation memory update failed: {e}", exc_info=True)
388
+ logger.error(f"MOS-based activation memory update failed: {e}", exc_info=True)
389
+ # Re-raise the exception if it's critical for the operation
390
+ # For now, we'll continue execution but this should be reviewed
337
391
 
338
392
  def update_activation_memory_periodically(
339
393
  self,
@@ -358,7 +412,8 @@ class BaseScheduler(RabbitMQSchedulerModule, RedisSchedulerModule, SchedulerLogg
358
412
  if (
359
413
  user_id not in self.monitor.working_memory_monitors
360
414
  or mem_cube_id not in self.monitor.working_memory_monitors[user_id]
361
- or len(self.monitor.working_memory_monitors[user_id][mem_cube_id].memories) == 0
415
+ or len(self.monitor.working_memory_monitors[user_id][mem_cube_id].obj.memories)
416
+ == 0
362
417
  ):
363
418
  logger.warning(
364
419
  "No memories found in working_memory_monitors, activation memory update is skipped"
@@ -369,9 +424,13 @@ class BaseScheduler(RabbitMQSchedulerModule, RedisSchedulerModule, SchedulerLogg
369
424
  user_id=user_id, mem_cube_id=mem_cube_id, mem_cube=mem_cube
370
425
  )
371
426
 
427
+ # Sync with database to get latest activation memories
428
+ activation_db_manager = self.monitor.activation_memory_monitors[user_id][
429
+ mem_cube_id
430
+ ]
431
+ activation_db_manager.sync_with_orm()
372
432
  new_activation_memories = [
373
- m.memory_text
374
- for m in self.monitor.activation_memory_monitors[user_id][mem_cube_id].memories
433
+ m.memory_text for m in activation_db_manager.obj.memories
375
434
  ]
376
435
 
377
436
  logger.info(
@@ -412,6 +471,11 @@ class BaseScheduler(RabbitMQSchedulerModule, RedisSchedulerModule, SchedulerLogg
412
471
  messages = [messages] # transform single message to list
413
472
 
414
473
  for message in messages:
474
+ if not isinstance(message, ScheduleMessageItem):
475
+ error_msg = f"Invalid message type: {type(message)}, expected ScheduleMessageItem"
476
+ logger.error(error_msg)
477
+ raise TypeError(error_msg)
478
+
415
479
  self.memos_message_queue.put(message)
416
480
  logger.info(f"Submitted message: {message.label} - {message.content}")
417
481
 
@@ -427,6 +491,11 @@ class BaseScheduler(RabbitMQSchedulerModule, RedisSchedulerModule, SchedulerLogg
427
491
  messages = [messages] # transform single message to list
428
492
 
429
493
  for message in messages:
494
+ if not isinstance(message, ScheduleLogForWebItem):
495
+ error_msg = f"Invalid message type: {type(message)}, expected ScheduleLogForWebItem"
496
+ logger.error(error_msg)
497
+ raise TypeError(error_msg)
498
+
430
499
  self._web_log_message_queue.put(message)
431
500
  message_info = message.debug_info()
432
501
  logger.debug(f"Submitted Scheduling log for web: {message_info}")
@@ -461,25 +530,26 @@ class BaseScheduler(RabbitMQSchedulerModule, RedisSchedulerModule, SchedulerLogg
461
530
  """
462
531
  while self._running: # Use a running flag for graceful shutdown
463
532
  try:
464
- # Check if queue has messages (non-blocking)
465
- if not self.memos_message_queue.empty():
466
- # Get all available messages at once
467
- messages = []
468
- while not self.memos_message_queue.empty():
469
- try:
470
- messages.append(self.memos_message_queue.get_nowait())
471
- except queue.Empty:
472
- break
473
-
474
- if messages:
475
- try:
476
- self.dispatcher.dispatch(messages)
477
- except Exception as e:
478
- logger.error(f"Error dispatching messages: {e!s}")
479
- finally:
480
- # Mark all messages as processed
481
- for _ in messages:
482
- self.memos_message_queue.task_done()
533
+ # Get all available messages at once (thread-safe approach)
534
+ messages = []
535
+ while True:
536
+ try:
537
+ # Use get_nowait() directly without empty() check to avoid race conditions
538
+ message = self.memos_message_queue.get_nowait()
539
+ messages.append(message)
540
+ except queue.Empty:
541
+ # No more messages available
542
+ break
543
+
544
+ if messages:
545
+ try:
546
+ self.dispatcher.dispatch(messages)
547
+ except Exception as e:
548
+ logger.error(f"Error dispatching messages: {e!s}")
549
+ finally:
550
+ # Mark all messages as processed
551
+ for _ in messages:
552
+ self.memos_message_queue.task_done()
483
553
 
484
554
  # Sleep briefly to prevent busy waiting
485
555
  time.sleep(self._consume_interval) # Adjust interval as needed
@@ -2,8 +2,8 @@ import concurrent
2
2
 
3
3
  from collections import defaultdict
4
4
  from collections.abc import Callable
5
- from concurrent.futures import ThreadPoolExecutor
6
5
 
6
+ from memos.context.context import ContextThreadPoolExecutor
7
7
  from memos.log import get_logger
8
8
  from memos.mem_scheduler.general_modules.base import BaseSchedulerModule
9
9
  from memos.mem_scheduler.schemas.message_schemas import ScheduleMessageItem
@@ -33,7 +33,7 @@ class SchedulerDispatcher(BaseSchedulerModule):
33
33
  self.enable_parallel_dispatch = enable_parallel_dispatch
34
34
  self.thread_name_prefix = "dispatcher"
35
35
  if self.enable_parallel_dispatch:
36
- self.dispatcher_executor = ThreadPoolExecutor(
36
+ self.dispatcher_executor = ContextThreadPoolExecutor(
37
37
  max_workers=self.max_workers, thread_name_prefix=self.thread_name_prefix
38
38
  )
39
39
  else: