MemoryOS 2.0.3__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.
Files changed (315) hide show
  1. memoryos-2.0.3.dist-info/METADATA +418 -0
  2. memoryos-2.0.3.dist-info/RECORD +315 -0
  3. memoryos-2.0.3.dist-info/WHEEL +4 -0
  4. memoryos-2.0.3.dist-info/entry_points.txt +3 -0
  5. memoryos-2.0.3.dist-info/licenses/LICENSE +201 -0
  6. memos/__init__.py +20 -0
  7. memos/api/client.py +571 -0
  8. memos/api/config.py +1018 -0
  9. memos/api/context/dependencies.py +50 -0
  10. memos/api/exceptions.py +53 -0
  11. memos/api/handlers/__init__.py +62 -0
  12. memos/api/handlers/add_handler.py +158 -0
  13. memos/api/handlers/base_handler.py +194 -0
  14. memos/api/handlers/chat_handler.py +1401 -0
  15. memos/api/handlers/component_init.py +388 -0
  16. memos/api/handlers/config_builders.py +190 -0
  17. memos/api/handlers/feedback_handler.py +93 -0
  18. memos/api/handlers/formatters_handler.py +237 -0
  19. memos/api/handlers/memory_handler.py +316 -0
  20. memos/api/handlers/scheduler_handler.py +497 -0
  21. memos/api/handlers/search_handler.py +222 -0
  22. memos/api/handlers/suggestion_handler.py +117 -0
  23. memos/api/mcp_serve.py +614 -0
  24. memos/api/middleware/request_context.py +101 -0
  25. memos/api/product_api.py +38 -0
  26. memos/api/product_models.py +1206 -0
  27. memos/api/routers/__init__.py +1 -0
  28. memos/api/routers/product_router.py +477 -0
  29. memos/api/routers/server_router.py +394 -0
  30. memos/api/server_api.py +44 -0
  31. memos/api/start_api.py +433 -0
  32. memos/chunkers/__init__.py +4 -0
  33. memos/chunkers/base.py +24 -0
  34. memos/chunkers/charactertext_chunker.py +41 -0
  35. memos/chunkers/factory.py +24 -0
  36. memos/chunkers/markdown_chunker.py +62 -0
  37. memos/chunkers/sentence_chunker.py +54 -0
  38. memos/chunkers/simple_chunker.py +50 -0
  39. memos/cli.py +113 -0
  40. memos/configs/__init__.py +0 -0
  41. memos/configs/base.py +82 -0
  42. memos/configs/chunker.py +59 -0
  43. memos/configs/embedder.py +88 -0
  44. memos/configs/graph_db.py +236 -0
  45. memos/configs/internet_retriever.py +100 -0
  46. memos/configs/llm.py +151 -0
  47. memos/configs/mem_agent.py +54 -0
  48. memos/configs/mem_chat.py +81 -0
  49. memos/configs/mem_cube.py +105 -0
  50. memos/configs/mem_os.py +83 -0
  51. memos/configs/mem_reader.py +91 -0
  52. memos/configs/mem_scheduler.py +385 -0
  53. memos/configs/mem_user.py +70 -0
  54. memos/configs/memory.py +324 -0
  55. memos/configs/parser.py +38 -0
  56. memos/configs/reranker.py +18 -0
  57. memos/configs/utils.py +8 -0
  58. memos/configs/vec_db.py +80 -0
  59. memos/context/context.py +355 -0
  60. memos/dependency.py +52 -0
  61. memos/deprecation.py +262 -0
  62. memos/embedders/__init__.py +0 -0
  63. memos/embedders/ark.py +95 -0
  64. memos/embedders/base.py +106 -0
  65. memos/embedders/factory.py +29 -0
  66. memos/embedders/ollama.py +77 -0
  67. memos/embedders/sentence_transformer.py +49 -0
  68. memos/embedders/universal_api.py +51 -0
  69. memos/exceptions.py +30 -0
  70. memos/graph_dbs/__init__.py +0 -0
  71. memos/graph_dbs/base.py +274 -0
  72. memos/graph_dbs/factory.py +27 -0
  73. memos/graph_dbs/item.py +46 -0
  74. memos/graph_dbs/nebular.py +1794 -0
  75. memos/graph_dbs/neo4j.py +1942 -0
  76. memos/graph_dbs/neo4j_community.py +1058 -0
  77. memos/graph_dbs/polardb.py +5446 -0
  78. memos/hello_world.py +97 -0
  79. memos/llms/__init__.py +0 -0
  80. memos/llms/base.py +25 -0
  81. memos/llms/deepseek.py +13 -0
  82. memos/llms/factory.py +38 -0
  83. memos/llms/hf.py +443 -0
  84. memos/llms/hf_singleton.py +114 -0
  85. memos/llms/ollama.py +135 -0
  86. memos/llms/openai.py +222 -0
  87. memos/llms/openai_new.py +198 -0
  88. memos/llms/qwen.py +13 -0
  89. memos/llms/utils.py +14 -0
  90. memos/llms/vllm.py +218 -0
  91. memos/log.py +237 -0
  92. memos/mem_agent/base.py +19 -0
  93. memos/mem_agent/deepsearch_agent.py +391 -0
  94. memos/mem_agent/factory.py +36 -0
  95. memos/mem_chat/__init__.py +0 -0
  96. memos/mem_chat/base.py +30 -0
  97. memos/mem_chat/factory.py +21 -0
  98. memos/mem_chat/simple.py +200 -0
  99. memos/mem_cube/__init__.py +0 -0
  100. memos/mem_cube/base.py +30 -0
  101. memos/mem_cube/general.py +240 -0
  102. memos/mem_cube/navie.py +172 -0
  103. memos/mem_cube/utils.py +169 -0
  104. memos/mem_feedback/base.py +15 -0
  105. memos/mem_feedback/feedback.py +1192 -0
  106. memos/mem_feedback/simple_feedback.py +40 -0
  107. memos/mem_feedback/utils.py +230 -0
  108. memos/mem_os/client.py +5 -0
  109. memos/mem_os/core.py +1203 -0
  110. memos/mem_os/main.py +582 -0
  111. memos/mem_os/product.py +1608 -0
  112. memos/mem_os/product_server.py +455 -0
  113. memos/mem_os/utils/default_config.py +359 -0
  114. memos/mem_os/utils/format_utils.py +1403 -0
  115. memos/mem_os/utils/reference_utils.py +162 -0
  116. memos/mem_reader/__init__.py +0 -0
  117. memos/mem_reader/base.py +47 -0
  118. memos/mem_reader/factory.py +53 -0
  119. memos/mem_reader/memory.py +298 -0
  120. memos/mem_reader/multi_modal_struct.py +965 -0
  121. memos/mem_reader/read_multi_modal/__init__.py +43 -0
  122. memos/mem_reader/read_multi_modal/assistant_parser.py +311 -0
  123. memos/mem_reader/read_multi_modal/base.py +273 -0
  124. memos/mem_reader/read_multi_modal/file_content_parser.py +826 -0
  125. memos/mem_reader/read_multi_modal/image_parser.py +359 -0
  126. memos/mem_reader/read_multi_modal/multi_modal_parser.py +252 -0
  127. memos/mem_reader/read_multi_modal/string_parser.py +139 -0
  128. memos/mem_reader/read_multi_modal/system_parser.py +327 -0
  129. memos/mem_reader/read_multi_modal/text_content_parser.py +131 -0
  130. memos/mem_reader/read_multi_modal/tool_parser.py +210 -0
  131. memos/mem_reader/read_multi_modal/user_parser.py +218 -0
  132. memos/mem_reader/read_multi_modal/utils.py +358 -0
  133. memos/mem_reader/simple_struct.py +912 -0
  134. memos/mem_reader/strategy_struct.py +163 -0
  135. memos/mem_reader/utils.py +157 -0
  136. memos/mem_scheduler/__init__.py +0 -0
  137. memos/mem_scheduler/analyzer/__init__.py +0 -0
  138. memos/mem_scheduler/analyzer/api_analyzer.py +714 -0
  139. memos/mem_scheduler/analyzer/eval_analyzer.py +219 -0
  140. memos/mem_scheduler/analyzer/mos_for_test_scheduler.py +571 -0
  141. memos/mem_scheduler/analyzer/scheduler_for_eval.py +280 -0
  142. memos/mem_scheduler/base_scheduler.py +1319 -0
  143. memos/mem_scheduler/general_modules/__init__.py +0 -0
  144. memos/mem_scheduler/general_modules/api_misc.py +137 -0
  145. memos/mem_scheduler/general_modules/base.py +80 -0
  146. memos/mem_scheduler/general_modules/init_components_for_scheduler.py +425 -0
  147. memos/mem_scheduler/general_modules/misc.py +313 -0
  148. memos/mem_scheduler/general_modules/scheduler_logger.py +389 -0
  149. memos/mem_scheduler/general_modules/task_threads.py +315 -0
  150. memos/mem_scheduler/general_scheduler.py +1495 -0
  151. memos/mem_scheduler/memory_manage_modules/__init__.py +5 -0
  152. memos/mem_scheduler/memory_manage_modules/memory_filter.py +306 -0
  153. memos/mem_scheduler/memory_manage_modules/retriever.py +547 -0
  154. memos/mem_scheduler/monitors/__init__.py +0 -0
  155. memos/mem_scheduler/monitors/dispatcher_monitor.py +366 -0
  156. memos/mem_scheduler/monitors/general_monitor.py +394 -0
  157. memos/mem_scheduler/monitors/task_schedule_monitor.py +254 -0
  158. memos/mem_scheduler/optimized_scheduler.py +410 -0
  159. memos/mem_scheduler/orm_modules/__init__.py +0 -0
  160. memos/mem_scheduler/orm_modules/api_redis_model.py +518 -0
  161. memos/mem_scheduler/orm_modules/base_model.py +729 -0
  162. memos/mem_scheduler/orm_modules/monitor_models.py +261 -0
  163. memos/mem_scheduler/orm_modules/redis_model.py +699 -0
  164. memos/mem_scheduler/scheduler_factory.py +23 -0
  165. memos/mem_scheduler/schemas/__init__.py +0 -0
  166. memos/mem_scheduler/schemas/analyzer_schemas.py +52 -0
  167. memos/mem_scheduler/schemas/api_schemas.py +233 -0
  168. memos/mem_scheduler/schemas/general_schemas.py +55 -0
  169. memos/mem_scheduler/schemas/message_schemas.py +173 -0
  170. memos/mem_scheduler/schemas/monitor_schemas.py +406 -0
  171. memos/mem_scheduler/schemas/task_schemas.py +132 -0
  172. memos/mem_scheduler/task_schedule_modules/__init__.py +0 -0
  173. memos/mem_scheduler/task_schedule_modules/dispatcher.py +740 -0
  174. memos/mem_scheduler/task_schedule_modules/local_queue.py +247 -0
  175. memos/mem_scheduler/task_schedule_modules/orchestrator.py +74 -0
  176. memos/mem_scheduler/task_schedule_modules/redis_queue.py +1385 -0
  177. memos/mem_scheduler/task_schedule_modules/task_queue.py +162 -0
  178. memos/mem_scheduler/utils/__init__.py +0 -0
  179. memos/mem_scheduler/utils/api_utils.py +77 -0
  180. memos/mem_scheduler/utils/config_utils.py +100 -0
  181. memos/mem_scheduler/utils/db_utils.py +50 -0
  182. memos/mem_scheduler/utils/filter_utils.py +176 -0
  183. memos/mem_scheduler/utils/metrics.py +125 -0
  184. memos/mem_scheduler/utils/misc_utils.py +290 -0
  185. memos/mem_scheduler/utils/monitor_event_utils.py +67 -0
  186. memos/mem_scheduler/utils/status_tracker.py +229 -0
  187. memos/mem_scheduler/webservice_modules/__init__.py +0 -0
  188. memos/mem_scheduler/webservice_modules/rabbitmq_service.py +485 -0
  189. memos/mem_scheduler/webservice_modules/redis_service.py +380 -0
  190. memos/mem_user/factory.py +94 -0
  191. memos/mem_user/mysql_persistent_user_manager.py +271 -0
  192. memos/mem_user/mysql_user_manager.py +502 -0
  193. memos/mem_user/persistent_factory.py +98 -0
  194. memos/mem_user/persistent_user_manager.py +260 -0
  195. memos/mem_user/redis_persistent_user_manager.py +225 -0
  196. memos/mem_user/user_manager.py +488 -0
  197. memos/memories/__init__.py +0 -0
  198. memos/memories/activation/__init__.py +0 -0
  199. memos/memories/activation/base.py +42 -0
  200. memos/memories/activation/item.py +56 -0
  201. memos/memories/activation/kv.py +292 -0
  202. memos/memories/activation/vllmkv.py +219 -0
  203. memos/memories/base.py +19 -0
  204. memos/memories/factory.py +42 -0
  205. memos/memories/parametric/__init__.py +0 -0
  206. memos/memories/parametric/base.py +19 -0
  207. memos/memories/parametric/item.py +11 -0
  208. memos/memories/parametric/lora.py +41 -0
  209. memos/memories/textual/__init__.py +0 -0
  210. memos/memories/textual/base.py +92 -0
  211. memos/memories/textual/general.py +236 -0
  212. memos/memories/textual/item.py +304 -0
  213. memos/memories/textual/naive.py +187 -0
  214. memos/memories/textual/prefer_text_memory/__init__.py +0 -0
  215. memos/memories/textual/prefer_text_memory/adder.py +504 -0
  216. memos/memories/textual/prefer_text_memory/config.py +106 -0
  217. memos/memories/textual/prefer_text_memory/extractor.py +221 -0
  218. memos/memories/textual/prefer_text_memory/factory.py +85 -0
  219. memos/memories/textual/prefer_text_memory/retrievers.py +177 -0
  220. memos/memories/textual/prefer_text_memory/spliter.py +132 -0
  221. memos/memories/textual/prefer_text_memory/utils.py +93 -0
  222. memos/memories/textual/preference.py +344 -0
  223. memos/memories/textual/simple_preference.py +161 -0
  224. memos/memories/textual/simple_tree.py +69 -0
  225. memos/memories/textual/tree.py +459 -0
  226. memos/memories/textual/tree_text_memory/__init__.py +0 -0
  227. memos/memories/textual/tree_text_memory/organize/__init__.py +0 -0
  228. memos/memories/textual/tree_text_memory/organize/handler.py +184 -0
  229. memos/memories/textual/tree_text_memory/organize/manager.py +518 -0
  230. memos/memories/textual/tree_text_memory/organize/relation_reason_detector.py +238 -0
  231. memos/memories/textual/tree_text_memory/organize/reorganizer.py +622 -0
  232. memos/memories/textual/tree_text_memory/retrieve/__init__.py +0 -0
  233. memos/memories/textual/tree_text_memory/retrieve/advanced_searcher.py +364 -0
  234. memos/memories/textual/tree_text_memory/retrieve/bm25_util.py +186 -0
  235. memos/memories/textual/tree_text_memory/retrieve/bochasearch.py +419 -0
  236. memos/memories/textual/tree_text_memory/retrieve/internet_retriever.py +270 -0
  237. memos/memories/textual/tree_text_memory/retrieve/internet_retriever_factory.py +102 -0
  238. memos/memories/textual/tree_text_memory/retrieve/reasoner.py +61 -0
  239. memos/memories/textual/tree_text_memory/retrieve/recall.py +497 -0
  240. memos/memories/textual/tree_text_memory/retrieve/reranker.py +111 -0
  241. memos/memories/textual/tree_text_memory/retrieve/retrieval_mid_structs.py +16 -0
  242. memos/memories/textual/tree_text_memory/retrieve/retrieve_utils.py +472 -0
  243. memos/memories/textual/tree_text_memory/retrieve/searcher.py +848 -0
  244. memos/memories/textual/tree_text_memory/retrieve/task_goal_parser.py +135 -0
  245. memos/memories/textual/tree_text_memory/retrieve/utils.py +54 -0
  246. memos/memories/textual/tree_text_memory/retrieve/xinyusearch.py +387 -0
  247. memos/memos_tools/dinding_report_bot.py +453 -0
  248. memos/memos_tools/lockfree_dict.py +120 -0
  249. memos/memos_tools/notification_service.py +44 -0
  250. memos/memos_tools/notification_utils.py +142 -0
  251. memos/memos_tools/singleton.py +174 -0
  252. memos/memos_tools/thread_safe_dict.py +310 -0
  253. memos/memos_tools/thread_safe_dict_segment.py +382 -0
  254. memos/multi_mem_cube/__init__.py +0 -0
  255. memos/multi_mem_cube/composite_cube.py +86 -0
  256. memos/multi_mem_cube/single_cube.py +874 -0
  257. memos/multi_mem_cube/views.py +54 -0
  258. memos/parsers/__init__.py +0 -0
  259. memos/parsers/base.py +15 -0
  260. memos/parsers/factory.py +21 -0
  261. memos/parsers/markitdown.py +28 -0
  262. memos/reranker/__init__.py +4 -0
  263. memos/reranker/base.py +25 -0
  264. memos/reranker/concat.py +103 -0
  265. memos/reranker/cosine_local.py +102 -0
  266. memos/reranker/factory.py +72 -0
  267. memos/reranker/http_bge.py +324 -0
  268. memos/reranker/http_bge_strategy.py +327 -0
  269. memos/reranker/noop.py +19 -0
  270. memos/reranker/strategies/__init__.py +4 -0
  271. memos/reranker/strategies/base.py +61 -0
  272. memos/reranker/strategies/concat_background.py +94 -0
  273. memos/reranker/strategies/concat_docsource.py +110 -0
  274. memos/reranker/strategies/dialogue_common.py +109 -0
  275. memos/reranker/strategies/factory.py +31 -0
  276. memos/reranker/strategies/single_turn.py +107 -0
  277. memos/reranker/strategies/singleturn_outmem.py +98 -0
  278. memos/settings.py +10 -0
  279. memos/templates/__init__.py +0 -0
  280. memos/templates/advanced_search_prompts.py +211 -0
  281. memos/templates/cloud_service_prompt.py +107 -0
  282. memos/templates/instruction_completion.py +66 -0
  283. memos/templates/mem_agent_prompts.py +85 -0
  284. memos/templates/mem_feedback_prompts.py +822 -0
  285. memos/templates/mem_reader_prompts.py +1096 -0
  286. memos/templates/mem_reader_strategy_prompts.py +238 -0
  287. memos/templates/mem_scheduler_prompts.py +626 -0
  288. memos/templates/mem_search_prompts.py +93 -0
  289. memos/templates/mos_prompts.py +403 -0
  290. memos/templates/prefer_complete_prompt.py +735 -0
  291. memos/templates/tool_mem_prompts.py +139 -0
  292. memos/templates/tree_reorganize_prompts.py +230 -0
  293. memos/types/__init__.py +34 -0
  294. memos/types/general_types.py +151 -0
  295. memos/types/openai_chat_completion_types/__init__.py +15 -0
  296. memos/types/openai_chat_completion_types/chat_completion_assistant_message_param.py +56 -0
  297. memos/types/openai_chat_completion_types/chat_completion_content_part_image_param.py +27 -0
  298. memos/types/openai_chat_completion_types/chat_completion_content_part_input_audio_param.py +23 -0
  299. memos/types/openai_chat_completion_types/chat_completion_content_part_param.py +43 -0
  300. memos/types/openai_chat_completion_types/chat_completion_content_part_refusal_param.py +16 -0
  301. memos/types/openai_chat_completion_types/chat_completion_content_part_text_param.py +16 -0
  302. memos/types/openai_chat_completion_types/chat_completion_message_custom_tool_call_param.py +27 -0
  303. memos/types/openai_chat_completion_types/chat_completion_message_function_tool_call_param.py +32 -0
  304. memos/types/openai_chat_completion_types/chat_completion_message_param.py +18 -0
  305. memos/types/openai_chat_completion_types/chat_completion_message_tool_call_union_param.py +15 -0
  306. memos/types/openai_chat_completion_types/chat_completion_system_message_param.py +36 -0
  307. memos/types/openai_chat_completion_types/chat_completion_tool_message_param.py +30 -0
  308. memos/types/openai_chat_completion_types/chat_completion_user_message_param.py +34 -0
  309. memos/utils.py +123 -0
  310. memos/vec_dbs/__init__.py +0 -0
  311. memos/vec_dbs/base.py +117 -0
  312. memos/vec_dbs/factory.py +23 -0
  313. memos/vec_dbs/item.py +50 -0
  314. memos/vec_dbs/milvus.py +654 -0
  315. memos/vec_dbs/qdrant.py +355 -0
