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,106 @@
1
+ from typing import Any, ClassVar
2
+
3
+ from pydantic import Field, field_validator, model_validator
4
+
5
+ from memos.configs.base import BaseConfig
6
+
7
+
8
+ class BaseAdderConfig(BaseConfig):
9
+ """Base configuration class for Adder."""
10
+
11
+
12
+ class NaiveAdderConfig(BaseAdderConfig):
13
+ """Configuration for Naive Adder."""
14
+
15
+ # No additional config needed since components are passed from parent
16
+
17
+
18
+ class AdderConfigFactory(BaseConfig):
19
+ """Factory class for creating Adder configurations."""
20
+
21
+ backend: str = Field(..., description="Backend for Adder")
22
+ config: dict[str, Any] = Field(..., description="Configuration for the Adder backend")
23
+
24
+ backend_to_class: ClassVar[dict[str, Any]] = {
25
+ "naive": NaiveAdderConfig,
26
+ }
27
+
28
+ @field_validator("backend")
29
+ @classmethod
30
+ def validate_backend(cls, backend: str) -> str:
31
+ """Validate the backend field."""
32
+ if backend not in cls.backend_to_class:
33
+ raise ValueError(f"Invalid backend: {backend}")
34
+ return backend
35
+
36
+ @model_validator(mode="after")
37
+ def create_config(self) -> "AdderConfigFactory":
38
+ config_class = self.backend_to_class[self.backend]
39
+ self.config = config_class(**self.config)
40
+ return self
41
+
42
+
43
+ class BaseExtractorConfig(BaseConfig):
44
+ """Base configuration class for Extractor."""
45
+
46
+
47
+ class NaiveExtractorConfig(BaseExtractorConfig):
48
+ """Configuration for Naive Extractor."""
49
+
50
+
51
+ class ExtractorConfigFactory(BaseConfig):
52
+ """Factory class for creating Extractor configurations."""
53
+
54
+ backend: str = Field(..., description="Backend for Extractor")
55
+ config: dict[str, Any] = Field(..., description="Configuration for the Extractor backend")
56
+
57
+ backend_to_class: ClassVar[dict[str, Any]] = {
58
+ "naive": NaiveExtractorConfig,
59
+ }
60
+
61
+ @field_validator("backend")
62
+ @classmethod
63
+ def validate_backend(cls, backend: str) -> str:
64
+ """Validate the backend field."""
65
+ if backend not in cls.backend_to_class:
66
+ raise ValueError(f"Invalid backend: {backend}")
67
+ return backend
68
+
69
+ @model_validator(mode="after")
70
+ def create_config(self) -> "ExtractorConfigFactory":
71
+ config_class = self.backend_to_class[self.backend]
72
+ self.config = config_class(**self.config)
73
+ return self
74
+
75
+
76
+ class BaseRetrieverConfig(BaseConfig):
77
+ """Base configuration class for Retrievers."""
78
+
79
+
80
+ class NaiveRetrieverConfig(BaseRetrieverConfig):
81
+ """Configuration for Naive Retriever."""
82
+
83
+
84
+ class RetrieverConfigFactory(BaseConfig):
85
+ """Factory class for creating Retriever configurations."""
86
+
87
+ backend: str = Field(..., description="Backend for Retriever")
88
+ config: dict[str, Any] = Field(..., description="Configuration for the Retriever backend")
89
+
90
+ backend_to_class: ClassVar[dict[str, Any]] = {
91
+ "naive": NaiveRetrieverConfig,
92
+ }
93
+
94
+ @field_validator("backend")
95
+ @classmethod
96
+ def validate_backend(cls, backend: str) -> str:
97
+ """Validate the backend field."""
98
+ if backend not in cls.backend_to_class:
99
+ raise ValueError(f"Invalid backend: {backend}")
100
+ return backend
101
+
102
+ @model_validator(mode="after")
103
+ def create_config(self) -> "RetrieverConfigFactory":
104
+ config_class = self.backend_to_class[self.backend]
105
+ self.config = config_class(**self.config)
106
+ return self
@@ -0,0 +1,221 @@
1
+ import json
2
+ import uuid
3
+
4
+ from abc import ABC, abstractmethod
5
+ from concurrent.futures import as_completed
6
+ from datetime import datetime
7
+ from typing import Any
8
+
9
+ from memos.context.context import ContextThreadPoolExecutor
10
+ from memos.log import get_logger
11
+ from memos.mem_reader.read_multi_modal import detect_lang
12
+ from memos.memories.textual.item import (
13
+ PreferenceTextualMemoryMetadata,
14
+ TextualMemoryItem,
15
+ list_all_fields,
16
+ )
17
+ from memos.memories.textual.prefer_text_memory.spliter import Splitter
18
+ from memos.memories.textual.prefer_text_memory.utils import convert_messages_to_string
19
+ from memos.templates.prefer_complete_prompt import (
20
+ NAIVE_EXPLICIT_PREFERENCE_EXTRACT_PROMPT,
21
+ NAIVE_EXPLICIT_PREFERENCE_EXTRACT_PROMPT_ZH,
22
+ NAIVE_IMPLICIT_PREFERENCE_EXTRACT_PROMPT,
23
+ NAIVE_IMPLICIT_PREFERENCE_EXTRACT_PROMPT_ZH,
24
+ )
25
+ from memos.types import MessageList
26
+
27
+
28
+ logger = get_logger(__name__)
29
+
30
+
31
+ class BaseExtractor(ABC):
32
+ """Abstract base class for extractors."""
33
+
34
+ @abstractmethod
35
+ def __init__(self, llm_provider=None, embedder=None, vector_db=None):
36
+ """Initialize the extractor."""
37
+
38
+
39
+ class NaiveExtractor(BaseExtractor):
40
+ """Extractor."""
41
+
42
+ def __init__(self, llm_provider=None, embedder=None, vector_db=None):
43
+ """Initialize the extractor."""
44
+ super().__init__(llm_provider, embedder, vector_db)
45
+ self.llm_provider = llm_provider
46
+ self.embedder = embedder
47
+ self.vector_db = vector_db
48
+ self.splitter = Splitter()
49
+
50
+ def extract_basic_info(self, qa_pair: MessageList) -> dict[str, Any]:
51
+ """Extract basic information from a QA pair (no LLM needed)."""
52
+ basic_info = {
53
+ "dialog_id": str(uuid.uuid4()),
54
+ "original_text": convert_messages_to_string(qa_pair),
55
+ "created_at": datetime.now().isoformat(),
56
+ }
57
+
58
+ return basic_info
59
+
60
+ def extract_explicit_preference(self, qa_pair: MessageList | str) -> dict[str, Any] | None:
61
+ """Extract explicit preference from a QA pair."""
62
+ qa_pair_str = convert_messages_to_string(qa_pair) if isinstance(qa_pair, list) else qa_pair
63
+ lang = detect_lang(qa_pair_str)
64
+ _map = {
65
+ "zh": NAIVE_EXPLICIT_PREFERENCE_EXTRACT_PROMPT_ZH,
66
+ "en": NAIVE_EXPLICIT_PREFERENCE_EXTRACT_PROMPT,
67
+ }
68
+ prompt = _map[lang].replace("{qa_pair}", qa_pair_str)
69
+
70
+ try:
71
+ response = self.llm_provider.generate([{"role": "user", "content": prompt}])
72
+ if not response:
73
+ logger.info(
74
+ f"[prefer_extractor]: (Error) LLM response content is {response} when extracting explicit preference"
75
+ )
76
+ return None
77
+ response = response.strip().replace("```json", "").replace("```", "").strip()
78
+ result = json.loads(response)
79
+ for d in result:
80
+ d["preference"] = d.pop("explicit_preference")
81
+ return result
82
+ except Exception as e:
83
+ logger.info(f"Error extracting explicit preference: {e}, return None")
84
+ return None
85
+
86
+ def extract_implicit_preference(self, qa_pair: MessageList | str) -> dict[str, Any] | None:
87
+ """Extract implicit preferences from cluster qa pairs."""
88
+ if not qa_pair:
89
+ return None
90
+ qa_pair_str = convert_messages_to_string(qa_pair) if isinstance(qa_pair, list) else qa_pair
91
+ lang = detect_lang(qa_pair_str)
92
+ _map = {
93
+ "zh": NAIVE_IMPLICIT_PREFERENCE_EXTRACT_PROMPT_ZH,
94
+ "en": NAIVE_IMPLICIT_PREFERENCE_EXTRACT_PROMPT,
95
+ }
96
+ prompt = _map[lang].replace("{qa_pair}", qa_pair_str)
97
+
98
+ try:
99
+ response = self.llm_provider.generate([{"role": "user", "content": prompt}])
100
+ if not response:
101
+ logger.info(
102
+ f"[prefer_extractor]: (Error) LLM response content is {response} when extracting implicit preference"
103
+ )
104
+ return None
105
+ response = response.strip().replace("```json", "").replace("```", "").strip()
106
+ result = json.loads(response)
107
+ for d in result:
108
+ d["preference"] = d.pop("implicit_preference")
109
+ return result
110
+ except Exception as e:
111
+ logger.info(f"Error extracting implicit preferences: {e}, return None")
112
+ return None
113
+
114
+ def _process_single_chunk_explicit(
115
+ self, chunk: MessageList, msg_type: str, info: dict[str, Any]
116
+ ) -> TextualMemoryItem | None:
117
+ """Process a single chunk and return a TextualMemoryItem."""
118
+ basic_info = self.extract_basic_info(chunk)
119
+ if not basic_info["original_text"]:
120
+ return None
121
+
122
+ explicit_pref = self.extract_explicit_preference(basic_info["original_text"])
123
+ if not explicit_pref:
124
+ return None
125
+
126
+ memories = []
127
+ for pref in explicit_pref:
128
+ vector_info = {
129
+ "embedding": self.embedder.embed([pref["context_summary"]])[0],
130
+ }
131
+ user_info = {k: v for k, v in info.items() if k not in list_all_fields()}
132
+ extract_info = {**basic_info, **pref, **vector_info, **info, "info": user_info}
133
+
134
+ metadata = PreferenceTextualMemoryMetadata(
135
+ type=msg_type, preference_type="explicit_preference", **extract_info
136
+ )
137
+ memory = TextualMemoryItem(
138
+ id=str(uuid.uuid4()), memory=pref["context_summary"], metadata=metadata
139
+ )
140
+
141
+ memories.append(memory)
142
+
143
+ return memories
144
+
145
+ def _process_single_chunk_implicit(
146
+ self, chunk: MessageList, msg_type: str, info: dict[str, Any]
147
+ ) -> TextualMemoryItem | None:
148
+ basic_info = self.extract_basic_info(chunk)
149
+ if not basic_info["original_text"]:
150
+ return None
151
+ implicit_pref = self.extract_implicit_preference(basic_info["original_text"])
152
+ if not implicit_pref:
153
+ return None
154
+
155
+ memories = []
156
+ for pref in implicit_pref:
157
+ vector_info = {
158
+ "embedding": self.embedder.embed([pref["context_summary"]])[0],
159
+ }
160
+ user_info = {k: v for k, v in info.items() if k not in list_all_fields()}
161
+ extract_info = {**basic_info, **pref, **vector_info, **info, "info": user_info}
162
+
163
+ metadata = PreferenceTextualMemoryMetadata(
164
+ type=msg_type, preference_type="implicit_preference", **extract_info
165
+ )
166
+ memory = TextualMemoryItem(
167
+ id=str(uuid.uuid4()), memory=pref["context_summary"], metadata=metadata
168
+ )
169
+
170
+ memories.append(memory)
171
+
172
+ return memories
173
+
174
+ def extract(
175
+ self,
176
+ messages: list[MessageList],
177
+ msg_type: str,
178
+ info: dict[str, Any],
179
+ max_workers: int = 10,
180
+ ) -> list[TextualMemoryItem]:
181
+ """Extract preference memories based on the messages using thread pool for acceleration."""
182
+ chunks: list[MessageList] = []
183
+ for message in messages:
184
+ chunk = self.splitter.split_chunks(message, split_type="overlap")
185
+ chunks.extend(chunk)
186
+ if not chunks:
187
+ return []
188
+
189
+ memories = []
190
+ with ContextThreadPoolExecutor(max_workers=min(max_workers, len(chunks))) as executor:
191
+ futures = {
192
+ executor.submit(self._process_single_chunk_explicit, chunk, msg_type, info): (
193
+ "explicit",
194
+ chunk,
195
+ )
196
+ for chunk in chunks
197
+ }
198
+ futures.update(
199
+ {
200
+ executor.submit(self._process_single_chunk_implicit, chunk, msg_type, info): (
201
+ "implicit",
202
+ chunk,
203
+ )
204
+ for chunk in chunks
205
+ }
206
+ )
207
+
208
+ for future in as_completed(futures):
209
+ try:
210
+ memory = future.result()
211
+ if memory:
212
+ if isinstance(memory, list):
213
+ memories.extend(memory)
214
+ else:
215
+ memories.append(memory)
216
+ except Exception as e:
217
+ task_type, chunk = futures[future]
218
+ logger.error(f"Error processing {task_type} chunk: {chunk}\n{e}")
219
+ continue
220
+
221
+ return memories
@@ -0,0 +1,85 @@
1
+ from typing import Any, ClassVar
2
+
3
+ from memos.memories.textual.prefer_text_memory.adder import BaseAdder, NaiveAdder
4
+ from memos.memories.textual.prefer_text_memory.config import (
5
+ AdderConfigFactory,
6
+ ExtractorConfigFactory,
7
+ RetrieverConfigFactory,
8
+ )
9
+ from memos.memories.textual.prefer_text_memory.extractor import BaseExtractor, NaiveExtractor
10
+ from memos.memories.textual.prefer_text_memory.retrievers import BaseRetriever, NaiveRetriever
11
+
12
+
13
+ class AdderFactory(BaseAdder):
14
+ """Factory class for creating Adder instances."""
15
+
16
+ backend_to_class: ClassVar[dict[str, Any]] = {
17
+ "naive": NaiveAdder,
18
+ }
19
+
20
+ @classmethod
21
+ def from_config(
22
+ cls,
23
+ config_factory: AdderConfigFactory,
24
+ llm_provider=None,
25
+ embedder=None,
26
+ vector_db=None,
27
+ text_mem=None,
28
+ ) -> BaseAdder:
29
+ """Create a Adder instance from a configuration factory."""
30
+ backend = config_factory.backend
31
+ if backend not in cls.backend_to_class:
32
+ raise ValueError(f"Invalid backend: {backend}")
33
+ adder_class = cls.backend_to_class[backend]
34
+ return adder_class(
35
+ llm_provider=llm_provider, embedder=embedder, vector_db=vector_db, text_mem=text_mem
36
+ )
37
+
38
+
39
+ class ExtractorFactory(BaseExtractor):
40
+ """Factory class for creating Extractor instances."""
41
+
42
+ backend_to_class: ClassVar[dict[str, Any]] = {
43
+ "naive": NaiveExtractor,
44
+ }
45
+
46
+ @classmethod
47
+ def from_config(
48
+ cls,
49
+ config_factory: ExtractorConfigFactory,
50
+ llm_provider=None,
51
+ embedder=None,
52
+ vector_db=None,
53
+ ) -> BaseExtractor:
54
+ """Create a Extractor instance from a configuration factory."""
55
+ backend = config_factory.backend
56
+ if backend not in cls.backend_to_class:
57
+ raise ValueError(f"Invalid backend: {backend}")
58
+ extractor_class = cls.backend_to_class[backend]
59
+ return extractor_class(llm_provider=llm_provider, embedder=embedder, vector_db=vector_db)
60
+
61
+
62
+ class RetrieverFactory(BaseRetriever):
63
+ """Factory class for creating Retriever instances."""
64
+
65
+ backend_to_class: ClassVar[dict[str, Any]] = {
66
+ "naive": NaiveRetriever,
67
+ }
68
+
69
+ @classmethod
70
+ def from_config(
71
+ cls,
72
+ config_factory: RetrieverConfigFactory,
73
+ llm_provider=None,
74
+ embedder=None,
75
+ reranker=None,
76
+ vector_db=None,
77
+ ) -> BaseRetriever:
78
+ """Create a Retriever instance from a configuration factory."""
79
+ backend = config_factory.backend
80
+ if backend not in cls.backend_to_class:
81
+ raise ValueError(f"Invalid backend: {backend}")
82
+ retriever_class = cls.backend_to_class[backend]
83
+ return retriever_class(
84
+ llm_provider=llm_provider, embedder=embedder, reranker=reranker, vector_db=vector_db
85
+ )
@@ -0,0 +1,177 @@
1
+ import os
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import Any
5
+
6
+ from memos.context.context import ContextThreadPoolExecutor
7
+ from memos.memories.textual.item import PreferenceTextualMemoryMetadata, TextualMemoryItem
8
+ from memos.vec_dbs.item import MilvusVecDBItem
9
+
10
+
11
+ class BaseRetriever(ABC):
12
+ """Abstract base class for retrievers."""
13
+
14
+ @abstractmethod
15
+ def __init__(self, llm_provider=None, embedder=None, reranker=None, vector_db=None):
16
+ """Initialize the retriever."""
17
+
18
+ @abstractmethod
19
+ def retrieve(
20
+ self,
21
+ query: str,
22
+ top_k: int,
23
+ info: dict[str, Any] | None = None,
24
+ search_filter: dict[str, Any] | None = None,
25
+ ) -> list[TextualMemoryItem]:
26
+ """Retrieve memories from the retriever."""
27
+
28
+
29
+ class NaiveRetriever(BaseRetriever):
30
+ """Naive retriever."""
31
+
32
+ def __init__(self, llm_provider=None, embedder=None, reranker=None, vector_db=None):
33
+ """Initialize the naive retriever."""
34
+ super().__init__(llm_provider, embedder, reranker, vector_db)
35
+ self.reranker = reranker
36
+ self.vector_db = vector_db
37
+ self.embedder = embedder
38
+
39
+ def _naive_reranker(
40
+ self, query: str, prefs_mem: list[TextualMemoryItem], top_k: int, **kwargs: Any
41
+ ) -> list[TextualMemoryItem]:
42
+ if self.reranker:
43
+ prefs_mem_reranked = []
44
+ prefs_mem_tuple = self.reranker.rerank(query, prefs_mem, top_k)
45
+ for item, score in prefs_mem_tuple:
46
+ item.metadata.score = score
47
+ prefs_mem_reranked.append(item)
48
+ return prefs_mem_reranked
49
+
50
+ def _original_text_reranker(
51
+ self,
52
+ query: str,
53
+ prefs_mem: list[TextualMemoryItem],
54
+ prefs: list[MilvusVecDBItem],
55
+ top_k: int,
56
+ **kwargs: Any,
57
+ ) -> list[TextualMemoryItem]:
58
+ if self.reranker:
59
+ from copy import deepcopy
60
+
61
+ prefs_mem_for_reranker = deepcopy(prefs_mem)
62
+ for pref_mem, pref in zip(prefs_mem_for_reranker, prefs, strict=False):
63
+ pref_mem.memory = pref_mem.memory + "\n" + pref.original_text
64
+ reranked_results = self.reranker.rerank(query, prefs_mem_for_reranker, top_k)
65
+ prefs_mem_for_reranker = [item for item, _ in reranked_results]
66
+ prefs_ids = [item.id for item in prefs_mem_for_reranker]
67
+ prefs_dict = {item.id: item for item in prefs_mem}
68
+
69
+ # Create mapping from id to score from reranked results
70
+ reranked_scores = {item.id: score for item, score in reranked_results}
71
+
72
+ # Assign scores to the original items
73
+ result_items = []
74
+ for item_id in prefs_ids:
75
+ if item_id in prefs_dict:
76
+ original_item = prefs_dict[item_id]
77
+ original_item.metadata.score = reranked_scores.get(item_id)
78
+ result_items.append(original_item)
79
+ return result_items
80
+ return prefs_mem
81
+
82
+ def retrieve(
83
+ self,
84
+ query: str,
85
+ top_k: int,
86
+ info: dict[str, Any] | None = None,
87
+ search_filter: dict[str, Any] | None = None,
88
+ ) -> list[TextualMemoryItem]:
89
+ """Retrieve memories from the naive retriever."""
90
+ # TODO: un-support rewrite query and session filter now
91
+ if info:
92
+ info = info.copy() # Create a copy to avoid modifying the original
93
+ info.pop("chat_history", None)
94
+ info.pop("session_id", None)
95
+ search_filter = {"and": [info, search_filter]}
96
+ query_embeddings = self.embedder.embed([query]) # Pass as list to get list of embeddings
97
+ query_embedding = query_embeddings[0] # Get the first (and only) embedding
98
+
99
+ # Use thread pool to parallelize the searches
100
+ with ContextThreadPoolExecutor(max_workers=2) as executor:
101
+ # Submit all search tasks
102
+ future_explicit = executor.submit(
103
+ self.vector_db.search,
104
+ query_embedding,
105
+ query,
106
+ "explicit_preference",
107
+ top_k * 2,
108
+ search_filter,
109
+ )
110
+ future_implicit = executor.submit(
111
+ self.vector_db.search,
112
+ query_embedding,
113
+ query,
114
+ "implicit_preference",
115
+ top_k * 2,
116
+ search_filter,
117
+ )
118
+
119
+ # Wait for all results
120
+ explicit_prefs = future_explicit.result()
121
+ implicit_prefs = future_implicit.result()
122
+
123
+ # sort by score
124
+ explicit_prefs.sort(key=lambda x: x.score, reverse=True)
125
+ implicit_prefs.sort(key=lambda x: x.score, reverse=True)
126
+
127
+ explicit_prefs_mem = [
128
+ TextualMemoryItem(
129
+ id=pref.id,
130
+ memory=pref.memory,
131
+ metadata=PreferenceTextualMemoryMetadata(**pref.payload),
132
+ )
133
+ for pref in explicit_prefs
134
+ if pref.payload.get("preference", None)
135
+ ]
136
+
137
+ implicit_prefs_mem = [
138
+ TextualMemoryItem(
139
+ id=pref.id,
140
+ memory=pref.memory,
141
+ metadata=PreferenceTextualMemoryMetadata(**pref.payload),
142
+ )
143
+ for pref in implicit_prefs
144
+ if pref.payload.get("preference", None)
145
+ ]
146
+
147
+ reranker_map = {
148
+ "naive": self._naive_reranker,
149
+ "original_text": self._original_text_reranker,
150
+ }
151
+ reranker_func = reranker_map["naive"]
152
+ prefs_mem_explicit = reranker_func(
153
+ query=query,
154
+ prefs_mem=explicit_prefs_mem,
155
+ prefs=explicit_prefs,
156
+ top_k=top_k,
157
+ )
158
+ prefs_mem_implicit = reranker_func(
159
+ query=query,
160
+ prefs_mem=implicit_prefs_mem,
161
+ prefs=implicit_prefs,
162
+ top_k=top_k,
163
+ )
164
+
165
+ # filter explicit mem by score bigger than threshold
166
+ prefs_mem_explicit = [
167
+ item
168
+ for item in prefs_mem_explicit
169
+ if item.metadata.score >= float(os.getenv("PREFERENCE_SEARCH_THRESHOLD", 0.0))
170
+ ]
171
+ prefs_mem_implicit = [
172
+ item
173
+ for item in prefs_mem_implicit
174
+ if item.metadata.score >= float(os.getenv("PREFERENCE_SEARCH_THRESHOLD", 0.0))
175
+ ]
176
+
177
+ return prefs_mem_explicit + prefs_mem_implicit