MemoryOS 1.0.1__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 (82) hide show
  1. {memoryos-1.0.1.dist-info → memoryos-1.1.1.dist-info}/METADATA +7 -2
  2. {memoryos-1.0.1.dist-info → memoryos-1.1.1.dist-info}/RECORD +79 -65
  3. {memoryos-1.0.1.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 +11 -9
  7. memos/api/context/dependencies.py +15 -55
  8. memos/api/middleware/request_context.py +9 -40
  9. memos/api/product_api.py +2 -3
  10. memos/api/product_models.py +91 -16
  11. memos/api/routers/product_router.py +23 -16
  12. memos/api/start_api.py +10 -0
  13. memos/configs/graph_db.py +4 -0
  14. memos/configs/mem_scheduler.py +38 -3
  15. memos/context/context.py +255 -0
  16. memos/embedders/factory.py +2 -0
  17. memos/graph_dbs/nebular.py +230 -232
  18. memos/graph_dbs/neo4j.py +35 -1
  19. memos/graph_dbs/neo4j_community.py +7 -0
  20. memos/llms/factory.py +2 -0
  21. memos/llms/openai.py +74 -2
  22. memos/log.py +27 -15
  23. memos/mem_cube/general.py +3 -1
  24. memos/mem_os/core.py +60 -22
  25. memos/mem_os/main.py +3 -6
  26. memos/mem_os/product.py +35 -11
  27. memos/mem_reader/factory.py +2 -0
  28. memos/mem_reader/simple_struct.py +127 -74
  29. memos/mem_scheduler/analyzer/__init__.py +0 -0
  30. memos/mem_scheduler/analyzer/mos_for_test_scheduler.py +569 -0
  31. memos/mem_scheduler/analyzer/scheduler_for_eval.py +280 -0
  32. memos/mem_scheduler/base_scheduler.py +126 -56
  33. memos/mem_scheduler/general_modules/dispatcher.py +2 -2
  34. memos/mem_scheduler/general_modules/misc.py +99 -1
  35. memos/mem_scheduler/general_modules/scheduler_logger.py +17 -11
  36. memos/mem_scheduler/general_scheduler.py +40 -88
  37. memos/mem_scheduler/memory_manage_modules/__init__.py +5 -0
  38. memos/mem_scheduler/memory_manage_modules/memory_filter.py +308 -0
  39. memos/mem_scheduler/{general_modules → memory_manage_modules}/retriever.py +34 -7
  40. memos/mem_scheduler/monitors/dispatcher_monitor.py +9 -8
  41. memos/mem_scheduler/monitors/general_monitor.py +119 -39
  42. memos/mem_scheduler/optimized_scheduler.py +124 -0
  43. memos/mem_scheduler/orm_modules/__init__.py +0 -0
  44. memos/mem_scheduler/orm_modules/base_model.py +635 -0
  45. memos/mem_scheduler/orm_modules/monitor_models.py +261 -0
  46. memos/mem_scheduler/scheduler_factory.py +2 -0
  47. memos/mem_scheduler/schemas/monitor_schemas.py +96 -29
  48. memos/mem_scheduler/utils/config_utils.py +100 -0
  49. memos/mem_scheduler/utils/db_utils.py +33 -0
  50. memos/mem_scheduler/utils/filter_utils.py +1 -1
  51. memos/mem_scheduler/webservice_modules/__init__.py +0 -0
  52. memos/memories/activation/kv.py +2 -1
  53. memos/memories/textual/item.py +95 -16
  54. memos/memories/textual/naive.py +1 -1
  55. memos/memories/textual/tree.py +27 -3
  56. memos/memories/textual/tree_text_memory/organize/handler.py +4 -2
  57. memos/memories/textual/tree_text_memory/organize/manager.py +28 -14
  58. memos/memories/textual/tree_text_memory/organize/relation_reason_detector.py +1 -2
  59. memos/memories/textual/tree_text_memory/organize/reorganizer.py +75 -23
  60. memos/memories/textual/tree_text_memory/retrieve/bochasearch.py +7 -5
  61. memos/memories/textual/tree_text_memory/retrieve/internet_retriever.py +6 -2
  62. memos/memories/textual/tree_text_memory/retrieve/internet_retriever_factory.py +2 -0
  63. memos/memories/textual/tree_text_memory/retrieve/recall.py +70 -22
  64. memos/memories/textual/tree_text_memory/retrieve/searcher.py +101 -33
  65. memos/memories/textual/tree_text_memory/retrieve/xinyusearch.py +5 -4
  66. memos/memos_tools/singleton.py +174 -0
  67. memos/memos_tools/thread_safe_dict.py +22 -0
  68. memos/memos_tools/thread_safe_dict_segment.py +382 -0
  69. memos/parsers/factory.py +2 -0
  70. memos/reranker/concat.py +59 -0
  71. memos/reranker/cosine_local.py +1 -0
  72. memos/reranker/factory.py +5 -0
  73. memos/reranker/http_bge.py +225 -12
  74. memos/templates/mem_scheduler_prompts.py +242 -0
  75. memos/types.py +4 -1
  76. memos/api/context/context.py +0 -147
  77. memos/api/context/context_thread.py +0 -96
  78. memos/mem_scheduler/mos_for_test_scheduler.py +0 -146
  79. {memoryos-1.0.1.dist-info → memoryos-1.1.1.dist-info}/entry_points.txt +0 -0
  80. {memoryos-1.0.1.dist-info → memoryos-1.1.1.dist-info/licenses}/LICENSE +0 -0
  81. /memos/mem_scheduler/{general_modules → webservice_modules}/rabbitmq_service.py +0 -0
  82. /memos/mem_scheduler/{general_modules → webservice_modules}/redis_service.py +0 -0
@@ -0,0 +1,569 @@
1
+ from datetime import datetime
2
+
3
+ from memos.configs.mem_os import MOSConfig
4
+ from memos.log import get_logger
5
+ from memos.mem_os.main import MOS
6
+ from memos.mem_scheduler.schemas.general_schemas import (
7
+ ANSWER_LABEL,
8
+ MONITOR_WORKING_MEMORY_TYPE,
9
+ QUERY_LABEL,
10
+ )
11
+ from memos.mem_scheduler.schemas.message_schemas import ScheduleMessageItem
12
+
13
+
14
+ logger = get_logger(__name__)
15
+
16
+
17
+ class MOSForTestScheduler(MOS):
18
+ """This class is only to test abilities of mem scheduler with enhanced monitoring"""
19
+
20
+ def __init__(self, config: MOSConfig):
21
+ super().__init__(config)
22
+ self.memory_helpfulness_analysis = []
23
+
24
+ def _str_memories(self, memories: list[str]) -> str:
25
+ """Format memories for display."""
26
+ if not memories:
27
+ return "No memories."
28
+ return "\n".join(f"{i + 1}. {memory}" for i, memory in enumerate(memories))
29
+
30
+ def _analyze_memory_helpfulness(
31
+ self,
32
+ query: str,
33
+ working_memories_before: list,
34
+ working_memories_after: list,
35
+ scheduler_memories: list,
36
+ ):
37
+ """Analyze how helpful each memory is for answering the current query."""
38
+ print("\n" + "=" * 80)
39
+ print("🧠 MEMORY HELPFULNESS ANALYSIS FOR QUERY")
40
+ print("=" * 80)
41
+
42
+ print(f"📝 Query: {query}")
43
+ print(f"📊 Working Memories Before Scheduler: {len(working_memories_before)}")
44
+ print(f"📊 Working Memories After Scheduler: {len(working_memories_after)}")
45
+ print(f"📊 Working Memories from Monitor: {len(scheduler_memories)}")
46
+
47
+ # Display working memories before scheduler (first 5 only)
48
+ if working_memories_before:
49
+ print("\n🔄 WORKING MEMORIES BEFORE SCHEDULER (first 5):")
50
+ for i, mem in enumerate(working_memories_before[:5]):
51
+ print(f" {i + 1}. {mem}")
52
+
53
+ # Display working memories after scheduler (first 5 only)
54
+ if working_memories_after:
55
+ print("\n🔄 WORKING MEMORIES AFTER SCHEDULER (first 5):")
56
+ for i, mem in enumerate(working_memories_after[:5]):
57
+ print(f" {i + 1}. {mem}")
58
+
59
+ # Display scheduler memories from monitor (first 5 only)
60
+ if scheduler_memories:
61
+ print("\n🔄 WORKING MEMORIES FROM MONITOR (first 5):")
62
+ for i, mem in enumerate(scheduler_memories[:5]):
63
+ print(f" {i + 1}. {mem}")
64
+
65
+ # Batch assess working memory helpfulness before scheduler
66
+ if working_memories_before:
67
+ print(
68
+ f"\n🔄 WORKING MEMORY HELPFULNESS BEFORE SCHEDULER ({len(working_memories_before)}):"
69
+ )
70
+ before_assessment = self._batch_assess_memories(
71
+ query, working_memories_before[:5], "before scheduler"
72
+ )
73
+ for i, (_mem, score, reason) in enumerate(before_assessment):
74
+ print(f" {i + 1}. Helpfulness: {score}/10 - {reason}")
75
+
76
+ # Batch assess working memory helpfulness after scheduler
77
+ if working_memories_after:
78
+ print(
79
+ f"\n🔄 WORKING MEMORY HELPFULNESS AFTER SCHEDULER ({len(working_memories_after)}):"
80
+ )
81
+ after_assessment = self._batch_assess_memories(
82
+ query, working_memories_after[:5], "after scheduler"
83
+ )
84
+ for i, (_mem, score, reason) in enumerate(after_assessment):
85
+ print(f" {i + 1}. Helpfulness: {score}/10 - {reason}")
86
+
87
+ # Batch assess scheduler memories from monitor
88
+ if scheduler_memories:
89
+ print(f"\n🔄 WORKINGMEMORIES FROM MONITOR HELPFULNESS ({len(scheduler_memories)}):")
90
+ scheduler_assessment = self._batch_assess_memories(
91
+ query, scheduler_memories[:5], "from monitor"
92
+ )
93
+ for i, (_mem, score, reason) in enumerate(scheduler_assessment):
94
+ print(f" {i + 1}. Helpfulness: {score}/10 - {reason}")
95
+
96
+ # Overall assessment - compare before vs after vs scheduler
97
+ print("\n💡 OVERALL ASSESSMENT:")
98
+ if working_memories_before and working_memories_after:
99
+ before_scores = (
100
+ [score for _, score, _ in before_assessment]
101
+ if "before_assessment" in locals()
102
+ else []
103
+ )
104
+ after_scores = (
105
+ [score for _, score, _ in after_assessment]
106
+ if "after_assessment" in locals()
107
+ else []
108
+ )
109
+ scheduler_scores = (
110
+ [score for _, score, _ in scheduler_assessment]
111
+ if "scheduler_assessment" in locals()
112
+ else []
113
+ )
114
+
115
+ avg_before_helpfulness = sum(before_scores) / len(before_scores)
116
+ avg_after_helpfulness = sum(after_scores) / len(after_scores)
117
+
118
+ print(f" Average Helpfulness Before Scheduler: {avg_before_helpfulness:.1f}/10")
119
+ print(f" Average Helpfulness After Scheduler: {avg_after_helpfulness:.1f}/10")
120
+ print(f" Improvement: {avg_after_helpfulness - avg_before_helpfulness:+.1f}")
121
+
122
+ if avg_after_helpfulness > avg_before_helpfulness:
123
+ print(" ✅ Scheduler improved working memory quality")
124
+ elif avg_after_helpfulness < avg_before_helpfulness:
125
+ print(" ❌ Scheduler decreased working memory quality")
126
+ else:
127
+ print(" ⚖️ Scheduler maintained working memory quality")
128
+
129
+ # Compare scheduler memories vs working memories
130
+
131
+ avg_scheduler_helpfulness = sum(scheduler_scores) / len(scheduler_scores)
132
+ print(
133
+ f" Average Helpfulness of Memories from Monitors: {avg_scheduler_helpfulness:.1f}/10"
134
+ )
135
+
136
+ if avg_scheduler_helpfulness > avg_after_helpfulness:
137
+ print(" 🎯 Memories from Monitors are more helpful than working memories")
138
+ elif avg_scheduler_helpfulness < avg_after_helpfulness:
139
+ print(" ⚠️ Working memories are more helpful than Memories from Monitors")
140
+ else:
141
+ print(
142
+ " ⚖️ WORKING Memories from Monitors and working memories have similar helpfulness"
143
+ )
144
+
145
+ # Record analysis results
146
+ self.memory_helpfulness_analysis.append(
147
+ {
148
+ "query": query,
149
+ "working_memories_before_count": len(working_memories_before),
150
+ "working_memories_after_count": len(working_memories_after),
151
+ "scheduler_memories_count": len(scheduler_memories),
152
+ "working_helpfulness_before": [score for _, score, _ in before_assessment]
153
+ if "before_assessment" in locals()
154
+ else [],
155
+ "working_helpfulness_after": [score for _, score, _ in after_assessment]
156
+ if "after_assessment" in locals()
157
+ else [],
158
+ "scheduler_helpfulness": [score for _, score, _ in scheduler_assessment]
159
+ if "scheduler_assessment" in locals()
160
+ else [],
161
+ }
162
+ )
163
+
164
+ print("=" * 80 + "\n")
165
+
166
+ def _batch_assess_memories(self, query: str, memories: list, context: str) -> list:
167
+ """Use LLM to assess multiple memories at once and compare their quality."""
168
+ try:
169
+ # Create prompt for batch assessment
170
+ memories_text = "\n".join([f"{i + 1}. {mem}" for i, mem in enumerate(memories)])
171
+
172
+ assessment_prompt = f"""
173
+ Task: Assess and compare the helpfulness of multiple memories for answering a query.
174
+
175
+ Query: "{query}"
176
+
177
+ Context: These are working memories {context}.
178
+
179
+ Memories to assess:
180
+ {memories_text}
181
+
182
+ Please provide:
183
+ 1. A helpfulness score from 1-10 for each memory (where 10 = extremely helpful, 1 = not helpful at all)
184
+ 2. A brief reason for each score
185
+ 3. Rank the memories from most helpful to least helpful
186
+
187
+ Format your response as:
188
+ Memory 1: Score [number] - [reason]
189
+ Memory 2: Score [number] - [reason]
190
+ Memory 3: Score [number] - [reason]
191
+ Memory 4: Score [number] - [reason]
192
+ Memory 5: Score [number] - [reason]
193
+
194
+ Ranking: [memory numbers in order from most to least helpful]
195
+
196
+ Consider:
197
+ - Direct relevance to the query
198
+ - Information completeness
199
+ - How directly it answers the question
200
+ - Whether it provides useful context or background
201
+ - Compare memories against each other for relative quality
202
+ """
203
+
204
+ # Use the chat LLM to get batch assessment
205
+ messages = [{"role": "user", "content": assessment_prompt}]
206
+ response = self.chat_llm.generate(messages)
207
+
208
+ # Parse the response to extract scores and reasons
209
+ assessment_results = []
210
+ lines = response.strip().split("\n")
211
+
212
+ for i, mem in enumerate(memories):
213
+ score = 5 # Default score
214
+ reason = "LLM assessment failed, using default score"
215
+
216
+ # Look for the corresponding memory line
217
+ for line in lines:
218
+ if line.startswith(f"Memory {i + 1}:"):
219
+ try:
220
+ # Extract score and reason from line like "Memory 1: Score 8 - Highly relevant"
221
+ parts = line.split("Score ")[1].split(" - ", 1)
222
+ score = int(parts[0])
223
+ score = max(1, min(10, score)) # Ensure score is 1-10
224
+ reason = parts[1] if len(parts) > 1 else "No reason provided"
225
+ except Exception:
226
+ pass
227
+ break
228
+
229
+ assessment_results.append((mem, score, reason))
230
+
231
+ return assessment_results
232
+
233
+ except Exception as e:
234
+ logger.warning(f"LLM batch assessment failed: {e}, using fallback scoring")
235
+ # Fallback to individual assessment if batch fails
236
+ return [
237
+ (
238
+ mem,
239
+ self._assess_memory_helpfulness(query, mem)["score"],
240
+ self._assess_memory_helpfulness(query, mem)["reason"],
241
+ )
242
+ for mem in memories
243
+ ]
244
+
245
+ def _assess_memory_helpfulness(self, query: str, memory: str) -> dict:
246
+ """Use LLM to assess how helpful a memory is for answering the current query (1-10 scale)"""
247
+ try:
248
+ # Create prompt for LLM assessment
249
+ assessment_prompt = f"""
250
+ Task: Rate how helpful this memory is for answering the given query on a scale of 1-10.
251
+
252
+ Query: "{query}"
253
+
254
+ Memory: "{memory}"
255
+
256
+ Please provide:
257
+ 1. A score from 1-10 (where 10 = extremely helpful, 1 = not helpful at all)
258
+ 2. A brief reason for your score
259
+
260
+ Format your response as:
261
+ Score: [number]
262
+ Reason: [your explanation]
263
+
264
+ Consider:
265
+ - Direct relevance to the query
266
+ - Information completeness
267
+ - How directly it answers the question
268
+ - Whether it provides useful context or background
269
+ """
270
+
271
+ # Use the chat LLM to get assessment
272
+ messages = [{"role": "user", "content": assessment_prompt}]
273
+ response = self.chat_llm.generate(messages)
274
+
275
+ # Parse the response to extract score and reason
276
+ lines = response.strip().split("\n")
277
+ score = 5 # Default score
278
+ reason = "LLM assessment failed, using default score"
279
+
280
+ for line in lines:
281
+ if line.startswith("Score:"):
282
+ try:
283
+ score_text = line.split(":")[1].strip()
284
+ score = int(score_text)
285
+ score = max(1, min(10, score)) # Ensure score is 1-10
286
+ except Exception:
287
+ pass
288
+ elif line.startswith("Reason:"):
289
+ reason = line.split(":", 1)[1].strip()
290
+
291
+ return {"score": score, "reason": reason}
292
+
293
+ except Exception as e:
294
+ logger.warning(f"LLM assessment failed: {e}, using fallback scoring")
295
+ # Fallback to simple keyword matching if LLM fails
296
+ return self._fallback_memory_assessment(query, memory)
297
+
298
+ def _fallback_memory_assessment(self, query: str, memory: str) -> dict:
299
+ """Fallback assessment method using keyword matching if LLM fails"""
300
+ query_lower = query.lower()
301
+ memory_lower = memory.lower()
302
+
303
+ # Keyword matching
304
+ query_words = set(query_lower.split())
305
+ memory_words = set(memory_lower.split())
306
+ common_words = query_words.intersection(memory_words)
307
+
308
+ # Semantic relevance scoring
309
+ score = 0
310
+
311
+ # Exact keyword matches (highest weight)
312
+ if len(common_words) > 0:
313
+ score += min(len(common_words) * 2, 6)
314
+
315
+ # Partial matches (medium weight)
316
+ partial_matches = sum(
317
+ 1 for qw in query_words for mw in memory_words if qw in mw or mw in qw
318
+ )
319
+ if partial_matches > 0:
320
+ score += min(partial_matches, 3)
321
+
322
+ # Topic relevance (through common topic words)
323
+ topic_words = [
324
+ "problem",
325
+ "solution",
326
+ "answer",
327
+ "method",
328
+ "reason",
329
+ "result",
330
+ "analysis",
331
+ "compare",
332
+ "explain",
333
+ ]
334
+ topic_matches = sum(1 for topic in topic_words if topic in memory_lower)
335
+ score += topic_matches
336
+
337
+ # Ensure score is 1-10
338
+ score = max(1, min(10, score))
339
+
340
+ # Determine helpfulness level
341
+ if score >= 8:
342
+ reason = "Highly relevant, directly answers the query"
343
+ elif score >= 6:
344
+ reason = "Relevant, provides useful information"
345
+ elif score >= 4:
346
+ reason = "Partially relevant, somewhat helpful"
347
+ elif score >= 2:
348
+ reason = "Low relevance, limited help"
349
+ else:
350
+ reason = "Very low relevance, minimal help"
351
+
352
+ return {"score": score, "reason": reason}
353
+
354
+ def _assess_ranking_quality(self, rank: int, helpfulness: int) -> str:
355
+ """Use LLM to assess whether the memory ranking is reasonable"""
356
+ try:
357
+ # Create prompt for LLM ranking assessment
358
+ ranking_prompt = f"""
359
+ Task: Assess whether this memory ranking is reasonable.
360
+
361
+ Context: A memory with helpfulness score {helpfulness}/10 is ranked at position {rank}.
362
+
363
+ Please evaluate if this ranking makes sense and provide a brief assessment.
364
+
365
+ Consider:
366
+ - Higher helpfulness scores should generally rank higher
367
+ - Rank 1 should typically have the highest helpfulness
368
+ - The relationship between rank and helpfulness
369
+
370
+ Provide a brief assessment in one sentence.
371
+ """
372
+
373
+ # Use the chat LLM to get assessment
374
+ messages = [{"role": "user", "content": ranking_prompt}]
375
+ response = self.chat_llm.generate(messages)
376
+
377
+ return response.strip()
378
+
379
+ except Exception as e:
380
+ logger.warning(f"LLM ranking assessment failed: {e}, using fallback assessment")
381
+ # Fallback assessment
382
+ if rank == 1 and helpfulness >= 8:
383
+ return "✅ Ranking is reasonable - most helpful memory ranked first"
384
+ elif rank == 1 and helpfulness <= 4:
385
+ return "❌ Ranking is unreasonable - first ranked memory has low helpfulness"
386
+ elif rank <= 3 and helpfulness >= 6:
387
+ return "✅ Ranking is reasonable - high helpfulness memory ranked high"
388
+ elif rank <= 3 and helpfulness <= 3:
389
+ return "⚠️ Ranking may be unreasonable - low helpfulness memory ranked high"
390
+ elif rank > 3 and helpfulness >= 7:
391
+ return "⚠️ Ranking may be unreasonable - high helpfulness memory ranked low"
392
+ else:
393
+ return "🟡 Ranking is acceptable - helpfulness and rank generally match"
394
+
395
+ def chat(self, query: str, user_id: str | None = None) -> str:
396
+ """
397
+ Chat with the MOS with memory helpfulness analysis.
398
+
399
+ Args:
400
+ query (str): The user's query.
401
+ user_id (str | None): The user ID.
402
+
403
+ Returns:
404
+ str: The response from the MOS.
405
+ """
406
+ target_user_id = user_id if user_id is not None else self.user_id
407
+ accessible_cubes = self.user_manager.get_user_cubes(target_user_id)
408
+ user_cube_ids = [cube.cube_id for cube in accessible_cubes]
409
+
410
+ if target_user_id not in self.chat_history_manager:
411
+ self._register_chat_history(target_user_id)
412
+
413
+ chat_history = self.chat_history_manager[target_user_id]
414
+ topk_for_scheduler = 2
415
+
416
+ if self.config.enable_textual_memory and self.mem_cubes:
417
+ memories_all = []
418
+ for mem_cube_id, mem_cube in self.mem_cubes.items():
419
+ if mem_cube_id not in user_cube_ids:
420
+ continue
421
+ if not mem_cube.text_mem:
422
+ continue
423
+
424
+ # Get working memories BEFORE scheduler
425
+ working_memories_before = [m.memory for m in mem_cube.text_mem.get_working_memory()]
426
+
427
+ message_item = ScheduleMessageItem(
428
+ user_id=target_user_id,
429
+ mem_cube_id=mem_cube_id,
430
+ mem_cube=mem_cube,
431
+ label=QUERY_LABEL,
432
+ content=query,
433
+ timestamp=datetime.now(),
434
+ )
435
+
436
+ print(f"\n🚀 Starting Scheduler for {mem_cube_id}...")
437
+
438
+ # Force scheduler to run immediately
439
+ self.mem_scheduler.monitor.query_trigger_interval = 0
440
+ self.mem_scheduler._query_message_consumer(messages=[message_item])
441
+
442
+ # Get scheduler memories
443
+ scheduler_memories = self.mem_scheduler.monitor.get_monitor_memories(
444
+ user_id=target_user_id,
445
+ mem_cube_id=mem_cube_id,
446
+ memory_type=MONITOR_WORKING_MEMORY_TYPE,
447
+ top_k=20,
448
+ )
449
+
450
+ # Get working memories AFTER scheduler
451
+ working_memories_after = [m.memory for m in mem_cube.text_mem.get_working_memory()]
452
+
453
+ # Get mem_cube memories for response generation
454
+ memories = mem_cube.text_mem.search(
455
+ query,
456
+ top_k=self.config.top_k - topk_for_scheduler,
457
+ info={
458
+ "user_id": target_user_id,
459
+ "session_id": self.session_id,
460
+ "chat_history": chat_history.chat_history,
461
+ },
462
+ )
463
+ text_memories = [m.memory for m in memories]
464
+
465
+ # Analyze memory helpfulness - compare before vs after vs scheduler
466
+ self._analyze_memory_helpfulness(
467
+ query, working_memories_before, working_memories_after, scheduler_memories
468
+ )
469
+
470
+ # Combine all memories for response generation
471
+ memories_all.extend(scheduler_memories[:topk_for_scheduler])
472
+ memories_all.extend(text_memories)
473
+ memories_all = list(set(memories_all))
474
+
475
+ logger.info(f"🧠 [Memory] Searched memories:\n{self._str_memories(memories_all)}\n")
476
+ system_prompt = self._build_system_prompt(memories_all)
477
+ else:
478
+ system_prompt = self._build_system_prompt()
479
+
480
+ current_messages = [
481
+ {"role": "system", "content": system_prompt},
482
+ *chat_history.chat_history,
483
+ {"role": "user", "content": query},
484
+ ]
485
+ past_key_values = None
486
+
487
+ if self.config.enable_activation_memory:
488
+ assert self.config.chat_model.backend == "huggingface", (
489
+ "Activation memory only used for huggingface backend."
490
+ )
491
+ # TODO this only one cubes
492
+ for mem_cube_id, mem_cube in self.mem_cubes.items():
493
+ if mem_cube_id not in user_cube_ids:
494
+ continue
495
+ if mem_cube.act_mem:
496
+ kv_cache = next(iter(mem_cube.act_mem.get_all()), None)
497
+ past_key_values = (
498
+ kv_cache.memory if (kv_cache and hasattr(kv_cache, "memory")) else None
499
+ )
500
+ break
501
+ # Generate response
502
+ response = self.chat_llm.generate(current_messages, past_key_values=past_key_values)
503
+ else:
504
+ response = self.chat_llm.generate(current_messages)
505
+
506
+ logger.info(f"🤖 [Assistant] {response}\n")
507
+ chat_history.chat_history.append({"role": "user", "content": query})
508
+ chat_history.chat_history.append({"role": "assistant", "content": response})
509
+ self.chat_history_manager[user_id] = chat_history
510
+
511
+ # Submit message to scheduler for answer processing
512
+ for accessible_mem_cube in accessible_cubes:
513
+ mem_cube_id = accessible_mem_cube.cube_id
514
+ mem_cube = self.mem_cubes[mem_cube_id]
515
+ if self.enable_mem_scheduler and self.mem_scheduler is not None:
516
+ message_item = ScheduleMessageItem(
517
+ user_id=target_user_id,
518
+ mem_cube_id=mem_cube_id,
519
+ mem_cube=mem_cube,
520
+ label=ANSWER_LABEL,
521
+ content=response,
522
+ timestamp=datetime.now(),
523
+ )
524
+ self.mem_scheduler.submit_messages(messages=[message_item])
525
+
526
+ return response
527
+
528
+ def get_memory_helpfulness_summary(self) -> dict:
529
+ """Get summary of memory helpfulness analysis."""
530
+ if not self.memory_helpfulness_analysis:
531
+ return {"message": "No memory helpfulness analysis data available"}
532
+
533
+ total_queries = len(self.memory_helpfulness_analysis)
534
+
535
+ # Calculate average helpfulness for working memories before scheduler
536
+ before_scores = []
537
+ for analysis in self.memory_helpfulness_analysis:
538
+ before_scores.extend(analysis["working_helpfulness_before"])
539
+
540
+ # Calculate average helpfulness for working memories after scheduler
541
+ after_scores = []
542
+ for analysis in self.memory_helpfulness_analysis:
543
+ after_scores.extend(analysis["working_helpfulness_after"])
544
+
545
+ # Calculate average helpfulness for scheduler memories from monitor
546
+ scheduler_scores = []
547
+ for analysis in self.memory_helpfulness_analysis:
548
+ scheduler_scores.extend(analysis["scheduler_helpfulness"])
549
+
550
+ avg_before_helpfulness = sum(before_scores) / len(before_scores) if before_scores else 0
551
+ avg_after_helpfulness = sum(after_scores) / len(after_scores) if after_scores else 0
552
+ avg_scheduler_helpfulness = (
553
+ sum(scheduler_scores) / len(scheduler_scores) if scheduler_scores else 0
554
+ )
555
+
556
+ return {
557
+ "total_queries": total_queries,
558
+ "working_memories_before_analyzed": len(before_scores),
559
+ "working_memories_after_analyzed": len(after_scores),
560
+ "scheduler_memories_analyzed": len(scheduler_scores),
561
+ "average_helpfulness_before_scheduler": f"{avg_before_helpfulness:.1f}/10",
562
+ "average_helpfulness_after_scheduler": f"{avg_after_helpfulness:.1f}/10",
563
+ "average_helpfulness_scheduler_memories": f"{avg_scheduler_helpfulness:.1f}/10",
564
+ "overall_improvement": f"{avg_after_helpfulness - avg_before_helpfulness:+.1f}",
565
+ "improvement_percentage": f"{((avg_after_helpfulness - avg_before_helpfulness) / avg_before_helpfulness * 100):+.1f}%"
566
+ if avg_before_helpfulness > 0
567
+ else "N/A",
568
+ "scheduler_vs_working_comparison": f"{avg_scheduler_helpfulness - avg_after_helpfulness:+.1f}",
569
+ }