MemoryOS 0.0.1__py3-none-any.whl → 0.1.13__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 (124) hide show
  1. memoryos-0.1.13.dist-info/METADATA +288 -0
  2. memoryos-0.1.13.dist-info/RECORD +122 -0
  3. memos/__init__.py +20 -1
  4. memos/api/start_api.py +420 -0
  5. memos/chunkers/__init__.py +4 -0
  6. memos/chunkers/base.py +24 -0
  7. memos/chunkers/factory.py +22 -0
  8. memos/chunkers/sentence_chunker.py +35 -0
  9. memos/configs/__init__.py +0 -0
  10. memos/configs/base.py +82 -0
  11. memos/configs/chunker.py +45 -0
  12. memos/configs/embedder.py +53 -0
  13. memos/configs/graph_db.py +45 -0
  14. memos/configs/internet_retriever.py +81 -0
  15. memos/configs/llm.py +71 -0
  16. memos/configs/mem_chat.py +81 -0
  17. memos/configs/mem_cube.py +89 -0
  18. memos/configs/mem_os.py +74 -0
  19. memos/configs/mem_reader.py +53 -0
  20. memos/configs/mem_scheduler.py +78 -0
  21. memos/configs/memory.py +195 -0
  22. memos/configs/parser.py +38 -0
  23. memos/configs/utils.py +8 -0
  24. memos/configs/vec_db.py +64 -0
  25. memos/deprecation.py +262 -0
  26. memos/embedders/__init__.py +0 -0
  27. memos/embedders/base.py +15 -0
  28. memos/embedders/factory.py +23 -0
  29. memos/embedders/ollama.py +74 -0
  30. memos/embedders/sentence_transformer.py +40 -0
  31. memos/exceptions.py +30 -0
  32. memos/graph_dbs/__init__.py +0 -0
  33. memos/graph_dbs/base.py +215 -0
  34. memos/graph_dbs/factory.py +21 -0
  35. memos/graph_dbs/neo4j.py +827 -0
  36. memos/hello_world.py +97 -0
  37. memos/llms/__init__.py +0 -0
  38. memos/llms/base.py +16 -0
  39. memos/llms/factory.py +25 -0
  40. memos/llms/hf.py +231 -0
  41. memos/llms/ollama.py +82 -0
  42. memos/llms/openai.py +34 -0
  43. memos/llms/utils.py +14 -0
  44. memos/log.py +78 -0
  45. memos/mem_chat/__init__.py +0 -0
  46. memos/mem_chat/base.py +30 -0
  47. memos/mem_chat/factory.py +21 -0
  48. memos/mem_chat/simple.py +200 -0
  49. memos/mem_cube/__init__.py +0 -0
  50. memos/mem_cube/base.py +29 -0
  51. memos/mem_cube/general.py +146 -0
  52. memos/mem_cube/utils.py +24 -0
  53. memos/mem_os/client.py +5 -0
  54. memos/mem_os/core.py +819 -0
  55. memos/mem_os/main.py +503 -0
  56. memos/mem_os/product.py +89 -0
  57. memos/mem_reader/__init__.py +0 -0
  58. memos/mem_reader/base.py +27 -0
  59. memos/mem_reader/factory.py +21 -0
  60. memos/mem_reader/memory.py +298 -0
  61. memos/mem_reader/simple_struct.py +241 -0
  62. memos/mem_scheduler/__init__.py +0 -0
  63. memos/mem_scheduler/base_scheduler.py +164 -0
  64. memos/mem_scheduler/general_scheduler.py +305 -0
  65. memos/mem_scheduler/modules/__init__.py +0 -0
  66. memos/mem_scheduler/modules/base.py +74 -0
  67. memos/mem_scheduler/modules/dispatcher.py +103 -0
  68. memos/mem_scheduler/modules/monitor.py +82 -0
  69. memos/mem_scheduler/modules/redis_service.py +146 -0
  70. memos/mem_scheduler/modules/retriever.py +41 -0
  71. memos/mem_scheduler/modules/schemas.py +146 -0
  72. memos/mem_scheduler/scheduler_factory.py +21 -0
  73. memos/mem_scheduler/utils.py +26 -0
  74. memos/mem_user/user_manager.py +488 -0
  75. memos/memories/__init__.py +0 -0
  76. memos/memories/activation/__init__.py +0 -0
  77. memos/memories/activation/base.py +42 -0
  78. memos/memories/activation/item.py +25 -0
  79. memos/memories/activation/kv.py +232 -0
  80. memos/memories/base.py +19 -0
  81. memos/memories/factory.py +34 -0
  82. memos/memories/parametric/__init__.py +0 -0
  83. memos/memories/parametric/base.py +19 -0
  84. memos/memories/parametric/item.py +11 -0
  85. memos/memories/parametric/lora.py +41 -0
  86. memos/memories/textual/__init__.py +0 -0
  87. memos/memories/textual/base.py +89 -0
  88. memos/memories/textual/general.py +286 -0
  89. memos/memories/textual/item.py +167 -0
  90. memos/memories/textual/naive.py +185 -0
  91. memos/memories/textual/tree.py +321 -0
  92. memos/memories/textual/tree_text_memory/__init__.py +0 -0
  93. memos/memories/textual/tree_text_memory/organize/__init__.py +0 -0
  94. memos/memories/textual/tree_text_memory/organize/manager.py +305 -0
  95. memos/memories/textual/tree_text_memory/retrieve/__init__.py +0 -0
  96. memos/memories/textual/tree_text_memory/retrieve/internet_retriever.py +263 -0
  97. memos/memories/textual/tree_text_memory/retrieve/internet_retriever_factory.py +89 -0
  98. memos/memories/textual/tree_text_memory/retrieve/reasoner.py +61 -0
  99. memos/memories/textual/tree_text_memory/retrieve/recall.py +158 -0
  100. memos/memories/textual/tree_text_memory/retrieve/reranker.py +111 -0
  101. memos/memories/textual/tree_text_memory/retrieve/retrieval_mid_structs.py +13 -0
  102. memos/memories/textual/tree_text_memory/retrieve/searcher.py +208 -0
  103. memos/memories/textual/tree_text_memory/retrieve/task_goal_parser.py +68 -0
  104. memos/memories/textual/tree_text_memory/retrieve/utils.py +48 -0
  105. memos/memories/textual/tree_text_memory/retrieve/xinyusearch.py +335 -0
  106. memos/parsers/__init__.py +0 -0
  107. memos/parsers/base.py +15 -0
  108. memos/parsers/factory.py +19 -0
  109. memos/parsers/markitdown.py +22 -0
  110. memos/settings.py +8 -0
  111. memos/templates/__init__.py +0 -0
  112. memos/templates/mem_reader_prompts.py +98 -0
  113. memos/templates/mem_scheduler_prompts.py +65 -0
  114. memos/templates/mos_prompts.py +63 -0
  115. memos/types.py +55 -0
  116. memos/vec_dbs/__init__.py +0 -0
  117. memos/vec_dbs/base.py +105 -0
  118. memos/vec_dbs/factory.py +21 -0
  119. memos/vec_dbs/item.py +43 -0
  120. memos/vec_dbs/qdrant.py +292 -0
  121. memoryos-0.0.1.dist-info/METADATA +0 -53
  122. memoryos-0.0.1.dist-info/RECORD +0 -5
  123. {memoryos-0.0.1.dist-info → memoryos-0.1.13.dist-info}/LICENSE +0 -0
  124. {memoryos-0.0.1.dist-info → memoryos-0.1.13.dist-info}/WHEEL +0 -0
