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
memos/api/config.py ADDED
@@ -0,0 +1,1018 @@
1
+ import base64
2
+ import hashlib
3
+ import hmac
4
+ import json
5
+ import logging
6
+ import os
7
+ import re
8
+ import time
9
+
10
+ from typing import TYPE_CHECKING, Any
11
+
12
+ import requests
13
+
14
+ from dotenv import load_dotenv
15
+
16
+ from memos.context.context import ContextThread
17
+
18
+
19
+ if TYPE_CHECKING:
20
+ from memos.configs.mem_cube import GeneralMemCubeConfig
21
+ from memos.configs.mem_os import MOSConfig
22
+ from memos.mem_cube.general import GeneralMemCube
23
+
24
+
25
+ # Load environment variables
26
+ load_dotenv(override=True)
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+
31
+ def _update_env_from_dict(data: dict[str, Any]) -> None:
32
+ """Apply a dict to environment variables, with change logging."""
33
+
34
+ def _is_sensitive(name: str) -> bool:
35
+ n = name.upper()
36
+ return any(s in n for s in ["PASSWORD", "SECRET", "AK", "SK", "TOKEN", "KEY"])
37
+
38
+ for k, v in data.items():
39
+ if isinstance(v, dict):
40
+ new_val = json.dumps(v, ensure_ascii=False)
41
+ elif isinstance(v, bool):
42
+ new_val = "true" if v else "false"
43
+ elif v is None:
44
+ new_val = ""
45
+ else:
46
+ new_val = str(v)
47
+
48
+ old_val = os.environ.get(k)
49
+ os.environ[k] = new_val
50
+
51
+ try:
52
+ log_old = "***" if _is_sensitive(k) else (old_val if old_val is not None else "<unset>")
53
+ log_new = "***" if _is_sensitive(k) else new_val
54
+ if old_val != new_val:
55
+ logger.info(f"Nacos config update: {k}={log_new} (was {log_old})")
56
+ except Exception as e:
57
+ # Avoid logging failures blocking config updates
58
+ logger.debug(f"Skip logging change for {k}: {e}")
59
+
60
+
61
+ def get_config_json(name: str, default: Any | None = None) -> Any:
62
+ """Read JSON object/array from env and parse. Returns default on missing/invalid."""
63
+ raw = os.getenv(name)
64
+ if not raw:
65
+ return default
66
+ try:
67
+ return json.loads(raw)
68
+ except Exception:
69
+ logger.warning(f"Invalid JSON in env '{name}', returning default.")
70
+ return default
71
+
72
+
73
+ def get_config_value(path: str, default: Any | None = None) -> Any:
74
+ """Read value from env with optional dot-path for structured configs.
75
+
76
+ Examples:
77
+ - get_config_value("MONGODB_CONFIG.base_uri")
78
+ - get_config_value("MONGODB_BASE_URI")
79
+ """
80
+ if "." not in path:
81
+ val = os.getenv(path)
82
+ return val if val is not None else default
83
+ root, *subkeys = path.split(".")
84
+ data = get_config_json(root, default=None)
85
+ if not isinstance(data, dict):
86
+ return default
87
+ cur: Any = data
88
+ for key in subkeys:
89
+ if isinstance(cur, dict) and key in cur:
90
+ cur = cur[key]
91
+ else:
92
+ return default
93
+ return cur
94
+
95
+
96
+ class NacosConfigManager:
97
+ _client = None
98
+ _data_id = None
99
+ _group = None
100
+ _enabled = False
101
+
102
+ # Pre-compile regex patterns for better performance
103
+ _KEY_VALUE_PATTERN = re.compile(r"^([^=]+)=(.*)$")
104
+ _INTEGER_PATTERN = re.compile(r"^[+-]?\d+$")
105
+ _FLOAT_PATTERN = re.compile(r"^[+-]?(\d+\.?\d*|\.\d+)([eE][+-]?\d+)?$")
106
+
107
+ @classmethod
108
+ def _sign(cls, secret_key: str, data: str) -> str:
109
+ """HMAC-SHA1 sgin"""
110
+ signature = hmac.new(secret_key.encode("utf-8"), data.encode("utf-8"), hashlib.sha1)
111
+ return base64.b64encode(signature.digest()).decode()
112
+
113
+ @staticmethod
114
+ def _parse_value(value: str) -> Any:
115
+ """Parse string value to appropriate Python type.
116
+
117
+ Supports: bool, int, float, and string.
118
+ """
119
+ if not value:
120
+ return value
121
+
122
+ val_lower = value.lower()
123
+
124
+ # Boolean
125
+ if val_lower in ("true", "false"):
126
+ return val_lower == "true"
127
+
128
+ # Integer
129
+ if NacosConfigManager._INTEGER_PATTERN.match(value):
130
+ try:
131
+ return int(value)
132
+ except (ValueError, OverflowError):
133
+ return value
134
+
135
+ # Float
136
+ if NacosConfigManager._FLOAT_PATTERN.match(value):
137
+ try:
138
+ return float(value)
139
+ except (ValueError, OverflowError):
140
+ return value
141
+
142
+ # Default to string
143
+ return value
144
+
145
+ @staticmethod
146
+ def parse_properties(content: str) -> dict[str, Any]:
147
+ """Parse properties file content to dictionary with type inference.
148
+
149
+ Supports:
150
+ - Comments (lines starting with #)
151
+ - Key-value pairs (KEY=VALUE)
152
+ - Type inference (bool, int, float, string)
153
+ """
154
+ data: dict[str, Any] = {}
155
+
156
+ for line in content.splitlines():
157
+ line = line.strip()
158
+
159
+ # Skip empty lines and comments
160
+ if not line or line.startswith("#"):
161
+ continue
162
+
163
+ # Parse key-value pair
164
+ match = NacosConfigManager._KEY_VALUE_PATTERN.match(line)
165
+ if match:
166
+ key = match.group(1).strip()
167
+ value = match.group(2).strip()
168
+ data[key] = NacosConfigManager._parse_value(value)
169
+
170
+ return data
171
+
172
+ @classmethod
173
+ def start_config_watch(cls):
174
+ while True:
175
+ cls.init()
176
+ time.sleep(60)
177
+
178
+ @classmethod
179
+ def start_watch_if_enabled(cls) -> None:
180
+ enable = os.getenv("NACOS_ENABLE_WATCH", "false").lower() == "true"
181
+ logger.info(f"NACOS_ENABLE_WATCH: {enable}")
182
+ if not enable:
183
+ return
184
+ interval = int(os.getenv("NACOS_WATCH_INTERVAL", "60"))
185
+
186
+ def _loop() -> None:
187
+ while True:
188
+ try:
189
+ cls.init()
190
+ except Exception as e:
191
+ logger.error(f"❌ Nacos watch loop error: {e}")
192
+ time.sleep(interval)
193
+
194
+ ContextThread(target=_loop, daemon=True).start()
195
+ logger.info(f"Nacos watch thread started (interval={interval}s).")
196
+
197
+ @classmethod
198
+ def init(cls) -> None:
199
+ server_addr = os.getenv("NACOS_SERVER_ADDR")
200
+ data_id = os.getenv("NACOS_DATA_ID")
201
+ group = os.getenv("NACOS_GROUP", "DEFAULT_GROUP")
202
+ namespace = os.getenv("NACOS_NAMESPACE", "")
203
+ ak = os.getenv("AK")
204
+ sk = os.getenv("SK")
205
+
206
+ if not (server_addr and data_id and ak and sk):
207
+ logger.warning("missing NACOS_SERVER_ADDR / AK / SK / DATA_ID")
208
+ return
209
+
210
+ base_url = f"http://{server_addr}/nacos/v1/cs/configs"
211
+
212
+ def _auth_headers():
213
+ ts = str(int(time.time() * 1000))
214
+
215
+ sign_data = namespace + "+" + group + "+" + ts if namespace else group + "+" + ts
216
+ signature = cls._sign(sk, sign_data)
217
+ return {
218
+ "Spas-AccessKey": ak,
219
+ "Spas-Signature": signature,
220
+ "timeStamp": ts,
221
+ }
222
+
223
+ try:
224
+ params = {
225
+ "dataId": data_id,
226
+ "group": group,
227
+ "tenant": namespace,
228
+ }
229
+
230
+ headers = _auth_headers()
231
+ resp = requests.get(base_url, headers=headers, params=params, timeout=10)
232
+
233
+ if resp.status_code != 200:
234
+ logger.error(f"Nacos AK/SK fail: {resp.status_code} {resp.text}")
235
+ return
236
+
237
+ content = resp.text.strip()
238
+ if not content:
239
+ logger.warning("⚠️ Nacos is empty")
240
+ return
241
+ try:
242
+ data_props = cls.parse_properties(content)
243
+ logger.info("nacos config:", data_props)
244
+ _update_env_from_dict(data_props)
245
+ logger.info("✅ parse Nacos setting is Properties ")
246
+ except Exception as e:
247
+ logger.error(f"⚠️ Nacos parse fail(not JSON/YAML/Properties): {e}")
248
+ raise Exception(f"Nacos configuration parsing failed: {e}") from e
249
+
250
+ except Exception as e:
251
+ logger.error(f"❌ Nacos AK/SK init fail: {e}")
252
+ raise Exception(f"❌ Nacos AK/SK init fail: {e}") from e
253
+
254
+
255
+ # init Nacos
256
+ NacosConfigManager.init()
257
+ NacosConfigManager.start_watch_if_enabled()
258
+
259
+
260
+ class APIConfig:
261
+ """Centralized configuration management for MemOS APIs."""
262
+
263
+ @staticmethod
264
+ def get_openai_config() -> dict[str, Any]:
265
+ """Get OpenAI configuration."""
266
+ return {
267
+ "model_name_or_path": os.getenv("MOS_CHAT_MODEL", "gpt-4o-mini"),
268
+ "temperature": float(os.getenv("MOS_CHAT_TEMPERATURE", "0.8")),
269
+ "max_tokens": int(os.getenv("MOS_MAX_TOKENS", "8000")),
270
+ "top_p": float(os.getenv("MOS_TOP_P", "0.9")),
271
+ "top_k": int(os.getenv("MOS_TOP_K", "50")),
272
+ "remove_think_prefix": True,
273
+ "api_key": os.getenv("OPENAI_API_KEY", "your-api-key-here"),
274
+ "api_base": os.getenv("OPENAI_API_BASE", "https://api.openai.com/v1"),
275
+ }
276
+
277
+ @staticmethod
278
+ def qwen_config() -> dict[str, Any]:
279
+ """Get Qwen configuration."""
280
+ return {
281
+ "model_name_or_path": os.getenv("MOS_CHAT_MODEL", "Qwen/Qwen3-1.7B"),
282
+ "temperature": float(os.getenv("MOS_CHAT_TEMPERATURE", "0.8")),
283
+ "max_tokens": int(os.getenv("MOS_MAX_TOKENS", "4096")),
284
+ "remove_think_prefix": True,
285
+ }
286
+
287
+ @staticmethod
288
+ def vllm_config() -> dict[str, Any]:
289
+ """Get Qwen configuration."""
290
+ return {
291
+ "model_name_or_path": os.getenv("MOS_CHAT_MODEL", "Qwen/Qwen3-1.7B"),
292
+ "temperature": float(os.getenv("MOS_CHAT_TEMPERATURE", "0.8")),
293
+ "max_tokens": int(os.getenv("MOS_MAX_TOKENS", "4096")),
294
+ "remove_think_prefix": True,
295
+ "api_key": os.getenv("VLLM_API_KEY", ""),
296
+ "api_base": os.getenv("VLLM_API_BASE", "http://localhost:8088/v1"),
297
+ "model_schema": os.getenv("MOS_MODEL_SCHEMA", "memos.configs.llm.VLLMLLMConfig"),
298
+ }
299
+
300
+ @staticmethod
301
+ def get_activation_config() -> dict[str, Any]:
302
+ """Get Ollama configuration."""
303
+ return {
304
+ "backend": "kv_cache",
305
+ "config": {
306
+ "memory_filename": "activation_memory.pickle",
307
+ "extractor_llm": {
308
+ "backend": "huggingface_singleton",
309
+ "config": {
310
+ "model_name_or_path": os.getenv("MOS_CHAT_MODEL", "Qwen/Qwen3-1.7B"),
311
+ "temperature": 0.8,
312
+ "max_tokens": 1024,
313
+ "top_p": 0.9,
314
+ "top_k": 50,
315
+ "add_generation_prompt": True,
316
+ "remove_think_prefix": False,
317
+ },
318
+ },
319
+ },
320
+ }
321
+
322
+ @staticmethod
323
+ def get_memreader_config() -> dict[str, Any]:
324
+ """Get MemReader configuration."""
325
+ return {
326
+ "backend": "openai",
327
+ "config": {
328
+ "model_name_or_path": os.getenv("MEMRADER_MODEL", "gpt-4o-mini"),
329
+ "temperature": 0.6,
330
+ "max_tokens": int(os.getenv("MEMRADER_MAX_TOKENS", "8000")),
331
+ "top_p": 0.95,
332
+ "top_k": 20,
333
+ "api_key": os.getenv("MEMRADER_API_KEY", "EMPTY"),
334
+ # Default to OpenAI base URL when env var is not provided to satisfy pydantic
335
+ # validation requirements during tests/import.
336
+ "api_base": os.getenv("MEMRADER_API_BASE", "https://api.openai.com/v1"),
337
+ "remove_think_prefix": True,
338
+ "extra_body": {"chat_template_kwargs": {"enable_thinking": False}},
339
+ },
340
+ }
341
+
342
+ @staticmethod
343
+ def get_activation_vllm_config() -> dict[str, Any]:
344
+ """Get Ollama configuration."""
345
+ return {
346
+ "backend": "vllm_kv_cache",
347
+ "config": {
348
+ "memory_filename": "activation_memory.pickle",
349
+ "extractor_llm": {
350
+ "backend": "vllm",
351
+ "config": APIConfig.vllm_config(),
352
+ },
353
+ },
354
+ }
355
+
356
+ @staticmethod
357
+ def get_preference_memory_config() -> dict[str, Any]:
358
+ """Get preference memory configuration."""
359
+ return {
360
+ "backend": "pref_text",
361
+ "config": {
362
+ "extractor_llm": APIConfig.get_memreader_config(),
363
+ "vector_db": {
364
+ "backend": "milvus",
365
+ "config": APIConfig.get_milvus_config(),
366
+ },
367
+ "embedder": APIConfig.get_embedder_config(),
368
+ "reranker": APIConfig.get_reranker_config(),
369
+ "extractor": {"backend": "naive", "config": {}},
370
+ "adder": {"backend": "naive", "config": {}},
371
+ "retriever": {"backend": "naive", "config": {}},
372
+ },
373
+ }
374
+
375
+ @staticmethod
376
+ def get_reranker_config() -> dict[str, Any]:
377
+ """Get embedder configuration."""
378
+ embedder_backend = os.getenv("MOS_RERANKER_BACKEND", "http_bge")
379
+
380
+ if embedder_backend in ["http_bge", "http_bge_strategy"]:
381
+ return {
382
+ "backend": embedder_backend,
383
+ "config": {
384
+ "url": os.getenv("MOS_RERANKER_URL", "localhost:8000/v1/rerank"),
385
+ "model": os.getenv("MOS_RERANKER_MODEL", "bge-reranker-v2-m3"),
386
+ "timeout": 10,
387
+ "headers_extra": json.loads(os.getenv("MOS_RERANKER_HEADERS_EXTRA", "{}")),
388
+ "rerank_source": os.getenv("MOS_RERANK_SOURCE"),
389
+ "reranker_strategy": os.getenv("MOS_RERANKER_STRATEGY", "single_turn"),
390
+ },
391
+ }
392
+ else:
393
+ return {
394
+ "backend": "cosine_local",
395
+ "config": {
396
+ "level_weights": {"topic": 1.0, "concept": 1.0, "fact": 1.0},
397
+ "level_field": "background",
398
+ },
399
+ }
400
+
401
+ @staticmethod
402
+ def get_feedback_reranker_config() -> dict[str, Any]:
403
+ """Get embedder configuration."""
404
+ embedder_backend = os.getenv("MOS_FEEDBACK_RERANKER_BACKEND", "http_bge")
405
+
406
+ if embedder_backend in ["http_bge", "http_bge_strategy"]:
407
+ return {
408
+ "backend": embedder_backend,
409
+ "config": {
410
+ "url": os.getenv("MOS_RERANKER_URL", "localhost:8000/v1/rerank"),
411
+ "model": os.getenv("MOS_FEEDBACK_RERANKER_MODEL", "bge-reranker-v2-m3"),
412
+ "timeout": 10,
413
+ "max_query_tokens": int(os.getenv("MOS_RERANKER_MAX_TOKENS", 8000)),
414
+ "concate_len": int(os.getenv("MOS_RERANKER_CONCAT_LEN", 1000)),
415
+ "headers_extra": json.loads(os.getenv("MOS_RERANKER_HEADERS_EXTRA", "{}")),
416
+ "rerank_source": os.getenv("MOS_RERANK_SOURCE"),
417
+ "reranker_strategy": os.getenv("MOS_RERANKER_STRATEGY", "single_turn"),
418
+ },
419
+ }
420
+ else:
421
+ return {
422
+ "backend": "cosine_local",
423
+ "config": {
424
+ "level_weights": {"topic": 1.0, "concept": 1.0, "fact": 1.0},
425
+ "level_field": "background",
426
+ },
427
+ }
428
+
429
+ @staticmethod
430
+ def get_embedder_config() -> dict[str, Any]:
431
+ """Get embedder configuration."""
432
+ embedder_backend = os.getenv("MOS_EMBEDDER_BACKEND", "ollama")
433
+
434
+ if embedder_backend == "universal_api":
435
+ return {
436
+ "backend": "universal_api",
437
+ "config": {
438
+ "provider": os.getenv("MOS_EMBEDDER_PROVIDER", "openai"),
439
+ "api_key": os.getenv("MOS_EMBEDDER_API_KEY", "sk-xxxx"),
440
+ "model_name_or_path": os.getenv("MOS_EMBEDDER_MODEL", "text-embedding-3-large"),
441
+ "headers_extra": json.loads(os.getenv("MOS_EMBEDDER_HEADERS_EXTRA", "{}")),
442
+ "base_url": os.getenv("MOS_EMBEDDER_API_BASE", "http://openai.com"),
443
+ },
444
+ }
445
+ else: # ollama
446
+ return {
447
+ "backend": "ollama",
448
+ "config": {
449
+ "model_name_or_path": os.getenv(
450
+ "MOS_EMBEDDER_MODEL", "nomic-embed-text:latest"
451
+ ),
452
+ "api_base": os.getenv("OLLAMA_API_BASE", "http://localhost:11434"),
453
+ },
454
+ }
455
+
456
+ @staticmethod
457
+ def get_reader_config() -> dict[str, Any]:
458
+ """Get reader configuration."""
459
+ return {
460
+ "backend": os.getenv("MEM_READER_BACKEND", "multimodal_struct"),
461
+ "config": {
462
+ "chunk_type": os.getenv("MEM_READER_CHAT_CHUNK_TYPE", "default"),
463
+ "chunk_length": int(os.getenv("MEM_READER_CHAT_CHUNK_TOKEN_SIZE", 1600)),
464
+ "chunk_session": int(os.getenv("MEM_READER_CHAT_CHUNK_SESS_SIZE", 10)),
465
+ "chunk_overlap": int(os.getenv("MEM_READER_CHAT_CHUNK_OVERLAP", 2)),
466
+ },
467
+ }
468
+
469
+ @staticmethod
470
+ def get_internet_config() -> dict[str, Any]:
471
+ """Get embedder configuration."""
472
+ reader_config = APIConfig.get_reader_config()
473
+ return {
474
+ "backend": "bocha",
475
+ "config": {
476
+ "api_key": os.getenv("BOCHA_API_KEY", "bocha"),
477
+ "max_results": 15,
478
+ "num_per_request": 10,
479
+ "reader": {
480
+ "backend": reader_config["backend"],
481
+ "config": {
482
+ "llm": {
483
+ "backend": "openai",
484
+ "config": {
485
+ "model_name_or_path": os.getenv("MEMRADER_MODEL"),
486
+ "temperature": 0.6,
487
+ "max_tokens": 5000,
488
+ "top_p": 0.95,
489
+ "top_k": 20,
490
+ "api_key": os.getenv("MEMRADER_API_KEY", "EMPTY"),
491
+ "api_base": os.getenv("MEMRADER_API_BASE"),
492
+ "remove_think_prefix": True,
493
+ "extra_body": {"chat_template_kwargs": {"enable_thinking": False}},
494
+ },
495
+ },
496
+ "embedder": APIConfig.get_embedder_config(),
497
+ "chunker": {
498
+ "backend": "sentence",
499
+ "config": {
500
+ "tokenizer_or_token_counter": "gpt2",
501
+ "chunk_size": 512,
502
+ "chunk_overlap": 128,
503
+ "min_sentences_per_chunk": 1,
504
+ },
505
+ },
506
+ "chat_chunker": reader_config,
507
+ },
508
+ },
509
+ },
510
+ }
511
+
512
+ @staticmethod
513
+ def get_neo4j_community_config(user_id: str | None = None) -> dict[str, Any]:
514
+ """Get Neo4j community configuration."""
515
+ return {
516
+ "uri": os.getenv("NEO4J_URI", "bolt://localhost:7687"),
517
+ "user": os.getenv("NEO4J_USER", "neo4j"),
518
+ "db_name": os.getenv("NEO4J_DB_NAME", "neo4j"),
519
+ "password": os.getenv("NEO4J_PASSWORD", "12345678"),
520
+ "user_name": f"memos{user_id.replace('-', '')}",
521
+ "auto_create": False,
522
+ "use_multi_db": False,
523
+ "embedding_dimension": int(os.getenv("EMBEDDING_DIMENSION", 1024)),
524
+ "vec_config": {
525
+ # Pass nested config to initialize external vector DB
526
+ # If you use qdrant, please use Server instead of local mode.
527
+ "backend": "qdrant",
528
+ "config": {
529
+ "collection_name": "neo4j_vec_db",
530
+ "vector_dimension": int(os.getenv("EMBEDDING_DIMENSION", 1024)),
531
+ "distance_metric": "cosine",
532
+ "host": os.getenv("QDRANT_HOST", "localhost"),
533
+ "port": int(os.getenv("QDRANT_PORT", "6333")),
534
+ "path": os.getenv("QDRANT_PATH"),
535
+ "url": os.getenv("QDRANT_URL"),
536
+ "api_key": os.getenv("QDRANT_API_KEY"),
537
+ },
538
+ },
539
+ }
540
+
541
+ @staticmethod
542
+ def get_neo4j_config(user_id: str | None = None) -> dict[str, Any]:
543
+ """Get Neo4j configuration."""
544
+ if os.getenv("MOS_NEO4J_SHARED_DB", "false").lower() == "true":
545
+ return APIConfig.get_neo4j_shared_config(user_id)
546
+ else:
547
+ return APIConfig.get_noshared_neo4j_config(user_id)
548
+
549
+ @staticmethod
550
+ def get_noshared_neo4j_config(user_id) -> dict[str, Any]:
551
+ """Get Neo4j configuration."""
552
+ return {
553
+ "uri": os.getenv("NEO4J_URI", "bolt://localhost:7687"),
554
+ "user": os.getenv("NEO4J_USER", "neo4j"),
555
+ "db_name": f"memos{user_id.replace('-', '')}",
556
+ "password": os.getenv("NEO4J_PASSWORD", "12345678"),
557
+ "auto_create": True,
558
+ "use_multi_db": True,
559
+ "embedding_dimension": int(os.getenv("EMBEDDING_DIMENSION", 3072)),
560
+ }
561
+
562
+ @staticmethod
563
+ def get_neo4j_shared_config(user_id: str | None = None) -> dict[str, Any]:
564
+ """Get Neo4j configuration."""
565
+ return {
566
+ "uri": os.getenv("NEO4J_URI", "bolt://localhost:7687"),
567
+ "user": os.getenv("NEO4J_USER", "neo4j"),
568
+ "db_name": os.getenv("NEO4J_DB_NAME", "shared-tree-textual-memory"),
569
+ "password": os.getenv("NEO4J_PASSWORD", "12345678"),
570
+ "user_name": f"memos{user_id.replace('-', '')}",
571
+ "auto_create": True,
572
+ "use_multi_db": False,
573
+ "embedding_dimension": int(os.getenv("EMBEDDING_DIMENSION", 3072)),
574
+ }
575
+
576
+ @staticmethod
577
+ def get_nebular_config(user_id: str | None = None) -> dict[str, Any]:
578
+ """Get Nebular configuration."""
579
+ return {
580
+ "uri": json.loads(os.getenv("NEBULAR_HOSTS", '["localhost"]')),
581
+ "user": os.getenv("NEBULAR_USER", "root"),
582
+ "password": os.getenv("NEBULAR_PASSWORD", "xxxxxx"),
583
+ "space": os.getenv("NEBULAR_SPACE", "shared-tree-textual-memory"),
584
+ "user_name": f"memos{user_id.replace('-', '')}",
585
+ "use_multi_db": False,
586
+ "auto_create": True,
587
+ "embedding_dimension": int(os.getenv("EMBEDDING_DIMENSION", 3072)),
588
+ }
589
+
590
+ @staticmethod
591
+ def get_milvus_config():
592
+ return {
593
+ "collection_name": [
594
+ "explicit_preference",
595
+ "implicit_preference",
596
+ ],
597
+ "vector_dimension": int(os.getenv("EMBEDDING_DIMENSION", 1024)),
598
+ "distance_metric": "cosine",
599
+ "uri": os.getenv("MILVUS_URI", "http://localhost:19530"),
600
+ "user_name": os.getenv("MILVUS_USER_NAME", "root"),
601
+ "password": os.getenv("MILVUS_PASSWORD", "12345678"),
602
+ }
603
+
604
+ @staticmethod
605
+ def get_polardb_config(user_id: str | None = None) -> dict[str, Any]:
606
+ """Get PolarDB configuration."""
607
+ use_multi_db = os.getenv("POLAR_DB_USE_MULTI_DB", "false").lower() == "true"
608
+
609
+ if use_multi_db:
610
+ # Multi-DB mode: each user gets their own database (physical isolation)
611
+ db_name = f"memos{user_id.replace('-', '')}" if user_id else "memos_default"
612
+ user_name = None
613
+ else:
614
+ # Shared-DB mode: all users share one database with user_name tag (logical isolation)
615
+ db_name = os.getenv("POLAR_DB_DB_NAME", "shared_memos_db")
616
+ user_name = f"memos{user_id.replace('-', '')}" if user_id else "memos_default"
617
+
618
+ return {
619
+ "host": os.getenv("POLAR_DB_HOST", "localhost"),
620
+ "port": int(os.getenv("POLAR_DB_PORT", "5432")),
621
+ "user": os.getenv("POLAR_DB_USER", "root"),
622
+ "password": os.getenv("POLAR_DB_PASSWORD", "123456"),
623
+ "db_name": db_name,
624
+ "maxconn": int(os.getenv("POLARDB_POOL_MAX_CONN", "100")),
625
+ "user_name": user_name,
626
+ "use_multi_db": use_multi_db,
627
+ "auto_create": True,
628
+ "embedding_dimension": int(os.getenv("EMBEDDING_DIMENSION", 1024)),
629
+ }
630
+
631
+ @staticmethod
632
+ def get_mysql_config() -> dict[str, Any]:
633
+ """Get MySQL configuration."""
634
+ return {
635
+ "host": os.getenv("MYSQL_HOST", "localhost"),
636
+ "port": int(os.getenv("MYSQL_PORT", "3306")),
637
+ "username": os.getenv("MYSQL_USERNAME", "root"),
638
+ "password": os.getenv("MYSQL_PASSWORD", "12345678"),
639
+ "database": os.getenv("MYSQL_DATABASE", "memos_users"),
640
+ "charset": os.getenv("MYSQL_CHARSET", "utf8mb4"),
641
+ }
642
+
643
+ @staticmethod
644
+ def get_scheduler_config() -> dict[str, Any]:
645
+ """Get scheduler configuration."""
646
+ return {
647
+ "backend": "optimized_scheduler",
648
+ "config": {
649
+ "top_k": int(os.getenv("MOS_SCHEDULER_TOP_K", "10")),
650
+ "act_mem_update_interval": int(
651
+ os.getenv("MOS_SCHEDULER_ACT_MEM_UPDATE_INTERVAL", "300")
652
+ ),
653
+ "context_window_size": int(os.getenv("MOS_SCHEDULER_CONTEXT_WINDOW_SIZE", "5")),
654
+ "thread_pool_max_workers": int(
655
+ os.getenv("MOS_SCHEDULER_THREAD_POOL_MAX_WORKERS", "10000")
656
+ ),
657
+ "consume_interval_seconds": float(
658
+ os.getenv("MOS_SCHEDULER_CONSUME_INTERVAL_SECONDS", "0.01")
659
+ ),
660
+ "enable_parallel_dispatch": os.getenv(
661
+ "MOS_SCHEDULER_ENABLE_PARALLEL_DISPATCH", "true"
662
+ ).lower()
663
+ == "true",
664
+ "enable_activation_memory": os.getenv(
665
+ "MOS_SCHEDULER_ENABLE_ACTIVATION_MEMORY", "false"
666
+ ).lower()
667
+ == "true",
668
+ },
669
+ }
670
+
671
+ @staticmethod
672
+ def is_scheduler_enabled() -> bool:
673
+ """Check if scheduler is enabled via environment variable."""
674
+ return os.getenv("MOS_ENABLE_SCHEDULER", "false").lower() == "true"
675
+
676
+ @staticmethod
677
+ def is_default_cube_config_enabled() -> bool:
678
+ """Check if default cube config is enabled via environment variable."""
679
+ return os.getenv("MOS_ENABLE_DEFAULT_CUBE_CONFIG", "true").lower() == "true"
680
+
681
+ @staticmethod
682
+ def is_dingding_bot_enabled() -> bool:
683
+ """Check if DingDing bot is enabled via environment variable."""
684
+ return os.getenv("ENABLE_DINGDING_BOT", "false").lower() == "true"
685
+
686
+ @staticmethod
687
+ def get_dingding_bot_config() -> dict[str, Any] | None:
688
+ """Get DingDing bot configuration if enabled."""
689
+ if not APIConfig.is_dingding_bot_enabled():
690
+ return None
691
+
692
+ return {
693
+ "enabled": True,
694
+ "access_token_user": os.getenv("DINGDING_ACCESS_TOKEN_USER", ""),
695
+ "secret_user": os.getenv("DINGDING_SECRET_USER", ""),
696
+ "access_token_error": os.getenv("DINGDING_ACCESS_TOKEN_ERROR", ""),
697
+ "secret_error": os.getenv("DINGDING_SECRET_ERROR", ""),
698
+ "robot_code": os.getenv("DINGDING_ROBOT_CODE", ""),
699
+ "app_key": os.getenv("DINGDING_APP_KEY", ""),
700
+ "app_secret": os.getenv("DINGDING_APP_SECRET", ""),
701
+ "oss_endpoint": os.getenv("OSS_ENDPOINT", ""),
702
+ "oss_region": os.getenv("OSS_REGION", ""),
703
+ "oss_bucket_name": os.getenv("OSS_BUCKET_NAME", ""),
704
+ "oss_access_key_id": os.getenv("OSS_ACCESS_KEY_ID", ""),
705
+ "oss_access_key_secret": os.getenv("OSS_ACCESS_KEY_SECRET", ""),
706
+ "oss_public_base_url": os.getenv("OSS_PUBLIC_BASE_URL", ""),
707
+ }
708
+
709
+ @staticmethod
710
+ def get_product_default_config() -> dict[str, Any]:
711
+ """Get default configuration for Product API."""
712
+ openai_config = APIConfig.get_openai_config()
713
+ qwen_config = APIConfig.qwen_config()
714
+ vllm_config = APIConfig.vllm_config()
715
+ reader_config = APIConfig.get_reader_config()
716
+
717
+ backend_model = {
718
+ "openai": openai_config,
719
+ "huggingface": qwen_config,
720
+ "vllm": vllm_config,
721
+ }
722
+ backend = os.getenv("MOS_CHAT_MODEL_PROVIDER", "openai")
723
+ mysql_config = APIConfig.get_mysql_config()
724
+ config = {
725
+ "user_id": os.getenv("MOS_USER_ID", "root"),
726
+ "chat_model": {"backend": backend, "config": backend_model[backend]},
727
+ "mem_reader": {
728
+ "backend": reader_config["backend"],
729
+ "config": {
730
+ "llm": APIConfig.get_memreader_config(),
731
+ "embedder": APIConfig.get_embedder_config(),
732
+ "chunker": {
733
+ "backend": "sentence",
734
+ "config": {
735
+ "tokenizer_or_token_counter": "gpt2",
736
+ "chunk_size": 512,
737
+ "chunk_overlap": 128,
738
+ "min_sentences_per_chunk": 1,
739
+ },
740
+ },
741
+ "chat_chunker": reader_config,
742
+ "direct_markdown_hostnames": [
743
+ h.strip()
744
+ for h in os.getenv(
745
+ "FILE_PARSER_DIRECT_MARKDOWN_HOSTNAMES", "139.196.232.20"
746
+ ).split(",")
747
+ if h.strip()
748
+ ],
749
+ },
750
+ },
751
+ "enable_textual_memory": True,
752
+ "enable_activation_memory": os.getenv("ENABLE_ACTIVATION_MEMORY", "false").lower()
753
+ == "true",
754
+ "enable_preference_memory": os.getenv("ENABLE_PREFERENCE_MEMORY", "false").lower()
755
+ == "true",
756
+ "top_k": int(os.getenv("MOS_TOP_K", "50")),
757
+ "max_turns_window": int(os.getenv("MOS_MAX_TURNS_WINDOW", "20")),
758
+ }
759
+
760
+ # Add scheduler configuration if enabled
761
+ if APIConfig.is_scheduler_enabled():
762
+ config["mem_scheduler"] = APIConfig.get_scheduler_config()
763
+ config["enable_mem_scheduler"] = True
764
+ else:
765
+ config["enable_mem_scheduler"] = False
766
+
767
+ # Add user manager configuration if enabled
768
+ if os.getenv("MOS_USER_MANAGER_BACKEND", "sqlite").lower() == "mysql":
769
+ config["user_manager"] = {
770
+ "backend": "mysql",
771
+ "config": mysql_config,
772
+ }
773
+
774
+ return config
775
+
776
+ @staticmethod
777
+ def get_start_default_config() -> dict[str, Any]:
778
+ """Get default configuration for Start API."""
779
+ config = {
780
+ "user_id": os.getenv("MOS_USER_ID", "default_user"),
781
+ "session_id": os.getenv("MOS_SESSION_ID", "default_session"),
782
+ "enable_textual_memory": True,
783
+ "enable_activation_memory": os.getenv("ENABLE_ACTIVATION_MEMORY", "false").lower()
784
+ == "true",
785
+ "enable_preference_memory": os.getenv("ENABLE_PREFERENCE_MEMORY", "false").lower()
786
+ == "true",
787
+ "top_k": int(os.getenv("MOS_TOP_K", "5")),
788
+ "chat_model": {
789
+ "backend": os.getenv("MOS_CHAT_MODEL_PROVIDER", "openai"),
790
+ "config": {
791
+ "model_name_or_path": os.getenv("MOS_CHAT_MODEL", "gpt-4o-mini"),
792
+ "api_key": os.getenv("OPENAI_API_KEY", "sk-xxxxxx"),
793
+ "temperature": float(os.getenv("MOS_CHAT_TEMPERATURE", 0.7)),
794
+ "api_base": os.getenv("OPENAI_API_BASE", "http://xxxxxx:3000/v1"),
795
+ "max_tokens": int(os.getenv("MOS_MAX_TOKENS", 1024)),
796
+ "top_p": float(os.getenv("MOS_TOP_P", 0.9)),
797
+ "top_k": int(os.getenv("MOS_TOP_K", 50)),
798
+ "remove_think_prefix": True,
799
+ },
800
+ },
801
+ }
802
+
803
+ # Add scheduler configuration if enabled
804
+ if APIConfig.is_scheduler_enabled():
805
+ config["mem_scheduler"] = APIConfig.get_scheduler_config()
806
+ config["enable_mem_scheduler"] = True
807
+ else:
808
+ config["enable_mem_scheduler"] = False
809
+
810
+ return config
811
+
812
+ @staticmethod
813
+ def create_user_config(user_name: str, user_id: str) -> tuple["MOSConfig", "GeneralMemCube"]:
814
+ """Create configuration for a specific user."""
815
+ from memos.configs.mem_cube import GeneralMemCubeConfig
816
+ from memos.configs.mem_os import MOSConfig
817
+ from memos.mem_cube.general import GeneralMemCube
818
+
819
+ openai_config = APIConfig.get_openai_config()
820
+ qwen_config = APIConfig.qwen_config()
821
+ vllm_config = APIConfig.vllm_config()
822
+ mysql_config = APIConfig.get_mysql_config()
823
+ reader_config = APIConfig.get_reader_config()
824
+ backend = os.getenv("MOS_CHAT_MODEL_PROVIDER", "openai")
825
+ backend_model = {
826
+ "openai": openai_config,
827
+ "huggingface": qwen_config,
828
+ "vllm": vllm_config,
829
+ }
830
+ # Create MOSConfig
831
+ config_dict = {
832
+ "user_id": user_id,
833
+ "chat_model": {
834
+ "backend": backend,
835
+ "config": backend_model[backend],
836
+ },
837
+ "mem_reader": {
838
+ "backend": reader_config["backend"],
839
+ "config": {
840
+ "llm": APIConfig.get_memreader_config(),
841
+ "embedder": APIConfig.get_embedder_config(),
842
+ "chunker": {
843
+ "backend": "sentence",
844
+ "config": {
845
+ "tokenizer_or_token_counter": "gpt2",
846
+ "chunk_size": 512,
847
+ "chunk_overlap": 128,
848
+ "min_sentences_per_chunk": 1,
849
+ },
850
+ },
851
+ "chat_chunker": reader_config,
852
+ },
853
+ },
854
+ "enable_textual_memory": True,
855
+ "enable_activation_memory": os.getenv("ENABLE_ACTIVATION_MEMORY", "false").lower()
856
+ == "true",
857
+ "enable_preference_memory": os.getenv("ENABLE_PREFERENCE_MEMORY", "false").lower()
858
+ == "true",
859
+ "top_k": 30,
860
+ "max_turns_window": 20,
861
+ }
862
+ # Add scheduler configuration if enabled
863
+ if APIConfig.is_scheduler_enabled():
864
+ config_dict["mem_scheduler"] = APIConfig.get_scheduler_config()
865
+ config_dict["enable_mem_scheduler"] = True
866
+ else:
867
+ config_dict["enable_mem_scheduler"] = False
868
+
869
+ # Add user manager configuration if enabled
870
+ if os.getenv("MOS_USER_MANAGER_BACKEND", "sqlite").lower() == "mysql":
871
+ config_dict["user_manager"] = {
872
+ "backend": "mysql",
873
+ "config": mysql_config,
874
+ }
875
+
876
+ default_config = MOSConfig(**config_dict)
877
+
878
+ neo4j_community_config = APIConfig.get_neo4j_community_config(user_id)
879
+ neo4j_config = APIConfig.get_neo4j_config(user_id)
880
+ nebular_config = APIConfig.get_nebular_config(user_id)
881
+ polardb_config = APIConfig.get_polardb_config(user_id)
882
+ internet_config = (
883
+ APIConfig.get_internet_config()
884
+ if os.getenv("ENABLE_INTERNET", "false").lower() == "true"
885
+ else None
886
+ )
887
+ graph_db_backend_map = {
888
+ "neo4j-community": neo4j_community_config,
889
+ "neo4j": neo4j_config,
890
+ "nebular": nebular_config,
891
+ "polardb": polardb_config,
892
+ }
893
+ graph_db_backend = os.getenv("NEO4J_BACKEND", "neo4j-community").lower()
894
+ if graph_db_backend in graph_db_backend_map:
895
+ # Create MemCube config
896
+
897
+ default_cube_config = GeneralMemCubeConfig.model_validate(
898
+ {
899
+ "user_id": user_id,
900
+ "cube_id": f"{user_name}_default_cube",
901
+ "text_mem": {
902
+ "backend": "tree_text",
903
+ "config": {
904
+ "extractor_llm": {"backend": "openai", "config": openai_config},
905
+ "dispatcher_llm": {"backend": "openai", "config": openai_config},
906
+ "graph_db": {
907
+ "backend": graph_db_backend,
908
+ "config": graph_db_backend_map[graph_db_backend],
909
+ },
910
+ "embedder": APIConfig.get_embedder_config(),
911
+ "internet_retriever": internet_config,
912
+ "reranker": APIConfig.get_reranker_config(),
913
+ "reorganize": os.getenv("MOS_ENABLE_REORGANIZE", "false").lower()
914
+ == "true",
915
+ "memory_size": {
916
+ "WorkingMemory": int(os.getenv("NEBULAR_WORKING_MEMORY", 20)),
917
+ "LongTermMemory": int(os.getenv("NEBULAR_LONGTERM_MEMORY", 1e6)),
918
+ "UserMemory": int(os.getenv("NEBULAR_USER_MEMORY", 1e6)),
919
+ },
920
+ "search_strategy": {
921
+ "fast_graph": bool(os.getenv("FAST_GRAPH", "false") == "true"),
922
+ "bm25": bool(os.getenv("BM25_CALL", "false") == "true"),
923
+ "cot": bool(os.getenv("VEC_COT_CALL", "false") == "true"),
924
+ },
925
+ "include_embedding": bool(
926
+ os.getenv("INCLUDE_EMBEDDING", "false") == "true"
927
+ ),
928
+ },
929
+ },
930
+ "act_mem": {}
931
+ if os.getenv("ENABLE_ACTIVATION_MEMORY", "false").lower() == "false"
932
+ else APIConfig.get_activation_vllm_config(),
933
+ "para_mem": {},
934
+ "pref_mem": {}
935
+ if os.getenv("ENABLE_PREFERENCE_MEMORY", "false").lower() == "false"
936
+ else APIConfig.get_preference_memory_config(),
937
+ }
938
+ )
939
+ else:
940
+ raise ValueError(f"Invalid Neo4j backend: {graph_db_backend}")
941
+ default_mem_cube = GeneralMemCube(default_cube_config)
942
+ return default_config, default_mem_cube
943
+
944
+ @staticmethod
945
+ def get_default_cube_config() -> "GeneralMemCubeConfig | None":
946
+ """Get default cube configuration for product initialization.
947
+
948
+ Returns:
949
+ GeneralMemCubeConfig | None: Default cube configuration if enabled, None otherwise.
950
+ """
951
+ from memos.configs.mem_cube import GeneralMemCubeConfig
952
+
953
+ if not APIConfig.is_default_cube_config_enabled():
954
+ return None
955
+
956
+ openai_config = APIConfig.get_openai_config()
957
+ neo4j_community_config = APIConfig.get_neo4j_community_config(user_id="default")
958
+ neo4j_config = APIConfig.get_neo4j_config(user_id="default")
959
+ nebular_config = APIConfig.get_nebular_config(user_id="default")
960
+ polardb_config = APIConfig.get_polardb_config(user_id="default")
961
+ graph_db_backend_map = {
962
+ "neo4j-community": neo4j_community_config,
963
+ "neo4j": neo4j_config,
964
+ "nebular": nebular_config,
965
+ "polardb": polardb_config,
966
+ }
967
+ internet_config = (
968
+ APIConfig.get_internet_config()
969
+ if os.getenv("ENABLE_INTERNET", "false").lower() == "true"
970
+ else None
971
+ )
972
+ graph_db_backend = os.getenv("NEO4J_BACKEND", "neo4j-community").lower()
973
+ if graph_db_backend in graph_db_backend_map:
974
+ return GeneralMemCubeConfig.model_validate(
975
+ {
976
+ "user_id": "default",
977
+ "cube_id": "default_cube",
978
+ "text_mem": {
979
+ "backend": "tree_text",
980
+ "config": {
981
+ "extractor_llm": {"backend": "openai", "config": openai_config},
982
+ "dispatcher_llm": {"backend": "openai", "config": openai_config},
983
+ "graph_db": {
984
+ "backend": graph_db_backend,
985
+ "config": graph_db_backend_map[graph_db_backend],
986
+ },
987
+ "embedder": APIConfig.get_embedder_config(),
988
+ "reranker": APIConfig.get_reranker_config(),
989
+ "reorganize": os.getenv("MOS_ENABLE_REORGANIZE", "false").lower()
990
+ == "true",
991
+ "internet_retriever": internet_config,
992
+ "memory_size": {
993
+ "WorkingMemory": int(os.getenv("NEBULAR_WORKING_MEMORY", 20)),
994
+ "LongTermMemory": int(os.getenv("NEBULAR_LONGTERM_MEMORY", 1e6)),
995
+ "UserMemory": int(os.getenv("NEBULAR_USER_MEMORY", 1e6)),
996
+ },
997
+ "search_strategy": {
998
+ "fast_graph": bool(os.getenv("FAST_GRAPH", "false") == "true"),
999
+ "bm25": bool(os.getenv("BM25_CALL", "false") == "true"),
1000
+ "cot": bool(os.getenv("VEC_COT_CALL", "false") == "true"),
1001
+ },
1002
+ "mode": os.getenv("ASYNC_MODE", "sync"),
1003
+ "include_embedding": bool(
1004
+ os.getenv("INCLUDE_EMBEDDING", "false") == "true"
1005
+ ),
1006
+ },
1007
+ },
1008
+ "act_mem": {}
1009
+ if os.getenv("ENABLE_ACTIVATION_MEMORY", "false").lower() == "false"
1010
+ else APIConfig.get_activation_vllm_config(),
1011
+ "para_mem": {},
1012
+ "pref_mem": {}
1013
+ if os.getenv("ENABLE_PREFERENCE_MEMORY", "false").lower() == "false"
1014
+ else APIConfig.get_preference_memory_config(),
1015
+ }
1016
+ )
1017
+ else:
1018
+ raise ValueError(f"Invalid Neo4j backend: {graph_db_backend}")