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,380 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import os
|
|
3
|
+
import subprocess
|
|
4
|
+
import time
|
|
5
|
+
|
|
6
|
+
from collections.abc import Callable
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from memos.context.context import ContextThread
|
|
10
|
+
from memos.dependency import require_python_package
|
|
11
|
+
from memos.log import get_logger
|
|
12
|
+
from memos.mem_scheduler.general_modules.base import BaseSchedulerModule
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
logger = get_logger(__name__)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class RedisSchedulerModule(BaseSchedulerModule):
|
|
19
|
+
@require_python_package(
|
|
20
|
+
import_name="redis",
|
|
21
|
+
install_command="pip install redis",
|
|
22
|
+
install_link="https://redis.readthedocs.io/en/stable/",
|
|
23
|
+
)
|
|
24
|
+
def __init__(self):
|
|
25
|
+
"""
|
|
26
|
+
intent_detector: Object used for intent recognition (such as the above IntentDetector)
|
|
27
|
+
scheduler: The actual scheduling module/interface object
|
|
28
|
+
trigger_intents: The types of intents that need to be triggered (list)
|
|
29
|
+
"""
|
|
30
|
+
super().__init__()
|
|
31
|
+
|
|
32
|
+
# settings for redis
|
|
33
|
+
self.redis_host: str | None = None
|
|
34
|
+
self.redis_port: int | None = None
|
|
35
|
+
self.redis_db: int | None = None
|
|
36
|
+
self.redis_password: str | None = None
|
|
37
|
+
self.socket_timeout: float | None = None
|
|
38
|
+
self.socket_connect_timeout: float | None = None
|
|
39
|
+
self._redis_conn = None
|
|
40
|
+
self._local_redis_process = None
|
|
41
|
+
self.query_list_capacity = 1000
|
|
42
|
+
|
|
43
|
+
self._redis_listener_running = False
|
|
44
|
+
self._redis_listener_thread: ContextThread | None = None
|
|
45
|
+
self._redis_listener_loop: asyncio.AbstractEventLoop | None = None
|
|
46
|
+
|
|
47
|
+
@property
|
|
48
|
+
def redis(self) -> Any:
|
|
49
|
+
if self._redis_conn is None:
|
|
50
|
+
self.auto_initialize_redis()
|
|
51
|
+
return self._redis_conn
|
|
52
|
+
|
|
53
|
+
@redis.setter
|
|
54
|
+
def redis(self, value: Any) -> None:
|
|
55
|
+
self._redis_conn = value
|
|
56
|
+
|
|
57
|
+
def initialize_redis(
|
|
58
|
+
self,
|
|
59
|
+
redis_host: str = "localhost",
|
|
60
|
+
redis_port: int = 6379,
|
|
61
|
+
redis_db: int = 0,
|
|
62
|
+
redis_password: str | None = None,
|
|
63
|
+
socket_timeout: float | None = None,
|
|
64
|
+
socket_connect_timeout: float | None = None,
|
|
65
|
+
):
|
|
66
|
+
import redis
|
|
67
|
+
|
|
68
|
+
self.redis_host = redis_host
|
|
69
|
+
self.redis_port = redis_port
|
|
70
|
+
self.redis_db = redis_db
|
|
71
|
+
self.redis_password = redis_password
|
|
72
|
+
self.socket_timeout = socket_timeout
|
|
73
|
+
self.socket_connect_timeout = socket_connect_timeout
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
logger.debug(f"Connecting to Redis at {redis_host}:{redis_port}/{redis_db}")
|
|
77
|
+
redis_kwargs = {
|
|
78
|
+
"host": self.redis_host,
|
|
79
|
+
"port": self.redis_port,
|
|
80
|
+
"db": self.redis_db,
|
|
81
|
+
"password": redis_password,
|
|
82
|
+
"decode_responses": True,
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
# Add timeout parameters if provided
|
|
86
|
+
if socket_timeout is not None:
|
|
87
|
+
redis_kwargs["socket_timeout"] = socket_timeout
|
|
88
|
+
if socket_connect_timeout is not None:
|
|
89
|
+
redis_kwargs["socket_connect_timeout"] = socket_connect_timeout
|
|
90
|
+
|
|
91
|
+
self._redis_conn = redis.Redis(**redis_kwargs)
|
|
92
|
+
# test conn
|
|
93
|
+
if not self._redis_conn.ping():
|
|
94
|
+
logger.error("Redis connection failed")
|
|
95
|
+
except redis.ConnectionError as e:
|
|
96
|
+
self._redis_conn = None
|
|
97
|
+
logger.error(f"Redis connection error: {e}")
|
|
98
|
+
self._redis_conn.xtrim("user:queries:stream", self.query_list_capacity)
|
|
99
|
+
return self._redis_conn
|
|
100
|
+
|
|
101
|
+
@require_python_package(
|
|
102
|
+
import_name="redis",
|
|
103
|
+
install_command="pip install redis",
|
|
104
|
+
install_link="https://redis.readthedocs.io/en/stable/",
|
|
105
|
+
)
|
|
106
|
+
def auto_initialize_redis(self) -> bool:
|
|
107
|
+
"""
|
|
108
|
+
Auto-initialize Redis with fallback strategies:
|
|
109
|
+
1. Try to initialize from config
|
|
110
|
+
2. Try to initialize from environment variables
|
|
111
|
+
3. Try to start local Redis server as fallback
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
bool: True if Redis connection is successfully established, False otherwise
|
|
115
|
+
"""
|
|
116
|
+
# Skip remote initialization in CI/pytest unless explicitly enabled
|
|
117
|
+
enable_env = os.getenv("MEMOS_ENABLE_REDIS", "").lower() == "true"
|
|
118
|
+
in_ci = os.getenv("CI", "").lower() == "true"
|
|
119
|
+
in_pytest = os.getenv("PYTEST_CURRENT_TEST") is not None
|
|
120
|
+
if (in_ci or in_pytest) and not enable_env:
|
|
121
|
+
logger.info(
|
|
122
|
+
"Skipping Redis auto-initialization in CI/test environment. Set MEMOS_ENABLE_REDIS=true to enable."
|
|
123
|
+
)
|
|
124
|
+
return False
|
|
125
|
+
|
|
126
|
+
import redis
|
|
127
|
+
|
|
128
|
+
# Strategy 1: Try to initialize from config
|
|
129
|
+
if hasattr(self, "config") and hasattr(self.config, "redis_config"):
|
|
130
|
+
try:
|
|
131
|
+
redis_config = self.config.redis_config
|
|
132
|
+
logger.info("Attempting to initialize Redis from config")
|
|
133
|
+
|
|
134
|
+
self._redis_conn = redis.Redis(
|
|
135
|
+
host=redis_config.get("host", "localhost"),
|
|
136
|
+
port=redis_config.get("port", 6379),
|
|
137
|
+
db=redis_config.get("db", 0),
|
|
138
|
+
password=redis_config.get("password", None),
|
|
139
|
+
decode_responses=True,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
# Test connection
|
|
143
|
+
if self._redis_conn.ping():
|
|
144
|
+
logger.info("Redis initialized successfully from config")
|
|
145
|
+
self.redis_host = redis_config.get("host", "localhost")
|
|
146
|
+
self.redis_port = redis_config.get("port", 6379)
|
|
147
|
+
self.redis_db = redis_config.get("db", 0)
|
|
148
|
+
self.redis_password = redis_config.get("password", None)
|
|
149
|
+
self.socket_timeout = redis_config.get("socket_timeout", None)
|
|
150
|
+
self.socket_connect_timeout = redis_config.get("socket_connect_timeout", None)
|
|
151
|
+
return True
|
|
152
|
+
else:
|
|
153
|
+
logger.warning("Redis config connection test failed")
|
|
154
|
+
self._redis_conn = None
|
|
155
|
+
except Exception as e:
|
|
156
|
+
logger.warning(f"Failed to initialize Redis from config: {e}")
|
|
157
|
+
self._redis_conn = None
|
|
158
|
+
|
|
159
|
+
# Strategy 2: Try to initialize from environment variables
|
|
160
|
+
try:
|
|
161
|
+
redis_host = os.getenv("MEMSCHEDULER_REDIS_HOST", "localhost")
|
|
162
|
+
redis_port = int(os.getenv("MEMSCHEDULER_REDIS_PORT", "6379"))
|
|
163
|
+
redis_db = int(os.getenv("MEMSCHEDULER_REDIS_DB", "0"))
|
|
164
|
+
redis_password = os.getenv("MEMSCHEDULER_REDIS_PASSWORD", None)
|
|
165
|
+
socket_timeout = os.getenv("MEMSCHEDULER_REDIS_TIMEOUT", None)
|
|
166
|
+
socket_connect_timeout = os.getenv("MEMSCHEDULER_REDIS_CONNECT_TIMEOUT", None)
|
|
167
|
+
|
|
168
|
+
logger.info(
|
|
169
|
+
f"Attempting to initialize Redis from environment variables: {redis_host}:{redis_port}"
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
redis_kwargs = {
|
|
173
|
+
"host": redis_host,
|
|
174
|
+
"port": redis_port,
|
|
175
|
+
"db": redis_db,
|
|
176
|
+
"password": redis_password,
|
|
177
|
+
"decode_responses": True,
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
# Add timeout parameters if provided
|
|
181
|
+
if socket_timeout is not None:
|
|
182
|
+
try:
|
|
183
|
+
redis_kwargs["socket_timeout"] = float(socket_timeout)
|
|
184
|
+
except ValueError:
|
|
185
|
+
logger.warning(
|
|
186
|
+
f"Invalid MEMSCHEDULER_REDIS_TIMEOUT value: {socket_timeout}, ignoring"
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
if socket_connect_timeout is not None:
|
|
190
|
+
try:
|
|
191
|
+
redis_kwargs["socket_connect_timeout"] = float(socket_connect_timeout)
|
|
192
|
+
except ValueError:
|
|
193
|
+
logger.warning(
|
|
194
|
+
f"Invalid MEMSCHEDULER_REDIS_CONNECT_TIMEOUT value: {socket_connect_timeout}, ignoring"
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
self._redis_conn = redis.Redis(**redis_kwargs)
|
|
198
|
+
|
|
199
|
+
# Test connection
|
|
200
|
+
if self._redis_conn.ping():
|
|
201
|
+
logger.info("Redis initialized successfully from environment variables")
|
|
202
|
+
self.redis_host = redis_host
|
|
203
|
+
self.redis_port = redis_port
|
|
204
|
+
self.redis_db = redis_db
|
|
205
|
+
self.redis_password = redis_password
|
|
206
|
+
self.socket_timeout = float(socket_timeout) if socket_timeout is not None else None
|
|
207
|
+
self.socket_connect_timeout = (
|
|
208
|
+
float(socket_connect_timeout) if socket_connect_timeout is not None else None
|
|
209
|
+
)
|
|
210
|
+
return True
|
|
211
|
+
else:
|
|
212
|
+
logger.warning("Redis environment connection test failed")
|
|
213
|
+
self._redis_conn = None
|
|
214
|
+
except Exception as e:
|
|
215
|
+
logger.warning(f"Failed to initialize Redis from environment variables: {e}")
|
|
216
|
+
self._redis_conn = None
|
|
217
|
+
|
|
218
|
+
# Strategy 3: Try to start local Redis server as fallback
|
|
219
|
+
try:
|
|
220
|
+
logger.warning(
|
|
221
|
+
"Attempting to start local Redis server as fallback (not recommended for production)"
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
# Try to start Redis server locally
|
|
225
|
+
self._local_redis_process = subprocess.Popen(
|
|
226
|
+
["redis-server", "--port", "6379", "--daemonize", "no"],
|
|
227
|
+
stdout=subprocess.PIPE,
|
|
228
|
+
stderr=subprocess.PIPE,
|
|
229
|
+
preexec_fn=os.setsid if hasattr(os, "setsid") else None,
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
# Wait a moment for Redis to start
|
|
233
|
+
time.sleep(0.5)
|
|
234
|
+
|
|
235
|
+
# Try to connect to local Redis
|
|
236
|
+
self._redis_conn = redis.Redis(host="localhost", port=6379, db=0, decode_responses=True)
|
|
237
|
+
|
|
238
|
+
# Test connection
|
|
239
|
+
if self._redis_conn.ping():
|
|
240
|
+
logger.warning("Local Redis server started and connected successfully")
|
|
241
|
+
logger.warning("WARNING: Using local Redis server - not suitable for production!")
|
|
242
|
+
self.redis_host = "localhost"
|
|
243
|
+
self.redis_port = 6379
|
|
244
|
+
self.redis_db = 0
|
|
245
|
+
self.redis_password = None
|
|
246
|
+
self.socket_timeout = None
|
|
247
|
+
self.socket_connect_timeout = None
|
|
248
|
+
return True
|
|
249
|
+
else:
|
|
250
|
+
logger.error("Local Redis server connection test failed")
|
|
251
|
+
self._cleanup_local_redis()
|
|
252
|
+
return False
|
|
253
|
+
|
|
254
|
+
except Exception as e:
|
|
255
|
+
logger.error(f"Failed to start local Redis server: {e}")
|
|
256
|
+
self._cleanup_local_redis()
|
|
257
|
+
return False
|
|
258
|
+
|
|
259
|
+
def _cleanup_local_redis(self):
|
|
260
|
+
"""Clean up local Redis process if it exists"""
|
|
261
|
+
if self._local_redis_process:
|
|
262
|
+
try:
|
|
263
|
+
self._local_redis_process.terminate()
|
|
264
|
+
self._local_redis_process.wait(timeout=5)
|
|
265
|
+
logger.info("Local Redis process terminated")
|
|
266
|
+
except subprocess.TimeoutExpired:
|
|
267
|
+
logger.warning("Local Redis process did not terminate gracefully, killing it")
|
|
268
|
+
self._local_redis_process.kill()
|
|
269
|
+
self._local_redis_process.wait()
|
|
270
|
+
except Exception as e:
|
|
271
|
+
logger.error(f"Error cleaning up local Redis process: {e}")
|
|
272
|
+
finally:
|
|
273
|
+
self._local_redis_process = None
|
|
274
|
+
|
|
275
|
+
def _cleanup_redis_resources(self):
|
|
276
|
+
"""Clean up Redis connection and local process"""
|
|
277
|
+
if self._redis_conn:
|
|
278
|
+
try:
|
|
279
|
+
self._redis_conn.close()
|
|
280
|
+
logger.info("Redis connection closed")
|
|
281
|
+
except Exception as e:
|
|
282
|
+
logger.error(f"Error closing Redis connection: {e}")
|
|
283
|
+
finally:
|
|
284
|
+
self._redis_conn = None
|
|
285
|
+
|
|
286
|
+
self._cleanup_local_redis()
|
|
287
|
+
|
|
288
|
+
def redis_add_message_stream(self, message: dict):
|
|
289
|
+
logger.debug(f"add_message_stream: {message}")
|
|
290
|
+
return self._redis_conn.xadd("user:queries:stream", message)
|
|
291
|
+
|
|
292
|
+
async def redis_consume_message_stream(self, message: dict):
|
|
293
|
+
logger.debug(f"consume_message_stream: {message}")
|
|
294
|
+
|
|
295
|
+
def _redis_run_listener_async(self, handler: Callable):
|
|
296
|
+
"""Run the async listener in a separate thread"""
|
|
297
|
+
self._redis_listener_loop = asyncio.new_event_loop()
|
|
298
|
+
asyncio.set_event_loop(self._redis_listener_loop)
|
|
299
|
+
|
|
300
|
+
async def listener_wrapper():
|
|
301
|
+
try:
|
|
302
|
+
await self.__redis_listen_query_stream(handler)
|
|
303
|
+
except Exception as e:
|
|
304
|
+
logger.error(f"Listener thread error: {e}")
|
|
305
|
+
finally:
|
|
306
|
+
self._redis_listener_running = False
|
|
307
|
+
|
|
308
|
+
self._redis_listener_loop.run_until_complete(listener_wrapper())
|
|
309
|
+
|
|
310
|
+
async def __redis_listen_query_stream(
|
|
311
|
+
self, handler=None, last_id: str = "$", block_time: int = 2000
|
|
312
|
+
):
|
|
313
|
+
"""Internal async stream listener"""
|
|
314
|
+
import redis
|
|
315
|
+
|
|
316
|
+
self._redis_listener_running = True
|
|
317
|
+
while self._redis_listener_running:
|
|
318
|
+
try:
|
|
319
|
+
# Blocking read for new messages
|
|
320
|
+
messages = self.redis.xread(
|
|
321
|
+
{"user:queries:stream": last_id}, count=1, block=block_time
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
if messages:
|
|
325
|
+
for _, stream_messages in messages:
|
|
326
|
+
for message_id, message_data in stream_messages:
|
|
327
|
+
try:
|
|
328
|
+
print(f"deal with message_data {message_data}")
|
|
329
|
+
await handler(message_data)
|
|
330
|
+
last_id = message_id
|
|
331
|
+
except Exception as e:
|
|
332
|
+
logger.error(f"Error processing message {message_id}: {e}")
|
|
333
|
+
|
|
334
|
+
except redis.ConnectionError as e:
|
|
335
|
+
logger.error(f"Redis connection error: {e}")
|
|
336
|
+
await asyncio.sleep(5) # Wait before reconnecting
|
|
337
|
+
self._redis_conn = None # Force reconnection
|
|
338
|
+
except Exception as e:
|
|
339
|
+
logger.error(f"Unexpected error: {e}")
|
|
340
|
+
await asyncio.sleep(1)
|
|
341
|
+
|
|
342
|
+
def redis_start_listening(self, handler: Callable | None = None):
|
|
343
|
+
"""Start the Redis stream listener in a background thread"""
|
|
344
|
+
if self._redis_listener_thread and self._redis_listener_thread.is_alive():
|
|
345
|
+
logger.warning("Listener is already running")
|
|
346
|
+
return
|
|
347
|
+
|
|
348
|
+
# Check Redis connection before starting listener
|
|
349
|
+
if self.redis is None:
|
|
350
|
+
logger.warning(
|
|
351
|
+
"Redis connection is None, attempting to auto-initialize before starting listener..."
|
|
352
|
+
)
|
|
353
|
+
if not self.auto_initialize_redis():
|
|
354
|
+
logger.error("Failed to initialize Redis connection, cannot start listener")
|
|
355
|
+
return
|
|
356
|
+
|
|
357
|
+
if handler is None:
|
|
358
|
+
handler = self.redis_consume_message_stream
|
|
359
|
+
|
|
360
|
+
self._redis_listener_thread = ContextThread(
|
|
361
|
+
target=self._redis_run_listener_async,
|
|
362
|
+
args=(handler,),
|
|
363
|
+
daemon=True,
|
|
364
|
+
name="RedisListenerThread",
|
|
365
|
+
)
|
|
366
|
+
self._redis_listener_thread.start()
|
|
367
|
+
logger.info("Started Redis stream listener thread")
|
|
368
|
+
|
|
369
|
+
def redis_stop_listening(self):
|
|
370
|
+
"""Stop the listener thread gracefully"""
|
|
371
|
+
self._redis_listener_running = False
|
|
372
|
+
if self._redis_listener_thread and self._redis_listener_thread.is_alive():
|
|
373
|
+
self._redis_listener_thread.join(timeout=5.0)
|
|
374
|
+
if self._redis_listener_thread.is_alive():
|
|
375
|
+
logger.warning("Listener thread did not stop gracefully")
|
|
376
|
+
logger.info("Redis stream listener stopped")
|
|
377
|
+
|
|
378
|
+
def redis_close(self):
|
|
379
|
+
"""Close Redis connection and clean up resources"""
|
|
380
|
+
self._cleanup_redis_resources()
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
from typing import Any, ClassVar
|
|
2
|
+
|
|
3
|
+
from memos.configs.mem_user import UserManagerConfigFactory
|
|
4
|
+
from memos.mem_user.mysql_user_manager import MySQLUserManager
|
|
5
|
+
from memos.mem_user.user_manager import UserManager
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class UserManagerFactory:
|
|
9
|
+
"""Factory class for creating user manager instances."""
|
|
10
|
+
|
|
11
|
+
backend_to_class: ClassVar[dict[str, Any]] = {
|
|
12
|
+
"sqlite": UserManager,
|
|
13
|
+
"mysql": MySQLUserManager,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
@classmethod
|
|
17
|
+
def from_config(
|
|
18
|
+
cls, config_factory: UserManagerConfigFactory
|
|
19
|
+
) -> UserManager | MySQLUserManager:
|
|
20
|
+
"""Create a user manager instance from configuration.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
config_factory: Configuration factory containing backend and config
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
User manager instance
|
|
27
|
+
|
|
28
|
+
Raises:
|
|
29
|
+
ValueError: If backend is not supported
|
|
30
|
+
"""
|
|
31
|
+
backend = config_factory.backend
|
|
32
|
+
if backend not in cls.backend_to_class:
|
|
33
|
+
raise ValueError(f"Invalid user manager backend: {backend}")
|
|
34
|
+
|
|
35
|
+
user_manager_class = cls.backend_to_class[backend]
|
|
36
|
+
config = config_factory.config
|
|
37
|
+
|
|
38
|
+
# Use model_dump() to convert Pydantic model to dict and unpack as kwargs
|
|
39
|
+
return user_manager_class(**config.model_dump())
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
def create_sqlite(cls, db_path: str | None = None, user_id: str = "root") -> UserManager:
|
|
43
|
+
"""Create SQLite user manager with default configuration.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
db_path: Path to SQLite database file
|
|
47
|
+
user_id: Default user ID for initialization
|
|
48
|
+
|
|
49
|
+
Returns:
|
|
50
|
+
SQLite user manager instance
|
|
51
|
+
"""
|
|
52
|
+
config_factory = UserManagerConfigFactory(
|
|
53
|
+
backend="sqlite", config={"db_path": db_path, "user_id": user_id}
|
|
54
|
+
)
|
|
55
|
+
return cls.from_config(config_factory)
|
|
56
|
+
|
|
57
|
+
@classmethod
|
|
58
|
+
def create_mysql(
|
|
59
|
+
cls,
|
|
60
|
+
user_id: str = "root",
|
|
61
|
+
host: str = "localhost",
|
|
62
|
+
port: int = 3306,
|
|
63
|
+
username: str = "root",
|
|
64
|
+
password: str = "",
|
|
65
|
+
database: str = "memos_users",
|
|
66
|
+
charset: str = "utf8mb4",
|
|
67
|
+
) -> MySQLUserManager:
|
|
68
|
+
"""Create MySQL user manager with specified configuration.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
user_id: Default user ID for initialization
|
|
72
|
+
host: MySQL server host
|
|
73
|
+
port: MySQL server port
|
|
74
|
+
username: MySQL username
|
|
75
|
+
password: MySQL password
|
|
76
|
+
database: MySQL database name
|
|
77
|
+
charset: MySQL charset
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
MySQL user manager instance
|
|
81
|
+
"""
|
|
82
|
+
config_factory = UserManagerConfigFactory(
|
|
83
|
+
backend="mysql",
|
|
84
|
+
config={
|
|
85
|
+
"user_id": user_id,
|
|
86
|
+
"host": host,
|
|
87
|
+
"port": port,
|
|
88
|
+
"username": username,
|
|
89
|
+
"password": password,
|
|
90
|
+
"database": database,
|
|
91
|
+
"charset": charset,
|
|
92
|
+
},
|
|
93
|
+
)
|
|
94
|
+
return cls.from_config(config_factory)
|