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.
- memoryos-2.0.3.dist-info/METADATA +418 -0
- memoryos-2.0.3.dist-info/RECORD +315 -0
- memoryos-2.0.3.dist-info/WHEEL +4 -0
- memoryos-2.0.3.dist-info/entry_points.txt +3 -0
- memoryos-2.0.3.dist-info/licenses/LICENSE +201 -0
- memos/__init__.py +20 -0
- memos/api/client.py +571 -0
- memos/api/config.py +1018 -0
- memos/api/context/dependencies.py +50 -0
- memos/api/exceptions.py +53 -0
- memos/api/handlers/__init__.py +62 -0
- memos/api/handlers/add_handler.py +158 -0
- memos/api/handlers/base_handler.py +194 -0
- memos/api/handlers/chat_handler.py +1401 -0
- memos/api/handlers/component_init.py +388 -0
- memos/api/handlers/config_builders.py +190 -0
- memos/api/handlers/feedback_handler.py +93 -0
- memos/api/handlers/formatters_handler.py +237 -0
- memos/api/handlers/memory_handler.py +316 -0
- memos/api/handlers/scheduler_handler.py +497 -0
- memos/api/handlers/search_handler.py +222 -0
- memos/api/handlers/suggestion_handler.py +117 -0
- memos/api/mcp_serve.py +614 -0
- memos/api/middleware/request_context.py +101 -0
- memos/api/product_api.py +38 -0
- memos/api/product_models.py +1206 -0
- memos/api/routers/__init__.py +1 -0
- memos/api/routers/product_router.py +477 -0
- memos/api/routers/server_router.py +394 -0
- memos/api/server_api.py +44 -0
- memos/api/start_api.py +433 -0
- memos/chunkers/__init__.py +4 -0
- memos/chunkers/base.py +24 -0
- memos/chunkers/charactertext_chunker.py +41 -0
- memos/chunkers/factory.py +24 -0
- memos/chunkers/markdown_chunker.py +62 -0
- memos/chunkers/sentence_chunker.py +54 -0
- memos/chunkers/simple_chunker.py +50 -0
- memos/cli.py +113 -0
- memos/configs/__init__.py +0 -0
- memos/configs/base.py +82 -0
- memos/configs/chunker.py +59 -0
- memos/configs/embedder.py +88 -0
- memos/configs/graph_db.py +236 -0
- memos/configs/internet_retriever.py +100 -0
- memos/configs/llm.py +151 -0
- memos/configs/mem_agent.py +54 -0
- memos/configs/mem_chat.py +81 -0
- memos/configs/mem_cube.py +105 -0
- memos/configs/mem_os.py +83 -0
- memos/configs/mem_reader.py +91 -0
- memos/configs/mem_scheduler.py +385 -0
- memos/configs/mem_user.py +70 -0
- memos/configs/memory.py +324 -0
- memos/configs/parser.py +38 -0
- memos/configs/reranker.py +18 -0
- memos/configs/utils.py +8 -0
- memos/configs/vec_db.py +80 -0
- memos/context/context.py +355 -0
- memos/dependency.py +52 -0
- memos/deprecation.py +262 -0
- memos/embedders/__init__.py +0 -0
- memos/embedders/ark.py +95 -0
- memos/embedders/base.py +106 -0
- memos/embedders/factory.py +29 -0
- memos/embedders/ollama.py +77 -0
- memos/embedders/sentence_transformer.py +49 -0
- memos/embedders/universal_api.py +51 -0
- memos/exceptions.py +30 -0
- memos/graph_dbs/__init__.py +0 -0
- memos/graph_dbs/base.py +274 -0
- memos/graph_dbs/factory.py +27 -0
- memos/graph_dbs/item.py +46 -0
- memos/graph_dbs/nebular.py +1794 -0
- memos/graph_dbs/neo4j.py +1942 -0
- memos/graph_dbs/neo4j_community.py +1058 -0
- memos/graph_dbs/polardb.py +5446 -0
- memos/hello_world.py +97 -0
- memos/llms/__init__.py +0 -0
- memos/llms/base.py +25 -0
- memos/llms/deepseek.py +13 -0
- memos/llms/factory.py +38 -0
- memos/llms/hf.py +443 -0
- memos/llms/hf_singleton.py +114 -0
- memos/llms/ollama.py +135 -0
- memos/llms/openai.py +222 -0
- memos/llms/openai_new.py +198 -0
- memos/llms/qwen.py +13 -0
- memos/llms/utils.py +14 -0
- memos/llms/vllm.py +218 -0
- memos/log.py +237 -0
- memos/mem_agent/base.py +19 -0
- memos/mem_agent/deepsearch_agent.py +391 -0
- memos/mem_agent/factory.py +36 -0
- memos/mem_chat/__init__.py +0 -0
- memos/mem_chat/base.py +30 -0
- memos/mem_chat/factory.py +21 -0
- memos/mem_chat/simple.py +200 -0
- memos/mem_cube/__init__.py +0 -0
- memos/mem_cube/base.py +30 -0
- memos/mem_cube/general.py +240 -0
- memos/mem_cube/navie.py +172 -0
- memos/mem_cube/utils.py +169 -0
- memos/mem_feedback/base.py +15 -0
- memos/mem_feedback/feedback.py +1192 -0
- memos/mem_feedback/simple_feedback.py +40 -0
- memos/mem_feedback/utils.py +230 -0
- memos/mem_os/client.py +5 -0
- memos/mem_os/core.py +1203 -0
- memos/mem_os/main.py +582 -0
- memos/mem_os/product.py +1608 -0
- memos/mem_os/product_server.py +455 -0
- memos/mem_os/utils/default_config.py +359 -0
- memos/mem_os/utils/format_utils.py +1403 -0
- memos/mem_os/utils/reference_utils.py +162 -0
- memos/mem_reader/__init__.py +0 -0
- memos/mem_reader/base.py +47 -0
- memos/mem_reader/factory.py +53 -0
- memos/mem_reader/memory.py +298 -0
- memos/mem_reader/multi_modal_struct.py +965 -0
- memos/mem_reader/read_multi_modal/__init__.py +43 -0
- memos/mem_reader/read_multi_modal/assistant_parser.py +311 -0
- memos/mem_reader/read_multi_modal/base.py +273 -0
- memos/mem_reader/read_multi_modal/file_content_parser.py +826 -0
- memos/mem_reader/read_multi_modal/image_parser.py +359 -0
- memos/mem_reader/read_multi_modal/multi_modal_parser.py +252 -0
- memos/mem_reader/read_multi_modal/string_parser.py +139 -0
- memos/mem_reader/read_multi_modal/system_parser.py +327 -0
- memos/mem_reader/read_multi_modal/text_content_parser.py +131 -0
- memos/mem_reader/read_multi_modal/tool_parser.py +210 -0
- memos/mem_reader/read_multi_modal/user_parser.py +218 -0
- memos/mem_reader/read_multi_modal/utils.py +358 -0
- memos/mem_reader/simple_struct.py +912 -0
- memos/mem_reader/strategy_struct.py +163 -0
- memos/mem_reader/utils.py +157 -0
- memos/mem_scheduler/__init__.py +0 -0
- memos/mem_scheduler/analyzer/__init__.py +0 -0
- memos/mem_scheduler/analyzer/api_analyzer.py +714 -0
- memos/mem_scheduler/analyzer/eval_analyzer.py +219 -0
- memos/mem_scheduler/analyzer/mos_for_test_scheduler.py +571 -0
- memos/mem_scheduler/analyzer/scheduler_for_eval.py +280 -0
- memos/mem_scheduler/base_scheduler.py +1319 -0
- memos/mem_scheduler/general_modules/__init__.py +0 -0
- memos/mem_scheduler/general_modules/api_misc.py +137 -0
- memos/mem_scheduler/general_modules/base.py +80 -0
- memos/mem_scheduler/general_modules/init_components_for_scheduler.py +425 -0
- memos/mem_scheduler/general_modules/misc.py +313 -0
- memos/mem_scheduler/general_modules/scheduler_logger.py +389 -0
- memos/mem_scheduler/general_modules/task_threads.py +315 -0
- memos/mem_scheduler/general_scheduler.py +1495 -0
- memos/mem_scheduler/memory_manage_modules/__init__.py +5 -0
- memos/mem_scheduler/memory_manage_modules/memory_filter.py +306 -0
- memos/mem_scheduler/memory_manage_modules/retriever.py +547 -0
- memos/mem_scheduler/monitors/__init__.py +0 -0
- memos/mem_scheduler/monitors/dispatcher_monitor.py +366 -0
- memos/mem_scheduler/monitors/general_monitor.py +394 -0
- memos/mem_scheduler/monitors/task_schedule_monitor.py +254 -0
- memos/mem_scheduler/optimized_scheduler.py +410 -0
- memos/mem_scheduler/orm_modules/__init__.py +0 -0
- memos/mem_scheduler/orm_modules/api_redis_model.py +518 -0
- memos/mem_scheduler/orm_modules/base_model.py +729 -0
- memos/mem_scheduler/orm_modules/monitor_models.py +261 -0
- memos/mem_scheduler/orm_modules/redis_model.py +699 -0
- memos/mem_scheduler/scheduler_factory.py +23 -0
- memos/mem_scheduler/schemas/__init__.py +0 -0
- memos/mem_scheduler/schemas/analyzer_schemas.py +52 -0
- memos/mem_scheduler/schemas/api_schemas.py +233 -0
- memos/mem_scheduler/schemas/general_schemas.py +55 -0
- memos/mem_scheduler/schemas/message_schemas.py +173 -0
- memos/mem_scheduler/schemas/monitor_schemas.py +406 -0
- memos/mem_scheduler/schemas/task_schemas.py +132 -0
- memos/mem_scheduler/task_schedule_modules/__init__.py +0 -0
- memos/mem_scheduler/task_schedule_modules/dispatcher.py +740 -0
- memos/mem_scheduler/task_schedule_modules/local_queue.py +247 -0
- memos/mem_scheduler/task_schedule_modules/orchestrator.py +74 -0
- memos/mem_scheduler/task_schedule_modules/redis_queue.py +1385 -0
- memos/mem_scheduler/task_schedule_modules/task_queue.py +162 -0
- memos/mem_scheduler/utils/__init__.py +0 -0
- memos/mem_scheduler/utils/api_utils.py +77 -0
- memos/mem_scheduler/utils/config_utils.py +100 -0
- memos/mem_scheduler/utils/db_utils.py +50 -0
- memos/mem_scheduler/utils/filter_utils.py +176 -0
- memos/mem_scheduler/utils/metrics.py +125 -0
- memos/mem_scheduler/utils/misc_utils.py +290 -0
- memos/mem_scheduler/utils/monitor_event_utils.py +67 -0
- memos/mem_scheduler/utils/status_tracker.py +229 -0
- memos/mem_scheduler/webservice_modules/__init__.py +0 -0
- memos/mem_scheduler/webservice_modules/rabbitmq_service.py +485 -0
- memos/mem_scheduler/webservice_modules/redis_service.py +380 -0
- memos/mem_user/factory.py +94 -0
- memos/mem_user/mysql_persistent_user_manager.py +271 -0
- memos/mem_user/mysql_user_manager.py +502 -0
- memos/mem_user/persistent_factory.py +98 -0
- memos/mem_user/persistent_user_manager.py +260 -0
- memos/mem_user/redis_persistent_user_manager.py +225 -0
- memos/mem_user/user_manager.py +488 -0
- memos/memories/__init__.py +0 -0
- memos/memories/activation/__init__.py +0 -0
- memos/memories/activation/base.py +42 -0
- memos/memories/activation/item.py +56 -0
- memos/memories/activation/kv.py +292 -0
- memos/memories/activation/vllmkv.py +219 -0
- memos/memories/base.py +19 -0
- memos/memories/factory.py +42 -0
- memos/memories/parametric/__init__.py +0 -0
- memos/memories/parametric/base.py +19 -0
- memos/memories/parametric/item.py +11 -0
- memos/memories/parametric/lora.py +41 -0
- memos/memories/textual/__init__.py +0 -0
- memos/memories/textual/base.py +92 -0
- memos/memories/textual/general.py +236 -0
- memos/memories/textual/item.py +304 -0
- memos/memories/textual/naive.py +187 -0
- memos/memories/textual/prefer_text_memory/__init__.py +0 -0
- memos/memories/textual/prefer_text_memory/adder.py +504 -0
- memos/memories/textual/prefer_text_memory/config.py +106 -0
- memos/memories/textual/prefer_text_memory/extractor.py +221 -0
- memos/memories/textual/prefer_text_memory/factory.py +85 -0
- memos/memories/textual/prefer_text_memory/retrievers.py +177 -0
- memos/memories/textual/prefer_text_memory/spliter.py +132 -0
- memos/memories/textual/prefer_text_memory/utils.py +93 -0
- memos/memories/textual/preference.py +344 -0
- memos/memories/textual/simple_preference.py +161 -0
- memos/memories/textual/simple_tree.py +69 -0
- memos/memories/textual/tree.py +459 -0
- memos/memories/textual/tree_text_memory/__init__.py +0 -0
- memos/memories/textual/tree_text_memory/organize/__init__.py +0 -0
- memos/memories/textual/tree_text_memory/organize/handler.py +184 -0
- memos/memories/textual/tree_text_memory/organize/manager.py +518 -0
- memos/memories/textual/tree_text_memory/organize/relation_reason_detector.py +238 -0
- memos/memories/textual/tree_text_memory/organize/reorganizer.py +622 -0
- memos/memories/textual/tree_text_memory/retrieve/__init__.py +0 -0
- memos/memories/textual/tree_text_memory/retrieve/advanced_searcher.py +364 -0
- memos/memories/textual/tree_text_memory/retrieve/bm25_util.py +186 -0
- memos/memories/textual/tree_text_memory/retrieve/bochasearch.py +419 -0
- memos/memories/textual/tree_text_memory/retrieve/internet_retriever.py +270 -0
- memos/memories/textual/tree_text_memory/retrieve/internet_retriever_factory.py +102 -0
- memos/memories/textual/tree_text_memory/retrieve/reasoner.py +61 -0
- memos/memories/textual/tree_text_memory/retrieve/recall.py +497 -0
- memos/memories/textual/tree_text_memory/retrieve/reranker.py +111 -0
- memos/memories/textual/tree_text_memory/retrieve/retrieval_mid_structs.py +16 -0
- memos/memories/textual/tree_text_memory/retrieve/retrieve_utils.py +472 -0
- memos/memories/textual/tree_text_memory/retrieve/searcher.py +848 -0
- memos/memories/textual/tree_text_memory/retrieve/task_goal_parser.py +135 -0
- memos/memories/textual/tree_text_memory/retrieve/utils.py +54 -0
- memos/memories/textual/tree_text_memory/retrieve/xinyusearch.py +387 -0
- memos/memos_tools/dinding_report_bot.py +453 -0
- memos/memos_tools/lockfree_dict.py +120 -0
- memos/memos_tools/notification_service.py +44 -0
- memos/memos_tools/notification_utils.py +142 -0
- memos/memos_tools/singleton.py +174 -0
- memos/memos_tools/thread_safe_dict.py +310 -0
- memos/memos_tools/thread_safe_dict_segment.py +382 -0
- memos/multi_mem_cube/__init__.py +0 -0
- memos/multi_mem_cube/composite_cube.py +86 -0
- memos/multi_mem_cube/single_cube.py +874 -0
- memos/multi_mem_cube/views.py +54 -0
- memos/parsers/__init__.py +0 -0
- memos/parsers/base.py +15 -0
- memos/parsers/factory.py +21 -0
- memos/parsers/markitdown.py +28 -0
- memos/reranker/__init__.py +4 -0
- memos/reranker/base.py +25 -0
- memos/reranker/concat.py +103 -0
- memos/reranker/cosine_local.py +102 -0
- memos/reranker/factory.py +72 -0
- memos/reranker/http_bge.py +324 -0
- memos/reranker/http_bge_strategy.py +327 -0
- memos/reranker/noop.py +19 -0
- memos/reranker/strategies/__init__.py +4 -0
- memos/reranker/strategies/base.py +61 -0
- memos/reranker/strategies/concat_background.py +94 -0
- memos/reranker/strategies/concat_docsource.py +110 -0
- memos/reranker/strategies/dialogue_common.py +109 -0
- memos/reranker/strategies/factory.py +31 -0
- memos/reranker/strategies/single_turn.py +107 -0
- memos/reranker/strategies/singleturn_outmem.py +98 -0
- memos/settings.py +10 -0
- memos/templates/__init__.py +0 -0
- memos/templates/advanced_search_prompts.py +211 -0
- memos/templates/cloud_service_prompt.py +107 -0
- memos/templates/instruction_completion.py +66 -0
- memos/templates/mem_agent_prompts.py +85 -0
- memos/templates/mem_feedback_prompts.py +822 -0
- memos/templates/mem_reader_prompts.py +1096 -0
- memos/templates/mem_reader_strategy_prompts.py +238 -0
- memos/templates/mem_scheduler_prompts.py +626 -0
- memos/templates/mem_search_prompts.py +93 -0
- memos/templates/mos_prompts.py +403 -0
- memos/templates/prefer_complete_prompt.py +735 -0
- memos/templates/tool_mem_prompts.py +139 -0
- memos/templates/tree_reorganize_prompts.py +230 -0
- memos/types/__init__.py +34 -0
- memos/types/general_types.py +151 -0
- memos/types/openai_chat_completion_types/__init__.py +15 -0
- memos/types/openai_chat_completion_types/chat_completion_assistant_message_param.py +56 -0
- memos/types/openai_chat_completion_types/chat_completion_content_part_image_param.py +27 -0
- memos/types/openai_chat_completion_types/chat_completion_content_part_input_audio_param.py +23 -0
- memos/types/openai_chat_completion_types/chat_completion_content_part_param.py +43 -0
- memos/types/openai_chat_completion_types/chat_completion_content_part_refusal_param.py +16 -0
- memos/types/openai_chat_completion_types/chat_completion_content_part_text_param.py +16 -0
- memos/types/openai_chat_completion_types/chat_completion_message_custom_tool_call_param.py +27 -0
- memos/types/openai_chat_completion_types/chat_completion_message_function_tool_call_param.py +32 -0
- memos/types/openai_chat_completion_types/chat_completion_message_param.py +18 -0
- memos/types/openai_chat_completion_types/chat_completion_message_tool_call_union_param.py +15 -0
- memos/types/openai_chat_completion_types/chat_completion_system_message_param.py +36 -0
- memos/types/openai_chat_completion_types/chat_completion_tool_message_param.py +30 -0
- memos/types/openai_chat_completion_types/chat_completion_user_message_param.py +34 -0
- memos/utils.py +123 -0
- memos/vec_dbs/__init__.py +0 -0
- memos/vec_dbs/base.py +117 -0
- memos/vec_dbs/factory.py +23 -0
- memos/vec_dbs/item.py +50 -0
- memos/vec_dbs/milvus.py +654 -0
- 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)
|