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,547 @@
1
+ import time
2
+
3
+ from concurrent.futures import as_completed
4
+
5
+ from memos.configs.mem_scheduler import BaseSchedulerConfig
6
+ from memos.context.context import ContextThreadPoolExecutor
7
+ from memos.llms.base import BaseLLM
8
+ from memos.log import get_logger
9
+ from memos.mem_cube.general import GeneralMemCube
10
+ from memos.mem_scheduler.general_modules.base import BaseSchedulerModule
11
+ from memos.mem_scheduler.schemas.general_schemas import (
12
+ DEFAULT_SCHEDULER_RETRIEVER_BATCH_SIZE,
13
+ DEFAULT_SCHEDULER_RETRIEVER_RETRIES,
14
+ TreeTextMemory_FINE_SEARCH_METHOD,
15
+ TreeTextMemory_SEARCH_METHOD,
16
+ )
17
+ from memos.mem_scheduler.utils.filter_utils import (
18
+ filter_too_short_memories,
19
+ filter_vector_based_similar_memories,
20
+ transform_name_to_key,
21
+ )
22
+ from memos.mem_scheduler.utils.misc_utils import extract_json_obj, extract_list_items_in_answer
23
+ from memos.memories.textual.item import TextualMemoryMetadata
24
+ from memos.memories.textual.tree import TextualMemoryItem, TreeTextMemory
25
+ from memos.types.general_types import (
26
+ FINE_STRATEGY,
27
+ FineStrategy,
28
+ SearchMode,
29
+ )
30
+
31
+ # Extract JSON response
32
+ from .memory_filter import MemoryFilter
33
+
34
+
35
+ logger = get_logger(__name__)
36
+
37
+
38
+ class SchedulerRetriever(BaseSchedulerModule):
39
+ def __init__(self, process_llm: BaseLLM, config: BaseSchedulerConfig):
40
+ super().__init__()
41
+
42
+ # hyper-parameters
43
+ self.filter_similarity_threshold = 0.75
44
+ self.filter_min_length_threshold = 6
45
+ self.memory_filter = MemoryFilter(process_llm=process_llm, config=config)
46
+ self.process_llm = process_llm
47
+ self.config = config
48
+
49
+ # Configure enhancement batching & retries from config with safe defaults
50
+ self.batch_size: int | None = getattr(
51
+ config, "scheduler_retriever_batch_size", DEFAULT_SCHEDULER_RETRIEVER_BATCH_SIZE
52
+ )
53
+ self.retries: int = getattr(
54
+ config, "scheduler_retriever_enhance_retries", DEFAULT_SCHEDULER_RETRIEVER_RETRIES
55
+ )
56
+
57
+ def evaluate_memory_answer_ability(
58
+ self, query: str, memory_texts: list[str], top_k: int | None = None
59
+ ) -> bool:
60
+ limited_memories = memory_texts[:top_k] if top_k is not None else memory_texts
61
+ # Build prompt using the template
62
+ prompt = self.build_prompt(
63
+ template_name="memory_answer_ability_evaluation",
64
+ query=query,
65
+ memory_list="\n".join([f"- {memory}" for memory in limited_memories])
66
+ if limited_memories
67
+ else "No memories available",
68
+ )
69
+
70
+ # Use the process LLM to generate response
71
+ response = self.process_llm.generate([{"role": "user", "content": prompt}])
72
+
73
+ try:
74
+ result = extract_json_obj(response)
75
+
76
+ # Validate response structure
77
+ if "result" in result:
78
+ logger.info(
79
+ f"Answerability: result={result['result']}; reason={result.get('reason', 'n/a')}; evaluated={len(limited_memories)}"
80
+ )
81
+ return result["result"]
82
+ else:
83
+ logger.warning(f"Answerability: invalid LLM JSON structure; payload={result}")
84
+ return False
85
+
86
+ except Exception as e:
87
+ logger.error(f"Answerability: parse failed; err={e}; raw={str(response)[:200]}...")
88
+ # Fallback: return False if we can't determine answer ability
89
+ return False
90
+
91
+ # ---------------------- Enhancement helpers ----------------------
92
+ def _build_enhancement_prompt(self, query_history: list[str], batch_texts: list[str]) -> str:
93
+ if len(query_history) == 1:
94
+ query_history = query_history[0]
95
+ else:
96
+ query_history = (
97
+ [f"[{i}] {query}" for i, query in enumerate(query_history)]
98
+ if len(query_history) > 1
99
+ else query_history[0]
100
+ )
101
+ # Include numbering for rewrite mode to help LLM reference original memory IDs
102
+ if FINE_STRATEGY == FineStrategy.REWRITE:
103
+ text_memories = "\n".join([f"- [{i}] {mem}" for i, mem in enumerate(batch_texts)])
104
+ prompt_name = "memory_rewrite_enhancement"
105
+ else:
106
+ text_memories = "\n".join([f"- {mem}" for i, mem in enumerate(batch_texts)])
107
+ prompt_name = "memory_recreate_enhancement"
108
+ return self.build_prompt(
109
+ prompt_name,
110
+ query_history=query_history,
111
+ memories=text_memories,
112
+ )
113
+
114
+ def _process_enhancement_batch(
115
+ self,
116
+ batch_index: int,
117
+ query_history: list[str],
118
+ memories: list[TextualMemoryItem],
119
+ retries: int,
120
+ ) -> tuple[list[TextualMemoryItem], bool]:
121
+ attempt = 0
122
+ text_memories = [one.memory for one in memories]
123
+
124
+ prompt = self._build_enhancement_prompt(
125
+ query_history=query_history, batch_texts=text_memories
126
+ )
127
+
128
+ llm_response = None
129
+ while attempt <= max(0, retries) + 1:
130
+ try:
131
+ llm_response = self.process_llm.generate([{"role": "user", "content": prompt}])
132
+ processed_text_memories = extract_list_items_in_answer(llm_response)
133
+ if len(processed_text_memories) > 0:
134
+ # create new
135
+ enhanced_memories = []
136
+ user_id = memories[0].metadata.user_id
137
+ if FINE_STRATEGY == FineStrategy.RECREATE:
138
+ for new_mem in processed_text_memories:
139
+ enhanced_memories.append(
140
+ TextualMemoryItem(
141
+ memory=new_mem,
142
+ metadata=TextualMemoryMetadata(
143
+ user_id=user_id, memory_type="LongTermMemory"
144
+ ), # TODO add memory_type
145
+ )
146
+ )
147
+ elif FINE_STRATEGY == FineStrategy.REWRITE:
148
+ # Parse index from each processed line and rewrite corresponding original memory
149
+ def _parse_index_and_text(s: str) -> tuple[int | None, str]:
150
+ import re
151
+
152
+ s = (s or "").strip()
153
+ # Preferred: [index] text
154
+ m = re.match(r"^\s*\[(\d+)\]\s*(.+)$", s)
155
+ if m:
156
+ return int(m.group(1)), m.group(2).strip()
157
+ # Fallback: index: text or index - text
158
+ m = re.match(r"^\s*(\d+)\s*[:\-\)]\s*(.+)$", s)
159
+ if m:
160
+ return int(m.group(1)), m.group(2).strip()
161
+ return None, s
162
+
163
+ idx_to_original = dict(enumerate(memories))
164
+ for j, item in enumerate(processed_text_memories):
165
+ idx, new_text = _parse_index_and_text(item)
166
+ if idx is not None and idx in idx_to_original:
167
+ orig = idx_to_original[idx]
168
+ else:
169
+ # Fallback: align by order if index missing/invalid
170
+ orig = memories[j] if j < len(memories) else None
171
+ if not orig:
172
+ continue
173
+ enhanced_memories.append(
174
+ TextualMemoryItem(
175
+ id=orig.id,
176
+ memory=new_text,
177
+ metadata=orig.metadata,
178
+ )
179
+ )
180
+ else:
181
+ logger.error(f"Fine search strategy {FINE_STRATEGY} not exists")
182
+
183
+ logger.info(
184
+ f"[enhance_memories_with_query] ✅ done | Strategy={FINE_STRATEGY} | prompt={prompt} | llm_response={llm_response}"
185
+ )
186
+ return enhanced_memories, True
187
+ else:
188
+ raise ValueError(
189
+ f"Fail to run memory enhancement; retry {attempt}/{max(1, retries) + 1}; processed_text_memories: {processed_text_memories}"
190
+ )
191
+ except Exception as e:
192
+ attempt += 1
193
+ time.sleep(1)
194
+ logger.debug(
195
+ f"[enhance_memories_with_query][batch={batch_index}] 🔁 retry {attempt}/{max(1, retries) + 1} failed: {e}"
196
+ )
197
+ logger.error(
198
+ f"Fail to run memory enhancement; prompt: {prompt};\n llm_response: {llm_response}",
199
+ exc_info=True,
200
+ )
201
+ return memories, False
202
+
203
+ @staticmethod
204
+ def _split_batches(
205
+ memories: list[TextualMemoryItem], batch_size: int
206
+ ) -> list[tuple[int, int, list[TextualMemoryItem]]]:
207
+ batches: list[tuple[int, int, list[TextualMemoryItem]]] = []
208
+ start = 0
209
+ n = len(memories)
210
+ while start < n:
211
+ end = min(start + batch_size, n)
212
+ batches.append((start, end, memories[start:end]))
213
+ start = end
214
+ return batches
215
+
216
+ def recall_for_missing_memories(
217
+ self,
218
+ query: str,
219
+ memories: list[str],
220
+ ) -> tuple[str, bool]:
221
+ text_memories = "\n".join([f"- {mem}" for i, mem in enumerate(memories)])
222
+
223
+ prompt = self.build_prompt(
224
+ template_name="enlarge_recall",
225
+ query=query,
226
+ memories_inline=text_memories,
227
+ )
228
+ llm_response = self.process_llm.generate([{"role": "user", "content": prompt}])
229
+
230
+ json_result: dict = extract_json_obj(llm_response)
231
+
232
+ logger.info(
233
+ f"[recall_for_missing_memories] ✅ done | prompt={prompt} | llm_response={llm_response}"
234
+ )
235
+
236
+ hint = json_result.get("hint", "")
237
+ if len(hint) == 0:
238
+ return hint, False
239
+ return hint, json_result.get("trigger_recall", False)
240
+
241
+ def search(
242
+ self,
243
+ query: str,
244
+ user_id: str,
245
+ mem_cube_id: str,
246
+ mem_cube: GeneralMemCube,
247
+ top_k: int,
248
+ method: str = TreeTextMemory_SEARCH_METHOD,
249
+ search_args: dict | None = None,
250
+ ) -> list[TextualMemoryItem]:
251
+ """Search in text memory with the given query.
252
+
253
+ Args:
254
+ query: The search query string
255
+ top_k: Number of top results to return
256
+ method: Search method to use
257
+
258
+ Returns:
259
+ Search results or None if not implemented
260
+ """
261
+ text_mem_base = mem_cube.text_mem
262
+ # Normalize default for mutable argument
263
+ search_args = search_args or {}
264
+ try:
265
+ if method in [TreeTextMemory_SEARCH_METHOD, TreeTextMemory_FINE_SEARCH_METHOD]:
266
+ assert isinstance(text_mem_base, TreeTextMemory)
267
+ session_id = search_args.get("session_id", "default_session")
268
+ target_session_id = session_id
269
+ search_priority = (
270
+ {"session_id": target_session_id} if "session_id" in search_args else None
271
+ )
272
+ search_filter = search_args.get("filter")
273
+ search_source = search_args.get("source")
274
+ plugin = bool(search_source is not None and search_source == "plugin")
275
+ user_name = search_args.get("user_name", mem_cube_id)
276
+ internet_search = search_args.get("internet_search", False)
277
+ chat_history = search_args.get("chat_history")
278
+ search_tool_memory = search_args.get("search_tool_memory", False)
279
+ tool_mem_top_k = search_args.get("tool_mem_top_k", 6)
280
+ playground_search_goal_parser = search_args.get(
281
+ "playground_search_goal_parser", False
282
+ )
283
+
284
+ info = search_args.get(
285
+ "info",
286
+ {
287
+ "user_id": user_id,
288
+ "session_id": target_session_id,
289
+ "chat_history": chat_history,
290
+ },
291
+ )
292
+
293
+ results_long_term = mem_cube.text_mem.search(
294
+ query=query,
295
+ user_name=user_name,
296
+ top_k=top_k,
297
+ mode=SearchMode.FAST,
298
+ manual_close_internet=not internet_search,
299
+ memory_type="LongTermMemory",
300
+ search_filter=search_filter,
301
+ search_priority=search_priority,
302
+ info=info,
303
+ plugin=plugin,
304
+ search_tool_memory=search_tool_memory,
305
+ tool_mem_top_k=tool_mem_top_k,
306
+ playground_search_goal_parser=playground_search_goal_parser,
307
+ )
308
+
309
+ results_user = mem_cube.text_mem.search(
310
+ query=query,
311
+ user_name=user_name,
312
+ top_k=top_k,
313
+ mode=SearchMode.FAST,
314
+ manual_close_internet=not internet_search,
315
+ memory_type="UserMemory",
316
+ search_filter=search_filter,
317
+ search_priority=search_priority,
318
+ info=info,
319
+ plugin=plugin,
320
+ search_tool_memory=search_tool_memory,
321
+ tool_mem_top_k=tool_mem_top_k,
322
+ playground_search_goal_parser=playground_search_goal_parser,
323
+ )
324
+ results = results_long_term + results_user
325
+ else:
326
+ raise NotImplementedError(str(type(text_mem_base)))
327
+ except Exception as e:
328
+ logger.error(f"Fail to search. The exeption is {e}.", exc_info=True)
329
+ results = []
330
+ return results
331
+
332
+ def enhance_memories_with_query(
333
+ self,
334
+ query_history: list[str],
335
+ memories: list[TextualMemoryItem],
336
+ ) -> (list[TextualMemoryItem], bool):
337
+ """
338
+ Enhance memories by adding context and making connections to better answer queries.
339
+
340
+ Args:
341
+ query_history: List of user queries in chronological order
342
+ memories: List of memory items to enhance
343
+
344
+ Returns:
345
+ Tuple of (enhanced_memories, success_flag)
346
+ """
347
+ if not memories:
348
+ logger.warning("[Enhance] ⚠️ skipped (no memories to process)")
349
+ return memories, True
350
+
351
+ batch_size = self.batch_size
352
+ retries = self.retries
353
+ num_of_memories = len(memories)
354
+ try:
355
+ # no parallel
356
+ if batch_size is None or num_of_memories <= batch_size:
357
+ # Single batch path with retry
358
+ enhanced_memories, success_flag = self._process_enhancement_batch(
359
+ batch_index=0,
360
+ query_history=query_history,
361
+ memories=memories,
362
+ retries=retries,
363
+ )
364
+
365
+ all_success = success_flag
366
+ else:
367
+ # parallel running batches
368
+ # Split into batches preserving order
369
+ batches = self._split_batches(memories=memories, batch_size=batch_size)
370
+
371
+ # Process batches concurrently
372
+ all_success = True
373
+ failed_batches = 0
374
+ with ContextThreadPoolExecutor(max_workers=len(batches)) as executor:
375
+ future_map = {
376
+ executor.submit(
377
+ self._process_enhancement_batch, bi, query_history, texts, retries
378
+ ): (bi, s, e)
379
+ for bi, (s, e, texts) in enumerate(batches)
380
+ }
381
+ enhanced_memories = []
382
+ for fut in as_completed(future_map):
383
+ bi, s, e = future_map[fut]
384
+
385
+ batch_memories, ok = fut.result()
386
+ enhanced_memories.extend(batch_memories)
387
+ if not ok:
388
+ all_success = False
389
+ failed_batches += 1
390
+ logger.info(
391
+ f"[Enhance] ✅ multi-batch done | batches={len(batches)} | enhanced={len(enhanced_memories)} |"
392
+ f" failed_batches={failed_batches} | success={all_success}"
393
+ )
394
+
395
+ except Exception as e:
396
+ logger.error(f"[Enhance] ❌ fatal error: {e}", exc_info=True)
397
+ all_success = False
398
+ enhanced_memories = memories
399
+
400
+ if len(enhanced_memories) == 0:
401
+ enhanced_memories = []
402
+ logger.error("[Enhance] ❌ fatal error: enhanced_memories is empty", exc_info=True)
403
+ return enhanced_memories, all_success
404
+
405
+ def rerank_memories(
406
+ self, queries: list[str], original_memories: list[str], top_k: int
407
+ ) -> (list[str], bool):
408
+ """
409
+ Rerank memories based on relevance to given queries using LLM.
410
+
411
+ Args:
412
+ queries: List of query strings to determine relevance
413
+ original_memories: List of memory strings to be reranked
414
+ top_k: Number of top memories to return after reranking
415
+
416
+ Returns:
417
+ List of reranked memory strings (length <= top_k)
418
+
419
+ Note:
420
+ If LLM reranking fails, falls back to original order (truncated to top_k)
421
+ """
422
+
423
+ logger.info(f"Starting memory reranking for {len(original_memories)} memories")
424
+
425
+ # Build LLM prompt for memory reranking
426
+ prompt = self.build_prompt(
427
+ "memory_reranking",
428
+ queries=[f"[0] {queries[0]}"],
429
+ current_order=[f"[{i}] {mem}" for i, mem in enumerate(original_memories)],
430
+ )
431
+ logger.debug(f"Generated reranking prompt: {prompt[:200]}...") # Log first 200 chars
432
+
433
+ # Get LLM response
434
+ response = self.process_llm.generate([{"role": "user", "content": prompt}])
435
+ logger.debug(f"Received LLM response: {response[:200]}...") # Log first 200 chars
436
+
437
+ try:
438
+ # Parse JSON response
439
+ response = extract_json_obj(response)
440
+ new_order = response["new_order"][:top_k]
441
+ text_memories_with_new_order = [original_memories[idx] for idx in new_order]
442
+ logger.info(
443
+ f"Successfully reranked memories. Returning top {len(text_memories_with_new_order)} items;"
444
+ f"Ranking reasoning: {response['reasoning']}"
445
+ )
446
+ success_flag = True
447
+ except Exception as e:
448
+ logger.error(
449
+ f"Failed to rerank memories with LLM. Exception: {e}. Raw response: {response} ",
450
+ exc_info=True,
451
+ )
452
+ text_memories_with_new_order = original_memories[:top_k]
453
+ success_flag = False
454
+ return text_memories_with_new_order, success_flag
455
+
456
+ def process_and_rerank_memories(
457
+ self,
458
+ queries: list[str],
459
+ original_memory: list[TextualMemoryItem],
460
+ new_memory: list[TextualMemoryItem],
461
+ top_k: int = 10,
462
+ ) -> list[TextualMemoryItem] | None:
463
+ """
464
+ Process and rerank memory items by combining original and new memories,
465
+ applying filters, and then reranking based on relevance to queries.
466
+
467
+ Args:
468
+ queries: List of query strings to rerank memories against
469
+ original_memory: List of original TextualMemoryItem objects
470
+ new_memory: List of new TextualMemoryItem objects to merge
471
+ top_k: Maximum number of memories to return after reranking
472
+
473
+ Returns:
474
+ List of reranked TextualMemoryItem objects, or None if processing fails
475
+ """
476
+ # Combine original and new memories into a single list
477
+ combined_memory = original_memory + new_memory
478
+
479
+ # Create a mapping from normalized text to memory objects
480
+ memory_map = {
481
+ transform_name_to_key(name=mem_obj.memory): mem_obj for mem_obj in combined_memory
482
+ }
483
+
484
+ # Extract normalized text representations from all memory items
485
+ combined_text_memory = [m.memory for m in combined_memory]
486
+
487
+ # Apply similarity filter to remove overly similar memories
488
+ filtered_combined_text_memory = filter_vector_based_similar_memories(
489
+ text_memories=combined_text_memory,
490
+ similarity_threshold=self.filter_similarity_threshold,
491
+ )
492
+
493
+ # Apply length filter to remove memories that are too short
494
+ filtered_combined_text_memory = filter_too_short_memories(
495
+ text_memories=filtered_combined_text_memory,
496
+ min_length_threshold=self.filter_min_length_threshold,
497
+ )
498
+
499
+ # Ensure uniqueness of memory texts using dictionary keys (preserves order)
500
+ unique_memory = list(dict.fromkeys(filtered_combined_text_memory))
501
+
502
+ # Rerank the filtered memories based on relevance to the queries
503
+ text_memories_with_new_order, success_flag = self.rerank_memories(
504
+ queries=queries,
505
+ original_memories=unique_memory,
506
+ top_k=top_k,
507
+ )
508
+
509
+ # Map reranked text entries back to their original memory objects
510
+ memories_with_new_order = []
511
+ for text in text_memories_with_new_order:
512
+ normalized_text = transform_name_to_key(name=text)
513
+ if normalized_text in memory_map: # Ensure correct key matching
514
+ memories_with_new_order.append(memory_map[normalized_text])
515
+ else:
516
+ logger.warning(
517
+ f"Memory text not found in memory map. text: {text};\n"
518
+ f"Keys of memory_map: {memory_map.keys()}"
519
+ )
520
+
521
+ return memories_with_new_order, success_flag
522
+
523
+ def filter_unrelated_memories(
524
+ self,
525
+ query_history: list[str],
526
+ memories: list[TextualMemoryItem],
527
+ ) -> (list[TextualMemoryItem], bool):
528
+ return self.memory_filter.filter_unrelated_memories(query_history, memories)
529
+
530
+ def filter_redundant_memories(
531
+ self,
532
+ query_history: list[str],
533
+ memories: list[TextualMemoryItem],
534
+ ) -> (list[TextualMemoryItem], bool):
535
+ return self.memory_filter.filter_redundant_memories(query_history, memories)
536
+
537
+ def filter_unrelated_and_redundant_memories(
538
+ self,
539
+ query_history: list[str],
540
+ memories: list[TextualMemoryItem],
541
+ ) -> (list[TextualMemoryItem], bool):
542
+ """
543
+ Filter out both unrelated and redundant memories using LLM analysis.
544
+
545
+ This method delegates to the MemoryFilter class.
546
+ """
547
+ return self.memory_filter.filter_unrelated_and_redundant_memories(query_history, memories)
File without changes