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,142 @@
1
+ """
2
+ Notification utilities for MemOS product.
3
+ """
4
+
5
+ import asyncio
6
+ import logging
7
+
8
+ from collections.abc import Callable
9
+ from typing import Any
10
+
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ def send_online_bot_notification(
16
+ online_bot: Callable | None,
17
+ header_name: str,
18
+ sub_title_name: str,
19
+ title_color: str,
20
+ other_data1: dict[str, Any],
21
+ other_data2: dict[str, Any],
22
+ emoji: dict[str, str],
23
+ ) -> None:
24
+ """
25
+ Send notification via online_bot if available.
26
+
27
+ Args:
28
+ online_bot: The online_bot function or None
29
+ header_name: Header name for the report
30
+ sub_title_name: Subtitle for the report
31
+ title_color: Title color
32
+ other_data1: First data dict
33
+ other_data2: Second data dict
34
+ emoji: Emoji configuration dict
35
+ """
36
+ if online_bot is None:
37
+ return
38
+
39
+ try:
40
+ online_bot(
41
+ header_name=header_name,
42
+ sub_title_name=sub_title_name,
43
+ title_color=title_color,
44
+ other_data1=other_data1,
45
+ other_data2=other_data2,
46
+ emoji=emoji,
47
+ )
48
+
49
+ logger.info(f"Online bot notification sent successfully: {header_name}")
50
+
51
+ except Exception as e:
52
+ logger.warning(f"Failed to send online bot notification: {e}")
53
+
54
+
55
+ async def send_online_bot_notification_async(
56
+ online_bot: Callable | None,
57
+ header_name: str,
58
+ sub_title_name: str,
59
+ title_color: str,
60
+ other_data1: dict[str, Any],
61
+ other_data2: dict[str, Any],
62
+ emoji: dict[str, str],
63
+ ) -> None:
64
+ """
65
+ Send notification via online_bot asynchronously if available.
66
+
67
+ Args:
68
+ online_bot: The online_bot function or None
69
+ header_name: Header name for the report
70
+ sub_title_name: Subtitle for the report
71
+ title_color: Title color
72
+ other_data1: First data dict
73
+ other_data2: Second data dict
74
+ emoji: Emoji configuration dict
75
+ """
76
+ if online_bot is None:
77
+ return
78
+
79
+ try:
80
+ # Run the potentially blocking notification in a thread pool
81
+ loop = asyncio.get_event_loop()
82
+ await loop.run_in_executor(
83
+ None,
84
+ lambda: online_bot(
85
+ header_name=header_name,
86
+ sub_title_name=sub_title_name,
87
+ title_color=title_color,
88
+ other_data1=other_data1,
89
+ other_data2=other_data2,
90
+ emoji=emoji,
91
+ ),
92
+ )
93
+
94
+ logger.info(f"Online bot notification sent successfully (async): {header_name}")
95
+
96
+ except Exception as e:
97
+ logger.warning(f"Failed to send online bot notification (async): {e}")
98
+
99
+
100
+ def send_error_bot_notification(
101
+ error_bot: Callable | None,
102
+ err: str,
103
+ title: str = "MemOS Error",
104
+ level: str = "P2",
105
+ user_ids: list | None = None,
106
+ ) -> None:
107
+ """
108
+ Send error alert if error_bot is available.
109
+
110
+ Args:
111
+ error_bot: The error_bot function or None
112
+ err: Error message
113
+ title: Alert title
114
+ level: Alert level (P0, P1, P2)
115
+ user_ids: List of user IDs to notify
116
+ """
117
+ if error_bot is None:
118
+ return
119
+
120
+ try:
121
+ error_bot(
122
+ err=err,
123
+ title=title,
124
+ level=level,
125
+ user_ids=user_ids or [],
126
+ )
127
+ logger.info(f"Error alert sent successfully: {title}")
128
+ except Exception as e:
129
+ logger.warning(f"Failed to send error alert: {e}")
130
+
131
+
132
+ # Keep backward compatibility
133
+ def send_error_alert(
134
+ error_bot: Callable | None,
135
+ error_message: str,
136
+ title: str = "MemOS Error",
137
+ level: str = "P2",
138
+ ) -> None:
139
+ """
140
+ Send error alert if error_bot is available (backward compatibility).
141
+ """
142
+ send_error_bot_notification(error_bot, error_message, title, level)
@@ -0,0 +1,174 @@
1
+ """
2
+ Singleton decorator module for caching factory instances to avoid excessive memory usage
3
+ from repeated initialization.
4
+ """
5
+
6
+ import hashlib
7
+ import json
8
+
9
+ from collections.abc import Callable
10
+ from functools import wraps
11
+ from typing import Any, TypeVar
12
+ from weakref import WeakValueDictionary
13
+
14
+
15
+ T = TypeVar("T")
16
+
17
+
18
+ class FactorySingleton:
19
+ """Factory singleton manager that caches instances based on configuration parameters"""
20
+
21
+ def __init__(self):
22
+ # Use weak reference dictionary for automatic cleanup when instances are no longer referenced
23
+ self._instances: dict[str, WeakValueDictionary] = {}
24
+
25
+ def _generate_cache_key(self, config: Any, *args, **kwargs) -> str:
26
+ """Generate cache key based on configuration only (ignoring other parameters)"""
27
+
28
+ # Handle configuration objects - only use the config parameter
29
+ if hasattr(config, "model_dump"): # Pydantic model
30
+ config_data = config.model_dump()
31
+ elif hasattr(config, "dict"): # Legacy Pydantic model
32
+ config_data = config.dict()
33
+ elif isinstance(config, dict):
34
+ config_data = config
35
+ else:
36
+ # For other types, try to convert to string
37
+ config_data = str(config)
38
+
39
+ # Filter out time-related fields that shouldn't affect caching
40
+ filtered_config = self._filter_temporal_fields(config_data)
41
+
42
+ # Generate hash key based only on config
43
+ try:
44
+ cache_str = json.dumps(filtered_config, sort_keys=True, ensure_ascii=False, default=str)
45
+ except (TypeError, ValueError):
46
+ # If JSON serialization fails, convert the entire config to string
47
+ cache_str = str(filtered_config)
48
+
49
+ return hashlib.md5(cache_str.encode("utf-8")).hexdigest()
50
+
51
+ def _filter_temporal_fields(self, config_data: Any) -> Any:
52
+ """Filter out temporal fields that shouldn't affect instance caching"""
53
+ if isinstance(config_data, dict):
54
+ filtered = {}
55
+ for key, value in config_data.items():
56
+ # Skip common temporal field names
57
+ if key.lower() in {
58
+ "created_at",
59
+ "updated_at",
60
+ "timestamp",
61
+ "time",
62
+ "date",
63
+ "created_time",
64
+ "updated_time",
65
+ "last_modified",
66
+ "modified_at",
67
+ "start_time",
68
+ "end_time",
69
+ "execution_time",
70
+ "run_time",
71
+ }:
72
+ continue
73
+ # Recursively filter nested dictionaries
74
+ filtered[key] = self._filter_temporal_fields(value)
75
+ return filtered
76
+ elif isinstance(config_data, list):
77
+ # Recursively filter lists
78
+ return [self._filter_temporal_fields(item) for item in config_data]
79
+ else:
80
+ # For primitive types, return as-is
81
+ return config_data
82
+
83
+ def get_or_create(self, factory_class: type, cache_key: str, creator_func: Callable) -> Any:
84
+ """Get or create instance"""
85
+ class_name = factory_class.__name__
86
+
87
+ if class_name not in self._instances:
88
+ self._instances[class_name] = WeakValueDictionary()
89
+
90
+ class_cache = self._instances[class_name]
91
+
92
+ if cache_key in class_cache:
93
+ return class_cache[cache_key]
94
+
95
+ # Create new instance
96
+ instance = creator_func()
97
+ class_cache[cache_key] = instance
98
+ return instance
99
+
100
+ def clear_cache(self, factory_class: type | None = None):
101
+ """Clear cache"""
102
+ if factory_class:
103
+ class_name = factory_class.__name__
104
+ if class_name in self._instances:
105
+ self._instances[class_name].clear()
106
+ else:
107
+ for cache in self._instances.values():
108
+ cache.clear()
109
+
110
+
111
+ # Global singleton manager
112
+ _factory_singleton = FactorySingleton()
113
+
114
+
115
+ def singleton_factory(factory_class: type | str | None = None):
116
+ """
117
+ Factory singleton decorator
118
+
119
+ Usage:
120
+ @singleton_factory()
121
+ def from_config(cls, config):
122
+ return SomeClass(config)
123
+
124
+ Or specify factory class:
125
+ @singleton_factory(EmbedderFactory)
126
+ def from_config(cls, config):
127
+ return SomeClass(config)
128
+ """
129
+
130
+ def decorator(func: Callable[..., T]) -> Callable[..., T]:
131
+ @wraps(func)
132
+ def wrapper(*args, **kwargs) -> T:
133
+ # Determine factory class and config parameter
134
+ target_factory_class = factory_class
135
+ config = None
136
+
137
+ # Simple logic: check if first parameter is a class or config
138
+ if args:
139
+ if hasattr(args[0], "__name__") and hasattr(args[0], "__module__"):
140
+ # First parameter is a class (cls), so this is a @classmethod
141
+ if target_factory_class is None:
142
+ target_factory_class = args[0]
143
+ config = args[1] if len(args) > 1 else None
144
+ else:
145
+ # First parameter is config, so this is a @staticmethod
146
+ if target_factory_class is None:
147
+ raise ValueError(
148
+ "Factory class must be explicitly specified for static methods"
149
+ )
150
+ if isinstance(target_factory_class, str):
151
+ # Convert string to a mock class for caching purposes
152
+ class MockFactoryClass:
153
+ __name__ = target_factory_class
154
+
155
+ target_factory_class = MockFactoryClass
156
+ config = args[0]
157
+
158
+ if config is None:
159
+ # If no configuration parameter, call original function directly
160
+ return func(*args, **kwargs)
161
+
162
+ # Generate cache key based only on config
163
+ cache_key = _factory_singleton._generate_cache_key(config)
164
+
165
+ # Function to create instance
166
+ def creator():
167
+ return func(*args, **kwargs)
168
+
169
+ # Get or create instance
170
+ return _factory_singleton.get_or_create(target_factory_class, cache_key, creator)
171
+
172
+ return wrapper
173
+
174
+ return decorator
@@ -0,0 +1,310 @@
1
+ """
2
+ Thread-safe dictionary wrapper for concurrent access with optimized read-write locks.
3
+ """
4
+
5
+ import threading
6
+
7
+ from collections.abc import ItemsView, Iterator, KeysView, ValuesView
8
+ from typing import Generic, TypeVar
9
+
10
+ from memos.log import get_logger
11
+ from memos.utils import timed
12
+
13
+
14
+ K = TypeVar("K")
15
+ V = TypeVar("V")
16
+
17
+ logger = get_logger(__name__)
18
+
19
+
20
+ class ReadWriteLock:
21
+ """A simple read-write lock implementation. use for product-server scenario"""
22
+
23
+ def __init__(self):
24
+ self._read_ready = threading.Condition(threading.RLock())
25
+ self._readers = 0
26
+
27
+ @timed
28
+ def acquire_read(self):
29
+ """Acquire a read lock. Multiple readers can hold the lock simultaneously."""
30
+ self._read_ready.acquire()
31
+ try:
32
+ self._readers += 1
33
+ finally:
34
+ self._read_ready.release()
35
+
36
+ def release_read(self):
37
+ """Release a read lock."""
38
+ self._read_ready.acquire()
39
+ try:
40
+ self._readers -= 1
41
+ if self._readers == 0:
42
+ self._read_ready.notify_all()
43
+ finally:
44
+ self._read_ready.release()
45
+
46
+ @timed
47
+ def acquire_write(self):
48
+ """Acquire a write lock. Only one writer can hold the lock."""
49
+ self._read_ready.acquire()
50
+ while self._readers > 0:
51
+ self._read_ready.wait()
52
+
53
+ def release_write(self):
54
+ """Release a write lock."""
55
+ self._read_ready.release()
56
+
57
+
58
+ class ThreadSafeDict(Generic[K, V]):
59
+ """
60
+ A thread-safe dictionary wrapper with optimized read-write locks.
61
+
62
+ This class allows multiple concurrent readers while ensuring exclusive access for writers.
63
+ Read operations (get, contains, iteration) can happen concurrently.
64
+ Write operations (set, delete, update) are exclusive.
65
+ """
66
+
67
+ def __init__(self, initial_dict: dict[K, V] | None = None):
68
+ """
69
+ Initialize the thread-safe dictionary.
70
+
71
+ Args:
72
+ initial_dict: Optional initial dictionary to copy from
73
+ """
74
+ self._dict: dict[K, V] = initial_dict.copy() if initial_dict else {}
75
+ self._lock = ReadWriteLock()
76
+
77
+ @timed
78
+ def __getitem__(self, key: K) -> V:
79
+ """Get item by key."""
80
+ self._lock.acquire_read()
81
+ try:
82
+ return self._dict[key]
83
+ finally:
84
+ self._lock.release_read()
85
+
86
+ @timed
87
+ def __setitem__(self, key: K, value: V) -> None:
88
+ """Set item by key."""
89
+ self._lock.acquire_write()
90
+ try:
91
+ self._dict[key] = value
92
+ finally:
93
+ self._lock.release_write()
94
+
95
+ @timed
96
+ def __delitem__(self, key: K) -> None:
97
+ """Delete item by key."""
98
+ self._lock.acquire_write()
99
+ try:
100
+ del self._dict[key]
101
+ finally:
102
+ self._lock.release_write()
103
+
104
+ @timed
105
+ def __contains__(self, key: K) -> bool:
106
+ """Check if key exists in dictionary."""
107
+ self._lock.acquire_read()
108
+ try:
109
+ return key in self._dict
110
+ finally:
111
+ self._lock.release_read()
112
+
113
+ @timed
114
+ def __len__(self) -> int:
115
+ """Get length of dictionary."""
116
+ self._lock.acquire_read()
117
+ try:
118
+ return len(self._dict)
119
+ finally:
120
+ self._lock.release_read()
121
+
122
+ def __bool__(self) -> bool:
123
+ """Check if dictionary is not empty."""
124
+ self._lock.acquire_read()
125
+ try:
126
+ return bool(self._dict)
127
+ finally:
128
+ self._lock.release_read()
129
+
130
+ @timed
131
+ def __iter__(self) -> Iterator[K]:
132
+ """Iterate over keys. Returns a snapshot to avoid iteration issues."""
133
+ self._lock.acquire_read()
134
+ try:
135
+ # Return a snapshot of keys to avoid iteration issues
136
+ return iter(list(self._dict.keys()))
137
+ finally:
138
+ self._lock.release_read()
139
+
140
+ @timed
141
+ def get(self, key: K, default: V | None = None) -> V:
142
+ """Get item by key with optional default."""
143
+ self._lock.acquire_read()
144
+ try:
145
+ return self._dict.get(key, default)
146
+ finally:
147
+ self._lock.release_read()
148
+
149
+ @timed
150
+ def pop(self, key: K, *args) -> V:
151
+ """Pop item by key."""
152
+ self._lock.acquire_write()
153
+ try:
154
+ return self._dict.pop(key, *args)
155
+ finally:
156
+ self._lock.release_write()
157
+
158
+ @timed
159
+ def update(self, *args, **kwargs) -> None:
160
+ """Update dictionary."""
161
+ self._lock.acquire_write()
162
+ try:
163
+ self._dict.update(*args, **kwargs)
164
+ finally:
165
+ self._lock.release_write()
166
+
167
+ @timed
168
+ def clear(self) -> None:
169
+ """Clear all items."""
170
+ self._lock.acquire_write()
171
+ try:
172
+ self._dict.clear()
173
+ finally:
174
+ self._lock.release_write()
175
+
176
+ @timed
177
+ def keys(self) -> KeysView[K]:
178
+ """Get dictionary keys view (snapshot)."""
179
+ self._lock.acquire_read()
180
+ try:
181
+ return list(self._dict.keys())
182
+ finally:
183
+ self._lock.release_read()
184
+
185
+ @timed
186
+ def values(self) -> ValuesView[V]:
187
+ """Get dictionary values view (snapshot)."""
188
+ self._lock.acquire_read()
189
+ try:
190
+ return list(self._dict.values())
191
+ finally:
192
+ self._lock.release_read()
193
+
194
+ @timed
195
+ def items(self) -> ItemsView[K, V]:
196
+ """Get dictionary items view (snapshot)."""
197
+ self._lock.acquire_read()
198
+ try:
199
+ return list(self._dict.items())
200
+ finally:
201
+ self._lock.release_read()
202
+
203
+ @timed
204
+ def copy(self) -> dict[K, V]:
205
+ """Create a copy of the dictionary."""
206
+ self._lock.acquire_read()
207
+ try:
208
+ return self._dict.copy()
209
+ finally:
210
+ self._lock.release_read()
211
+
212
+ @timed
213
+ def setdefault(self, key: K, default: V | None = None) -> V:
214
+ """Set default value for key if not exists."""
215
+ self._lock.acquire_write()
216
+ try:
217
+ return self._dict.setdefault(key, default)
218
+ finally:
219
+ self._lock.release_write()
220
+
221
+ def __repr__(self) -> str:
222
+ """String representation."""
223
+ self._lock.acquire_read()
224
+ try:
225
+ return f"ThreadSafeDict({self._dict})"
226
+ finally:
227
+ self._lock.release_read()
228
+
229
+ def __str__(self) -> str:
230
+ """String representation."""
231
+ self._lock.acquire_read()
232
+ try:
233
+ return str(self._dict)
234
+ finally:
235
+ self._lock.release_read()
236
+
237
+
238
+ class SimpleThreadSafeDict(Generic[K, V]):
239
+ """
240
+ Simple thread-safe dictionary with exclusive locks for all operations.
241
+ Use this if you prefer simplicity over performance.
242
+ """
243
+
244
+ def __init__(self, initial_dict: dict[K, V] | None = None):
245
+ self._dict: dict[K, V] = initial_dict.copy() if initial_dict else {}
246
+ self._lock = threading.RLock()
247
+
248
+ def __getitem__(self, key: K) -> V:
249
+ with self._lock:
250
+ return self._dict[key]
251
+
252
+ def __setitem__(self, key: K, value: V) -> None:
253
+ with self._lock:
254
+ self._dict[key] = value
255
+
256
+ def __delitem__(self, key: K) -> None:
257
+ with self._lock:
258
+ del self._dict[key]
259
+
260
+ def __contains__(self, key: K) -> bool:
261
+ with self._lock:
262
+ return key in self._dict
263
+
264
+ def __len__(self) -> int:
265
+ with self._lock:
266
+ return len(self._dict)
267
+
268
+ def __bool__(self) -> bool:
269
+ with self._lock:
270
+ return bool(self._dict)
271
+
272
+ def __iter__(self) -> Iterator[K]:
273
+ with self._lock:
274
+ return iter(list(self._dict.keys()))
275
+
276
+ def get(self, key: K, default: V | None = None) -> V:
277
+ with self._lock:
278
+ return self._dict.get(key, default)
279
+
280
+ def pop(self, key: K, *args) -> V:
281
+ with self._lock:
282
+ return self._dict.pop(key, *args)
283
+
284
+ def update(self, *args, **kwargs) -> None:
285
+ with self._lock:
286
+ self._dict.update(*args, **kwargs)
287
+
288
+ def clear(self) -> None:
289
+ with self._lock:
290
+ self._dict.clear()
291
+
292
+ def keys(self):
293
+ with self._lock:
294
+ return list(self._dict.keys())
295
+
296
+ def values(self):
297
+ with self._lock:
298
+ return list(self._dict.values())
299
+
300
+ def items(self):
301
+ with self._lock:
302
+ return list(self._dict.items())
303
+
304
+ def copy(self) -> dict[K, V]:
305
+ with self._lock:
306
+ return self._dict.copy()
307
+
308
+ def setdefault(self, key: K, default: V | None = None) -> V:
309
+ with self._lock:
310
+ return self._dict.setdefault(key, default)