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,848 @@
1
+ import traceback
2
+
3
+ from memos.context.context import ContextThreadPoolExecutor
4
+ from memos.embedders.factory import OllamaEmbedder
5
+ from memos.graph_dbs.factory import Neo4jGraphDB
6
+ from memos.llms.factory import AzureLLM, OllamaLLM, OpenAILLM
7
+ from memos.log import get_logger
8
+ from memos.memories.textual.item import SearchedTreeNodeTextualMemoryMetadata, TextualMemoryItem
9
+ from memos.memories.textual.tree_text_memory.retrieve.bm25_util import EnhancedBM25
10
+ from memos.memories.textual.tree_text_memory.retrieve.retrieve_utils import (
11
+ FastTokenizer,
12
+ cosine_similarity_matrix,
13
+ detect_lang,
14
+ find_best_unrelated_subgroup,
15
+ parse_json_result,
16
+ )
17
+ from memos.reranker.base import BaseReranker
18
+ from memos.templates.mem_search_prompts import (
19
+ COT_PROMPT,
20
+ COT_PROMPT_ZH,
21
+ SIMPLE_COT_PROMPT,
22
+ SIMPLE_COT_PROMPT_ZH,
23
+ )
24
+ from memos.utils import timed
25
+
26
+ from .reasoner import MemoryReasoner
27
+ from .recall import GraphMemoryRetriever
28
+ from .task_goal_parser import TaskGoalParser
29
+
30
+
31
+ logger = get_logger(__name__)
32
+ COT_DICT = {
33
+ "fine": {"en": COT_PROMPT, "zh": COT_PROMPT_ZH},
34
+ "fast": {"en": SIMPLE_COT_PROMPT, "zh": SIMPLE_COT_PROMPT_ZH},
35
+ }
36
+
37
+
38
+ class Searcher:
39
+ def __init__(
40
+ self,
41
+ dispatcher_llm: OpenAILLM | OllamaLLM | AzureLLM,
42
+ graph_store: Neo4jGraphDB,
43
+ embedder: OllamaEmbedder,
44
+ reranker: BaseReranker,
45
+ bm25_retriever: EnhancedBM25 | None = None,
46
+ internet_retriever: None = None,
47
+ search_strategy: dict | None = None,
48
+ manual_close_internet: bool = True,
49
+ tokenizer: FastTokenizer | None = None,
50
+ include_embedding: bool = False,
51
+ ):
52
+ self.graph_store = graph_store
53
+ self.embedder = embedder
54
+ self.llm = dispatcher_llm
55
+
56
+ self.task_goal_parser = TaskGoalParser(dispatcher_llm)
57
+ self.graph_retriever = GraphMemoryRetriever(
58
+ graph_store, embedder, bm25_retriever, include_embedding=include_embedding
59
+ )
60
+ self.reranker = reranker
61
+ self.reasoner = MemoryReasoner(dispatcher_llm)
62
+
63
+ # Create internet retriever from config if provided
64
+ self.internet_retriever = internet_retriever
65
+ self.vec_cot = search_strategy.get("cot", False) if search_strategy else False
66
+ self.use_fast_graph = search_strategy.get("fast_graph", False) if search_strategy else False
67
+ self.manual_close_internet = manual_close_internet
68
+ self.tokenizer = tokenizer
69
+ self._usage_executor = ContextThreadPoolExecutor(max_workers=4, thread_name_prefix="usage")
70
+
71
+ @timed
72
+ def retrieve(
73
+ self,
74
+ query: str,
75
+ top_k: int,
76
+ info=None,
77
+ mode="fast",
78
+ memory_type="All",
79
+ search_filter: dict | None = None,
80
+ search_priority: dict | None = None,
81
+ user_name: str | None = None,
82
+ search_tool_memory: bool = False,
83
+ tool_mem_top_k: int = 6,
84
+ **kwargs,
85
+ ) -> list[tuple[TextualMemoryItem, float]]:
86
+ logger.info(
87
+ f"[RECALL] Start query='{query}', top_k={top_k}, mode={mode}, memory_type={memory_type}"
88
+ )
89
+ parsed_goal, query_embedding, context, query = self._parse_task(
90
+ query,
91
+ info,
92
+ mode,
93
+ search_filter=search_filter,
94
+ search_priority=search_priority,
95
+ user_name=user_name,
96
+ **kwargs,
97
+ )
98
+ results = self._retrieve_paths(
99
+ query,
100
+ parsed_goal,
101
+ query_embedding,
102
+ info,
103
+ top_k,
104
+ mode,
105
+ memory_type,
106
+ search_filter,
107
+ search_priority,
108
+ user_name,
109
+ search_tool_memory,
110
+ tool_mem_top_k,
111
+ )
112
+ return results
113
+
114
+ def post_retrieve(
115
+ self,
116
+ retrieved_results: list[tuple[TextualMemoryItem, float]],
117
+ top_k: int,
118
+ user_name: str | None = None,
119
+ info=None,
120
+ search_tool_memory: bool = False,
121
+ tool_mem_top_k: int = 6,
122
+ dedup: str | None = None,
123
+ plugin=False,
124
+ ):
125
+ if dedup == "no":
126
+ deduped = retrieved_results
127
+ else:
128
+ deduped = self._deduplicate_results(retrieved_results)
129
+ final_results = self._sort_and_trim(
130
+ deduped, top_k, plugin, search_tool_memory, tool_mem_top_k
131
+ )
132
+ self._update_usage_history(final_results, info, user_name)
133
+ return final_results
134
+
135
+ @timed
136
+ def search(
137
+ self,
138
+ query: str,
139
+ top_k: int = 10,
140
+ info=None,
141
+ mode="fast",
142
+ memory_type="All",
143
+ search_filter: dict | None = None,
144
+ search_priority: dict | None = None,
145
+ user_name: str | None = None,
146
+ search_tool_memory: bool = False,
147
+ tool_mem_top_k: int = 6,
148
+ dedup: str | None = None,
149
+ **kwargs,
150
+ ) -> list[TextualMemoryItem]:
151
+ """
152
+ Search for memories based on a query.
153
+ User query -> TaskGoalParser -> GraphMemoryRetriever ->
154
+ MemoryReranker -> MemoryReasoner -> Final output
155
+ Args:
156
+ query (str): The query to search for.
157
+ top_k (int): The number of top results to return.
158
+ info (dict): Leave a record of memory consumption.
159
+ mode (str, optional): The mode of the search.
160
+ - 'fast': Uses a faster search process, sacrificing some precision for speed.
161
+ - 'fine': Uses a more detailed search process, invoking large models for higher precision, but slower performance.
162
+ memory_type (str): Type restriction for search.
163
+ ['All', 'WorkingMemory', 'LongTermMemory', 'UserMemory']
164
+ search_filter (dict, optional): Optional metadata filters for search results.
165
+ search_priority (dict, optional): Optional metadata priority for search results.
166
+ Returns:
167
+ list[TextualMemoryItem]: List of matching memories.
168
+ """
169
+ if not info:
170
+ logger.warning(
171
+ "Please input 'info' when use tree.search so that "
172
+ "the database would store the consume history."
173
+ )
174
+ info = {"user_id": "", "session_id": ""}
175
+ else:
176
+ logger.debug(f"[SEARCH] Received info dict: {info}")
177
+
178
+ if kwargs.get("plugin", False):
179
+ logger.info(f"[SEARCH] Retrieve from plugin: {query}")
180
+ retrieved_results = self._retrieve_simple(
181
+ query=query, top_k=top_k, search_filter=search_filter, user_name=user_name
182
+ )
183
+ else:
184
+ retrieved_results = self.retrieve(
185
+ query=query,
186
+ top_k=top_k,
187
+ info=info,
188
+ mode=mode,
189
+ memory_type=memory_type,
190
+ search_filter=search_filter,
191
+ search_priority=search_priority,
192
+ user_name=user_name,
193
+ search_tool_memory=search_tool_memory,
194
+ tool_mem_top_k=tool_mem_top_k,
195
+ **kwargs,
196
+ )
197
+
198
+ full_recall = kwargs.get("full_recall", False)
199
+ if full_recall:
200
+ return retrieved_results
201
+
202
+ final_results = self.post_retrieve(
203
+ retrieved_results=retrieved_results,
204
+ top_k=top_k,
205
+ user_name=user_name,
206
+ info=None,
207
+ plugin=kwargs.get("plugin", False),
208
+ search_tool_memory=search_tool_memory,
209
+ tool_mem_top_k=tool_mem_top_k,
210
+ dedup=dedup,
211
+ )
212
+
213
+ logger.info(f"[SEARCH] Done. Total {len(final_results)} results.")
214
+ res_results = ""
215
+ for _num_i, result in enumerate(final_results):
216
+ res_results += "\n" + (
217
+ result.id + "|" + result.metadata.memory_type + "|" + result.memory
218
+ )
219
+ logger.info(f"[SEARCH] Results. {res_results}")
220
+ return final_results
221
+
222
+ @timed
223
+ def _parse_task(
224
+ self,
225
+ query,
226
+ info,
227
+ mode,
228
+ top_k=5,
229
+ search_filter: dict | None = None,
230
+ search_priority: dict | None = None,
231
+ user_name: str | None = None,
232
+ **kwargs,
233
+ ):
234
+ """Parse user query, do embedding search and create context"""
235
+ context = []
236
+ query_embedding = None
237
+
238
+ # fine mode will trigger initial embedding search
239
+ if mode == "fine_old":
240
+ logger.info("[SEARCH] Fine mode: embedding search")
241
+ query_embedding = self.embedder.embed([query])[0]
242
+
243
+ # retrieve related nodes by embedding
244
+ related_nodes = [
245
+ self.graph_store.get_node(n["id"])
246
+ for n in self.graph_store.search_by_embedding(
247
+ query_embedding,
248
+ top_k=top_k,
249
+ status="activated",
250
+ search_filter=search_priority,
251
+ filter=search_filter,
252
+ user_name=user_name,
253
+ )
254
+ ]
255
+ memories = []
256
+ for node in related_nodes:
257
+ try:
258
+ m = (
259
+ node.get("memory")
260
+ if isinstance(node, dict)
261
+ else (getattr(node, "memory", None))
262
+ )
263
+ if isinstance(m, str) and m:
264
+ memories.append(m)
265
+ except Exception:
266
+ logger.error(f"[SEARCH] Error during search: {traceback.format_exc()}")
267
+ continue
268
+ context = list(dict.fromkeys(memories))
269
+
270
+ # optional: supplement context with internet knowledge
271
+ """if self.internet_retriever:
272
+ extra = self.internet_retriever.retrieve_from_internet(query=query, top_k=3)
273
+ context.extend(item.memory.partition("\nContent: ")[-1] for item in extra)
274
+ """
275
+
276
+ # parse goal using LLM
277
+ parsed_goal = self.task_goal_parser.parse(
278
+ task_description=query,
279
+ context="\n".join(context),
280
+ conversation=info.get("chat_history", []),
281
+ mode=mode,
282
+ use_fast_graph=self.use_fast_graph,
283
+ **kwargs,
284
+ )
285
+
286
+ query = parsed_goal.rephrased_query or query
287
+ # if goal has extra memories, embed them too
288
+ if parsed_goal.memories:
289
+ query_embedding = self.embedder.embed(list({query, *parsed_goal.memories}))
290
+
291
+ return parsed_goal, query_embedding, context, query
292
+
293
+ @timed
294
+ def _retrieve_paths(
295
+ self,
296
+ query,
297
+ parsed_goal,
298
+ query_embedding,
299
+ info,
300
+ top_k,
301
+ mode,
302
+ memory_type,
303
+ search_filter: dict | None = None,
304
+ search_priority: dict | None = None,
305
+ user_name: str | None = None,
306
+ search_tool_memory: bool = False,
307
+ tool_mem_top_k: int = 6,
308
+ ):
309
+ """Run A/B/C retrieval paths in parallel"""
310
+ tasks = []
311
+ id_filter = {
312
+ "user_id": info.get("user_id", None),
313
+ "session_id": info.get("session_id", None),
314
+ }
315
+ id_filter = {k: v for k, v in id_filter.items() if v is not None}
316
+
317
+ with ContextThreadPoolExecutor(max_workers=3) as executor:
318
+ tasks.append(
319
+ executor.submit(
320
+ self._retrieve_from_working_memory,
321
+ query,
322
+ parsed_goal,
323
+ query_embedding,
324
+ top_k,
325
+ memory_type,
326
+ search_filter,
327
+ search_priority,
328
+ user_name,
329
+ id_filter,
330
+ )
331
+ )
332
+ tasks.append(
333
+ executor.submit(
334
+ self._retrieve_from_long_term_and_user,
335
+ query,
336
+ parsed_goal,
337
+ query_embedding,
338
+ top_k,
339
+ memory_type,
340
+ search_filter,
341
+ search_priority,
342
+ user_name,
343
+ id_filter,
344
+ mode=mode,
345
+ )
346
+ )
347
+ tasks.append(
348
+ executor.submit(
349
+ self._retrieve_from_internet,
350
+ query,
351
+ parsed_goal,
352
+ query_embedding,
353
+ top_k,
354
+ info,
355
+ mode,
356
+ memory_type,
357
+ user_name,
358
+ )
359
+ )
360
+ if search_tool_memory:
361
+ tasks.append(
362
+ executor.submit(
363
+ self._retrieve_from_tool_memory,
364
+ query,
365
+ parsed_goal,
366
+ query_embedding,
367
+ tool_mem_top_k,
368
+ memory_type,
369
+ search_filter,
370
+ search_priority,
371
+ user_name,
372
+ id_filter,
373
+ mode=mode,
374
+ )
375
+ )
376
+ results = []
377
+ for t in tasks:
378
+ results.extend(t.result())
379
+
380
+ logger.info(f"[SEARCH] Total raw results: {len(results)}")
381
+ return results
382
+
383
+ # --- Path A
384
+ @timed
385
+ def _retrieve_from_working_memory(
386
+ self,
387
+ query,
388
+ parsed_goal,
389
+ query_embedding,
390
+ top_k,
391
+ memory_type,
392
+ search_filter: dict | None = None,
393
+ search_priority: dict | None = None,
394
+ user_name: str | None = None,
395
+ id_filter: dict | None = None,
396
+ ):
397
+ """Retrieve and rerank from WorkingMemory"""
398
+ if memory_type not in ["All", "WorkingMemory"]:
399
+ logger.info(f"[PATH-A] '{query}'Skipped (memory_type does not match)")
400
+ return []
401
+ items = self.graph_retriever.retrieve(
402
+ query=query,
403
+ parsed_goal=parsed_goal,
404
+ top_k=top_k,
405
+ memory_scope="WorkingMemory",
406
+ search_filter=search_filter,
407
+ search_priority=search_priority,
408
+ user_name=user_name,
409
+ id_filter=id_filter,
410
+ use_fast_graph=self.use_fast_graph,
411
+ )
412
+ return self.reranker.rerank(
413
+ query=query,
414
+ query_embedding=query_embedding[0],
415
+ graph_results=items,
416
+ top_k=top_k,
417
+ parsed_goal=parsed_goal,
418
+ search_filter=search_filter,
419
+ )
420
+
421
+ # --- Path B
422
+ @timed
423
+ def _retrieve_from_long_term_and_user(
424
+ self,
425
+ query,
426
+ parsed_goal,
427
+ query_embedding,
428
+ top_k,
429
+ memory_type,
430
+ search_filter: dict | None = None,
431
+ search_priority: dict | None = None,
432
+ user_name: str | None = None,
433
+ id_filter: dict | None = None,
434
+ mode: str = "fast",
435
+ ):
436
+ """Retrieve and rerank from LongTermMemory and UserMemory"""
437
+ results = []
438
+ tasks = []
439
+
440
+ # chain of thinking
441
+ cot_embeddings = []
442
+ if self.vec_cot:
443
+ queries = self._cot_query(query, mode=mode, context=parsed_goal.context)
444
+ if len(queries) > 1:
445
+ cot_embeddings = self.embedder.embed(queries)
446
+ cot_embeddings.extend(query_embedding)
447
+ else:
448
+ cot_embeddings = query_embedding
449
+
450
+ with ContextThreadPoolExecutor(max_workers=2) as executor:
451
+ if memory_type in ["All", "LongTermMemory"]:
452
+ tasks.append(
453
+ executor.submit(
454
+ self.graph_retriever.retrieve,
455
+ query=query,
456
+ parsed_goal=parsed_goal,
457
+ query_embedding=cot_embeddings,
458
+ top_k=top_k * 2,
459
+ memory_scope="LongTermMemory",
460
+ search_filter=search_filter,
461
+ search_priority=search_priority,
462
+ user_name=user_name,
463
+ id_filter=id_filter,
464
+ use_fast_graph=self.use_fast_graph,
465
+ )
466
+ )
467
+ if memory_type in ["All", "UserMemory"]:
468
+ tasks.append(
469
+ executor.submit(
470
+ self.graph_retriever.retrieve,
471
+ query=query,
472
+ parsed_goal=parsed_goal,
473
+ query_embedding=cot_embeddings,
474
+ top_k=top_k * 2,
475
+ memory_scope="UserMemory",
476
+ search_filter=search_filter,
477
+ search_priority=search_priority,
478
+ user_name=user_name,
479
+ id_filter=id_filter,
480
+ use_fast_graph=self.use_fast_graph,
481
+ )
482
+ )
483
+
484
+ # Collect results from all tasks
485
+ for task in tasks:
486
+ results.extend(task.result())
487
+
488
+ return self.reranker.rerank(
489
+ query=query,
490
+ query_embedding=query_embedding[0],
491
+ graph_results=results,
492
+ top_k=top_k,
493
+ parsed_goal=parsed_goal,
494
+ search_filter=search_filter,
495
+ )
496
+
497
+ @timed
498
+ def _retrieve_from_memcubes(
499
+ self, query, parsed_goal, query_embedding, top_k, cube_name="memos_cube01"
500
+ ):
501
+ """Retrieve and rerank from LongTermMemory and UserMemory"""
502
+ results = self.graph_retriever.retrieve_from_cube(
503
+ query_embedding=query_embedding,
504
+ top_k=top_k * 2,
505
+ memory_scope="LongTermMemory",
506
+ cube_name=cube_name,
507
+ user_name=cube_name,
508
+ )
509
+ return self.reranker.rerank(
510
+ query=query,
511
+ query_embedding=query_embedding[0],
512
+ graph_results=results,
513
+ top_k=top_k,
514
+ parsed_goal=parsed_goal,
515
+ )
516
+
517
+ # --- Path C
518
+ @timed
519
+ def _retrieve_from_internet(
520
+ self,
521
+ query,
522
+ parsed_goal,
523
+ query_embedding,
524
+ top_k,
525
+ info,
526
+ mode,
527
+ memory_type,
528
+ user_id: str | None = None,
529
+ ):
530
+ """Retrieve and rerank from Internet source"""
531
+ if not self.internet_retriever:
532
+ logger.info(f"[PATH-C] '{query}' Skipped (no retriever)")
533
+ return []
534
+ if self.manual_close_internet and not parsed_goal.internet_search:
535
+ logger.info(f"[PATH-C] '{query}' Skipped (no retriever, fast mode)")
536
+ return []
537
+ if memory_type not in ["All", "OuterMemory"]:
538
+ logger.info(f"[PATH-C] '{query}' Skipped (memory_type does not match)")
539
+ return []
540
+ logger.info(f"[PATH-C] '{query}' Retrieving from internet...")
541
+ items = self.internet_retriever.retrieve_from_internet(
542
+ query=query, top_k=2 * top_k, parsed_goal=parsed_goal, info=info, mode=mode
543
+ )
544
+ logger.info(f"[PATH-C] '{query}' Retrieved from internet {len(items)} items: {items}")
545
+ return self.reranker.rerank(
546
+ query=query,
547
+ query_embedding=query_embedding[0],
548
+ graph_results=items,
549
+ top_k=top_k,
550
+ parsed_goal=parsed_goal,
551
+ )
552
+
553
+ # --- Path D
554
+ @timed
555
+ def _retrieve_from_tool_memory(
556
+ self,
557
+ query,
558
+ parsed_goal,
559
+ query_embedding,
560
+ top_k,
561
+ memory_type,
562
+ search_filter: dict | None = None,
563
+ search_priority: dict | None = None,
564
+ user_name: str | None = None,
565
+ id_filter: dict | None = None,
566
+ mode: str = "fast",
567
+ ):
568
+ """Retrieve and rerank from ToolMemory"""
569
+ results = {
570
+ "ToolSchemaMemory": [],
571
+ "ToolTrajectoryMemory": [],
572
+ }
573
+ tasks = []
574
+
575
+ # chain of thinking
576
+ cot_embeddings = []
577
+ if self.vec_cot:
578
+ queries = self._cot_query(query, mode=mode, context=parsed_goal.context)
579
+ if len(queries) > 1:
580
+ cot_embeddings = self.embedder.embed(queries)
581
+ cot_embeddings.extend(query_embedding)
582
+ else:
583
+ cot_embeddings = query_embedding
584
+
585
+ with ContextThreadPoolExecutor(max_workers=2) as executor:
586
+ if memory_type in ["All", "ToolSchemaMemory"]:
587
+ tasks.append(
588
+ executor.submit(
589
+ self.graph_retriever.retrieve,
590
+ query=query,
591
+ parsed_goal=parsed_goal,
592
+ query_embedding=cot_embeddings,
593
+ top_k=top_k * 2,
594
+ memory_scope="ToolSchemaMemory",
595
+ search_filter=search_filter,
596
+ search_priority=search_priority,
597
+ user_name=user_name,
598
+ id_filter=id_filter,
599
+ use_fast_graph=self.use_fast_graph,
600
+ )
601
+ )
602
+ if memory_type in ["All", "ToolTrajectoryMemory"]:
603
+ tasks.append(
604
+ executor.submit(
605
+ self.graph_retriever.retrieve,
606
+ query=query,
607
+ parsed_goal=parsed_goal,
608
+ query_embedding=cot_embeddings,
609
+ top_k=top_k * 2,
610
+ memory_scope="ToolTrajectoryMemory",
611
+ search_filter=search_filter,
612
+ search_priority=search_priority,
613
+ user_name=user_name,
614
+ id_filter=id_filter,
615
+ use_fast_graph=self.use_fast_graph,
616
+ )
617
+ )
618
+
619
+ # Collect results from all tasks
620
+ for task in tasks:
621
+ rsp = task.result()
622
+ if rsp and rsp[0].metadata.memory_type == "ToolSchemaMemory":
623
+ results["ToolSchemaMemory"].extend(rsp)
624
+ elif rsp and rsp[0].metadata.memory_type == "ToolTrajectoryMemory":
625
+ results["ToolTrajectoryMemory"].extend(rsp)
626
+
627
+ schema_reranked = self.reranker.rerank(
628
+ query=query,
629
+ query_embedding=query_embedding[0],
630
+ graph_results=results["ToolSchemaMemory"],
631
+ top_k=top_k,
632
+ parsed_goal=parsed_goal,
633
+ search_filter=search_filter,
634
+ )
635
+ trajectory_reranked = self.reranker.rerank(
636
+ query=query,
637
+ query_embedding=query_embedding[0],
638
+ graph_results=results["ToolTrajectoryMemory"],
639
+ top_k=top_k,
640
+ parsed_goal=parsed_goal,
641
+ search_filter=search_filter,
642
+ )
643
+ return schema_reranked + trajectory_reranked
644
+
645
+ @timed
646
+ def _retrieve_simple(
647
+ self,
648
+ query: str,
649
+ top_k: int,
650
+ search_filter: dict | None = None,
651
+ user_name: str | None = None,
652
+ **kwargs,
653
+ ):
654
+ """
655
+ Retrieve from by keywords and embedding, this func is hotfix for sources=plugin mode
656
+ will merge with fulltext retrieval in the future
657
+ """
658
+ query_words = []
659
+ if self.tokenizer:
660
+ query_words = self.tokenizer.tokenize_mixed(query)
661
+ else:
662
+ query_words = query.strip().split()
663
+ query_words = list(set(query_words))[: top_k * 3]
664
+ query_words = [query, *query_words]
665
+ logger.info(f"[SIMPLESEARCH] Query words: {query_words}")
666
+ query_embeddings = self.embedder.embed(query_words)
667
+
668
+ items = self.graph_retriever.retrieve_from_mixed(
669
+ top_k=top_k * 2,
670
+ memory_scope=None,
671
+ query_embedding=query_embeddings,
672
+ search_filter=search_filter,
673
+ user_name=user_name,
674
+ )
675
+ logger.info(f"[SIMPLESEARCH] Items count: {len(items)}")
676
+ documents = [getattr(item, "memory", "") for item in items]
677
+ if not documents:
678
+ return []
679
+ documents_embeddings = self.embedder.embed(documents)
680
+ if not documents_embeddings:
681
+ logger.info("[SIMPLESEARCH] Documents embeddings is empty")
682
+ return []
683
+ similarity_matrix = cosine_similarity_matrix(documents_embeddings)
684
+ selected_indices, _ = find_best_unrelated_subgroup(documents, similarity_matrix)
685
+ selected_items = [items[i] for i in selected_indices]
686
+ logger.info(
687
+ f"[SIMPLESEARCH] after unrelated subgroup selection items count: {len(selected_items)}"
688
+ )
689
+ return self.reranker.rerank(
690
+ query=query,
691
+ query_embedding=query_embeddings[0],
692
+ graph_results=selected_items,
693
+ top_k=top_k,
694
+ )
695
+
696
+ @timed
697
+ def _deduplicate_results(self, results):
698
+ """Deduplicate results by memory text"""
699
+ deduped = {}
700
+ for item, score in results:
701
+ if item.memory not in deduped or score > deduped[item.memory][1]:
702
+ deduped[item.memory] = (item, score)
703
+ return list(deduped.values())
704
+
705
+ @timed
706
+ def _sort_and_trim(
707
+ self, results, top_k, plugin=False, search_tool_memory=False, tool_mem_top_k=6
708
+ ):
709
+ """Sort results by score and trim to top_k"""
710
+ final_items = []
711
+ if search_tool_memory:
712
+ tool_schema_results = [
713
+ (item, score)
714
+ for item, score in results
715
+ if item.metadata.memory_type == "ToolSchemaMemory"
716
+ ]
717
+ sorted_tool_schema_results = sorted(
718
+ tool_schema_results, key=lambda pair: pair[1], reverse=True
719
+ )[:tool_mem_top_k]
720
+ for item, score in sorted_tool_schema_results:
721
+ if plugin and round(score, 2) == 0.00:
722
+ continue
723
+ meta_data = item.metadata.model_dump()
724
+ meta_data["relativity"] = score
725
+ final_items.append(
726
+ TextualMemoryItem(
727
+ id=item.id,
728
+ memory=item.memory,
729
+ metadata=SearchedTreeNodeTextualMemoryMetadata(**meta_data),
730
+ )
731
+ )
732
+ tool_trajectory_results = [
733
+ (item, score)
734
+ for item, score in results
735
+ if item.metadata.memory_type == "ToolTrajectoryMemory"
736
+ ]
737
+ sorted_tool_trajectory_results = sorted(
738
+ tool_trajectory_results, key=lambda pair: pair[1], reverse=True
739
+ )[:tool_mem_top_k]
740
+ for item, score in sorted_tool_trajectory_results:
741
+ if plugin and round(score, 2) == 0.00:
742
+ continue
743
+ meta_data = item.metadata.model_dump()
744
+ meta_data["relativity"] = score
745
+ final_items.append(
746
+ TextualMemoryItem(
747
+ id=item.id,
748
+ memory=item.memory,
749
+ metadata=SearchedTreeNodeTextualMemoryMetadata(**meta_data),
750
+ )
751
+ )
752
+ # separate textual results
753
+ results = [
754
+ (item, score)
755
+ for item, score in results
756
+ if item.metadata.memory_type not in ["ToolSchemaMemory", "ToolTrajectoryMemory"]
757
+ ]
758
+
759
+ sorted_results = sorted(results, key=lambda pair: pair[1], reverse=True)[:top_k]
760
+
761
+ for item, score in sorted_results:
762
+ if plugin and round(score, 2) == 0.00:
763
+ continue
764
+ meta_data = item.metadata.model_dump()
765
+ meta_data["relativity"] = score
766
+ final_items.append(
767
+ TextualMemoryItem(
768
+ id=item.id,
769
+ memory=item.memory,
770
+ metadata=SearchedTreeNodeTextualMemoryMetadata(**meta_data),
771
+ )
772
+ )
773
+ return final_items
774
+
775
+ @timed
776
+ def _update_usage_history(self, items, info, user_name: str | None = None):
777
+ """Update usage history in graph DB
778
+ now_time = datetime.now().isoformat()
779
+ info_copy = dict(info or {})
780
+ info_copy.pop("chat_history", None)
781
+ usage_record = json.dumps({"time": now_time, "info": info_copy})
782
+ payload = []
783
+ for it in items:
784
+ try:
785
+ item_id = getattr(it, "id", None)
786
+ md = getattr(it, "metadata", None)
787
+ if md is None:
788
+ continue
789
+ if not hasattr(md, "usage") or md.usage is None:
790
+ md.usage = []
791
+ md.usage.append(usage_record)
792
+ if item_id:
793
+ payload.append((item_id, list(md.usage)))
794
+ except Exception:
795
+ logger.exception("[USAGE] snapshot item failed")
796
+
797
+ if payload:
798
+ self._usage_executor.submit(
799
+ self._update_usage_history_worker, payload, usage_record, user_name
800
+ )
801
+ """
802
+
803
+ def _update_usage_history_worker(
804
+ self, payload, usage_record: str, user_name: str | None = None
805
+ ):
806
+ try:
807
+ for item_id, usage_list in payload:
808
+ self.graph_store.update_node(item_id, {"usage": usage_list}, user_name=user_name)
809
+ except Exception:
810
+ logger.exception("[USAGE] update usage failed")
811
+
812
+ def _cot_query(
813
+ self,
814
+ query,
815
+ mode="fast",
816
+ split_num: int = 3,
817
+ context: list[str] | None = None,
818
+ ) -> list[str]:
819
+ """Generate chain-of-thought queries"""
820
+
821
+ lang = detect_lang(query)
822
+ if mode == "fine" and context:
823
+ template = COT_DICT["fine"][lang]
824
+ prompt = (
825
+ template.replace("${original_query}", query)
826
+ .replace("${split_num_threshold}", str(split_num))
827
+ .replace("${context}", "\n".join(context))
828
+ )
829
+ else:
830
+ template = COT_DICT["fast"][lang]
831
+ prompt = template.replace("${original_query}", query).replace(
832
+ "${split_num_threshold}", str(split_num)
833
+ )
834
+
835
+ messages = [{"role": "user", "content": prompt}]
836
+ try:
837
+ response_text = self.llm.generate(messages, temperature=0, top_p=1)
838
+ response_json = parse_json_result(response_text)
839
+ assert "is_complex" in response_json
840
+ if not response_json["is_complex"]:
841
+ return [query]
842
+ else:
843
+ assert "sub_questions" in response_json
844
+ logger.info("Query: {} COT: {}".format(query, response_json["sub_questions"]))
845
+ return response_json["sub_questions"][:split_num]
846
+ except Exception as e:
847
+ logger.error(f"[LLM] Exception during chat generation: {e}")
848
+ return [query]