MemoryOS 2.0.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (315) hide show
  1. memoryos-2.0.3.dist-info/METADATA +418 -0
  2. memoryos-2.0.3.dist-info/RECORD +315 -0
  3. memoryos-2.0.3.dist-info/WHEEL +4 -0
  4. memoryos-2.0.3.dist-info/entry_points.txt +3 -0
  5. memoryos-2.0.3.dist-info/licenses/LICENSE +201 -0
  6. memos/__init__.py +20 -0
  7. memos/api/client.py +571 -0
  8. memos/api/config.py +1018 -0
  9. memos/api/context/dependencies.py +50 -0
  10. memos/api/exceptions.py +53 -0
  11. memos/api/handlers/__init__.py +62 -0
  12. memos/api/handlers/add_handler.py +158 -0
  13. memos/api/handlers/base_handler.py +194 -0
  14. memos/api/handlers/chat_handler.py +1401 -0
  15. memos/api/handlers/component_init.py +388 -0
  16. memos/api/handlers/config_builders.py +190 -0
  17. memos/api/handlers/feedback_handler.py +93 -0
  18. memos/api/handlers/formatters_handler.py +237 -0
  19. memos/api/handlers/memory_handler.py +316 -0
  20. memos/api/handlers/scheduler_handler.py +497 -0
  21. memos/api/handlers/search_handler.py +222 -0
  22. memos/api/handlers/suggestion_handler.py +117 -0
  23. memos/api/mcp_serve.py +614 -0
  24. memos/api/middleware/request_context.py +101 -0
  25. memos/api/product_api.py +38 -0
  26. memos/api/product_models.py +1206 -0
  27. memos/api/routers/__init__.py +1 -0
  28. memos/api/routers/product_router.py +477 -0
  29. memos/api/routers/server_router.py +394 -0
  30. memos/api/server_api.py +44 -0
  31. memos/api/start_api.py +433 -0
  32. memos/chunkers/__init__.py +4 -0
  33. memos/chunkers/base.py +24 -0
  34. memos/chunkers/charactertext_chunker.py +41 -0
  35. memos/chunkers/factory.py +24 -0
  36. memos/chunkers/markdown_chunker.py +62 -0
  37. memos/chunkers/sentence_chunker.py +54 -0
  38. memos/chunkers/simple_chunker.py +50 -0
  39. memos/cli.py +113 -0
  40. memos/configs/__init__.py +0 -0
  41. memos/configs/base.py +82 -0
  42. memos/configs/chunker.py +59 -0
  43. memos/configs/embedder.py +88 -0
  44. memos/configs/graph_db.py +236 -0
  45. memos/configs/internet_retriever.py +100 -0
  46. memos/configs/llm.py +151 -0
  47. memos/configs/mem_agent.py +54 -0
  48. memos/configs/mem_chat.py +81 -0
  49. memos/configs/mem_cube.py +105 -0
  50. memos/configs/mem_os.py +83 -0
  51. memos/configs/mem_reader.py +91 -0
  52. memos/configs/mem_scheduler.py +385 -0
  53. memos/configs/mem_user.py +70 -0
  54. memos/configs/memory.py +324 -0
  55. memos/configs/parser.py +38 -0
  56. memos/configs/reranker.py +18 -0
  57. memos/configs/utils.py +8 -0
  58. memos/configs/vec_db.py +80 -0
  59. memos/context/context.py +355 -0
  60. memos/dependency.py +52 -0
  61. memos/deprecation.py +262 -0
  62. memos/embedders/__init__.py +0 -0
  63. memos/embedders/ark.py +95 -0
  64. memos/embedders/base.py +106 -0
  65. memos/embedders/factory.py +29 -0
  66. memos/embedders/ollama.py +77 -0
  67. memos/embedders/sentence_transformer.py +49 -0
  68. memos/embedders/universal_api.py +51 -0
  69. memos/exceptions.py +30 -0
  70. memos/graph_dbs/__init__.py +0 -0
  71. memos/graph_dbs/base.py +274 -0
  72. memos/graph_dbs/factory.py +27 -0
  73. memos/graph_dbs/item.py +46 -0
  74. memos/graph_dbs/nebular.py +1794 -0
  75. memos/graph_dbs/neo4j.py +1942 -0
  76. memos/graph_dbs/neo4j_community.py +1058 -0
  77. memos/graph_dbs/polardb.py +5446 -0
  78. memos/hello_world.py +97 -0
  79. memos/llms/__init__.py +0 -0
  80. memos/llms/base.py +25 -0
  81. memos/llms/deepseek.py +13 -0
  82. memos/llms/factory.py +38 -0
  83. memos/llms/hf.py +443 -0
  84. memos/llms/hf_singleton.py +114 -0
  85. memos/llms/ollama.py +135 -0
  86. memos/llms/openai.py +222 -0
  87. memos/llms/openai_new.py +198 -0
  88. memos/llms/qwen.py +13 -0
  89. memos/llms/utils.py +14 -0
  90. memos/llms/vllm.py +218 -0
  91. memos/log.py +237 -0
  92. memos/mem_agent/base.py +19 -0
  93. memos/mem_agent/deepsearch_agent.py +391 -0
  94. memos/mem_agent/factory.py +36 -0
  95. memos/mem_chat/__init__.py +0 -0
  96. memos/mem_chat/base.py +30 -0
  97. memos/mem_chat/factory.py +21 -0
  98. memos/mem_chat/simple.py +200 -0
  99. memos/mem_cube/__init__.py +0 -0
  100. memos/mem_cube/base.py +30 -0
  101. memos/mem_cube/general.py +240 -0
  102. memos/mem_cube/navie.py +172 -0
  103. memos/mem_cube/utils.py +169 -0
  104. memos/mem_feedback/base.py +15 -0
  105. memos/mem_feedback/feedback.py +1192 -0
  106. memos/mem_feedback/simple_feedback.py +40 -0
  107. memos/mem_feedback/utils.py +230 -0
  108. memos/mem_os/client.py +5 -0
  109. memos/mem_os/core.py +1203 -0
  110. memos/mem_os/main.py +582 -0
  111. memos/mem_os/product.py +1608 -0
  112. memos/mem_os/product_server.py +455 -0
  113. memos/mem_os/utils/default_config.py +359 -0
  114. memos/mem_os/utils/format_utils.py +1403 -0
  115. memos/mem_os/utils/reference_utils.py +162 -0
  116. memos/mem_reader/__init__.py +0 -0
  117. memos/mem_reader/base.py +47 -0
  118. memos/mem_reader/factory.py +53 -0
  119. memos/mem_reader/memory.py +298 -0
  120. memos/mem_reader/multi_modal_struct.py +965 -0
  121. memos/mem_reader/read_multi_modal/__init__.py +43 -0
  122. memos/mem_reader/read_multi_modal/assistant_parser.py +311 -0
  123. memos/mem_reader/read_multi_modal/base.py +273 -0
  124. memos/mem_reader/read_multi_modal/file_content_parser.py +826 -0
  125. memos/mem_reader/read_multi_modal/image_parser.py +359 -0
  126. memos/mem_reader/read_multi_modal/multi_modal_parser.py +252 -0
  127. memos/mem_reader/read_multi_modal/string_parser.py +139 -0
  128. memos/mem_reader/read_multi_modal/system_parser.py +327 -0
  129. memos/mem_reader/read_multi_modal/text_content_parser.py +131 -0
  130. memos/mem_reader/read_multi_modal/tool_parser.py +210 -0
  131. memos/mem_reader/read_multi_modal/user_parser.py +218 -0
  132. memos/mem_reader/read_multi_modal/utils.py +358 -0
  133. memos/mem_reader/simple_struct.py +912 -0
  134. memos/mem_reader/strategy_struct.py +163 -0
  135. memos/mem_reader/utils.py +157 -0
  136. memos/mem_scheduler/__init__.py +0 -0
  137. memos/mem_scheduler/analyzer/__init__.py +0 -0
  138. memos/mem_scheduler/analyzer/api_analyzer.py +714 -0
  139. memos/mem_scheduler/analyzer/eval_analyzer.py +219 -0
  140. memos/mem_scheduler/analyzer/mos_for_test_scheduler.py +571 -0
  141. memos/mem_scheduler/analyzer/scheduler_for_eval.py +280 -0
  142. memos/mem_scheduler/base_scheduler.py +1319 -0
  143. memos/mem_scheduler/general_modules/__init__.py +0 -0
  144. memos/mem_scheduler/general_modules/api_misc.py +137 -0
  145. memos/mem_scheduler/general_modules/base.py +80 -0
  146. memos/mem_scheduler/general_modules/init_components_for_scheduler.py +425 -0
  147. memos/mem_scheduler/general_modules/misc.py +313 -0
  148. memos/mem_scheduler/general_modules/scheduler_logger.py +389 -0
  149. memos/mem_scheduler/general_modules/task_threads.py +315 -0
  150. memos/mem_scheduler/general_scheduler.py +1495 -0
  151. memos/mem_scheduler/memory_manage_modules/__init__.py +5 -0
  152. memos/mem_scheduler/memory_manage_modules/memory_filter.py +306 -0
  153. memos/mem_scheduler/memory_manage_modules/retriever.py +547 -0
  154. memos/mem_scheduler/monitors/__init__.py +0 -0
  155. memos/mem_scheduler/monitors/dispatcher_monitor.py +366 -0
  156. memos/mem_scheduler/monitors/general_monitor.py +394 -0
  157. memos/mem_scheduler/monitors/task_schedule_monitor.py +254 -0
  158. memos/mem_scheduler/optimized_scheduler.py +410 -0
  159. memos/mem_scheduler/orm_modules/__init__.py +0 -0
  160. memos/mem_scheduler/orm_modules/api_redis_model.py +518 -0
  161. memos/mem_scheduler/orm_modules/base_model.py +729 -0
  162. memos/mem_scheduler/orm_modules/monitor_models.py +261 -0
  163. memos/mem_scheduler/orm_modules/redis_model.py +699 -0
  164. memos/mem_scheduler/scheduler_factory.py +23 -0
  165. memos/mem_scheduler/schemas/__init__.py +0 -0
  166. memos/mem_scheduler/schemas/analyzer_schemas.py +52 -0
  167. memos/mem_scheduler/schemas/api_schemas.py +233 -0
  168. memos/mem_scheduler/schemas/general_schemas.py +55 -0
  169. memos/mem_scheduler/schemas/message_schemas.py +173 -0
  170. memos/mem_scheduler/schemas/monitor_schemas.py +406 -0
  171. memos/mem_scheduler/schemas/task_schemas.py +132 -0
  172. memos/mem_scheduler/task_schedule_modules/__init__.py +0 -0
  173. memos/mem_scheduler/task_schedule_modules/dispatcher.py +740 -0
  174. memos/mem_scheduler/task_schedule_modules/local_queue.py +247 -0
  175. memos/mem_scheduler/task_schedule_modules/orchestrator.py +74 -0
  176. memos/mem_scheduler/task_schedule_modules/redis_queue.py +1385 -0
  177. memos/mem_scheduler/task_schedule_modules/task_queue.py +162 -0
  178. memos/mem_scheduler/utils/__init__.py +0 -0
  179. memos/mem_scheduler/utils/api_utils.py +77 -0
  180. memos/mem_scheduler/utils/config_utils.py +100 -0
  181. memos/mem_scheduler/utils/db_utils.py +50 -0
  182. memos/mem_scheduler/utils/filter_utils.py +176 -0
  183. memos/mem_scheduler/utils/metrics.py +125 -0
  184. memos/mem_scheduler/utils/misc_utils.py +290 -0
  185. memos/mem_scheduler/utils/monitor_event_utils.py +67 -0
  186. memos/mem_scheduler/utils/status_tracker.py +229 -0
  187. memos/mem_scheduler/webservice_modules/__init__.py +0 -0
  188. memos/mem_scheduler/webservice_modules/rabbitmq_service.py +485 -0
  189. memos/mem_scheduler/webservice_modules/redis_service.py +380 -0
  190. memos/mem_user/factory.py +94 -0
  191. memos/mem_user/mysql_persistent_user_manager.py +271 -0
  192. memos/mem_user/mysql_user_manager.py +502 -0
  193. memos/mem_user/persistent_factory.py +98 -0
  194. memos/mem_user/persistent_user_manager.py +260 -0
  195. memos/mem_user/redis_persistent_user_manager.py +225 -0
  196. memos/mem_user/user_manager.py +488 -0
  197. memos/memories/__init__.py +0 -0
  198. memos/memories/activation/__init__.py +0 -0
  199. memos/memories/activation/base.py +42 -0
  200. memos/memories/activation/item.py +56 -0
  201. memos/memories/activation/kv.py +292 -0
  202. memos/memories/activation/vllmkv.py +219 -0
  203. memos/memories/base.py +19 -0
  204. memos/memories/factory.py +42 -0
  205. memos/memories/parametric/__init__.py +0 -0
  206. memos/memories/parametric/base.py +19 -0
  207. memos/memories/parametric/item.py +11 -0
  208. memos/memories/parametric/lora.py +41 -0
  209. memos/memories/textual/__init__.py +0 -0
  210. memos/memories/textual/base.py +92 -0
  211. memos/memories/textual/general.py +236 -0
  212. memos/memories/textual/item.py +304 -0
  213. memos/memories/textual/naive.py +187 -0
  214. memos/memories/textual/prefer_text_memory/__init__.py +0 -0
  215. memos/memories/textual/prefer_text_memory/adder.py +504 -0
  216. memos/memories/textual/prefer_text_memory/config.py +106 -0
  217. memos/memories/textual/prefer_text_memory/extractor.py +221 -0
  218. memos/memories/textual/prefer_text_memory/factory.py +85 -0
  219. memos/memories/textual/prefer_text_memory/retrievers.py +177 -0
  220. memos/memories/textual/prefer_text_memory/spliter.py +132 -0
  221. memos/memories/textual/prefer_text_memory/utils.py +93 -0
  222. memos/memories/textual/preference.py +344 -0
  223. memos/memories/textual/simple_preference.py +161 -0
  224. memos/memories/textual/simple_tree.py +69 -0
  225. memos/memories/textual/tree.py +459 -0
  226. memos/memories/textual/tree_text_memory/__init__.py +0 -0
  227. memos/memories/textual/tree_text_memory/organize/__init__.py +0 -0
  228. memos/memories/textual/tree_text_memory/organize/handler.py +184 -0
  229. memos/memories/textual/tree_text_memory/organize/manager.py +518 -0
  230. memos/memories/textual/tree_text_memory/organize/relation_reason_detector.py +238 -0
  231. memos/memories/textual/tree_text_memory/organize/reorganizer.py +622 -0
  232. memos/memories/textual/tree_text_memory/retrieve/__init__.py +0 -0
  233. memos/memories/textual/tree_text_memory/retrieve/advanced_searcher.py +364 -0
  234. memos/memories/textual/tree_text_memory/retrieve/bm25_util.py +186 -0
  235. memos/memories/textual/tree_text_memory/retrieve/bochasearch.py +419 -0
  236. memos/memories/textual/tree_text_memory/retrieve/internet_retriever.py +270 -0
  237. memos/memories/textual/tree_text_memory/retrieve/internet_retriever_factory.py +102 -0
  238. memos/memories/textual/tree_text_memory/retrieve/reasoner.py +61 -0
  239. memos/memories/textual/tree_text_memory/retrieve/recall.py +497 -0
  240. memos/memories/textual/tree_text_memory/retrieve/reranker.py +111 -0
  241. memos/memories/textual/tree_text_memory/retrieve/retrieval_mid_structs.py +16 -0
  242. memos/memories/textual/tree_text_memory/retrieve/retrieve_utils.py +472 -0
  243. memos/memories/textual/tree_text_memory/retrieve/searcher.py +848 -0
  244. memos/memories/textual/tree_text_memory/retrieve/task_goal_parser.py +135 -0
  245. memos/memories/textual/tree_text_memory/retrieve/utils.py +54 -0
  246. memos/memories/textual/tree_text_memory/retrieve/xinyusearch.py +387 -0
  247. memos/memos_tools/dinding_report_bot.py +453 -0
  248. memos/memos_tools/lockfree_dict.py +120 -0
  249. memos/memos_tools/notification_service.py +44 -0
  250. memos/memos_tools/notification_utils.py +142 -0
  251. memos/memos_tools/singleton.py +174 -0
  252. memos/memos_tools/thread_safe_dict.py +310 -0
  253. memos/memos_tools/thread_safe_dict_segment.py +382 -0
  254. memos/multi_mem_cube/__init__.py +0 -0
  255. memos/multi_mem_cube/composite_cube.py +86 -0
  256. memos/multi_mem_cube/single_cube.py +874 -0
  257. memos/multi_mem_cube/views.py +54 -0
  258. memos/parsers/__init__.py +0 -0
  259. memos/parsers/base.py +15 -0
  260. memos/parsers/factory.py +21 -0
  261. memos/parsers/markitdown.py +28 -0
  262. memos/reranker/__init__.py +4 -0
  263. memos/reranker/base.py +25 -0
  264. memos/reranker/concat.py +103 -0
  265. memos/reranker/cosine_local.py +102 -0
  266. memos/reranker/factory.py +72 -0
  267. memos/reranker/http_bge.py +324 -0
  268. memos/reranker/http_bge_strategy.py +327 -0
  269. memos/reranker/noop.py +19 -0
  270. memos/reranker/strategies/__init__.py +4 -0
  271. memos/reranker/strategies/base.py +61 -0
  272. memos/reranker/strategies/concat_background.py +94 -0
  273. memos/reranker/strategies/concat_docsource.py +110 -0
  274. memos/reranker/strategies/dialogue_common.py +109 -0
  275. memos/reranker/strategies/factory.py +31 -0
  276. memos/reranker/strategies/single_turn.py +107 -0
  277. memos/reranker/strategies/singleturn_outmem.py +98 -0
  278. memos/settings.py +10 -0
  279. memos/templates/__init__.py +0 -0
  280. memos/templates/advanced_search_prompts.py +211 -0
  281. memos/templates/cloud_service_prompt.py +107 -0
  282. memos/templates/instruction_completion.py +66 -0
  283. memos/templates/mem_agent_prompts.py +85 -0
  284. memos/templates/mem_feedback_prompts.py +822 -0
  285. memos/templates/mem_reader_prompts.py +1096 -0
  286. memos/templates/mem_reader_strategy_prompts.py +238 -0
  287. memos/templates/mem_scheduler_prompts.py +626 -0
  288. memos/templates/mem_search_prompts.py +93 -0
  289. memos/templates/mos_prompts.py +403 -0
  290. memos/templates/prefer_complete_prompt.py +735 -0
  291. memos/templates/tool_mem_prompts.py +139 -0
  292. memos/templates/tree_reorganize_prompts.py +230 -0
  293. memos/types/__init__.py +34 -0
  294. memos/types/general_types.py +151 -0
  295. memos/types/openai_chat_completion_types/__init__.py +15 -0
  296. memos/types/openai_chat_completion_types/chat_completion_assistant_message_param.py +56 -0
  297. memos/types/openai_chat_completion_types/chat_completion_content_part_image_param.py +27 -0
  298. memos/types/openai_chat_completion_types/chat_completion_content_part_input_audio_param.py +23 -0
  299. memos/types/openai_chat_completion_types/chat_completion_content_part_param.py +43 -0
  300. memos/types/openai_chat_completion_types/chat_completion_content_part_refusal_param.py +16 -0
  301. memos/types/openai_chat_completion_types/chat_completion_content_part_text_param.py +16 -0
  302. memos/types/openai_chat_completion_types/chat_completion_message_custom_tool_call_param.py +27 -0
  303. memos/types/openai_chat_completion_types/chat_completion_message_function_tool_call_param.py +32 -0
  304. memos/types/openai_chat_completion_types/chat_completion_message_param.py +18 -0
  305. memos/types/openai_chat_completion_types/chat_completion_message_tool_call_union_param.py +15 -0
  306. memos/types/openai_chat_completion_types/chat_completion_system_message_param.py +36 -0
  307. memos/types/openai_chat_completion_types/chat_completion_tool_message_param.py +30 -0
  308. memos/types/openai_chat_completion_types/chat_completion_user_message_param.py +34 -0
  309. memos/utils.py +123 -0
  310. memos/vec_dbs/__init__.py +0 -0
  311. memos/vec_dbs/base.py +117 -0
  312. memos/vec_dbs/factory.py +23 -0
  313. memos/vec_dbs/item.py +50 -0
  314. memos/vec_dbs/milvus.py +654 -0
  315. memos/vec_dbs/qdrant.py +355 -0
@@ -0,0 +1,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}")