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
@@ -0,0 +1,874 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import os
5
+ import traceback
6
+
7
+ from dataclasses import dataclass
8
+ from datetime import datetime
9
+ from typing import TYPE_CHECKING, Any
10
+
11
+ from memos.api.handlers.formatters_handler import (
12
+ format_memory_item,
13
+ post_process_pref_mem,
14
+ post_process_textual_mem,
15
+ )
16
+ from memos.context.context import ContextThreadPoolExecutor
17
+ from memos.log import get_logger
18
+ from memos.mem_reader.utils import parse_keep_filter_response
19
+ from memos.mem_scheduler.schemas.message_schemas import ScheduleMessageItem
20
+ from memos.mem_scheduler.schemas.task_schemas import (
21
+ ADD_TASK_LABEL,
22
+ MEM_FEEDBACK_TASK_LABEL,
23
+ MEM_READ_TASK_LABEL,
24
+ PREF_ADD_TASK_LABEL,
25
+ )
26
+ from memos.multi_mem_cube.views import MemCubeView
27
+ from memos.templates.mem_reader_prompts import PROMPT_MAPPING
28
+ from memos.types.general_types import (
29
+ FINE_STRATEGY,
30
+ FineStrategy,
31
+ MOSSearchResult,
32
+ SearchMode,
33
+ UserContext,
34
+ )
35
+ from memos.utils import timed
36
+
37
+
38
+ logger = get_logger(__name__)
39
+
40
+
41
+ if TYPE_CHECKING:
42
+ from memos.api.product_models import APIADDRequest, APIFeedbackRequest, APISearchRequest
43
+ from memos.mem_cube.navie import NaiveMemCube
44
+ from memos.mem_reader.simple_struct import SimpleStructMemReader
45
+ from memos.mem_scheduler.optimized_scheduler import OptimizedScheduler
46
+ from memos.memories.textual.item import TextualMemoryItem
47
+
48
+
49
+ @dataclass
50
+ class SingleCubeView(MemCubeView):
51
+ cube_id: str
52
+ naive_mem_cube: NaiveMemCube
53
+ mem_reader: SimpleStructMemReader
54
+ mem_scheduler: OptimizedScheduler
55
+ logger: Any
56
+ searcher: Any
57
+ feedback_server: Any | None = None
58
+ deepsearch_agent: Any | None = None
59
+
60
+ @timed
61
+ def add_memories(self, add_req: APIADDRequest) -> list[dict[str, Any]]:
62
+ """
63
+ This is basically your current handle_add_memories logic,
64
+ but scoped to a single cube_id.
65
+ """
66
+ sync_mode = add_req.async_mode or self._get_sync_mode()
67
+ self.logger.info(
68
+ f"[DIAGNOSTIC] single_cube.add_memories called for cube_id: {self.cube_id}. sync_mode: {sync_mode}. Request: {add_req.model_dump_json(indent=2)}"
69
+ )
70
+ user_context = UserContext(
71
+ user_id=add_req.user_id,
72
+ mem_cube_id=self.cube_id,
73
+ session_id=add_req.session_id or "default_session",
74
+ )
75
+
76
+ target_session_id = add_req.session_id or "default_session"
77
+ sync_mode = add_req.async_mode or self._get_sync_mode()
78
+
79
+ self.logger.info(
80
+ f"[SingleCubeView] cube={self.cube_id} "
81
+ f"Processing add with mode={sync_mode}, session={target_session_id}"
82
+ )
83
+
84
+ with ContextThreadPoolExecutor(max_workers=2) as executor:
85
+ text_future = executor.submit(self._process_text_mem, add_req, user_context, sync_mode)
86
+ pref_future = executor.submit(self._process_pref_mem, add_req, user_context, sync_mode)
87
+
88
+ text_results = text_future.result()
89
+ pref_results = pref_future.result()
90
+
91
+ self.logger.info(
92
+ f"[SingleCubeView] cube={self.cube_id} text_results={len(text_results)}, "
93
+ f"pref_results={len(pref_results)}"
94
+ )
95
+
96
+ for item in text_results:
97
+ item["cube_id"] = self.cube_id
98
+ for item in pref_results:
99
+ item["cube_id"] = self.cube_id
100
+
101
+ all_memories = text_results + pref_results
102
+
103
+ # TODO: search existing memories and compare
104
+
105
+ return all_memories
106
+
107
+ @timed
108
+ def search_memories(self, search_req: APISearchRequest) -> dict[str, Any]:
109
+ # Create UserContext object
110
+ user_context = UserContext(
111
+ user_id=search_req.user_id,
112
+ mem_cube_id=self.cube_id,
113
+ session_id=search_req.session_id or "default_session",
114
+ )
115
+ self.logger.info(f"Search Req is: {search_req}")
116
+
117
+ memories_result: MOSSearchResult = {
118
+ "text_mem": [],
119
+ "act_mem": [],
120
+ "para_mem": [],
121
+ "pref_mem": [],
122
+ "pref_note": "",
123
+ "tool_mem": [],
124
+ }
125
+
126
+ # Determine search mode
127
+ search_mode = self._get_search_mode(search_req.mode)
128
+
129
+ # Execute search in parallel for text and preference memories
130
+ with ContextThreadPoolExecutor(max_workers=2) as executor:
131
+ text_future = executor.submit(self._search_text, search_req, user_context, search_mode)
132
+ pref_future = executor.submit(self._search_pref, search_req, user_context)
133
+
134
+ text_formatted_memories = text_future.result()
135
+ pref_formatted_memories = pref_future.result()
136
+
137
+ # Build result
138
+ memories_result = post_process_textual_mem(
139
+ memories_result,
140
+ text_formatted_memories,
141
+ self.cube_id,
142
+ )
143
+
144
+ memories_result = post_process_pref_mem(
145
+ memories_result,
146
+ pref_formatted_memories,
147
+ self.cube_id,
148
+ search_req.include_preference,
149
+ )
150
+
151
+ self.logger.info(f"Search memories result: {memories_result}")
152
+ self.logger.info(f"Search {len(memories_result)} memories.")
153
+ return memories_result
154
+
155
+ @timed
156
+ def feedback_memories(self, feedback_req: APIFeedbackRequest) -> dict[str, Any]:
157
+ target_session_id = feedback_req.session_id or "default_session"
158
+ if feedback_req.async_mode == "async":
159
+ try:
160
+ feedback_req_str = json.dumps(feedback_req.model_dump())
161
+ message_item_feedback = ScheduleMessageItem(
162
+ user_id=feedback_req.user_id,
163
+ task_id=feedback_req.task_id,
164
+ session_id=target_session_id,
165
+ mem_cube_id=self.cube_id,
166
+ mem_cube=self.naive_mem_cube,
167
+ label=MEM_FEEDBACK_TASK_LABEL,
168
+ content=feedback_req_str,
169
+ timestamp=datetime.utcnow(),
170
+ )
171
+ # Use scheduler submission to ensure tracking and metrics
172
+ self.mem_scheduler.submit_messages(messages=[message_item_feedback])
173
+ self.logger.info(f"[SingleCubeView] cube={self.cube_id} Submitted FEEDBACK async")
174
+ except Exception as e:
175
+ self.logger.error(
176
+ f"[SingleCubeView] cube={self.cube_id} Failed to submit FEEDBACK: {e}",
177
+ exc_info=True,
178
+ )
179
+ return []
180
+ else:
181
+ feedback_result = self.feedback_server.process_feedback(
182
+ user_id=feedback_req.user_id,
183
+ user_name=self.cube_id,
184
+ session_id=feedback_req.session_id,
185
+ chat_history=feedback_req.history,
186
+ retrieved_memory_ids=feedback_req.retrieved_memory_ids,
187
+ feedback_content=feedback_req.feedback_content,
188
+ feedback_time=feedback_req.feedback_time,
189
+ async_mode=feedback_req.async_mode,
190
+ corrected_answer=feedback_req.corrected_answer,
191
+ task_id=feedback_req.task_id,
192
+ info=feedback_req.info,
193
+ )
194
+ self.logger.info(f"[Feedback memories result:] {feedback_result}")
195
+ return feedback_result
196
+
197
+ def _get_search_mode(self, mode: str) -> str:
198
+ """
199
+ Get search mode with environment variable fallback.
200
+
201
+ Args:
202
+ mode: Requested search mode
203
+
204
+ Returns:
205
+ Search mode string
206
+ """
207
+ return mode
208
+
209
+ @timed
210
+ def _search_text(
211
+ self,
212
+ search_req: APISearchRequest,
213
+ user_context: UserContext,
214
+ search_mode: str,
215
+ ) -> list[dict[str, Any]]:
216
+ """
217
+ Search text memories based on mode.
218
+
219
+ Args:
220
+ search_req: Search request
221
+ user_context: User context
222
+ search_mode: Search mode (fast, fine, or mixture)
223
+
224
+ Returns:
225
+ List of formatted memory items
226
+ """
227
+ try:
228
+ if search_mode == SearchMode.FAST:
229
+ text_memories = self._fast_search(search_req, user_context)
230
+ elif search_mode == SearchMode.FINE:
231
+ text_memories = self._fine_search(search_req, user_context)
232
+ elif search_mode == SearchMode.MIXTURE:
233
+ text_memories = self._mix_search(search_req, user_context)
234
+ else:
235
+ self.logger.error(f"Unsupported search mode: {search_mode}")
236
+ return []
237
+ return text_memories
238
+
239
+ except Exception as e:
240
+ self.logger.error("Error in search_text: %s; traceback: %s", e, traceback.format_exc())
241
+ return []
242
+
243
+ def _deep_search(
244
+ self,
245
+ search_req: APISearchRequest,
246
+ user_context: UserContext,
247
+ ) -> list:
248
+ target_session_id = search_req.session_id or "default_session"
249
+ search_filter = {"session_id": search_req.session_id} if search_req.session_id else None
250
+
251
+ info = {
252
+ "user_id": search_req.user_id,
253
+ "session_id": target_session_id,
254
+ "chat_history": search_req.chat_history,
255
+ }
256
+
257
+ enhanced_memories = self.searcher.deep_search(
258
+ query=search_req.query,
259
+ user_name=user_context.mem_cube_id,
260
+ top_k=search_req.top_k,
261
+ mode=SearchMode.FINE,
262
+ manual_close_internet=not search_req.internet_search,
263
+ moscube=search_req.moscube,
264
+ search_filter=search_filter,
265
+ info=info,
266
+ )
267
+ formatted_memories = [
268
+ format_memory_item(data, include_embedding=search_req.dedup == "sim")
269
+ for data in enhanced_memories
270
+ ]
271
+ return formatted_memories
272
+
273
+ def _agentic_search(
274
+ self, search_req: APISearchRequest, user_context: UserContext, max_thinking_depth: int
275
+ ) -> list:
276
+ deepsearch_results = self.deepsearch_agent.run(
277
+ search_req.query, user_id=user_context.mem_cube_id
278
+ )
279
+ formatted_memories = [
280
+ format_memory_item(data, include_embedding=search_req.dedup == "sim")
281
+ for data in deepsearch_results
282
+ ]
283
+ return formatted_memories
284
+
285
+ def _fine_search(
286
+ self,
287
+ search_req: APISearchRequest,
288
+ user_context: UserContext,
289
+ ) -> list:
290
+ """
291
+ Fine-grained search with query enhancement.
292
+
293
+ Args:
294
+ search_req: Search request
295
+ user_context: User context
296
+
297
+ Returns:
298
+ List of enhanced search results
299
+ """
300
+ # TODO: support tool memory search in future
301
+
302
+ logger.info(f"Fine strategy: {FINE_STRATEGY}")
303
+ if FINE_STRATEGY == FineStrategy.DEEP_SEARCH:
304
+ return self._deep_search(search_req=search_req, user_context=user_context)
305
+ elif FINE_STRATEGY == FineStrategy.AGENTIC_SEARCH:
306
+ return self._agentic_search(search_req=search_req, user_context=user_context)
307
+
308
+ target_session_id = search_req.session_id or "default_session"
309
+ search_priority = {"session_id": search_req.session_id} if search_req.session_id else None
310
+ search_filter = search_req.filter
311
+
312
+ info = {
313
+ "user_id": search_req.user_id,
314
+ "session_id": target_session_id,
315
+ "chat_history": search_req.chat_history,
316
+ }
317
+
318
+ # Fine retrieve
319
+ raw_retrieved_memories = self.searcher.retrieve(
320
+ query=search_req.query,
321
+ user_name=user_context.mem_cube_id,
322
+ top_k=search_req.top_k,
323
+ mode=SearchMode.FINE,
324
+ manual_close_internet=not search_req.internet_search,
325
+ moscube=search_req.moscube,
326
+ search_filter=search_filter,
327
+ search_priority=search_priority,
328
+ info=info,
329
+ )
330
+
331
+ # Post retrieve
332
+ raw_memories = self.searcher.post_retrieve(
333
+ retrieved_results=raw_retrieved_memories,
334
+ top_k=search_req.top_k,
335
+ user_name=user_context.mem_cube_id,
336
+ info=info,
337
+ dedup=search_req.dedup,
338
+ )
339
+
340
+ # Enhance with query
341
+ enhanced_memories, _ = self.mem_scheduler.retriever.enhance_memories_with_query(
342
+ query_history=[search_req.query],
343
+ memories=raw_memories,
344
+ )
345
+
346
+ if len(enhanced_memories) < len(raw_memories):
347
+ logger.info(
348
+ f"Enhanced memories ({len(enhanced_memories)}) are less than raw memories ({len(raw_memories)}). Recalling for more."
349
+ )
350
+ missing_info_hint, trigger = self.mem_scheduler.retriever.recall_for_missing_memories(
351
+ query=search_req.query,
352
+ memories=[mem.memory for mem in enhanced_memories],
353
+ )
354
+ retrieval_size = len(raw_memories) - len(enhanced_memories)
355
+ logger.info(f"Retrieval size: {retrieval_size}")
356
+ if trigger:
357
+ logger.info(f"Triggering additional search with hint: {missing_info_hint}")
358
+ additional_memories = self.searcher.search(
359
+ query=missing_info_hint,
360
+ user_name=user_context.mem_cube_id,
361
+ top_k=retrieval_size,
362
+ mode=SearchMode.FAST,
363
+ memory_type="All",
364
+ search_priority=search_priority,
365
+ search_filter=search_filter,
366
+ info=info,
367
+ )
368
+ else:
369
+ logger.info("Not triggering additional search, using fast memories.")
370
+ additional_memories = raw_memories[:retrieval_size]
371
+
372
+ enhanced_memories += additional_memories
373
+ logger.info(
374
+ f"Added {len(additional_memories)} more memories. Total enhanced memories: {len(enhanced_memories)}"
375
+ )
376
+
377
+ def _dedup_by_content(memories: list) -> list:
378
+ seen = set()
379
+ unique_memories = []
380
+ for mem in memories:
381
+ key = " ".join(mem.memory.split())
382
+ if key in seen:
383
+ continue
384
+ seen.add(key)
385
+ unique_memories.append(mem)
386
+ return unique_memories
387
+
388
+ deduped_memories = (
389
+ enhanced_memories if search_req.dedup == "no" else _dedup_by_content(enhanced_memories)
390
+ )
391
+ formatted_memories = [
392
+ format_memory_item(data, include_embedding=search_req.dedup == "sim")
393
+ for data in deduped_memories
394
+ ]
395
+
396
+ logger.info(f"Found {len(formatted_memories)} memories for user {search_req.user_id}")
397
+
398
+ return formatted_memories
399
+
400
+ @timed
401
+ def _search_pref(
402
+ self,
403
+ search_req: APISearchRequest,
404
+ user_context: UserContext,
405
+ ) -> list[dict[str, Any]]:
406
+ """
407
+ Search preference memories.
408
+
409
+ Args:
410
+ search_req: Search request
411
+ user_context: User context
412
+
413
+ Returns:
414
+ List of formatted preference memory items
415
+ TODO: ADD CUBE ID IN PREFERENCE MEMORY
416
+ """
417
+ if os.getenv("ENABLE_PREFERENCE_MEMORY", "false").lower() != "true":
418
+ return []
419
+ if not search_req.include_preference:
420
+ return []
421
+
422
+ logger.info(f"search_req.filter for preference memory: {search_req.filter}")
423
+ logger.info(f"type of pref_mem: {type(self.naive_mem_cube.pref_mem)}")
424
+ try:
425
+ results = self.naive_mem_cube.pref_mem.search(
426
+ query=search_req.query,
427
+ top_k=search_req.pref_top_k,
428
+ info={
429
+ "user_id": search_req.user_id,
430
+ "mem_cube_id": user_context.mem_cube_id,
431
+ "session_id": search_req.session_id,
432
+ "chat_history": search_req.chat_history,
433
+ },
434
+ search_filter=search_req.filter,
435
+ )
436
+ return [format_memory_item(data) for data in results]
437
+ except Exception as e:
438
+ self.logger.error("Error in _search_pref: %s; traceback: %s", e, traceback.format_exc())
439
+ return []
440
+
441
+ def _fast_search(
442
+ self,
443
+ search_req: APISearchRequest,
444
+ user_context: UserContext,
445
+ ) -> list:
446
+ """
447
+ Fast search using vector database.
448
+
449
+ Args:
450
+ search_req: Search request
451
+ user_context: User context
452
+
453
+ Returns:
454
+ List of search results
455
+ """
456
+ target_session_id = search_req.session_id or "default_session"
457
+ search_priority = {"session_id": search_req.session_id} if search_req.session_id else None
458
+ search_filter = search_req.filter or None
459
+ plugin = bool(search_req.source is not None and search_req.source == "plugin")
460
+
461
+ search_results = self.naive_mem_cube.text_mem.search(
462
+ query=search_req.query,
463
+ user_name=user_context.mem_cube_id,
464
+ top_k=search_req.top_k,
465
+ mode=SearchMode.FAST,
466
+ manual_close_internet=not search_req.internet_search,
467
+ memory_type=search_req.search_memory_type,
468
+ search_filter=search_filter,
469
+ search_priority=search_priority,
470
+ info={
471
+ "user_id": search_req.user_id,
472
+ "session_id": target_session_id,
473
+ "chat_history": search_req.chat_history,
474
+ },
475
+ plugin=plugin,
476
+ search_tool_memory=search_req.search_tool_memory,
477
+ tool_mem_top_k=search_req.tool_mem_top_k,
478
+ dedup=search_req.dedup,
479
+ )
480
+
481
+ formatted_memories = [
482
+ format_memory_item(data, include_embedding=search_req.dedup == "sim")
483
+ for data in search_results
484
+ ]
485
+
486
+ return formatted_memories
487
+
488
+ def _mix_search(
489
+ self,
490
+ search_req: APISearchRequest,
491
+ user_context: UserContext,
492
+ ) -> list:
493
+ """
494
+ Mix search combining fast and fine-grained approaches.
495
+
496
+ Args:
497
+ search_req: Search request
498
+ user_context: User context
499
+
500
+ Returns:
501
+ List of formatted search results
502
+ """
503
+ return self.mem_scheduler.mix_search_memories(
504
+ search_req=search_req,
505
+ user_context=user_context,
506
+ )
507
+
508
+ def _get_sync_mode(self) -> str:
509
+ """
510
+ Get synchronization mode from memory cube.
511
+
512
+ Returns:
513
+ Sync mode string ("sync" or "async")
514
+ """
515
+ try:
516
+ return getattr(self.naive_mem_cube.text_mem, "mode", "sync")
517
+ except Exception:
518
+ return "sync"
519
+
520
+ def _schedule_memory_tasks(
521
+ self,
522
+ add_req: APIADDRequest,
523
+ user_context: UserContext,
524
+ mem_ids: list[str],
525
+ sync_mode: str,
526
+ ) -> None:
527
+ """
528
+ Schedule memory processing tasks based on sync mode.
529
+
530
+ Args:
531
+ add_req: Add memory request
532
+ user_context: User context
533
+ mem_ids: List of memory IDs
534
+ sync_mode: Synchronization mode
535
+ """
536
+ target_session_id = add_req.session_id or "default_session"
537
+
538
+ if sync_mode == "async":
539
+ # Async mode: submit MEM_READ_LABEL task
540
+ try:
541
+ message_item_read = ScheduleMessageItem(
542
+ user_id=add_req.user_id,
543
+ task_id=add_req.task_id,
544
+ session_id=target_session_id,
545
+ mem_cube_id=self.cube_id,
546
+ mem_cube=self.naive_mem_cube,
547
+ label=MEM_READ_TASK_LABEL,
548
+ content=json.dumps(mem_ids),
549
+ timestamp=datetime.utcnow(),
550
+ user_name=self.cube_id,
551
+ info=add_req.info,
552
+ )
553
+ self.mem_scheduler.submit_messages(messages=[message_item_read])
554
+ self.logger.info(
555
+ f"[SingleCubeView] cube={self.cube_id} Submitted async MEM_READ: {json.dumps(mem_ids)}"
556
+ )
557
+ except Exception as e:
558
+ self.logger.error(
559
+ f"[SingleCubeView] cube={self.cube_id} Failed to submit async memory tasks: {e}",
560
+ exc_info=True,
561
+ )
562
+ else:
563
+ message_item_add = ScheduleMessageItem(
564
+ user_id=add_req.user_id,
565
+ task_id=add_req.task_id,
566
+ session_id=target_session_id,
567
+ mem_cube_id=self.cube_id,
568
+ mem_cube=self.naive_mem_cube,
569
+ label=ADD_TASK_LABEL,
570
+ content=json.dumps(mem_ids),
571
+ timestamp=datetime.utcnow(),
572
+ user_name=self.cube_id,
573
+ )
574
+ self.mem_scheduler.submit_messages(messages=[message_item_add])
575
+
576
+ @timed
577
+ def _process_pref_mem(
578
+ self,
579
+ add_req: APIADDRequest,
580
+ user_context: UserContext,
581
+ sync_mode: str,
582
+ ) -> list[dict[str, Any]]:
583
+ """
584
+ Process and add preference memories.
585
+
586
+ Extracts preferences from messages and adds them to the preference memory system.
587
+ Handles both sync and async modes.
588
+
589
+ Args:
590
+ add_req: Add memory request
591
+ user_context: User context with IDs
592
+
593
+ Returns:
594
+ List of formatted preference responses
595
+ """
596
+ if os.getenv("ENABLE_PREFERENCE_MEMORY", "false").lower() != "true":
597
+ return []
598
+
599
+ if add_req.messages is None or isinstance(add_req.messages, str):
600
+ return []
601
+
602
+ for message in add_req.messages:
603
+ if isinstance(message, dict) and message.get("role", None) is None:
604
+ return []
605
+
606
+ target_session_id = add_req.session_id or "default_session"
607
+
608
+ if sync_mode == "async":
609
+ try:
610
+ messages_list = [add_req.messages]
611
+ message_item_pref = ScheduleMessageItem(
612
+ user_id=add_req.user_id,
613
+ session_id=target_session_id,
614
+ mem_cube_id=user_context.mem_cube_id,
615
+ mem_cube=self.naive_mem_cube,
616
+ label=PREF_ADD_TASK_LABEL,
617
+ content=json.dumps(messages_list),
618
+ timestamp=datetime.utcnow(),
619
+ info=add_req.info,
620
+ user_name=self.cube_id,
621
+ task_id=add_req.task_id,
622
+ )
623
+ self.mem_scheduler.submit_messages(messages=[message_item_pref])
624
+ self.logger.info(f"[SingleCubeView] cube={self.cube_id} Submitted PREF_ADD async")
625
+ except Exception as e:
626
+ self.logger.error(
627
+ f"[SingleCubeView] cube={self.cube_id} Failed to submit PREF_ADD: {e}",
628
+ exc_info=True,
629
+ )
630
+ return []
631
+ else:
632
+ pref_memories_local = self.naive_mem_cube.pref_mem.get_memory(
633
+ [add_req.messages],
634
+ type="chat",
635
+ info={
636
+ **(add_req.info or {}),
637
+ "user_id": add_req.user_id,
638
+ "session_id": target_session_id,
639
+ "mem_cube_id": user_context.mem_cube_id,
640
+ },
641
+ )
642
+ pref_ids_local: list[str] = self.naive_mem_cube.pref_mem.add(pref_memories_local)
643
+ self.logger.info(
644
+ f"[SingleCubeView] cube={self.cube_id} "
645
+ f"added {len(pref_ids_local)} preferences for user {add_req.user_id}: {pref_ids_local}"
646
+ )
647
+
648
+ return [
649
+ {
650
+ "memory": memory.metadata.preference,
651
+ "memory_id": memory_id,
652
+ "memory_type": memory.metadata.preference_type,
653
+ }
654
+ for memory_id, memory in zip(pref_ids_local, pref_memories_local, strict=False)
655
+ ]
656
+
657
+ def add_before_search(
658
+ self,
659
+ messages: list[dict],
660
+ memory_list: list[TextualMemoryItem],
661
+ user_name: str,
662
+ info: dict[str, Any],
663
+ ) -> list[TextualMemoryItem]:
664
+ # Build input objects with memory text and metadata (timestamps, sources, etc.)
665
+ template = PROMPT_MAPPING["add_before_search"]
666
+
667
+ if not self.searcher:
668
+ self.logger.warning("[add_before_search] Searcher is not initialized, skipping check.")
669
+ return memory_list
670
+
671
+ # 1. Gather candidates and search for related memories
672
+ candidates_data = []
673
+ for idx, mem in enumerate(memory_list):
674
+ try:
675
+ related_memories = self.searcher.search(
676
+ query=mem.memory, top_k=3, mode="fast", user_name=user_name, info=info
677
+ )
678
+ related_text = "None"
679
+ if related_memories:
680
+ related_text = "\n".join([f"- {r.memory}" for r in related_memories])
681
+
682
+ candidates_data.append(
683
+ {"idx": idx, "new_memory": mem.memory, "related_memories": related_text}
684
+ )
685
+ except Exception as e:
686
+ self.logger.error(
687
+ f"[add_before_search] Search error for memory '{mem.memory}': {e}"
688
+ )
689
+ # If search fails, we can either skip this check or treat related as empty
690
+ candidates_data.append(
691
+ {
692
+ "idx": idx,
693
+ "new_memory": mem.memory,
694
+ "related_memories": "None (Search Failed)",
695
+ }
696
+ )
697
+
698
+ if not candidates_data:
699
+ return memory_list
700
+
701
+ # 2. Build Prompt
702
+ messages_inline = "\n".join(
703
+ [
704
+ f"- [{message.get('role', 'unknown')}]: {message.get('content', '')}"
705
+ for message in messages
706
+ ]
707
+ )
708
+
709
+ candidates_inline_dict = {
710
+ str(item["idx"]): {
711
+ "new_memory": item["new_memory"],
712
+ "related_memories": item["related_memories"],
713
+ }
714
+ for item in candidates_data
715
+ }
716
+
717
+ candidates_inline = json.dumps(candidates_inline_dict, ensure_ascii=False, indent=2)
718
+
719
+ prompt = template.format(
720
+ messages_inline=messages_inline, candidates_inline=candidates_inline
721
+ )
722
+
723
+ # 3. Call LLM
724
+ try:
725
+ raw = self.mem_reader.llm.generate([{"role": "user", "content": prompt}])
726
+ success, parsed_result = parse_keep_filter_response(raw)
727
+
728
+ if not success:
729
+ self.logger.warning(
730
+ "[add_before_search] Failed to parse LLM response, keeping all."
731
+ )
732
+ return memory_list
733
+
734
+ # 4. Filter
735
+ filtered_list = []
736
+ for idx, mem in enumerate(memory_list):
737
+ res = parsed_result.get(idx)
738
+ if not res:
739
+ filtered_list.append(mem)
740
+ continue
741
+
742
+ if res.get("keep", True):
743
+ filtered_list.append(mem)
744
+ else:
745
+ self.logger.info(
746
+ f"[add_before_search] Dropping memory: '{mem.memory}', reason: '{res.get('reason')}'"
747
+ )
748
+
749
+ return filtered_list
750
+
751
+ except Exception as e:
752
+ self.logger.error(f"[add_before_search] LLM execution error: {e}")
753
+ return memory_list
754
+
755
+ @timed
756
+ def _process_text_mem(
757
+ self,
758
+ add_req: APIADDRequest,
759
+ user_context: UserContext,
760
+ sync_mode: str,
761
+ ) -> list[dict[str, Any]]:
762
+ """
763
+ Process and add text memories.
764
+
765
+ Extracts memories from messages and adds them to the text memory system.
766
+ Handles both sync and async modes.
767
+
768
+ Args:
769
+ add_req: Add memory request
770
+ user_context: User context with IDs
771
+
772
+ Returns:
773
+ List of formatted memory responses
774
+ """
775
+ target_session_id = add_req.session_id or "default_session"
776
+
777
+ # Decide extraction mode:
778
+ # - async: always fast (ignore add_req.mode)
779
+ # - sync: use add_req.mode == "fast" to switch to fast pipeline, otherwise fine
780
+ if sync_mode == "async":
781
+ extract_mode = "fast"
782
+ else: # sync
783
+ extract_mode = "fast" if add_req.mode == "fast" else "fine"
784
+
785
+ self.logger.info(
786
+ "[SingleCubeView] cube=%s Processing text memory "
787
+ "with sync_mode=%s, extract_mode=%s, add_mode=%s",
788
+ user_context.mem_cube_id,
789
+ sync_mode,
790
+ extract_mode,
791
+ add_req.mode,
792
+ )
793
+
794
+ # Extract memories
795
+ memories_local = self.mem_reader.get_memory(
796
+ [add_req.messages],
797
+ type="chat",
798
+ info={
799
+ **(add_req.info or {}),
800
+ "custom_tags": add_req.custom_tags,
801
+ "user_id": add_req.user_id,
802
+ "session_id": target_session_id,
803
+ },
804
+ mode=extract_mode,
805
+ user_name=user_context.mem_cube_id,
806
+ )
807
+ flattened_local = [mm for m in memories_local for mm in m]
808
+
809
+ # Explicitly set source_doc_id to metadata if present in info
810
+ source_doc_id = (add_req.info or {}).get("source_doc_id")
811
+ if source_doc_id:
812
+ for memory in flattened_local:
813
+ memory.metadata.source_doc_id = source_doc_id
814
+
815
+ self.logger.info(f"Memory extraction completed for user {add_req.user_id}")
816
+
817
+ # Add memories to text_mem
818
+ mem_ids_local: list[str] = self.naive_mem_cube.text_mem.add(
819
+ flattened_local,
820
+ user_name=user_context.mem_cube_id,
821
+ )
822
+ self.logger.info(
823
+ f"Added {len(mem_ids_local)} memories for user {add_req.user_id} "
824
+ f"in session {add_req.session_id}: {mem_ids_local}"
825
+ )
826
+
827
+ # Schedule async/sync tasks
828
+ self._schedule_memory_tasks(
829
+ add_req=add_req,
830
+ user_context=user_context,
831
+ mem_ids=mem_ids_local,
832
+ sync_mode=sync_mode,
833
+ )
834
+
835
+ # Mark merged_from memories as archived when provided in add_req.info
836
+ if sync_mode == "sync" and extract_mode == "fine":
837
+ for memory in flattened_local:
838
+ merged_from = (memory.metadata.info or {}).get("merged_from")
839
+ if merged_from:
840
+ old_ids = (
841
+ merged_from
842
+ if isinstance(merged_from, (list | tuple | set))
843
+ else [merged_from]
844
+ )
845
+ if self.mem_reader and self.mem_reader.graph_db:
846
+ for old_id in old_ids:
847
+ try:
848
+ self.mem_reader.graph_db.update_node(
849
+ str(old_id),
850
+ {"status": "archived"},
851
+ user_name=user_context.mem_cube_id,
852
+ )
853
+ self.logger.info(
854
+ f"[SingleCubeView] Archived merged_from memory: {old_id}"
855
+ )
856
+ except Exception as e:
857
+ self.logger.warning(
858
+ f"[SingleCubeView] Failed to archive merged_from memory {old_id}: {e}"
859
+ )
860
+ else:
861
+ self.logger.warning(
862
+ "[SingleCubeView] merged_from provided but graph_db is unavailable; skip archiving."
863
+ )
864
+
865
+ text_memories = [
866
+ {
867
+ "memory": memory.memory,
868
+ "memory_id": memory_id,
869
+ "memory_type": memory.metadata.memory_type,
870
+ }
871
+ for memory_id, memory in zip(mem_ids_local, flattened_local, strict=False)
872
+ ]
873
+
874
+ return text_memories