memos/mem_os/main.py ADDED
@@ -0,0 +1,503 @@
1
+ import concurrent.futures
2
+ import json
3
+
4
+ from typing import Any
5
+
6
+ from memos.configs.mem_os import MOSConfig
7
+ from memos.llms.factory import LLMFactory
8
+ from memos.log import get_logger
9
+ from memos.mem_os.core import MOSCore
10
+ from memos.memories.textual.base import BaseTextMemory
11
+ from memos.templates.mos_prompts import (
12
+ COT_DECOMPOSE_PROMPT,
13
+ PRO_MODE_WELCOME_MESSAGE,
14
+ SYNTHESIS_PROMPT,
15
+ )
16
+
17
+
18
+ logger = get_logger(__name__)
19
+
20
+
21
+ class MOS(MOSCore):
22
+ """
23
+ The MOS (Memory Operating System) class inherits from MOSCore.
24
+ This class maintains backward compatibility with the original MOS interface.
25
+ """
26
+
27
+ def __init__(self, config: MOSConfig):
28
+ self.enable_cot = config.PRO_MODE
29
+ if config.PRO_MODE:
30
+ print(PRO_MODE_WELCOME_MESSAGE)
31
+ logger.info(PRO_MODE_WELCOME_MESSAGE)
32
+ super().__init__(config)
33
+
34
+ def chat(self, query: str, user_id: str | None = None) -> str:
35
+ """
36
+ Enhanced chat method with optional CoT (Chain of Thought) enhancement.
37
+
38
+ Args:
39
+ query (str): The user's query.
40
+ user_id (str, optional): User ID for context.
41
+
42
+ Returns:
43
+ str: The response from the MOS.
44
+ """
45
+ # Check if CoT enhancement is enabled (either explicitly or via PRO mode)
46
+
47
+ if not self.enable_cot:
48
+ # Use the original chat method from core
49
+ return super().chat(query, user_id)
50
+
51
+ # Enhanced chat with CoT decomposition
52
+ return self._chat_with_cot_enhancement(query, user_id)
53
+
54
+ def _chat_with_cot_enhancement(self, query: str, user_id: str | None = None) -> str:
55
+ """
56
+ Chat with CoT enhancement for complex query decomposition.
57
+ This method includes all the same validation and processing logic as the core chat method.
58
+
59
+ Args:
60
+ query (str): The user's query.
61
+ user_id (str, optional): User ID for context.
62
+
63
+ Returns:
64
+ str: The enhanced response.
65
+ """
66
+ # Step 1: Perform all the same validation and setup as core chat method
67
+ target_user_id = user_id if user_id is not None else self.user_id
68
+ accessible_cubes = self.user_manager.get_user_cubes(target_user_id)
69
+ user_cube_ids = [cube.cube_id for cube in accessible_cubes]
70
+
71
+ # Register chat history if needed
72
+ if target_user_id not in self.chat_history_manager:
73
+ self._register_chat_history(target_user_id)
74
+
75
+ chat_history = self.chat_history_manager[target_user_id]
76
+
77
+ try:
78
+ # Step 2: Decompose the query using CoT
79
+ logger.info(f"🔍 [CoT] Decomposing query: {query}")
80
+ decomposition_result = self.cot_decompose(
81
+ query, self.config.chat_model, target_user_id, self.chat_llm
82
+ )
83
+
84
+ # Check if the query is complex and needs decomposition
85
+ if not decomposition_result.get("is_complex", False):
86
+ logger.info("🔍 [CoT] Query is not complex, using standard chat")
87
+ return super().chat(query, user_id)
88
+
89
+ sub_questions = decomposition_result.get("sub_questions", [])
90
+ logger.info(f"🔍 [CoT] Decomposed into {len(sub_questions)} sub-questions")
91
+
92
+ # Step 3: Get search engine for sub-questions (with proper validation)
93
+ search_engine = self._get_search_engine_for_cot_with_validation(user_cube_ids)
94
+ if not search_engine:
95
+ logger.warning("🔍 [CoT] No search engine available, using standard chat")
96
+ return super().chat(query, user_id)
97
+
98
+ # Step 4: Get answers for sub-questions
99
+ logger.info("🔍 [CoT] Getting answers for sub-questions...")
100
+ sub_questions, sub_answers = self.get_sub_answers(
101
+ sub_questions=sub_questions,
102
+ search_engine=search_engine,
103
+ llm_config=self.config.chat_model,
104
+ user_id=target_user_id,
105
+ top_k=getattr(self.config, "cot_top_k", 3),
106
+ llm=self.chat_llm,
107
+ )
108
+
109
+ # Step 5: Generate enhanced response using sub-answers
110
+ logger.info("🔍 [CoT] Generating enhanced response...")
111
+ enhanced_response = self._generate_enhanced_response_with_context(
112
+ original_query=query,
113
+ sub_questions=sub_questions,
114
+ sub_answers=sub_answers,
115
+ chat_history=chat_history,
116
+ user_id=target_user_id,
117
+ search_engine=search_engine,
118
+ )
119
+
120
+ # Step 6: Update chat history (same as core method)
121
+ chat_history.chat_history.append({"role": "user", "content": query})
122
+ chat_history.chat_history.append({"role": "assistant", "content": enhanced_response})
123
+ self.chat_history_manager[target_user_id] = chat_history
124
+
125
+ # Step 7: Submit message to scheduler (same as core method)
126
+ if len(accessible_cubes) == 1:
127
+ mem_cube_id = accessible_cubes[0].cube_id
128
+ mem_cube = self.mem_cubes[mem_cube_id]
129
+ if self.enable_mem_scheduler and self.mem_scheduler is not None:
130
+ from datetime import datetime
131
+
132
+ from memos.mem_scheduler.modules.schemas import (
133
+ ANSWER_LABEL,
134
+ ScheduleMessageItem,
135
+ )
136
+
137
+ message_item = ScheduleMessageItem(
138
+ user_id=target_user_id,
139
+ mem_cube_id=mem_cube_id,
140
+ mem_cube=mem_cube,
141
+ label=ANSWER_LABEL,
142
+ content=enhanced_response,
143
+ timestamp=datetime.now(),
144
+ )
145
+ self.mem_scheduler.submit_messages(messages=[message_item])
146
+
147
+ return enhanced_response
148
+
149
+ except Exception as e:
150
+ logger.error(f"🔍 [CoT] Error in CoT enhancement: {e}")
151
+ logger.info("🔍 [CoT] Falling back to standard chat")
152
+ return super().chat(query, user_id)
153
+
154
+ def _get_search_engine_for_cot_with_validation(
155
+ self, user_cube_ids: list[str]
156
+ ) -> BaseTextMemory | None:
157
+ """
158
+ Get the best available search engine for CoT operations with proper validation.
159
+
160
+ Args:
161
+ user_cube_ids (list[str]): List of cube IDs the user has access to.
162
+
163
+ Returns:
164
+ BaseTextMemory or None: The search engine to use for CoT.
165
+ """
166
+ if not self.mem_cubes:
167
+ return None
168
+
169
+ # Get the first available text memory from user's accessible cubes
170
+ for mem_cube_id, mem_cube in self.mem_cubes.items():
171
+ if mem_cube_id not in user_cube_ids:
172
+ continue
173
+ if mem_cube.text_mem:
174
+ return mem_cube.text_mem
175
+
176
+ return None
177
+
178
+ def _generate_enhanced_response_with_context(
179
+ self,
180
+ original_query: str,
181
+ sub_questions: list[str],
182
+ sub_answers: list[str],
183
+ chat_history: Any,
184
+ user_id: str | None = None,
185
+ search_engine: BaseTextMemory | None = None,
186
+ ) -> str:
187
+ """
188
+ Generate an enhanced response using sub-questions and their answers, with chat context.
189
+
190
+ Args:
191
+ original_query (str): The original user query.
192
+ sub_questions (list[str]): List of sub-questions.
193
+ sub_answers (list[str]): List of answers to sub-questions.
194
+ chat_history: The user's chat history.
195
+ user_id (str, optional): User ID for context.
196
+
197
+ Returns:
198
+ str: The enhanced response.
199
+ """
200
+ # Build the synthesis prompt
201
+ qa_text = ""
202
+ for i, (question, answer) in enumerate(zip(sub_questions, sub_answers, strict=False), 1):
203
+ qa_text += f"Q{i}: {question}\nA{i}: {answer}\n\n"
204
+
205
+ # Build messages with chat history context (similar to core method)
206
+ if (search_engine is not None) and self.config.enable_textual_memory:
207
+ if self.enable_cot:
208
+ search_memories = search_engine.search(
209
+ original_query, top_k=self.config.top_k, mode="fine"
210
+ )
211
+ else:
212
+ search_memories = search_engine.search(
213
+ original_query, top_k=self.config.top_k, mode="fast"
214
+ )
215
+ system_prompt = self._build_system_prompt(
216
+ search_memories
217
+ ) # Use the same system prompt builder
218
+ else:
219
+ system_prompt = self._build_system_prompt()
220
+ current_messages = [
221
+ {"role": "system", "content": system_prompt + SYNTHESIS_PROMPT.format(qa_text=qa_text)},
222
+ *chat_history.chat_history,
223
+ {
224
+ "role": "user",
225
+ "content": original_query,
226
+ },
227
+ ]
228
+
229
+ # Handle activation memory if enabled (same as core method)
230
+ past_key_values = None
231
+ if self.config.enable_activation_memory:
232
+ assert self.config.chat_model.backend == "huggingface", (
233
+ "Activation memory only used for huggingface backend."
234
+ )
235
+ # Get accessible cubes for the user
236
+ target_user_id = user_id if user_id is not None else self.user_id
237
+ accessible_cubes = self.user_manager.get_user_cubes(target_user_id)
238
+ user_cube_ids = [cube.cube_id for cube in accessible_cubes]
239
+
240
+ for mem_cube_id, mem_cube in self.mem_cubes.items():
241
+ if mem_cube_id not in user_cube_ids:
242
+ continue
243
+ if mem_cube.act_mem:
244
+ kv_cache = next(iter(mem_cube.act_mem.get_all()), None)
245
+ past_key_values = (
246
+ kv_cache.memory if (kv_cache and hasattr(kv_cache, "memory")) else None
247
+ )
248
+ break
249
+
250
+ try:
251
+ # Generate the enhanced response using the chat LLM with same parameters as core
252
+ if past_key_values is not None:
253
+ enhanced_response = self.chat_llm.generate(
254
+ current_messages, past_key_values=past_key_values
255
+ )
256
+ else:
257
+ enhanced_response = self.chat_llm.generate(current_messages)
258
+
259
+ logger.info("🔍 [CoT] Generated enhanced response")
260
+ return enhanced_response
261
+ except Exception as e:
262
+ logger.error(f"🔍 [CoT] Error generating enhanced response: {e}")
263
+ # Fallback to standard chat
264
+ return super().chat(original_query, user_id)
265
+
266
+ @classmethod
267
+ def cot_decompose(
268
+ cls, query: str, llm_config: Any, user_id: str | None = None, llm: LLMFactory | None = None
269
+ ) -> list[str] | dict[str, Any]:
270
+ """
271
+ Decompose a complex query into sub-questions using Chain of Thought reasoning.
272
+
273
+ Args:
274
+ query (str): The complex query to decompose
275
+ llm_config: LLM configuration for decomposition
276
+ user_id (str, optional): User ID for context
277
+
278
+ Returns:
279
+ Union[List[str], Dict[str, Any]]: List of decomposed sub-questions or dict with complexity analysis
280
+ """
281
+ # Create a temporary LLM instance for decomposition
282
+ if llm is None:
283
+ llm = LLMFactory.from_config(llm_config)
284
+
285
+ # System prompt for CoT decomposition with complexity analysis
286
+ system_prompt = COT_DECOMPOSE_PROMPT.format(query=query)
287
+
288
+ messages = [{"role": "system", "content": system_prompt}]
289
+
290
+ try:
291
+ response = llm.generate(messages)
292
+ # Try to parse JSON response
293
+ result = json.loads(response)
294
+ return result
295
+ except json.JSONDecodeError as e:
296
+ logger.warning(f"Failed to parse JSON response from LLM: {e}")
297
+ logger.warning(f"Raw response: {response}")
298
+
299
+ # Try to extract JSON-like content from the response
300
+ try:
301
+ # Look for JSON-like content between curly braces
302
+ import re
303
+
304
+ json_match = re.search(r"\{.*\}", response, re.DOTALL)
305
+ if json_match:
306
+ json_str = json_match.group(0)
307
+ result = json.loads(json_str)
308
+ return result
309
+ except Exception:
310
+ pass
311
+
312
+ # If all parsing attempts fail, return default
313
+ return {"is_complex": False, "sub_questions": []}
314
+ except Exception as e:
315
+ logger.error(f"Unexpected error in cot_decompose: {e}")
316
+ return {"is_complex": False, "sub_questions": []}
317
+
318
+ @classmethod
319
+ def get_sub_answers(
320
+ cls,
321
+ sub_questions: list[str] | dict[str, Any],
322
+ search_results: dict[str, Any] | None = None,
323
+ search_engine: BaseTextMemory | None = None,
324
+ llm_config: LLMFactory | None = None,
325
+ user_id: str | None = None,
326
+ top_k: int = 5,
327
+ llm: LLMFactory | None = None,
328
+ ) -> tuple[list[str], list[str]]:
329
+ """
330
+ Get answers for sub-questions using either search results or a search engine.
331
+
332
+ Args:
333
+ sub_questions (Union[List[str], Dict[str, Any]]): List of sub-questions from cot_decompose or dict with analysis
334
+ search_results (Dict[str, Any], optional): Search results containing relevant information
335
+ search_engine (BaseTextMemory, optional): Text memory engine for searching
336
+ llm_config (Any, optional): LLM configuration for processing (required if search_engine is provided)
337
+ user_id (str, optional): User ID for context
338
+ top_k (int): Number of top results to retrieve from search engine
339
+
340
+ Returns:
341
+ Tuple[List[str], List[str]]: (sub_questions, sub_answers)
342
+ """
343
+ # Extract sub-questions from decomposition result if needed
344
+ if isinstance(sub_questions, dict):
345
+ if not sub_questions.get("is_complex", False):
346
+ return [], []
347
+ sub_questions = sub_questions.get("sub_questions", [])
348
+
349
+ if not sub_questions:
350
+ return [], []
351
+
352
+ # Validate inputs
353
+ if search_results is None and search_engine is None:
354
+ raise ValueError("Either search_results or search_engine must be provided")
355
+ if llm is None:
356
+ llm = LLMFactory.from_config(llm_config)
357
+
358
+ # Step 1: Get search results if search_engine is provided
359
+ if search_engine is not None:
360
+ search_results = cls._search_with_engine(sub_questions, search_engine, top_k)
361
+
362
+ # Step 2: Generate answers for each sub-question using LLM in parallel
363
+ def generate_answer_for_question(question_index: int, sub_question: str) -> tuple[int, str]:
364
+ """Generate answer for a single sub-question."""
365
+ # Extract relevant information from search results
366
+ relevant_info = []
367
+ if search_results and search_results.get("text_mem"):
368
+ for cube_result in search_results["text_mem"]:
369
+ for memory in cube_result.get("memories", []):
370
+ relevant_info.append(memory.memory)
371
+
372
+ # Build system prompt with memories (similar to MOSCore._build_system_prompt)
373
+ base_prompt = (
374
+ "You are a knowledgeable and helpful AI assistant. "
375
+ "You have access to relevant information that helps you provide accurate answers. "
376
+ "Use the provided information to answer the question comprehensively. "
377
+ "If the information is not sufficient, acknowledge the limitations."
378
+ )
379
+
380
+ # Add memory context if available
381
+ if relevant_info:
382
+ memory_context = "\n\n## Relevant Information:\n"
383
+ for j, info in enumerate(relevant_info[:top_k], 1): # Take top 3 most relevant
384
+ memory_context += f"{j}. {info}\n"
385
+ system_prompt = base_prompt + memory_context
386
+ else:
387
+ system_prompt = (
388
+ base_prompt
389
+ + "\n\n## Relevant Information:\nNo specific information found in memory."
390
+ )
391
+
392
+ # Create messages for LLM
393
+ messages = [
394
+ {"role": "system", "content": system_prompt},
395
+ {"role": "user", "content": sub_question},
396
+ ]
397
+
398
+ try:
399
+ # Generate answer using LLM
400
+ response = llm.generate(messages)
401
+ return question_index, response
402
+ except Exception as e:
403
+ logger.error(f"Failed to generate answer for sub-question '{sub_question}': {e}")
404
+ return question_index, f"Unable to generate answer for: {sub_question}"
405
+
406
+ # Generate answers in parallel while maintaining order
407
+ sub_answers = [None] * len(sub_questions)
408
+ with concurrent.futures.ThreadPoolExecutor(
409
+ max_workers=min(len(sub_questions), 10)
410
+ ) as executor:
411
+ # Submit all answer generation tasks
412
+ future_to_index = {
413
+ executor.submit(generate_answer_for_question, i, question): i
414
+ for i, question in enumerate(sub_questions)
415
+ }
416
+
417
+ # Collect results as they complete, but store them in the correct position
418
+ for future in concurrent.futures.as_completed(future_to_index):
419
+ try:
420
+ question_index, answer = future.result()
421
+ sub_answers[question_index] = answer
422
+ except Exception as e:
423
+ question_index = future_to_index[future]
424
+ logger.error(
425
+ f"Exception occurred while generating answer for question at index {question_index}: {e}"
426
+ )
427
+ sub_answers[question_index] = (
428
+ f"Error generating answer for question {question_index + 1}"
429
+ )
430
+
431
+ return sub_questions, sub_answers
432
+
433
+ @classmethod
434
+ def _search_with_engine(
435
+ cls, sub_questions: list[str], search_engine: BaseTextMemory, top_k: int
436
+ ) -> dict[str, Any]:
437
+ """
438
+ Search for sub-questions using the provided search engine in parallel.
439
+
440
+ Args:
441
+ sub_questions (List[str]): List of sub-questions to search for
442
+ search_engine (BaseTextMemory): Text memory engine for searching
443
+ top_k (int): Number of top results to retrieve
444
+
445
+ Returns:
446
+ Dict[str, Any]: Search results in the expected format
447
+ """
448
+
449
+ def search_single_question(question: str) -> list[Any]:
450
+ """Search for a single question using the search engine."""
451
+ try:
452
+ # Handle different search method signatures
453
+ if hasattr(search_engine, "search"):
454
+ # Try different parameter combinations based on the engine type
455
+ try:
456
+ # For tree_text memory
457
+ return search_engine.search(question, top_k, mode="fast")
458
+ except TypeError:
459
+ try:
460
+ # For general_text memory
461
+ return search_engine.search(question, top_k)
462
+ except TypeError:
463
+ # For naive_text memory
464
+ return search_engine.search(question, top_k)
465
+ else:
466
+ return []
467
+ except Exception as e:
468
+ logger.error(f"Search failed for question '{question}': {e}")
469
+ return []
470
+
471
+ # Search in parallel while maintaining order
472
+ all_memories = []
473
+ with concurrent.futures.ThreadPoolExecutor(
474
+ max_workers=min(len(sub_questions), 10)
475
+ ) as executor:
476
+ # Submit all search tasks and keep track of their order
477
+ future_to_index = {
478
+ executor.submit(search_single_question, question): i
479
+ for i, question in enumerate(sub_questions)
480
+ }
481
+
482
+ # Initialize results list with None values to maintain order
483
+ results = [None] * len(sub_questions)
484
+
485
+ # Collect results as they complete, but store them in the correct position
486
+ for future in concurrent.futures.as_completed(future_to_index):
487
+ index = future_to_index[future]
488
+ try:
489
+ memories = future.result()
490
+ results[index] = memories
491
+ except Exception as e:
492
+ logger.error(
493
+ f"Exception occurred while searching for question at index {index}: {e}"
494
+ )
495
+ results[index] = []
496
+
497
+ # Combine all results in the correct order
498
+ for result in results:
499
+ if result is not None:
500
+ all_memories.extend(result)
501
+
502
+ # Format results in the expected structure
503
+ return {"text_mem": [{"cube_id": "search_engine", "memories": all_memories}]}
@@ -0,0 +1,89 @@
1
+ import json
2
+
3
+ from collections.abc import Generator
4
+ from typing import Literal
5
+
6
+ from memos.configs.mem_os import MOSConfig
7
+ from memos.mem_os.core import MOSCore
8
+ from memos.memories.activation.item import ActivationMemoryItem
9
+ from memos.memories.parametric.item import ParametricMemoryItem
10
+ from memos.memories.textual.item import TextualMemoryMetadata, TreeNodeTextualMemoryMetadata
11
+ from memos.types import MessageList
12
+
13
+
14
+ class MOSProduct(MOSCore):
15
+ """
16
+ The MOSProduct class inherits from MOSCore mainly for product usage.
17
+ """
18
+
19
+ def __init__(self, config: MOSConfig):
20
+ super().__init__(config)
21
+
22
+ def get_suggestion_query(self, user_id: str) -> list[str]:
23
+ """Get suggestion query from LLM.
24
+ Args:
25
+ user_id (str, optional): Custom user ID.
26
+
27
+ Returns:
28
+ list[str]: The suggestion query list.
29
+ """
30
+
31
+ def chat(
32
+ self,
33
+ query: str,
34
+ user_id: str,
35
+ cube_id: str | None = None,
36
+ history: MessageList | None = None,
37
+ ) -> Generator[str, None, None]:
38
+ """Chat with LLM SSE Type.
39
+ Args:
40
+ query (str): Query string.
41
+ user_id (str, optional): Custom user ID.
42
+ cube_id (str, optional): Custom cube ID for user.
43
+ history (list[dict], optional): Chat history.
44
+
45
+ Returns:
46
+ Generator[str, None, None]: The response string generator.
47
+ """
48
+ memories_list = self.search(query)["act_mem"]
49
+ content_list = []
50
+ for memory in memories_list:
51
+ content_list.append(memory.content)
52
+ yield f"data: {json.dumps({'type': 'metadata', 'content': content_list})}\n\n"
53
+ llm_response = super().chat(query, user_id)
54
+ for chunk in llm_response:
55
+ chunk_data: str = f"data: {json.dumps({'type': 'text', 'content': chunk})}\n\n"
56
+ yield chunk_data
57
+ reference = [{"id": "1234"}]
58
+ yield f"data: {json.dumps({'type': 'reference', 'content': reference})}\n\n"
59
+ yield f"data: {json.dumps({'type': 'end'})}\n\n"
60
+
61
+ def get_all(
62
+ self,
63
+ user_id: str,
64
+ memory_type: Literal["text_mem", "act_mem", "param_mem"],
65
+ cube_id: str | None = None,
66
+ ) -> list[
67
+ dict[
68
+ str,
69
+ str
70
+ | list[
71
+ TextualMemoryMetadata
72
+ | TreeNodeTextualMemoryMetadata
73
+ | ActivationMemoryItem
74
+ | ParametricMemoryItem
75
+ ],
76
+ ]
77
+ ]:
78
+ """Get all memory items for a user.
79
+
80
+ Args:
81
+ user_id (str): The ID of the user.
82
+ cube_id (str | None, optional): The ID of the cube. Defaults to None.
83
+ memory_type (Literal["text_mem", "act_mem", "param_mem"]): The type of memory to get.
84
+
85
+ Returns:
86
+ list[TextualMemoryMetadata | TreeNodeTextualMemoryMetadata | ActivationMemoryItem | ParametricMemoryItem]: A list of memory items.
87
+ """
88
+ memory_list = super().get_all(user_id, cube_id)[memory_type]
89
+ return memory_list
File without changes
@@ -0,0 +1,27 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Any
3
+
4
+ from memos.configs.mem_reader import BaseMemReaderConfig
5
+ from memos.memories.textual.item import TextualMemoryItem
6
+
7
+
8
+ class BaseMemReader(ABC):
9
+ """MemReader interface class for reading information."""
10
+
11
+ @abstractmethod
12
+ def __init__(self, config: BaseMemReaderConfig):
13
+ """Initialize the MemReader with the given configuration."""
14
+
15
+ @abstractmethod
16
+ def get_scene_data_info(self, scene_data: list, type: str) -> list[str]:
17
+ """Get raw information related to the current scene."""
18
+
19
+ @abstractmethod
20
+ def get_memory(
21
+ self, scene_data: list, type: str, info: dict[str, Any]
22
+ ) -> list[list[TextualMemoryItem]]:
23
+ """Various types of memories extracted from scene_data"""
24
+
25
+ @abstractmethod
26
+ def transform_memreader(self, data: dict) -> list[TextualMemoryItem]:
27
+ """Transform the memory data into a list of TextualMemoryItem objects."""
@@ -0,0 +1,21 @@
1
+ from typing import Any, ClassVar
2
+
3
+ from memos.configs.mem_reader import MemReaderConfigFactory
4
+ from memos.mem_reader.base import BaseMemReader
5
+ from memos.mem_reader.simple_struct import SimpleStructMemReader
6
+
7
+
8
+ class MemReaderFactory(BaseMemReader):
9
+ """Factory class for creating MemReader instances."""
10
+
11
+ backend_to_class: ClassVar[dict[str, Any]] = {
12
+ "simple_struct": SimpleStructMemReader,
13
+ }
14
+
15
+ @classmethod
16
+ def from_config(cls, config_factory: MemReaderConfigFactory) -> BaseMemReader:
17
+ backend = config_factory.backend
18
+ if backend not in cls.backend_to_class:
19
+ raise ValueError(f"Invalid backend: {backend}")
20
+ reader_class = cls.backend_to_class[backend]
21
+ return reader_class(config_factory.config)