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,260 @@
|
|
|
1
|
+
"""Persistent user management system for MemOS with configuration storage.
|
|
2
|
+
|
|
3
|
+
This module extends the base UserManager to provide persistent storage
|
|
4
|
+
for user configurations and MOS instances.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
|
|
9
|
+
from datetime import datetime
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
from sqlalchemy import Column, String, Text
|
|
13
|
+
|
|
14
|
+
from memos.configs.mem_os import MOSConfig
|
|
15
|
+
from memos.log import get_logger
|
|
16
|
+
from memos.mem_user.user_manager import Base, UserManager
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
logger = get_logger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class UserConfig(Base):
|
|
23
|
+
"""User configuration model for the database."""
|
|
24
|
+
|
|
25
|
+
__tablename__ = "user_configs"
|
|
26
|
+
|
|
27
|
+
user_id = Column(String, primary_key=True)
|
|
28
|
+
config_data = Column(Text, nullable=False) # JSON string of MOSConfig
|
|
29
|
+
created_at = Column(String, nullable=False) # ISO format timestamp
|
|
30
|
+
updated_at = Column(String, nullable=False) # ISO format timestamp
|
|
31
|
+
|
|
32
|
+
def __repr__(self):
|
|
33
|
+
return f"<UserConfig(user_id='{self.user_id}')>"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class PersistentUserManager(UserManager):
|
|
37
|
+
"""Extended UserManager with configuration persistence."""
|
|
38
|
+
|
|
39
|
+
def __init__(self, db_path: str | None = None, user_id: str = "root"):
|
|
40
|
+
"""Initialize the persistent user manager.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
db_path (str, optional): Path to the SQLite database file.
|
|
44
|
+
If None, uses default path in MEMOS_DIR.
|
|
45
|
+
user_id (str, optional): User ID. If None, uses default user ID.
|
|
46
|
+
"""
|
|
47
|
+
super().__init__(db_path, user_id)
|
|
48
|
+
|
|
49
|
+
# Create user_configs table
|
|
50
|
+
Base.metadata.create_all(bind=self.engine)
|
|
51
|
+
logger.info("PersistentUserManager initialized with configuration storage")
|
|
52
|
+
|
|
53
|
+
def _convert_datetime_strings(self, obj: Any) -> Any:
|
|
54
|
+
"""Recursively convert datetime strings back to datetime objects in config dict.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
obj: The object to process (dict, list, or primitive type)
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
The object with datetime strings converted to datetime objects
|
|
61
|
+
"""
|
|
62
|
+
if isinstance(obj, dict):
|
|
63
|
+
result = {}
|
|
64
|
+
for key, value in obj.items():
|
|
65
|
+
if key == "created_at" and isinstance(value, str):
|
|
66
|
+
try:
|
|
67
|
+
result[key] = datetime.fromisoformat(value)
|
|
68
|
+
except ValueError:
|
|
69
|
+
# If parsing fails, keep the original string
|
|
70
|
+
result[key] = value
|
|
71
|
+
else:
|
|
72
|
+
result[key] = self._convert_datetime_strings(value)
|
|
73
|
+
return result
|
|
74
|
+
elif isinstance(obj, list):
|
|
75
|
+
return [self._convert_datetime_strings(item) for item in obj]
|
|
76
|
+
else:
|
|
77
|
+
return obj
|
|
78
|
+
|
|
79
|
+
def save_user_config(self, user_id: str, config: MOSConfig) -> bool:
|
|
80
|
+
"""Save user configuration to database.
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
user_id (str): The user ID.
|
|
84
|
+
config (MOSConfig): The user's MOS configuration.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
bool: True if successful, False otherwise.
|
|
88
|
+
"""
|
|
89
|
+
session = self._get_session()
|
|
90
|
+
try:
|
|
91
|
+
# Convert config to JSON string with proper datetime handling
|
|
92
|
+
config_dict = config.model_dump(mode="json")
|
|
93
|
+
config_json = json.dumps(config_dict, indent=2)
|
|
94
|
+
|
|
95
|
+
from datetime import datetime
|
|
96
|
+
|
|
97
|
+
now = datetime.now().isoformat()
|
|
98
|
+
|
|
99
|
+
# Check if config already exists
|
|
100
|
+
existing_config = (
|
|
101
|
+
session.query(UserConfig).filter(UserConfig.user_id == user_id).first()
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
if existing_config:
|
|
105
|
+
# Update existing config
|
|
106
|
+
existing_config.config_data = config_json
|
|
107
|
+
existing_config.updated_at = now
|
|
108
|
+
logger.info(f"Updated configuration for user {user_id}")
|
|
109
|
+
else:
|
|
110
|
+
# Create new config
|
|
111
|
+
user_config = UserConfig(
|
|
112
|
+
user_id=user_id, config_data=config_json, created_at=now, updated_at=now
|
|
113
|
+
)
|
|
114
|
+
session.add(user_config)
|
|
115
|
+
logger.info(f"Saved new configuration for user {user_id}")
|
|
116
|
+
|
|
117
|
+
session.commit()
|
|
118
|
+
return True
|
|
119
|
+
|
|
120
|
+
except Exception as e:
|
|
121
|
+
session.rollback()
|
|
122
|
+
logger.error(f"Error saving user config for {user_id}: {e}")
|
|
123
|
+
return False
|
|
124
|
+
finally:
|
|
125
|
+
session.close()
|
|
126
|
+
|
|
127
|
+
def get_user_config(self, user_id: str) -> MOSConfig | None:
|
|
128
|
+
"""Get user configuration from database.
|
|
129
|
+
|
|
130
|
+
Args:
|
|
131
|
+
user_id (str): The user ID.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
MOSConfig | None: The user's configuration or None if not found.
|
|
135
|
+
"""
|
|
136
|
+
session = self._get_session()
|
|
137
|
+
try:
|
|
138
|
+
user_config = session.query(UserConfig).filter(UserConfig.user_id == user_id).first()
|
|
139
|
+
|
|
140
|
+
if user_config:
|
|
141
|
+
config_dict = json.loads(user_config.config_data)
|
|
142
|
+
# Convert datetime strings back to datetime objects
|
|
143
|
+
config_dict = self._convert_datetime_strings(config_dict)
|
|
144
|
+
return MOSConfig(**config_dict)
|
|
145
|
+
return None
|
|
146
|
+
|
|
147
|
+
except Exception as e:
|
|
148
|
+
logger.error(f"Error loading user config for {user_id}: {e}")
|
|
149
|
+
return None
|
|
150
|
+
finally:
|
|
151
|
+
session.close()
|
|
152
|
+
|
|
153
|
+
def delete_user_config(self, user_id: str) -> bool:
|
|
154
|
+
"""Delete user configuration from database.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
user_id (str): The user ID.
|
|
158
|
+
|
|
159
|
+
Returns:
|
|
160
|
+
bool: True if successful, False otherwise.
|
|
161
|
+
"""
|
|
162
|
+
session = self._get_session()
|
|
163
|
+
try:
|
|
164
|
+
user_config = session.query(UserConfig).filter(UserConfig.user_id == user_id).first()
|
|
165
|
+
|
|
166
|
+
if user_config:
|
|
167
|
+
session.delete(user_config)
|
|
168
|
+
session.commit()
|
|
169
|
+
logger.info(f"Deleted configuration for user {user_id}")
|
|
170
|
+
return True
|
|
171
|
+
return False
|
|
172
|
+
|
|
173
|
+
except Exception as e:
|
|
174
|
+
session.rollback()
|
|
175
|
+
logger.error(f"Error deleting user config for {user_id}: {e}")
|
|
176
|
+
return False
|
|
177
|
+
finally:
|
|
178
|
+
session.close()
|
|
179
|
+
|
|
180
|
+
def list_user_configs(self, limit: int = 1) -> dict[str, MOSConfig]:
|
|
181
|
+
"""List all user configurations.
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
Dict[str, MOSConfig]: Dictionary mapping user_id to MOSConfig.
|
|
185
|
+
"""
|
|
186
|
+
session = self._get_session()
|
|
187
|
+
try:
|
|
188
|
+
user_configs = session.query(UserConfig).limit(limit).all()
|
|
189
|
+
result = {}
|
|
190
|
+
|
|
191
|
+
for user_config in user_configs:
|
|
192
|
+
try:
|
|
193
|
+
config_dict = json.loads(user_config.config_data)
|
|
194
|
+
# Convert datetime strings back to datetime objects
|
|
195
|
+
config_dict = self._convert_datetime_strings(config_dict)
|
|
196
|
+
result[user_config.user_id] = MOSConfig(**config_dict)
|
|
197
|
+
except Exception as e:
|
|
198
|
+
logger.error(f"Error parsing config for user {user_config.user_id}: {e}")
|
|
199
|
+
continue
|
|
200
|
+
|
|
201
|
+
return result
|
|
202
|
+
|
|
203
|
+
except Exception as e:
|
|
204
|
+
logger.error(f"Error listing user configs: {e}")
|
|
205
|
+
return {}
|
|
206
|
+
finally:
|
|
207
|
+
session.close()
|
|
208
|
+
|
|
209
|
+
def create_user_with_config(
|
|
210
|
+
self, user_name: str, config: MOSConfig, role=None, user_id: str | None = None
|
|
211
|
+
) -> str:
|
|
212
|
+
"""Create a new user with configuration.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
user_name (str): Name of the user.
|
|
216
|
+
config (MOSConfig): The user's configuration.
|
|
217
|
+
role: User role (optional, uses default from UserManager).
|
|
218
|
+
user_id (str, optional): Custom user ID.
|
|
219
|
+
|
|
220
|
+
Returns:
|
|
221
|
+
str: The created user ID.
|
|
222
|
+
|
|
223
|
+
Raises:
|
|
224
|
+
ValueError: If user_name already exists.
|
|
225
|
+
"""
|
|
226
|
+
# Create user using parent method
|
|
227
|
+
created_user_id = self.create_user(user_name, role, user_id)
|
|
228
|
+
|
|
229
|
+
# Save configuration
|
|
230
|
+
if not self.save_user_config(created_user_id, config):
|
|
231
|
+
logger.error(f"Failed to save configuration for user {created_user_id}")
|
|
232
|
+
|
|
233
|
+
return created_user_id
|
|
234
|
+
|
|
235
|
+
def delete_user(self, user_id: str) -> bool:
|
|
236
|
+
"""Delete a user and their configuration.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
user_id (str): The user ID.
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
bool: True if successful, False otherwise.
|
|
243
|
+
"""
|
|
244
|
+
# Delete configuration first
|
|
245
|
+
self.delete_user_config(user_id)
|
|
246
|
+
|
|
247
|
+
# Delete user using parent method
|
|
248
|
+
return super().delete_user(user_id)
|
|
249
|
+
|
|
250
|
+
def get_user_cube_access(self, user_id: str) -> list[str]:
|
|
251
|
+
"""Get list of cube IDs that a user has access to.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
user_id (str): The user ID.
|
|
255
|
+
|
|
256
|
+
Returns:
|
|
257
|
+
list[str]: List of cube IDs the user can access.
|
|
258
|
+
"""
|
|
259
|
+
cubes = self.get_user_cubes(user_id)
|
|
260
|
+
return [cube.cube_id for cube in cubes]
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"""Redis-based persistent user management system for MemOS with configuration storage.
|
|
2
|
+
|
|
3
|
+
This module provides persistent storage for user configurations using Redis.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
|
|
8
|
+
from memos.configs.mem_os import MOSConfig
|
|
9
|
+
from memos.dependency import require_python_package
|
|
10
|
+
from memos.log import get_logger
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
logger = get_logger(__name__)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class RedisPersistentUserManager:
|
|
17
|
+
"""Redis-based user configuration manager with persistence."""
|
|
18
|
+
|
|
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__(
|
|
25
|
+
self,
|
|
26
|
+
host: str = "localhost",
|
|
27
|
+
port: int = 6379,
|
|
28
|
+
password: str = "",
|
|
29
|
+
db: int = 0,
|
|
30
|
+
decode_responses: bool = True,
|
|
31
|
+
):
|
|
32
|
+
"""Initialize the Redis persistent user manager.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
user_id (str, optional): User ID. Defaults to "root".
|
|
36
|
+
host (str): Redis server host. Defaults to "localhost".
|
|
37
|
+
port (int): Redis server port. Defaults to 6379.
|
|
38
|
+
password (str): Redis password. Defaults to "".
|
|
39
|
+
db (int): Redis database number. Defaults to 0.
|
|
40
|
+
decode_responses (bool): Whether to decode responses to strings. Defaults to True.
|
|
41
|
+
"""
|
|
42
|
+
import redis
|
|
43
|
+
|
|
44
|
+
self.host = host
|
|
45
|
+
self.port = port
|
|
46
|
+
self.db = db
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
# Create Redis connection
|
|
50
|
+
self._redis_client = redis.Redis(
|
|
51
|
+
host=host,
|
|
52
|
+
port=port,
|
|
53
|
+
password=password if password else None,
|
|
54
|
+
db=db,
|
|
55
|
+
decode_responses=decode_responses,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Test connection
|
|
59
|
+
if not self._redis_client.ping():
|
|
60
|
+
raise ConnectionError("Redis connection failed")
|
|
61
|
+
|
|
62
|
+
logger.info(
|
|
63
|
+
f"RedisPersistentUserManager initialized successfully, connected to {host}:{port}/{db}"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
except Exception as e:
|
|
67
|
+
logger.error(f"Redis connection error: {e}")
|
|
68
|
+
raise
|
|
69
|
+
|
|
70
|
+
def _get_config_key(self, user_id: str) -> str:
|
|
71
|
+
"""Generate Redis key for user configuration.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
user_id (str): User ID.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
str: Redis key name.
|
|
78
|
+
"""
|
|
79
|
+
return user_id
|
|
80
|
+
|
|
81
|
+
def save_user_config(self, user_id: str, config: MOSConfig) -> bool:
|
|
82
|
+
"""Save user configuration to Redis.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
user_id (str): User ID.
|
|
86
|
+
config (MOSConfig): User's MOS configuration.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
bool: True if successful, False otherwise.
|
|
90
|
+
"""
|
|
91
|
+
try:
|
|
92
|
+
# Convert config to JSON string
|
|
93
|
+
config_dict = config.model_dump(mode="json")
|
|
94
|
+
config_json = json.dumps(config_dict, ensure_ascii=False, indent=2)
|
|
95
|
+
|
|
96
|
+
# Save to Redis
|
|
97
|
+
key = self._get_config_key(user_id)
|
|
98
|
+
self._redis_client.set(key, config_json)
|
|
99
|
+
|
|
100
|
+
logger.info(f"Successfully saved configuration for user {user_id} to Redis")
|
|
101
|
+
return True
|
|
102
|
+
|
|
103
|
+
except Exception as e:
|
|
104
|
+
logger.error(f"Error saving configuration for user {user_id}: {e}")
|
|
105
|
+
return False
|
|
106
|
+
|
|
107
|
+
def get_user_config(self, user_id: str) -> dict | None:
|
|
108
|
+
"""Get user configuration from Redis (search interface).
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
user_id (str): User ID.
|
|
112
|
+
|
|
113
|
+
Returns:
|
|
114
|
+
MOSConfig | None: User's configuration object, or None if not found.
|
|
115
|
+
"""
|
|
116
|
+
try:
|
|
117
|
+
# Get configuration from Redis
|
|
118
|
+
key = self._get_config_key(user_id)
|
|
119
|
+
config_json = self._redis_client.get(key)
|
|
120
|
+
|
|
121
|
+
if config_json is None:
|
|
122
|
+
logger.info(f"Configuration for user {user_id} does not exist")
|
|
123
|
+
return None
|
|
124
|
+
|
|
125
|
+
# Parse JSON and create MOSConfig object
|
|
126
|
+
config_dict = json.loads(config_json)
|
|
127
|
+
|
|
128
|
+
logger.info(f"Successfully retrieved configuration for user {user_id}")
|
|
129
|
+
return config_dict
|
|
130
|
+
|
|
131
|
+
except json.JSONDecodeError as e:
|
|
132
|
+
logger.error(f"Error parsing JSON configuration for user {user_id}: {e}")
|
|
133
|
+
return None
|
|
134
|
+
except Exception as e:
|
|
135
|
+
logger.error(f"Error retrieving configuration for user {user_id}: {e}")
|
|
136
|
+
return None
|
|
137
|
+
|
|
138
|
+
def delete_user_config(self, user_id: str) -> bool:
|
|
139
|
+
"""Delete user configuration from Redis.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
user_id (str): User ID.
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
bool: True if successful, False otherwise.
|
|
146
|
+
"""
|
|
147
|
+
try:
|
|
148
|
+
key = self._get_config_key(user_id)
|
|
149
|
+
result = self._redis_client.delete(key)
|
|
150
|
+
|
|
151
|
+
if result > 0:
|
|
152
|
+
logger.info(f"Successfully deleted configuration for user {user_id}")
|
|
153
|
+
return True
|
|
154
|
+
else:
|
|
155
|
+
logger.warning(f"Configuration for user {user_id} does not exist, cannot delete")
|
|
156
|
+
return False
|
|
157
|
+
|
|
158
|
+
except Exception as e:
|
|
159
|
+
logger.error(f"Error deleting configuration for user {user_id}: {e}")
|
|
160
|
+
return False
|
|
161
|
+
|
|
162
|
+
def exists_user_config(self, user_id: str) -> bool:
|
|
163
|
+
"""Check if user configuration exists.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
user_id (str): User ID.
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
bool: True if exists, False otherwise.
|
|
170
|
+
"""
|
|
171
|
+
try:
|
|
172
|
+
key = self._get_config_key(user_id)
|
|
173
|
+
return self._redis_client.exists(key) > 0
|
|
174
|
+
except Exception as e:
|
|
175
|
+
logger.error(f"Error checking if configuration exists for user {user_id}: {e}")
|
|
176
|
+
return False
|
|
177
|
+
|
|
178
|
+
def list_user_configs(
|
|
179
|
+
self, pattern: str = "user_config:*", count: int = 100
|
|
180
|
+
) -> dict[str, dict]:
|
|
181
|
+
"""List all user configurations.
|
|
182
|
+
|
|
183
|
+
Args:
|
|
184
|
+
pattern (str): Redis key matching pattern. Defaults to "user_config:*".
|
|
185
|
+
count (int): Number of keys to return per scan. Defaults to 100.
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
dict[str, dict]: Dictionary mapping user_id to dict objects.
|
|
189
|
+
"""
|
|
190
|
+
result = {}
|
|
191
|
+
try:
|
|
192
|
+
# Use SCAN command to iterate through all matching keys
|
|
193
|
+
cursor = 0
|
|
194
|
+
while True:
|
|
195
|
+
cursor, keys = self._redis_client.scan(cursor, match=pattern, count=count)
|
|
196
|
+
|
|
197
|
+
for key in keys:
|
|
198
|
+
# Extract user_id (remove "user_config:" prefix)
|
|
199
|
+
user_id = key.replace("user_config:", "")
|
|
200
|
+
config = self.get_user_config(user_id)
|
|
201
|
+
if config:
|
|
202
|
+
result[user_id] = config
|
|
203
|
+
|
|
204
|
+
if cursor == 0:
|
|
205
|
+
break
|
|
206
|
+
|
|
207
|
+
logger.info(f"Successfully listed {len(result)} user configurations")
|
|
208
|
+
return result
|
|
209
|
+
|
|
210
|
+
except Exception as e:
|
|
211
|
+
logger.error(f"Error listing user configurations: {e}")
|
|
212
|
+
return {}
|
|
213
|
+
|
|
214
|
+
def close(self) -> None:
|
|
215
|
+
"""Close Redis connection.
|
|
216
|
+
|
|
217
|
+
This method should be called when the RedisPersistentUserManager is no longer needed
|
|
218
|
+
to ensure proper cleanup of Redis connections.
|
|
219
|
+
"""
|
|
220
|
+
try:
|
|
221
|
+
if hasattr(self, "_redis_client") and self._redis_client:
|
|
222
|
+
self._redis_client.close()
|
|
223
|
+
logger.info("Redis connection closed")
|
|
224
|
+
except Exception as e:
|
|
225
|
+
logger.error(f"Error closing Redis connection: {e}")
|