memos/mem_os/core.py ADDED
@@ -0,0 +1,1203 @@
1
+ import json
2
+ import os
3
+ import time
4
+
5
+ from datetime import datetime, timezone
6
+ from pathlib import Path
7
+ from threading import Lock
8
+ from typing import Any, Literal
9
+
10
+ from memos.configs.mem_os import MOSConfig
11
+ from memos.context.context import ContextThreadPoolExecutor
12
+ from memos.llms.factory import LLMFactory
13
+ from memos.log import get_logger
14
+ from memos.mem_cube.general import GeneralMemCube
15
+ from memos.mem_reader.factory import MemReaderFactory
16
+ from memos.mem_scheduler.general_scheduler import GeneralScheduler
17
+ from memos.mem_scheduler.scheduler_factory import SchedulerFactory
18
+ from memos.mem_scheduler.schemas.message_schemas import ScheduleMessageItem
19
+ from memos.mem_scheduler.schemas.task_schemas import (
20
+ ADD_TASK_LABEL,
21
+ ANSWER_TASK_LABEL,
22
+ MEM_READ_TASK_LABEL,
23
+ PREF_ADD_TASK_LABEL,
24
+ QUERY_TASK_LABEL,
25
+ )
26
+ from memos.mem_user.user_manager import UserManager, UserRole
27
+ from memos.memories.activation.item import ActivationMemoryItem
28
+ from memos.memories.parametric.item import ParametricMemoryItem
29
+ from memos.memories.textual.item import TextualMemoryItem, TextualMemoryMetadata
30
+ from memos.memos_tools.thread_safe_dict_segment import OptimizedThreadSafeDict
31
+ from memos.templates.mos_prompts import QUERY_REWRITING_PROMPT
32
+ from memos.types import ChatHistory, MessageList, MOSSearchResult
33
+
34
+
35
+ logger = get_logger(__name__)
36
+
37
+
38
+ class MOSCore:
39
+ """
40
+ The MOSCore (Memory Operating System Core) class manages multiple MemCube objects and their operations.
41
+ It provides methods for creating, searching, updating, and deleting MemCubes, supporting multi-user scenarios.
42
+ MOSCore acts as an operating system layer for handling and orchestrating MemCube instances.
43
+ """
44
+
45
+ def __init__(self, config: MOSConfig, user_manager: UserManager | None = None):
46
+ self.config = config
47
+ self.user_id = config.user_id
48
+ self.session_id = config.session_id
49
+ self.chat_llm = LLMFactory.from_config(config.chat_model)
50
+ self.mem_reader = MemReaderFactory.from_config(config.mem_reader)
51
+ self.chat_history_manager: dict[str, ChatHistory] = {}
52
+ # use thread safe dict for multi-user product-server scenario
53
+ self.mem_cubes: OptimizedThreadSafeDict[str, GeneralMemCube] = (
54
+ OptimizedThreadSafeDict() if user_manager is not None else {}
55
+ )
56
+ self._register_chat_history()
57
+
58
+ # Use provided user_manager or create a new one
59
+ if user_manager is not None:
60
+ self.user_manager = user_manager
61
+ else:
62
+ self.user_manager = UserManager(user_id=self.user_id if self.user_id else "root")
63
+
64
+ # Validate user exists
65
+ if not self.user_manager.validate_user(self.user_id):
66
+ raise ValueError(
67
+ f"User '{self.user_id}' does not exist or is inactive. Please create user first."
68
+ )
69
+
70
+ # Initialize mem_scheduler
71
+ self._mem_scheduler_lock = Lock()
72
+ self.enable_mem_scheduler = self.config.get("enable_mem_scheduler", False)
73
+ if self.enable_mem_scheduler:
74
+ self._mem_scheduler = self._initialize_mem_scheduler()
75
+ self._mem_scheduler.mem_cubes = self.mem_cubes
76
+ self._mem_scheduler.mem_reader = self.mem_reader
77
+ else:
78
+ self._mem_scheduler: GeneralScheduler = None
79
+
80
+ logger.info(f"MOS initialized for user: {self.user_id}")
81
+
82
+ @property
83
+ def mem_scheduler(self) -> GeneralScheduler:
84
+ """Lazy-loaded property for memory scheduler."""
85
+ if self.enable_mem_scheduler and self._mem_scheduler is None:
86
+ self._initialize_mem_scheduler()
87
+ self._mem_scheduler.mem_cubes = self.mem_cubes
88
+ return self._mem_scheduler
89
+
90
+ @mem_scheduler.setter
91
+ def mem_scheduler(self, value: GeneralScheduler | None) -> None:
92
+ """Setter for memory scheduler with validation.
93
+
94
+ Args:
95
+ value: GeneralScheduler instance or None to disable
96
+ Raises:
97
+ TypeError: If value is neither GeneralScheduler nor None
98
+ """
99
+ with self._mem_scheduler_lock:
100
+ if value is not None and not isinstance(value, GeneralScheduler):
101
+ raise TypeError(f"Expected GeneralScheduler or None, got {type(value)}")
102
+
103
+ self._mem_scheduler = value
104
+ self._mem_scheduler.mem_cubes = self.mem_cubes
105
+
106
+ if value:
107
+ logger.info("Memory scheduler manually set")
108
+ else:
109
+ logger.debug("Memory scheduler cleared")
110
+
111
+ def _initialize_mem_scheduler(self) -> GeneralScheduler:
112
+ """Initialize the memory scheduler on first access."""
113
+ if not self.config.enable_mem_scheduler:
114
+ logger.debug("Memory scheduler is disabled in config")
115
+ self._mem_scheduler = None
116
+ return self._mem_scheduler
117
+ elif not hasattr(self.config, "mem_scheduler"):
118
+ logger.error("Config of Memory scheduler is not available")
119
+ self._mem_scheduler = None
120
+ return self._mem_scheduler
121
+ else:
122
+ logger.info("Initializing memory scheduler...")
123
+ scheduler_config = self.config.mem_scheduler
124
+ self._mem_scheduler = SchedulerFactory.from_config(scheduler_config)
125
+ # Validate required components
126
+ if not hasattr(self.mem_reader, "llm"):
127
+ raise AttributeError(
128
+ f"Memory reader of type {type(self.mem_reader).__name__} "
129
+ "missing required 'llm' attribute"
130
+ )
131
+ else:
132
+ # Configure scheduler general_modules
133
+ self._mem_scheduler.initialize_modules(
134
+ chat_llm=self.chat_llm,
135
+ process_llm=self.mem_reader.llm,
136
+ db_engine=self.user_manager.engine,
137
+ )
138
+ self._mem_scheduler.start()
139
+ return self._mem_scheduler
140
+
141
+ def mem_scheduler_on(self) -> bool:
142
+ if not self.config.enable_mem_scheduler or self._mem_scheduler is None:
143
+ logger.error("Cannot start scheduler: disabled in configuration")
144
+
145
+ try:
146
+ self._mem_scheduler.start()
147
+ logger.info("Memory scheduler service started")
148
+ return True
149
+ except Exception as e:
150
+ logger.error(f"Failed to start scheduler: {e!s}")
151
+ return False
152
+
153
+ def mem_scheduler_off(self) -> bool:
154
+ if not self.config.enable_mem_scheduler:
155
+ logger.error("Cannot stop scheduler: disabled in configuration")
156
+
157
+ if self._mem_scheduler is None:
158
+ logger.warning("No scheduler instance to stop")
159
+ return False
160
+
161
+ try:
162
+ self._mem_scheduler.stop()
163
+ logger.info("Memory scheduler service stopped")
164
+ return True
165
+ except Exception as e:
166
+ logger.error(f"Failed to stop scheduler: {e!s}")
167
+ return False
168
+
169
+ def mem_reorganizer_on(self) -> bool:
170
+ pass
171
+
172
+ def mem_reorganizer_off(self) -> bool:
173
+ """temporally implement"""
174
+ for mem_cube in self.mem_cubes.values():
175
+ logger.info(f"try to close reorganizer for {mem_cube.text_mem.config.cube_id}")
176
+ if mem_cube.text_mem and mem_cube.text_mem.is_reorganize:
177
+ logger.info(f"close reorganizer for {mem_cube.text_mem.config.cube_id}")
178
+ mem_cube.text_mem.memory_manager.close()
179
+ mem_cube.text_mem.memory_manager.wait_reorganizer()
180
+
181
+ def mem_reorganizer_wait(self) -> bool:
182
+ for mem_cube in self.mem_cubes.values():
183
+ logger.info(f"try to close reorganizer for {mem_cube.text_mem.config.cube_id}")
184
+ if mem_cube.text_mem and mem_cube.text_mem.is_reorganize:
185
+ logger.info(f"close reorganizer for {mem_cube.text_mem.config.cube_id}")
186
+ mem_cube.text_mem.memory_manager.wait_reorganizer()
187
+
188
+ def _register_chat_history(
189
+ self, user_id: str | None = None, session_id: str | None = None
190
+ ) -> None:
191
+ """Initialize chat history with user ID."""
192
+ self.chat_history_manager[user_id] = ChatHistory(
193
+ user_id=user_id if user_id is not None else self.user_id,
194
+ session_id=session_id if session_id is not None else self.session_id,
195
+ created_at=datetime.now(timezone.utc),
196
+ total_messages=0,
197
+ chat_history=[],
198
+ )
199
+
200
+ def _validate_user_exists(self, user_id: str) -> None:
201
+ """Validate user exists and is active.
202
+
203
+ Args:
204
+ user_id (str): The user ID to validate.
205
+
206
+ Raises:
207
+ ValueError: If user doesn't exist or is inactive.
208
+ """
209
+ if not self.user_manager.validate_user(user_id):
210
+ raise ValueError(
211
+ f"User '{user_id}' does not exist or is inactive. Please register the user first."
212
+ )
213
+
214
+ def _validate_cube_access(self, user_id: str, cube_id: str) -> None:
215
+ """Validate user has access to the cube.
216
+
217
+ Args:
218
+ user_id (str): The user ID to validate.
219
+ cube_id (str): The cube ID to validate.
220
+
221
+ Raises:
222
+ ValueError: If user doesn't have access to the cube.
223
+ """
224
+ # First validate user exists
225
+ self._validate_user_exists(user_id)
226
+
227
+ # Then validate cube access
228
+ if not self.user_manager.validate_user_cube_access(user_id, cube_id):
229
+ raise ValueError(
230
+ f"User '{user_id}' does not have access to cube '{cube_id}'. Please register the cube first or request access."
231
+ )
232
+
233
+ def _get_all_documents(self, path: str) -> list[str]:
234
+ """Get all documents from path.
235
+
236
+ Args:
237
+ path (str): The path to get documents.
238
+
239
+ Returns:
240
+ list[str]: The list of documents.
241
+ """
242
+ documents = []
243
+
244
+ path_obj = Path(path)
245
+ doc_extensions = {".txt", ".pdf", ".json", ".md", ".ppt", ".pptx"}
246
+ for file_path in path_obj.rglob("*"):
247
+ if file_path.is_file() and (file_path.suffix.lower() in doc_extensions):
248
+ documents.append(str(file_path))
249
+ return documents
250
+
251
+ def chat(self, query: str, user_id: str | None = None, base_prompt: str | None = None) -> str:
252
+ """
253
+ Chat with the MOS.
254
+
255
+ Args:
256
+ query (str): The user's query.
257
+ user_id (str, optional): The user ID for the chat session. Defaults to the user ID from the config.
258
+ base_prompt (str, optional): A custom base prompt to use for the chat.
259
+ It can be a template string with a `{memories}` placeholder.
260
+ If not provided, a default prompt is used.
261
+
262
+ Returns:
263
+ str: The response from the MOS.
264
+ """
265
+ target_user_id = user_id if user_id is not None else self.user_id
266
+ accessible_cubes = self.user_manager.get_user_cubes(target_user_id)
267
+ user_cube_ids = [cube.cube_id for cube in accessible_cubes]
268
+ if target_user_id not in self.chat_history_manager:
269
+ self._register_chat_history(target_user_id)
270
+
271
+ chat_history = self.chat_history_manager[target_user_id]
272
+
273
+ if self.config.enable_textual_memory and self.mem_cubes:
274
+ memories_all = []
275
+ for mem_cube_id, mem_cube in self.mem_cubes.items():
276
+ if mem_cube_id not in user_cube_ids:
277
+ continue
278
+ if not mem_cube.text_mem:
279
+ continue
280
+
281
+ # submit message to scheduler
282
+ if self.enable_mem_scheduler and self.mem_scheduler is not None:
283
+ message_item = ScheduleMessageItem(
284
+ user_id=target_user_id,
285
+ mem_cube_id=mem_cube_id,
286
+ label=QUERY_TASK_LABEL,
287
+ content=query,
288
+ timestamp=datetime.utcnow(),
289
+ )
290
+ self.mem_scheduler.submit_messages(messages=[message_item])
291
+
292
+ memories = mem_cube.text_mem.search(
293
+ query,
294
+ top_k=self.config.top_k,
295
+ info={
296
+ "user_id": target_user_id,
297
+ "session_id": self.session_id,
298
+ "chat_history": chat_history.chat_history,
299
+ },
300
+ )
301
+ memories_all.extend(memories)
302
+ logger.info(f"🧠 [Memory] Searched memories:\n{self._str_memories(memories_all)}\n")
303
+ system_prompt = self._build_system_prompt(memories_all, base_prompt=base_prompt)
304
+ else:
305
+ system_prompt = self._build_system_prompt(base_prompt=base_prompt)
306
+ current_messages = [
307
+ {"role": "system", "content": system_prompt},
308
+ *chat_history.chat_history,
309
+ {"role": "user", "content": query},
310
+ ]
311
+ past_key_values = None
312
+
313
+ if self.config.enable_activation_memory:
314
+ if self.config.chat_model.backend not in ["huggingface", "huggingface_singleton"]:
315
+ logger.error(
316
+ "Activation memory only used for huggingface backend. Skipping activation memory."
317
+ )
318
+ else:
319
+ # TODO this only one cubes
320
+ for mem_cube_id, mem_cube in self.mem_cubes.items():
321
+ if mem_cube_id not in user_cube_ids:
322
+ continue
323
+ if mem_cube.act_mem:
324
+ kv_cache = next(iter(mem_cube.act_mem.get_all()), None)
325
+ past_key_values = (
326
+ kv_cache.memory if (kv_cache and hasattr(kv_cache, "memory")) else None
327
+ )
328
+ break
329
+ # Generate response
330
+ response = self.chat_llm.generate(current_messages, past_key_values=past_key_values)
331
+ else:
332
+ response = self.chat_llm.generate(current_messages)
333
+ logger.info(f"🤖 [Assistant] {response}\n")
334
+ chat_history.chat_history.append({"role": "user", "content": query})
335
+ chat_history.chat_history.append({"role": "assistant", "content": response})
336
+ self.chat_history_manager[user_id] = chat_history
337
+
338
+ # submit message to scheduler
339
+ for accessible_mem_cube in accessible_cubes:
340
+ mem_cube_id = accessible_mem_cube.cube_id
341
+ mem_cube = self.mem_cubes[mem_cube_id]
342
+ if self.enable_mem_scheduler and self.mem_scheduler is not None:
343
+ message_item = ScheduleMessageItem(
344
+ user_id=target_user_id,
345
+ mem_cube_id=mem_cube_id,
346
+ label=ANSWER_TASK_LABEL,
347
+ content=response,
348
+ timestamp=datetime.utcnow(),
349
+ )
350
+ self.mem_scheduler.submit_messages(messages=[message_item])
351
+
352
+ return response
353
+
354
+ def _build_system_prompt(
355
+ self,
356
+ memories: list[TextualMemoryItem] | list[str] | None = None,
357
+ base_prompt: str | None = None,
358
+ **kwargs,
359
+ ) -> str:
360
+ """Build system prompt with optional memories context."""
361
+ if base_prompt is None:
362
+ base_prompt = (
363
+ "You are a knowledgeable and helpful AI assistant. "
364
+ "You have access to conversation memories that help you provide more personalized responses. "
365
+ "Use the memories to understand the user's context, preferences, and past interactions. "
366
+ "If memories are provided, reference them naturally when relevant, but don't explicitly mention having memories."
367
+ )
368
+
369
+ memory_context = ""
370
+ if memories:
371
+ memory_list = []
372
+ for i, memory in enumerate(memories, 1):
373
+ if isinstance(memory, TextualMemoryItem):
374
+ text_memory = memory.memory
375
+ else:
376
+ if not isinstance(memory, str):
377
+ logger.error("Unexpected memory type.")
378
+ text_memory = memory
379
+ memory_list.append(f"{i}. {text_memory}")
380
+ memory_context = "\n".join(memory_list)
381
+
382
+ if "{memories}" in base_prompt:
383
+ return base_prompt.format(memories=memory_context)
384
+ elif memories:
385
+ # For backward compatibility, append memories if no placeholder is found
386
+ memory_context_with_header = "\n\n## Memories:\n" + memory_context
387
+ return base_prompt + memory_context_with_header
388
+ return base_prompt
389
+
390
+ def _str_memories(
391
+ self, memories: list[TextualMemoryItem], mode: Literal["concise", "full"] = "full"
392
+ ) -> str:
393
+ """Format memories for display."""
394
+ if not memories:
395
+ return "No memories."
396
+ if mode == "concise":
397
+ return "\n".join(f"{i + 1}. {memory.memory}" for i, memory in enumerate(memories))
398
+ elif mode == "full":
399
+ return "\n".join(f"{i + 1}. {memory}" for i, memory in enumerate(memories))
400
+
401
+ def clear_messages(self, user_id: str | None = None) -> None:
402
+ """Clear chat history."""
403
+ user_id = user_id if user_id is not None else self.user_id
404
+ self._register_chat_history(user_id)
405
+
406
+ def create_user(
407
+ self, user_id: str, role: UserRole = UserRole.USER, user_name: str | None = None
408
+ ) -> str:
409
+ """Create a new user.
410
+
411
+ Args:
412
+ user_name (str): Name of the user.
413
+ role (UserRole): Role of the user.
414
+ user_id (str, optional): Custom user ID.
415
+
416
+ Returns:
417
+ str: The created user ID.
418
+ """
419
+ if not user_name:
420
+ user_name = user_id
421
+ return self.user_manager.create_user(user_name, role, user_id)
422
+
423
+ def list_users(self) -> list:
424
+ """List all active users.
425
+
426
+ Returns:
427
+ list: List of user information dictionaries.
428
+ """
429
+ users = self.user_manager.list_users()
430
+ return [
431
+ {
432
+ "user_id": user.user_id,
433
+ "user_name": user.user_name,
434
+ "role": user.role.value,
435
+ "created_at": user.created_at.isoformat(),
436
+ "is_active": user.is_active,
437
+ }
438
+ for user in users
439
+ ]
440
+
441
+ def create_cube_for_user(
442
+ self,
443
+ cube_name: str,
444
+ owner_id: str,
445
+ cube_path: str | None = None,
446
+ cube_id: str | None = None,
447
+ ) -> str:
448
+ """Create a new cube for the current user.
449
+
450
+ Args:
451
+ cube_name (str): Name of the cube.
452
+ cube_path (str, optional): Path to the cube.
453
+ cube_id (str, optional): Custom cube ID.
454
+
455
+ Returns:
456
+ str: The created cube ID.
457
+ """
458
+ return self.user_manager.create_cube(cube_name, owner_id, cube_path, cube_id)
459
+
460
+ def register_mem_cube(
461
+ self,
462
+ mem_cube_name_or_path: str | GeneralMemCube,
463
+ mem_cube_id: str | None = None,
464
+ user_id: str | None = None,
465
+ ) -> None:
466
+ """
467
+ Register a MemCube with the MOS.
468
+
469
+ Args:
470
+ mem_cube_name_or_path (str): The name or path of the MemCube to register.
471
+ mem_cube_id (str, optional): The identifier for the MemCube. If not provided, a default ID is used.
472
+ """
473
+ target_user_id = user_id if user_id is not None else self.user_id
474
+ self._validate_user_exists(target_user_id)
475
+
476
+ if mem_cube_id is None:
477
+ if isinstance(mem_cube_name_or_path, GeneralMemCube):
478
+ mem_cube_id = f"cube_{target_user_id}"
479
+ else:
480
+ mem_cube_id = mem_cube_name_or_path
481
+
482
+ if mem_cube_id in self.mem_cubes:
483
+ logger.info(f"MemCube with ID {mem_cube_id} already in MOS, skip install.")
484
+ else:
485
+ if isinstance(mem_cube_name_or_path, GeneralMemCube):
486
+ self.mem_cubes[mem_cube_id] = mem_cube_name_or_path
487
+ logger.info(f"register new cube {mem_cube_id} for user {target_user_id}")
488
+ elif os.path.exists(mem_cube_name_or_path):
489
+ mem_cube_obj = GeneralMemCube.init_from_dir(mem_cube_name_or_path)
490
+ self.mem_cubes[mem_cube_id] = mem_cube_obj
491
+ else:
492
+ logger.warning(
493
+ f"MemCube {mem_cube_name_or_path} does not exist, try to init from remote repo."
494
+ )
495
+ mem_cube_obj = GeneralMemCube.init_from_remote_repo(mem_cube_name_or_path)
496
+ self.mem_cubes[mem_cube_id] = mem_cube_obj
497
+ # Check if cube already exists in database
498
+ existing_cube = self.user_manager.get_cube(mem_cube_id)
499
+
500
+ # check the embedder is it consistent with MOSConfig
501
+ if hasattr(
502
+ self.mem_cubes[mem_cube_id].text_mem.config, "embedder"
503
+ ) and self.config.mem_reader.config.embedder != (
504
+ cube_embedder := self.mem_cubes[mem_cube_id].text_mem.config.embedder
505
+ ):
506
+ logger.warning(
507
+ f"Cube Embedder is not consistent with MOSConfig for cube: {mem_cube_id}, will use Cube Embedder: {cube_embedder}"
508
+ )
509
+
510
+ if existing_cube:
511
+ # Cube exists, just add user to cube if not already associated
512
+ if not self.user_manager.validate_user_cube_access(target_user_id, mem_cube_id):
513
+ success = self.user_manager.add_user_to_cube(target_user_id, mem_cube_id)
514
+ if success:
515
+ logger.info(f"User {target_user_id} added to existing cube {mem_cube_id}")
516
+ else:
517
+ logger.error(f"Failed to add user {target_user_id} to cube {mem_cube_id}")
518
+ else:
519
+ logger.info(f"User {target_user_id} already has access to cube {mem_cube_id}")
520
+ else:
521
+ # Cube doesn't exist, create it
522
+ self.create_cube_for_user(
523
+ cube_name=mem_cube_name_or_path
524
+ if not isinstance(mem_cube_name_or_path, GeneralMemCube)
525
+ else mem_cube_id,
526
+ owner_id=target_user_id,
527
+ cube_id=mem_cube_id,
528
+ cube_path=mem_cube_name_or_path
529
+ if not isinstance(mem_cube_name_or_path, GeneralMemCube)
530
+ else "init",
531
+ )
532
+ logger.info(f"register new cube {mem_cube_id} for user {target_user_id}")
533
+
534
+ def unregister_mem_cube(self, mem_cube_id: str, user_id: str | None = None) -> None:
535
+ """
536
+ Unregister a MemCube by its identifier.
537
+
538
+ Args:
539
+ mem_cube_id (str): The identifier of the MemCube to unregister.
540
+ """
541
+ if mem_cube_id in self.mem_cubes:
542
+ del self.mem_cubes[mem_cube_id]
543
+ else:
544
+ raise ValueError(f"MemCube with ID {mem_cube_id} does not exist.")
545
+
546
+ def search(
547
+ self,
548
+ query: str,
549
+ user_id: str | None = None,
550
+ install_cube_ids: list[str] | None = None,
551
+ top_k: int | None = None,
552
+ mode: Literal["fast", "fine"] = "fast",
553
+ internet_search: bool = False,
554
+ moscube: bool = False,
555
+ session_id: str | None = None,
556
+ **kwargs,
557
+ ) -> MOSSearchResult:
558
+ """
559
+ Search for textual memories across all registered MemCubes.
560
+
561
+ Args:
562
+ query (str): The search query.
563
+ user_id (str, optional): The identifier of the user to search for.
564
+ If None, the default user is used.
565
+ install_cube_ids (list[str], optional): The list of MemCube IDs to install.
566
+ If None, all MemCube for the user is used.
567
+
568
+ Returns:
569
+ MemoryResult: A dictionary containing the search results.
570
+ """
571
+ target_session_id = session_id if session_id is not None else self.session_id
572
+ target_user_id = user_id if user_id is not None else self.user_id
573
+
574
+ self._validate_user_exists(target_user_id)
575
+ # Get all cubes accessible by the target user
576
+ accessible_cubes = self.user_manager.get_user_cubes(target_user_id)
577
+ user_cube_ids = [cube.cube_id for cube in accessible_cubes]
578
+
579
+ logger.info(
580
+ f"User {target_user_id} has access to {len(user_cube_ids)} cubes: {user_cube_ids}"
581
+ )
582
+ if target_user_id not in self.chat_history_manager:
583
+ self._register_chat_history(target_user_id)
584
+ chat_history = self.chat_history_manager[target_user_id]
585
+
586
+ # Create search filter if session_id is provided
587
+ search_filter = None
588
+ if session_id is not None:
589
+ search_filter = {"session_id": session_id}
590
+
591
+ result: MOSSearchResult = {
592
+ "text_mem": [],
593
+ "act_mem": [],
594
+ "para_mem": [],
595
+ "pref_mem": [],
596
+ }
597
+ if install_cube_ids is None:
598
+ install_cube_ids = user_cube_ids
599
+ # create exist dict in mem_cubes and avoid one search slow
600
+ tmp_mem_cubes = {}
601
+ time_start_cube_get = time.time()
602
+ for mem_cube_id in install_cube_ids:
603
+ if mem_cube_id in self.mem_cubes:
604
+ tmp_mem_cubes[mem_cube_id] = self.mem_cubes.get(mem_cube_id)
605
+ logger.info(
606
+ f"time search: transform cube time user_id: {target_user_id} time is: {time.time() - time_start_cube_get}"
607
+ )
608
+
609
+ for mem_cube_id, mem_cube in tmp_mem_cubes.items():
610
+ # Define internal functions for parallel search execution
611
+ def search_textual_memory(cube_id, cube):
612
+ if (
613
+ (cube_id in install_cube_ids)
614
+ and (cube.text_mem is not None)
615
+ and self.config.enable_textual_memory
616
+ ):
617
+ time_start = time.time()
618
+ memories = cube.text_mem.search(
619
+ query,
620
+ top_k=top_k if top_k else self.config.top_k,
621
+ mode=mode,
622
+ manual_close_internet=not internet_search,
623
+ info={
624
+ "user_id": target_user_id,
625
+ "session_id": target_session_id,
626
+ "chat_history": chat_history.chat_history,
627
+ },
628
+ moscube=moscube,
629
+ search_filter=search_filter,
630
+ )
631
+ search_time_end = time.time()
632
+ logger.info(
633
+ f"🧠 [Memory] Searched memories from {cube_id}:\n{self._str_memories(memories)}\n"
634
+ )
635
+ logger.info(
636
+ f"time search graph: search graph time user_id: {target_user_id} time is: {search_time_end - time_start}"
637
+ )
638
+ return {"cube_id": cube_id, "memories": memories}
639
+ return None
640
+
641
+ def search_preference_memory(cube_id, cube):
642
+ if (
643
+ (cube_id in install_cube_ids)
644
+ and (cube.pref_mem is not None)
645
+ and self.config.enable_preference_memory
646
+ ):
647
+ time_start = time.time()
648
+ memories = cube.pref_mem.search(
649
+ query,
650
+ top_k=top_k if top_k else self.config.top_k,
651
+ info={
652
+ "user_id": target_user_id,
653
+ "session_id": self.session_id,
654
+ "chat_history": chat_history.chat_history,
655
+ },
656
+ )
657
+ search_time_end = time.time()
658
+ logger.info(
659
+ f"🧠 [Memory] Searched preferences from {cube_id}:\n{self._str_memories(memories)}\n"
660
+ )
661
+ logger.info(
662
+ f"time search pref: search pref time user_id: {target_user_id} time is: {search_time_end - time_start}"
663
+ )
664
+ return {"cube_id": cube_id, "memories": memories}
665
+ return None
666
+
667
+ # Execute both search functions in parallel
668
+ with ContextThreadPoolExecutor(max_workers=2) as executor:
669
+ text_future = executor.submit(search_textual_memory, mem_cube_id, mem_cube)
670
+ pref_future = executor.submit(search_preference_memory, mem_cube_id, mem_cube)
671
+
672
+ # Wait for both tasks to complete and collect results
673
+ text_result = text_future.result()
674
+ pref_result = pref_future.result()
675
+
676
+ # Add results to the main result dictionary
677
+ if text_result is not None:
678
+ result["text_mem"].append(text_result)
679
+ if pref_result is not None:
680
+ result["pref_mem"].append(pref_result)
681
+
682
+ return result
683
+
684
+ def add(
685
+ self,
686
+ messages: MessageList | None = None,
687
+ memory_content: str | None = None,
688
+ doc_path: str | None = None,
689
+ mem_cube_id: str | None = None,
690
+ user_id: str | None = None,
691
+ session_id: str | None = None,
692
+ task_id: str | None = None, # New: Add task_id parameter
693
+ **kwargs,
694
+ ) -> None:
695
+ """
696
+ Add textual memories to a MemCube.
697
+
698
+ Args:
699
+ messages (Union[MessageList, str]): The path to a document or a list of messages.
700
+ memory_content (str, optional): The content of the memory to add.
701
+ doc_path (str, optional): The path to the document associated with the memory.
702
+ mem_cube_id (str, optional): The identifier of the MemCube to add the memories to.
703
+ If None, the default MemCube for the user is used.
704
+ user_id (str, optional): The identifier of the user to add the memories to.
705
+ If None, the default user is used.
706
+ session_id (str, optional): session_id
707
+ """
708
+ # user input messages
709
+ assert (messages is not None) or (memory_content is not None) or (doc_path is not None), (
710
+ "messages_or_doc_path or memory_content or doc_path must be provided."
711
+ )
712
+ # TODO: asure that session_id is a valid string
713
+ time_start = time.time()
714
+
715
+ target_session_id = session_id if session_id else self.session_id
716
+ target_user_id = user_id if user_id is not None else self.user_id
717
+ if mem_cube_id is None:
718
+ # Try to find a default cube for the user
719
+ accessible_cubes = self.user_manager.get_user_cubes(target_user_id)
720
+ if not accessible_cubes:
721
+ raise ValueError(
722
+ f"No accessible cubes found for user '{target_user_id}'. Please register a cube first."
723
+ )
724
+ mem_cube_id = accessible_cubes[0].cube_id # TODO not only first
725
+ else:
726
+ self._validate_cube_access(target_user_id, mem_cube_id)
727
+ logger.info(
728
+ f"time add: get mem_cube_id time user_id: {target_user_id} time is: {time.time() - time_start}"
729
+ )
730
+
731
+ if mem_cube_id not in self.mem_cubes:
732
+ raise ValueError(f"MemCube '{mem_cube_id}' is not loaded. Please register.")
733
+
734
+ sync_mode = self.mem_cubes[mem_cube_id].text_mem.mode
735
+ if sync_mode == "async":
736
+ assert self.mem_scheduler is not None, (
737
+ "Mem-Scheduler must be working when use asynchronous memory adding."
738
+ )
739
+ logger.debug(f"Mem-reader mode is: {sync_mode}")
740
+
741
+ def process_textual_memory():
742
+ if (
743
+ (messages is not None)
744
+ and self.config.enable_textual_memory
745
+ and self.mem_cubes[mem_cube_id].text_mem
746
+ ):
747
+ if self.mem_cubes[mem_cube_id].config.text_mem.backend != "tree_text":
748
+ add_memory = []
749
+ metadata = TextualMemoryMetadata(
750
+ user_id=target_user_id, session_id=target_session_id, source="conversation"
751
+ )
752
+ for message in messages:
753
+ add_memory.append(
754
+ TextualMemoryItem(memory=message["content"], metadata=metadata)
755
+ )
756
+ self.mem_cubes[mem_cube_id].text_mem.add(add_memory)
757
+ else:
758
+ messages_list = [messages]
759
+ memories = self.mem_reader.get_memory(
760
+ messages_list,
761
+ type="chat",
762
+ info={"user_id": target_user_id, "session_id": target_session_id},
763
+ mode="fast" if sync_mode == "async" else "fine",
764
+ )
765
+ memories_flatten = [m for m_list in memories for m in m_list]
766
+ mem_ids: list[str] = self.mem_cubes[mem_cube_id].text_mem.add(memories_flatten)
767
+ logger.info(
768
+ f"Added memory user {target_user_id} to memcube {mem_cube_id}: {mem_ids}"
769
+ )
770
+ # submit messages for scheduler
771
+ if self.enable_mem_scheduler and self.mem_scheduler is not None:
772
+ if sync_mode == "async":
773
+ message_item = ScheduleMessageItem(
774
+ user_id=target_user_id,
775
+ mem_cube_id=mem_cube_id,
776
+ label=MEM_READ_TASK_LABEL,
777
+ content=json.dumps(mem_ids),
778
+ timestamp=datetime.utcnow(),
779
+ task_id=task_id,
780
+ )
781
+ self.mem_scheduler.submit_messages(messages=[message_item])
782
+ else:
783
+ message_item = ScheduleMessageItem(
784
+ user_id=target_user_id,
785
+ mem_cube_id=mem_cube_id,
786
+ label=ADD_TASK_LABEL,
787
+ content=json.dumps(mem_ids),
788
+ timestamp=datetime.utcnow(),
789
+ task_id=task_id,
790
+ )
791
+ logger.info(
792
+ f"[DIAGNOSTIC] core.add: Submitting message to scheduler: {message_item.model_dump_json(indent=2)}"
793
+ )
794
+ self.mem_scheduler.submit_messages(messages=[message_item])
795
+
796
+ def process_preference_memory():
797
+ if (
798
+ (messages is not None)
799
+ and self.config.enable_preference_memory
800
+ and self.mem_cubes[mem_cube_id].pref_mem
801
+ ):
802
+ messages_list = [messages]
803
+ if sync_mode == "sync":
804
+ pref_memories = self.mem_cubes[mem_cube_id].pref_mem.get_memory(
805
+ messages_list,
806
+ type="chat",
807
+ info={
808
+ "user_id": target_user_id,
809
+ "session_id": self.session_id,
810
+ "mem_cube_id": mem_cube_id,
811
+ },
812
+ )
813
+ pref_ids = self.mem_cubes[mem_cube_id].pref_mem.add(pref_memories)
814
+ logger.info(
815
+ f"Added preferences user {target_user_id} to memcube {mem_cube_id}: {pref_ids}"
816
+ )
817
+ elif sync_mode == "async":
818
+ assert self.mem_scheduler is not None, (
819
+ "Mem-Scheduler must be working when use asynchronous memory adding."
820
+ )
821
+ message_item = ScheduleMessageItem(
822
+ user_id=target_user_id,
823
+ session_id=target_session_id,
824
+ mem_cube_id=mem_cube_id,
825
+ label=PREF_ADD_TASK_LABEL,
826
+ content=json.dumps(messages_list),
827
+ timestamp=datetime.utcnow(),
828
+ )
829
+ self.mem_scheduler.submit_messages(messages=[message_item])
830
+
831
+ # Execute both memory processing functions in parallel
832
+ with ContextThreadPoolExecutor(max_workers=2) as executor:
833
+ text_future = executor.submit(process_textual_memory)
834
+ pref_future = executor.submit(process_preference_memory)
835
+
836
+ # Wait for both tasks to complete
837
+ text_future.result()
838
+ pref_future.result()
839
+
840
+ # user profile
841
+ if (
842
+ (memory_content is not None)
843
+ and self.config.enable_textual_memory
844
+ and self.mem_cubes[mem_cube_id].text_mem
845
+ ):
846
+ if self.mem_cubes[mem_cube_id].config.text_mem.backend != "tree_text":
847
+ metadata = TextualMemoryMetadata(
848
+ user_id=target_user_id, session_id=target_session_id, source="conversation"
849
+ )
850
+ self.mem_cubes[mem_cube_id].text_mem.add(
851
+ [TextualMemoryItem(memory=memory_content, metadata=metadata)]
852
+ )
853
+ else:
854
+ messages_list = [
855
+ [{"role": "user", "content": memory_content}]
856
+ ] # for only user-str input and convert message
857
+
858
+ memories = self.mem_reader.get_memory(
859
+ messages_list,
860
+ type="chat",
861
+ info={"user_id": target_user_id, "session_id": target_session_id},
862
+ mode="fast" if sync_mode == "async" else "fine",
863
+ )
864
+
865
+ mem_ids = []
866
+ for mem in memories:
867
+ mem_id_list: list[str] = self.mem_cubes[mem_cube_id].text_mem.add(mem)
868
+ logger.info(
869
+ f"Added memory user {target_user_id} to memcube {mem_cube_id}: {mem_id_list}"
870
+ )
871
+ mem_ids.extend(mem_id_list)
872
+
873
+ # submit messages for scheduler
874
+ if self.enable_mem_scheduler and self.mem_scheduler is not None:
875
+ if sync_mode == "async":
876
+ message_item = ScheduleMessageItem(
877
+ user_id=target_user_id,
878
+ mem_cube_id=mem_cube_id,
879
+ label=MEM_READ_TASK_LABEL,
880
+ content=json.dumps(mem_ids),
881
+ timestamp=datetime.utcnow(),
882
+ )
883
+ self.mem_scheduler.submit_messages(messages=[message_item])
884
+ else:
885
+ message_item = ScheduleMessageItem(
886
+ user_id=target_user_id,
887
+ mem_cube_id=mem_cube_id,
888
+ label=ADD_TASK_LABEL,
889
+ content=json.dumps(mem_ids),
890
+ timestamp=datetime.utcnow(),
891
+ )
892
+ self.mem_scheduler.submit_messages(messages=[message_item])
893
+
894
+ # user doc input
895
+ if (
896
+ (doc_path is not None)
897
+ and self.config.enable_textual_memory
898
+ and self.mem_cubes[mem_cube_id].text_mem
899
+ ):
900
+ documents = self._get_all_documents(doc_path)
901
+ doc_memories = self.mem_reader.get_memory(
902
+ documents,
903
+ type="doc",
904
+ info={"user_id": target_user_id, "session_id": target_session_id},
905
+ )
906
+
907
+ mem_ids = []
908
+ for mem in doc_memories:
909
+ mem_id_list: list[str] = self.mem_cubes[mem_cube_id].text_mem.add(mem)
910
+ mem_ids.extend(mem_id_list)
911
+
912
+ # submit messages for scheduler
913
+ if self.enable_mem_scheduler and self.mem_scheduler is not None:
914
+ message_item = ScheduleMessageItem(
915
+ user_id=target_user_id,
916
+ mem_cube_id=mem_cube_id,
917
+ label=ADD_TASK_LABEL,
918
+ content=json.dumps(mem_ids),
919
+ timestamp=datetime.utcnow(),
920
+ )
921
+ self.mem_scheduler.submit_messages(messages=[message_item])
922
+
923
+ logger.info(f"Add memory to {mem_cube_id} successfully")
924
+
925
+ def get(
926
+ self, mem_cube_id: str, memory_id: str, user_id: str | None = None
927
+ ) -> TextualMemoryItem | ActivationMemoryItem | ParametricMemoryItem:
928
+ """
929
+ Get a textual memory from a MemCube.
930
+
931
+ Args:
932
+ mem_cube_id (str): The identifier of the MemCube to get the memory from.
933
+ memory_id (str): The identifier of the memory to get.
934
+ user_id (str, optional): The identifier of the user to get the memory from.
935
+ If None, the default user is used.
936
+
937
+ Returns:
938
+ Union[TextualMemoryItem, ActivationMemoryItem, ParametricMemoryItem]: The requested memory item.
939
+ """
940
+ target_user_id = user_id if user_id is not None else self.user_id
941
+ # Validate user has access to this cube
942
+ self._validate_cube_access(target_user_id, mem_cube_id)
943
+ if mem_cube_id is None:
944
+ # Try to find a default cube for the user
945
+ accessible_cubes = self.user_manager.get_user_cubes(target_user_id)
946
+ if not accessible_cubes:
947
+ raise ValueError(
948
+ f"No accessible cubes found for user '{target_user_id}'. Please register a cube first."
949
+ )
950
+ mem_cube_id = accessible_cubes[0].cube_id # TODO not only first
951
+ else:
952
+ self._validate_cube_access(target_user_id, mem_cube_id)
953
+
954
+ assert mem_cube_id in self.mem_cubes, (
955
+ f"MemCube with ID {mem_cube_id} does not exist. please regiester"
956
+ )
957
+ return self.mem_cubes[mem_cube_id].text_mem.get(memory_id)
958
+
959
+ def get_all(
960
+ self, mem_cube_id: str | None = None, user_id: str | None = None
961
+ ) -> MOSSearchResult:
962
+ """
963
+ Get all textual memories from a MemCube.
964
+
965
+ Args:
966
+ mem_cube_id (str, optional): The identifier of the MemCube to get the memories from.
967
+ If None, all MemCube for the user is used.
968
+ user_id (str, optional): The identifier of the user to get the memories from.
969
+ If None, the default user is used.
970
+
971
+ Returns:
972
+ MemoryResult: A dictionary containing the search results.
973
+ """
974
+ result: MOSSearchResult = {"para_mem": [], "act_mem": [], "text_mem": []}
975
+ target_user_id = user_id if user_id is not None else self.user_id
976
+ # Validate user has access to this cube
977
+ if mem_cube_id is None:
978
+ # Try to find a default cube for the user
979
+ accessible_cubes = self.user_manager.get_user_cubes(target_user_id)
980
+ if not accessible_cubes:
981
+ raise ValueError(
982
+ f"No accessible cubes found for user '{target_user_id}'. Please register a cube first."
983
+ )
984
+ mem_cube_id = accessible_cubes[0].cube_id # TODO not only first
985
+ else:
986
+ self._validate_cube_access(target_user_id, mem_cube_id)
987
+ if self.config.enable_textual_memory and self.mem_cubes[mem_cube_id].text_mem:
988
+ result["text_mem"].append(
989
+ {"cube_id": mem_cube_id, "memories": self.mem_cubes[mem_cube_id].text_mem.get_all()}
990
+ )
991
+ if self.config.enable_activation_memory and self.mem_cubes[mem_cube_id].act_mem:
992
+ result["act_mem"].append(
993
+ {"cube_id": mem_cube_id, "memories": self.mem_cubes[mem_cube_id].act_mem.get_all()}
994
+ )
995
+ return result
996
+
997
+ def update(
998
+ self,
999
+ mem_cube_id: str,
1000
+ memory_id: str,
1001
+ text_memory_item: TextualMemoryItem | dict[str, Any],
1002
+ user_id: str | None = None,
1003
+ ) -> None:
1004
+ """
1005
+ Update a textual memory in a MemCube by text_memory_id and text_memory_id.
1006
+
1007
+ Args:
1008
+ mem_cube_id (str): The identifier of the MemCube to update the memory in.
1009
+ memory_id (str): The identifier of the textual memory to update.
1010
+ text_memory_item (TextualMemoryItem | dict[str, Any]): The updated textual memory item.
1011
+ """
1012
+ assert mem_cube_id in self.mem_cubes, (
1013
+ f"MemCube with ID {mem_cube_id} does not exist. please regiester"
1014
+ )
1015
+ target_user_id = user_id if user_id is not None else self.user_id
1016
+ # Validate user has access to this cube
1017
+ self._validate_cube_access(target_user_id, mem_cube_id)
1018
+ if mem_cube_id is None:
1019
+ # Try to find a default cube for the user
1020
+ accessible_cubes = self.user_manager.get_user_cubes(target_user_id)
1021
+ if not accessible_cubes:
1022
+ raise ValueError(
1023
+ f"No accessible cubes found for user '{target_user_id}'. Please register a cube first."
1024
+ )
1025
+ mem_cube_id = accessible_cubes[0].cube_id # TODO not only first
1026
+ else:
1027
+ self._validate_cube_access(target_user_id, mem_cube_id)
1028
+ if self.mem_cubes[mem_cube_id].config.text_mem.backend != "tree_text":
1029
+ self.mem_cubes[mem_cube_id].text_mem.update(memory_id, memories=text_memory_item)
1030
+ logger.info(f"MemCube {mem_cube_id} updated memory {memory_id}")
1031
+ else:
1032
+ logger.warning(
1033
+ f" {self.mem_cubes[mem_cube_id].config.text_mem.backend} does not support update memory"
1034
+ )
1035
+
1036
+ def delete(self, mem_cube_id: str, memory_id: str, user_id: str | None = None) -> None:
1037
+ """
1038
+ Delete a textual memory from a MemCube by memory_id.
1039
+
1040
+ Args:
1041
+ mem_cube_id (str): The identifier of the MemCube to delete the memory from.
1042
+ memory_id (str): The identifier of the memory to delete.
1043
+ """
1044
+ assert mem_cube_id in self.mem_cubes, (
1045
+ f"MemCube with ID {mem_cube_id} does not exist. please regiester"
1046
+ )
1047
+ target_user_id = user_id if user_id is not None else self.user_id
1048
+ # Validate user has access to this cube
1049
+ self._validate_cube_access(target_user_id, mem_cube_id)
1050
+ if mem_cube_id is None:
1051
+ # Try to find a default cube for the user
1052
+ accessible_cubes = self.user_manager.get_user_cubes(target_user_id)
1053
+ if not accessible_cubes:
1054
+ raise ValueError(
1055
+ f"No accessible cubes found for user '{target_user_id}'. Please register a cube first."
1056
+ )
1057
+ mem_cube_id = accessible_cubes[0].cube_id # TODO not only first
1058
+ else:
1059
+ self._validate_cube_access(target_user_id, mem_cube_id)
1060
+ self.mem_cubes[mem_cube_id].text_mem.delete(memory_id)
1061
+ logger.info(f"MemCube {mem_cube_id} deleted memory {memory_id}")
1062
+
1063
+ def delete_all(self, mem_cube_id: str | None = None, user_id: str | None = None) -> None:
1064
+ """
1065
+ Delete all textual memories from a MemCube for user.
1066
+
1067
+ Args:
1068
+ mem_cube_id (str): The identifier of the MemCube to delete the memories from.
1069
+ """
1070
+ assert mem_cube_id in self.mem_cubes, (
1071
+ f"MemCube with ID {mem_cube_id} does not exist. please regiester"
1072
+ )
1073
+ target_user_id = user_id if user_id is not None else self.user_id
1074
+ # Validate user has access to this cube
1075
+ self._validate_cube_access(target_user_id, mem_cube_id)
1076
+ if mem_cube_id is None:
1077
+ # Try to find a default cube for the user
1078
+ accessible_cubes = self.user_manager.get_user_cubes(target_user_id)
1079
+ if not accessible_cubes:
1080
+ raise ValueError(
1081
+ f"No accessible cubes found for user '{target_user_id}'. Please register a cube first."
1082
+ )
1083
+ mem_cube_id = accessible_cubes[0].cube_id # TODO not only first
1084
+ else:
1085
+ self._validate_cube_access(target_user_id, mem_cube_id)
1086
+ self.mem_cubes[mem_cube_id].text_mem.delete_all()
1087
+ logger.info(f"MemCube {mem_cube_id} deleted all memories")
1088
+
1089
+ def dump(
1090
+ self, dump_dir: str, user_id: str | None = None, mem_cube_id: str | None = None
1091
+ ) -> None:
1092
+ """Dump the MemCube to a dictionary.
1093
+ Args:
1094
+ dump_dir (str): The directory to dump the MemCube to.
1095
+ user_id (str, optional): The identifier of the user to dump the MemCube from.
1096
+ If None, the default user is used.
1097
+ mem_cube_id (str, optional): The identifier of the MemCube to dump.
1098
+ If None, the default MemCube for the user is used.
1099
+ """
1100
+ target_user_id = user_id if user_id is not None else self.user_id
1101
+ accessible_cubes = self.user_manager.get_user_cubes(target_user_id)
1102
+ if not mem_cube_id:
1103
+ mem_cube_id = accessible_cubes[0].cube_id
1104
+ if mem_cube_id not in self.mem_cubes:
1105
+ raise ValueError(f"MemCube with ID {mem_cube_id} does not exist. please regiester")
1106
+ self.mem_cubes[mem_cube_id].dump(dump_dir)
1107
+ logger.info(f"MemCube {mem_cube_id} dumped to {dump_dir}")
1108
+
1109
+ def load(
1110
+ self,
1111
+ load_dir: str,
1112
+ user_id: str | None = None,
1113
+ mem_cube_id: str | None = None,
1114
+ memory_types: list[Literal["text_mem", "act_mem", "para_mem", "pref_mem"]] | None = None,
1115
+ ) -> None:
1116
+ """Dump the MemCube to a dictionary.
1117
+ Args:
1118
+ load_dir (str): The directory to load the MemCube from.
1119
+ user_id (str, optional): The identifier of the user to load the MemCube from.
1120
+ If None, the default user is used.
1121
+ mem_cube_id (str, optional): The identifier of the MemCube to load.
1122
+ If None, the default MemCube for the user is used.
1123
+ """
1124
+ target_user_id = user_id if user_id is not None else self.user_id
1125
+ accessible_cubes = self.user_manager.get_user_cubes(target_user_id)
1126
+ if not mem_cube_id:
1127
+ mem_cube_id = accessible_cubes[0].cube_id
1128
+ if mem_cube_id not in self.mem_cubes:
1129
+ raise ValueError(f"MemCube with ID {mem_cube_id} does not exist. please regiester")
1130
+ self.mem_cubes[mem_cube_id].load(load_dir, memory_types=memory_types)
1131
+ logger.info(f"MemCube {mem_cube_id} loaded from {load_dir}")
1132
+
1133
+ def get_user_info(self) -> dict[str, Any]:
1134
+ """Get current user information including accessible cubes.
1135
+ TODO: maybe input user_id
1136
+ Returns:
1137
+ dict: User information and accessible cubes.
1138
+ """
1139
+ user = self.user_manager.get_user(self.user_id)
1140
+ if not user:
1141
+ return {}
1142
+
1143
+ accessible_cubes = self.user_manager.get_user_cubes(self.user_id)
1144
+
1145
+ return {
1146
+ "user_id": user.user_id,
1147
+ "user_name": user.user_name,
1148
+ "role": user.role.value if hasattr(user.role, "value") else user.role,
1149
+ "created_at": user.created_at.isoformat(),
1150
+ "accessible_cubes": [
1151
+ {
1152
+ "cube_id": cube.cube_id,
1153
+ "cube_name": cube.cube_name,
1154
+ "cube_path": cube.cube_path,
1155
+ "owner_id": cube.owner_id,
1156
+ "is_loaded": cube.cube_id in self.mem_cubes,
1157
+ }
1158
+ for cube in accessible_cubes
1159
+ ],
1160
+ }
1161
+
1162
+ def share_cube_with_user(self, cube_id: str, target_user_id: str) -> bool:
1163
+ """Share a cube with another user.
1164
+
1165
+ Args:
1166
+ cube_id (str): The cube ID to share.
1167
+ target_user_id (str): The user ID to share with.
1168
+
1169
+ Returns:
1170
+ bool: True if successful, False otherwise.
1171
+ """
1172
+ # Validate current user has access to this cube
1173
+ self._validate_cube_access(cube_id, target_user_id)
1174
+
1175
+ # Validate target user exists
1176
+ if not self.user_manager.validate_user(target_user_id):
1177
+ raise ValueError(f"Target user '{target_user_id}' does not exist or is inactive.")
1178
+
1179
+ return self.user_manager.add_user_to_cube(target_user_id, cube_id)
1180
+
1181
+ def get_query_rewrite(self, query: str, user_id: str | None = None):
1182
+ """
1183
+ Rewrite user's query according the context.
1184
+ Args:
1185
+ query (str): The search query that needs rewriting.
1186
+ user_id(str, optional): The identifier of the user that the query belongs to.
1187
+ If None, the default user is used.
1188
+
1189
+ Returns:
1190
+ str: query after rewriting process.
1191
+ """
1192
+ target_user_id = user_id if user_id is not None else self.user_id
1193
+ chat_history = self.chat_history_manager[target_user_id]
1194
+
1195
+ dialogue = "————{}".format("\n————".join(chat_history.chat_history))
1196
+ user_prompt = QUERY_REWRITING_PROMPT.format(dialogue=dialogue, query=query)
1197
+ messages = {"role": "user", "content": user_prompt}
1198
+ rewritten_result = self.chat_llm.generate(messages=messages)
1199
+ rewritten_result = json.loads(rewritten_result)
1200
+ if rewritten_result.get("former_dialogue_related", False):
1201
+ rewritten_query = rewritten_result.get("rewritten_question")
1202
+ return rewritten_query if len(rewritten_query) > 0 else query
1203
+ return query