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,162 @@
1
+ """
2
+ Redis Queue implementation for SchedulerMessageItem objects.
3
+
4
+ This module provides a Redis-based queue implementation that can replace
5
+ the local memos_message_queue functionality in BaseScheduler.
6
+ """
7
+
8
+ from memos.context.context import get_current_trace_id
9
+ from memos.log import get_logger
10
+ from memos.mem_scheduler.schemas.message_schemas import ScheduleMessageItem
11
+ from memos.mem_scheduler.task_schedule_modules.local_queue import SchedulerLocalQueue
12
+ from memos.mem_scheduler.task_schedule_modules.orchestrator import SchedulerOrchestrator
13
+ from memos.mem_scheduler.task_schedule_modules.redis_queue import SchedulerRedisQueue
14
+ from memos.mem_scheduler.utils.db_utils import get_utc_now
15
+ from memos.mem_scheduler.utils.misc_utils import group_messages_by_user_and_mem_cube
16
+ from memos.mem_scheduler.utils.monitor_event_utils import emit_monitor_event, to_iso
17
+ from memos.mem_scheduler.utils.status_tracker import TaskStatusTracker
18
+
19
+
20
+ logger = get_logger(__name__)
21
+
22
+
23
+ class ScheduleTaskQueue:
24
+ def __init__(
25
+ self,
26
+ use_redis_queue: bool,
27
+ maxsize: int,
28
+ disabled_handlers: list | None = None,
29
+ orchestrator: SchedulerOrchestrator | None = None,
30
+ status_tracker: TaskStatusTracker | None = None,
31
+ ):
32
+ self.use_redis_queue = use_redis_queue
33
+ self.maxsize = maxsize
34
+ self.orchestrator = SchedulerOrchestrator() if orchestrator is None else orchestrator
35
+ self.status_tracker = status_tracker
36
+
37
+ if self.use_redis_queue:
38
+ if maxsize is None or not isinstance(maxsize, int) or maxsize <= 0:
39
+ maxsize = None
40
+ self.memos_message_queue = SchedulerRedisQueue(
41
+ max_len=maxsize,
42
+ consumer_group="scheduler_group",
43
+ consumer_name="scheduler_consumer",
44
+ orchestrator=self.orchestrator,
45
+ status_tracker=self.status_tracker, # Propagate status_tracker
46
+ )
47
+ else:
48
+ self.memos_message_queue = SchedulerLocalQueue(maxsize=self.maxsize)
49
+
50
+ self.disabled_handlers = disabled_handlers
51
+
52
+ def set_status_tracker(self, status_tracker: TaskStatusTracker) -> None:
53
+ """
54
+ Set the status tracker for this queue and propagate it to the underlying queue implementation.
55
+
56
+ This allows the tracker to be injected after initialization (e.g., when Redis connection becomes available).
57
+ """
58
+ self.status_tracker = status_tracker
59
+ if self.memos_message_queue and hasattr(self.memos_message_queue, "status_tracker"):
60
+ # SchedulerRedisQueue has status_tracker attribute (from our previous fix)
61
+ # SchedulerLocalQueue can also accept it dynamically if it doesn't use __slots__
62
+ self.memos_message_queue.status_tracker = status_tracker
63
+ logger.info("Propagated status_tracker to underlying message queue")
64
+
65
+ def ack_message(
66
+ self,
67
+ user_id: str,
68
+ mem_cube_id: str,
69
+ task_label: str,
70
+ redis_message_id,
71
+ message: ScheduleMessageItem | None,
72
+ ) -> None:
73
+ if not isinstance(self.memos_message_queue, SchedulerRedisQueue):
74
+ logger.warning("ack_message is only supported for Redis queues")
75
+ return
76
+
77
+ self.memos_message_queue.ack_message(
78
+ user_id=user_id,
79
+ mem_cube_id=mem_cube_id,
80
+ task_label=task_label,
81
+ redis_message_id=redis_message_id,
82
+ message=message,
83
+ )
84
+
85
+ def get_stream_keys(self) -> list[str]:
86
+ if isinstance(self.memos_message_queue, SchedulerRedisQueue):
87
+ stream_keys = self.memos_message_queue.get_stream_keys()
88
+ else:
89
+ stream_keys = list(self.memos_message_queue.queue_streams.keys())
90
+ return stream_keys
91
+
92
+ def submit_messages(self, messages: ScheduleMessageItem | list[ScheduleMessageItem]):
93
+ """Submit messages to the message queue (either local queue or Redis)."""
94
+ if isinstance(messages, ScheduleMessageItem):
95
+ messages = [messages]
96
+
97
+ current_trace_id = get_current_trace_id()
98
+
99
+ for msg in messages:
100
+ if current_trace_id:
101
+ # Prefer current request trace_id so logs can be correlated
102
+ msg.trace_id = current_trace_id
103
+ msg.stream_key = self.memos_message_queue.get_stream_key(
104
+ user_id=msg.user_id, mem_cube_id=msg.mem_cube_id, task_label=msg.label
105
+ )
106
+
107
+ if len(messages) < 1:
108
+ logger.error("Submit empty")
109
+ elif len(messages) == 1:
110
+ if getattr(messages[0], "timestamp", None) is None:
111
+ messages[0].timestamp = get_utc_now()
112
+ enqueue_ts = to_iso(getattr(messages[0], "timestamp", None))
113
+ emit_monitor_event(
114
+ "enqueue",
115
+ messages[0],
116
+ {"enqueue_ts": enqueue_ts, "event_duration_ms": 0, "total_duration_ms": 0},
117
+ )
118
+ self.memos_message_queue.put(messages[0])
119
+ else:
120
+ user_cube_groups = group_messages_by_user_and_mem_cube(messages)
121
+
122
+ # Process each user and mem_cube combination
123
+ for _user_id, cube_groups in user_cube_groups.items():
124
+ for _mem_cube_id, user_cube_msgs in cube_groups.items():
125
+ for message in user_cube_msgs:
126
+ if not isinstance(message, ScheduleMessageItem):
127
+ error_msg = f"Invalid message type: {type(message)}, expected ScheduleMessageItem"
128
+ logger.error(error_msg)
129
+ raise TypeError(error_msg)
130
+
131
+ if getattr(message, "timestamp", None) is None:
132
+ message.timestamp = get_utc_now()
133
+
134
+ if self.disabled_handlers and message.label in self.disabled_handlers:
135
+ logger.info(
136
+ f"Skipping disabled handler: {message.label} - {message.content}"
137
+ )
138
+ continue
139
+
140
+ enqueue_ts = to_iso(getattr(message, "timestamp", None))
141
+ emit_monitor_event(
142
+ "enqueue",
143
+ message,
144
+ {
145
+ "enqueue_ts": enqueue_ts,
146
+ "event_duration_ms": 0,
147
+ "total_duration_ms": 0,
148
+ },
149
+ )
150
+ self.memos_message_queue.put(message)
151
+ logger.info(
152
+ f"Submitted message to local queue: {message.label} - {message.content}"
153
+ )
154
+
155
+ def get_messages(self, batch_size: int) -> list[ScheduleMessageItem]:
156
+ return self.memos_message_queue.get_messages(batch_size=batch_size)
157
+
158
+ def clear(self):
159
+ self.memos_message_queue.clear()
160
+
161
+ def qsize(self):
162
+ return self.memos_message_queue.qsize()
File without changes
@@ -0,0 +1,77 @@
1
+ import uuid
2
+
3
+ from typing import Any
4
+
5
+ from memos.memories.textual.item import TreeNodeTextualMemoryMetadata
6
+ from memos.memories.textual.tree import TextualMemoryItem
7
+
8
+
9
+ def format_textual_memory_item(memory_data: Any, include_embedding: bool = False) -> dict[str, Any]:
10
+ """Format a single memory item for API response."""
11
+ memory = memory_data.model_dump()
12
+ memory_id = memory["id"]
13
+ ref_id = f"[{memory_id.split('-')[0]}]"
14
+
15
+ memory["ref_id"] = ref_id
16
+ if not include_embedding:
17
+ memory["metadata"]["embedding"] = []
18
+ memory["metadata"]["sources"] = []
19
+ memory["metadata"]["ref_id"] = ref_id
20
+ memory["metadata"]["id"] = memory_id
21
+ memory["metadata"]["memory"] = memory["memory"]
22
+
23
+ return memory
24
+
25
+
26
+ def make_textual_item(memory_data):
27
+ return memory_data
28
+
29
+
30
+ def text_to_textual_memory_item(
31
+ text: str,
32
+ user_id: str | None = None,
33
+ session_id: str | None = None,
34
+ memory_type: str = "WorkingMemory",
35
+ tags: list[str] | None = None,
36
+ key: str | None = None,
37
+ sources: list | None = None,
38
+ background: str = "",
39
+ confidence: float = 0.99,
40
+ embedding: list[float] | None = None,
41
+ ) -> TextualMemoryItem:
42
+ """
43
+ Convert text into a TextualMemoryItem object.
44
+
45
+ Args:
46
+ text: Memory content text
47
+ user_id: User ID
48
+ session_id: Session ID
49
+ memory_type: Memory type, defaults to "WorkingMemory"
50
+ tags: List of tags
51
+ key: Memory key or title
52
+ sources: List of sources
53
+ background: Background information
54
+ confidence: Confidence score (0-1)
55
+ embedding: Vector embedding
56
+
57
+ Returns:
58
+ TextualMemoryItem: Wrapped memory item
59
+ """
60
+ return TextualMemoryItem(
61
+ id=str(uuid.uuid4()),
62
+ memory=text,
63
+ metadata=TreeNodeTextualMemoryMetadata(
64
+ user_id=user_id,
65
+ session_id=session_id,
66
+ memory_type=memory_type,
67
+ status="activated",
68
+ tags=tags or [],
69
+ key=key,
70
+ embedding=embedding or [],
71
+ usage=[],
72
+ sources=sources or [],
73
+ background=background,
74
+ confidence=confidence,
75
+ type="fact",
76
+ ),
77
+ )
@@ -0,0 +1,100 @@
1
+ import json
2
+ import os
3
+
4
+ from typing import Any
5
+
6
+ import yaml
7
+
8
+
9
+ def flatten_dict(
10
+ data: dict[str, Any], parent_keys: list[str] | None = None, prefix: str = ""
11
+ ) -> dict[str, str]:
12
+ """
13
+ Recursively flattens a nested dictionary to generate environment variable keys following the specified format.
14
+ Combines nested keys with underscores, converts to uppercase, and prepends a custom prefix if provided.
15
+
16
+ Args:
17
+ data: Nested dictionary to be flattened (parsed from JSON/YAML)
18
+ parent_keys: List to track nested keys during recursion
19
+ prefix: Custom prefix to be added to all generated keys
20
+
21
+ Returns:
22
+ Flattened dictionary with keys in PREFIX_KEY1_KEY2... format and string values
23
+ """
24
+ parent_keys = parent_keys or []
25
+ flat_data = {}
26
+
27
+ for key, value in data.items():
28
+ # Clean and standardize key: convert to uppercase, replace spaces/hyphens with underscores
29
+ clean_key = key.upper().replace(" ", "_").replace("-", "_")
30
+ current_keys = [*parent_keys, clean_key]
31
+
32
+ if isinstance(value, dict):
33
+ # Recursively process nested dictionaries
34
+ nested_flat = flatten_dict(value, current_keys, prefix)
35
+ flat_data.update(nested_flat)
36
+ else:
37
+ # Construct full key name with prefix (if provided) and nested keys
38
+ if prefix:
39
+ full_key = f"{prefix.upper()}_{'_'.join(current_keys)}"
40
+ else:
41
+ full_key = "_".join(current_keys)
42
+
43
+ # Process value: ensure string type, convert None to empty string
44
+ flat_value = "" if value is None else str(value).strip()
45
+
46
+ flat_data[full_key] = flat_value
47
+
48
+ return flat_data
49
+
50
+
51
+ def convert_config_to_env(input_file: str, output_file: str = ".env", prefix: str = "") -> None:
52
+ """
53
+ Converts a JSON or YAML configuration file to a .env file with standardized environment variables.
54
+ Uses the flatten_dict function to generate keys in PREFIX_KEY1_KEY2... format.
55
+
56
+ Args:
57
+ input_file: Path to input configuration file (.json, .yaml, or .yml)
58
+ output_file: Path to output .env file (default: .env)
59
+ prefix: Custom prefix for all environment variable keys
60
+
61
+ Raises:
62
+ FileNotFoundError: If input file does not exist
63
+ ValueError: If file format is unsupported or parsing fails
64
+ """
65
+ # Check if input file exists
66
+ if not os.path.exists(input_file):
67
+ raise FileNotFoundError(f"Input file not found: {input_file}")
68
+
69
+ # Parse input file based on extension
70
+ file_ext = os.path.splitext(input_file)[1].lower()
71
+ config_data: dict[str, Any] = {}
72
+
73
+ try:
74
+ with open(input_file, encoding="utf-8") as f:
75
+ if file_ext in (".json",):
76
+ config_data = json.load(f)
77
+ elif file_ext in (".yaml", ".yml"):
78
+ config_data = yaml.safe_load(f)
79
+ else:
80
+ raise ValueError(
81
+ f"Unsupported file format: {file_ext}. Supported formats: .json, .yaml, .yml"
82
+ )
83
+ except (json.JSONDecodeError, yaml.YAMLError) as e:
84
+ raise ValueError(f"Error parsing file: {e!s}") from e
85
+
86
+ # Flatten configuration and generate environment variable key-value pairs
87
+ flat_config = flatten_dict(config_data, prefix=prefix)
88
+
89
+ # Write to .env file
90
+ with open(output_file, "w", encoding="utf-8") as f:
91
+ for key, value in flat_config.items():
92
+ # Handle values containing double quotes (use no surrounding quotes)
93
+ if '"' in value:
94
+ f.write(f"{key}={value}\n")
95
+ else:
96
+ f.write(f'{key}="{value}"\n') # Enclose regular values in double quotes
97
+
98
+ print(
99
+ f"Conversion complete! Generated {output_file} with {len(flat_config)} environment variables"
100
+ )
@@ -0,0 +1,50 @@
1
+ import os
2
+ import sqlite3
3
+ import sys
4
+
5
+ from datetime import datetime, timezone
6
+
7
+
8
+ # Compatibility handling: Python 3.11+ supports UTC, earlier versions use timezone.utc
9
+ if sys.version_info >= (3, 11):
10
+ from datetime import UTC
11
+
12
+ def get_utc_now():
13
+ """Get current UTC datetime with compatibility for different Python versions"""
14
+ return datetime.now(UTC)
15
+ else:
16
+
17
+ def get_utc_now():
18
+ """Get current UTC datetime with compatibility for different Python versions"""
19
+ return datetime.now(timezone.utc)
20
+
21
+
22
+ def print_db_tables(db_path: str):
23
+ """Print all table names and structures in the SQLite database"""
24
+ print(f"\n🔍 Checking database file: {db_path}")
25
+
26
+ if not os.path.exists(db_path):
27
+ print(f"❌ File does not exist! Path: {db_path}")
28
+ return
29
+
30
+ conn = sqlite3.connect(db_path)
31
+ cursor = conn.cursor()
32
+
33
+ # List all tables
34
+ cursor.execute("SELECT name FROM sqlite_master WHERE type='table';")
35
+ tables = cursor.fetchall()
36
+ if not tables:
37
+ print("❌ Database is empty, no tables created")
38
+ else:
39
+ print(f"✅ Database contains {len(tables)} table(s):")
40
+ for (table_name,) in tables:
41
+ print(f" 📂 Table name: {table_name}")
42
+
43
+ # Print table structure
44
+ cursor.execute(f"PRAGMA table_info({table_name});")
45
+ columns = cursor.fetchall()
46
+ print(" 🧩 Structure:")
47
+ for col in columns:
48
+ print(f" {col[1]} ({col[2]}) {'(PK)' if col[5] else ''}")
49
+
50
+ conn.close()
@@ -0,0 +1,176 @@
1
+ import re
2
+
3
+ from memos.dependency import require_python_package
4
+ from memos.log import get_logger
5
+
6
+
7
+ logger = get_logger(__name__)
8
+
9
+
10
+ def transform_name_to_key(name):
11
+ """
12
+ Normalize text by removing all punctuation marks, keeping only letters, numbers, and word characters.
13
+
14
+ Args:
15
+ name (str): Input text to be processed
16
+
17
+ Returns:
18
+ str: Processed text with all punctuation removed
19
+ """
20
+ # Match all characters that are NOT:
21
+ # \w - word characters (letters, digits, underscore)
22
+ # \u4e00-\u9fff - Chinese/Japanese/Korean characters
23
+ # \s - whitespace
24
+ pattern = r"[^\w\u4e00-\u9fff\s]"
25
+
26
+ # Substitute all matched punctuation marks with empty string
27
+ # re.UNICODE flag ensures proper handling of Unicode characters
28
+ normalized = re.sub(pattern, "", name, flags=re.UNICODE)
29
+
30
+ # Optional: Collapse multiple whitespaces into single space
31
+ normalized = "_".join(normalized.split())
32
+
33
+ normalized = normalized.lower()
34
+
35
+ return normalized
36
+
37
+
38
+ def is_all_english(input_string: str) -> bool:
39
+ """Determine if the string consists entirely of English characters (including spaces)"""
40
+ return all(char.isascii() or char.isspace() for char in input_string)
41
+
42
+
43
+ def is_all_chinese(input_string: str) -> bool:
44
+ """Determine if the string consists entirely of Chinese characters (including Chinese punctuation and spaces)"""
45
+ return all(
46
+ ("\u4e00" <= char <= "\u9fff") # Basic Chinese characters
47
+ or ("\u3400" <= char <= "\u4dbf") # Extension A
48
+ or ("\u20000" <= char <= "\u2a6df") # Extension B
49
+ or ("\u2a700" <= char <= "\u2b73f") # Extension C
50
+ or ("\u2b740" <= char <= "\u2b81f") # Extension D
51
+ or ("\u2b820" <= char <= "\u2ceaf") # Extension E
52
+ or ("\u2f800" <= char <= "\u2fa1f") # Extension F
53
+ or char.isspace() # Spaces
54
+ for char in input_string
55
+ )
56
+
57
+
58
+ @require_python_package(
59
+ import_name="sklearn",
60
+ install_command="pip install scikit-learn",
61
+ install_link="https://scikit-learn.org/stable/install.html",
62
+ )
63
+ def filter_vector_based_similar_memories(
64
+ text_memories: list[str], similarity_threshold: float = 0.75
65
+ ) -> list[str]:
66
+ """
67
+ Filters out low-quality or duplicate memories based on text similarity.
68
+
69
+ Args:
70
+ text_memories: List of text memories to filter
71
+ similarity_threshold: Threshold for considering memories duplicates (0.0-1.0)
72
+ Higher values mean stricter filtering
73
+
74
+ Returns:
75
+ List of filtered memories with duplicates removed
76
+ """
77
+ from sklearn.feature_extraction.text import TfidfVectorizer
78
+ from sklearn.metrics.pairwise import cosine_similarity
79
+
80
+ if not text_memories:
81
+ logger.warning("Received empty memories list - nothing to filter")
82
+ return []
83
+
84
+ for idx in range(len(text_memories)):
85
+ if not isinstance(text_memories[idx], str):
86
+ logger.error(
87
+ f"{text_memories[idx]} in memories is not a string,"
88
+ f" and now has been transformed to be a string."
89
+ )
90
+ text_memories[idx] = str(text_memories[idx])
91
+
92
+ try:
93
+ # Step 1: Vectorize texts using TF-IDF
94
+ vectorizer = TfidfVectorizer()
95
+ tfidf_matrix = vectorizer.fit_transform(text_memories)
96
+
97
+ # Step 2: Calculate pairwise similarity matrix
98
+ similarity_matrix = cosine_similarity(tfidf_matrix)
99
+
100
+ # Step 3: Identify duplicates
101
+ to_keep = set(range(len(text_memories))) # Start with all indices
102
+ for i in range(len(similarity_matrix)):
103
+ if i not in to_keep:
104
+ continue # Already marked for removal
105
+
106
+ # Find all similar items to this one (excluding self and already removed)
107
+ similar_indices = [
108
+ j
109
+ for j in range(i + 1, len(similarity_matrix))
110
+ if similarity_matrix[i][j] >= similarity_threshold and j in to_keep
111
+ ]
112
+ similar_indices = set(similar_indices)
113
+
114
+ # Remove all similar items (keeping the first one - i)
115
+ to_keep -= similar_indices
116
+
117
+ # Return filtered memories
118
+ filtered_memories = [text_memories[i] for i in sorted(to_keep)]
119
+ logger.debug(f"filtered_memories: {filtered_memories}")
120
+ return filtered_memories
121
+
122
+ except Exception as e:
123
+ logger.error(f"Error filtering memories: {e!s}")
124
+ return text_memories # Return original list if error occurs
125
+
126
+
127
+ def filter_too_short_memories(
128
+ text_memories: list[str], min_length_threshold: int = 20
129
+ ) -> list[str]:
130
+ """
131
+ Filters out text memories that fall below the minimum length requirement.
132
+ Handles both English (word count) and Chinese (character count) differently.
133
+
134
+ Args:
135
+ text_memories: List of text memories to be filtered
136
+ min_length_threshold: Minimum length required to keep a memory.
137
+ For English: word count, for Chinese: character count.
138
+
139
+ Returns:
140
+ List of filtered memories meeting the length requirement
141
+ """
142
+ if not text_memories:
143
+ logger.debug("Empty memories list received in short memory filter")
144
+ return []
145
+
146
+ filtered_memories = []
147
+ removed_count = 0
148
+
149
+ for memory in text_memories:
150
+ stripped_memory = memory.strip()
151
+ if not stripped_memory: # Skip empty/whitespace memories
152
+ removed_count += 1
153
+ continue
154
+
155
+ # Determine measurement method based on language
156
+ if is_all_english(stripped_memory):
157
+ length = len(stripped_memory.split()) # Word count for English
158
+ elif is_all_chinese(stripped_memory):
159
+ length = len(stripped_memory) # Character count for Chinese
160
+ else:
161
+ logger.debug(f"Mixed-language memory, using character count: {stripped_memory[:50]}...")
162
+ length = len(stripped_memory) # Default to character count
163
+
164
+ if length >= min_length_threshold:
165
+ filtered_memories.append(memory)
166
+ else:
167
+ removed_count += 1
168
+
169
+ if removed_count > 0:
170
+ logger.info(
171
+ f"Filtered out {removed_count} short memories "
172
+ f"(below {min_length_threshold} units). "
173
+ f"Total remaining: {len(filtered_memories)}"
174
+ )
175
+
176
+ return filtered_memories
@@ -0,0 +1,125 @@
1
+ # src/memos/mem_scheduler/utils/metrics.py
2
+ import time
3
+
4
+ from contextlib import ContextDecorator
5
+
6
+ from prometheus_client import Counter, Gauge, Histogram, Summary
7
+
8
+
9
+ # --- Metric Definitions ---
10
+
11
+ TASKS_ENQUEUED_TOTAL = Counter(
12
+ "memos_scheduler_tasks_enqueued_total",
13
+ "Total number of tasks enqueued",
14
+ ["user_id", "task_type"],
15
+ )
16
+
17
+ TASKS_DEQUEUED_TOTAL = Counter(
18
+ "memos_scheduler_tasks_dequeued_total",
19
+ "Total number of tasks dequeued",
20
+ ["user_id", "task_type"],
21
+ )
22
+
23
+ TASK_DURATION_SECONDS = Summary(
24
+ "memos_scheduler_task_duration_seconds",
25
+ "Task processing duration in seconds",
26
+ ["user_id", "task_type"],
27
+ )
28
+
29
+ TASK_WAIT_DURATION_SECONDS = Summary(
30
+ "memos_scheduler_task_wait_duration_seconds",
31
+ "Task waiting duration in seconds",
32
+ ["user_id", "task_type"],
33
+ )
34
+
35
+ TASKS_FAILED_TOTAL = Counter(
36
+ "memos_scheduler_tasks_failed_total",
37
+ "Total number of failed tasks",
38
+ ["user_id", "task_type", "error_type"],
39
+ )
40
+
41
+ TASKS_COMPLETED_TOTAL = Counter(
42
+ "memos_scheduler_tasks_completed_total",
43
+ "Total number of successfully completed tasks",
44
+ ["user_id", "task_type"],
45
+ )
46
+
47
+ QUEUE_LENGTH = Gauge(
48
+ "memos_scheduler_queue_length", "Current length of the task queue", ["user_id"]
49
+ )
50
+
51
+ INTERNAL_SPAN_DURATION = Histogram(
52
+ "memos_scheduler_internal_span_duration_seconds",
53
+ "Duration of internal operations",
54
+ ["span_name", "user_id", "task_id"],
55
+ )
56
+
57
+
58
+ # --- Instrumentation Functions ---
59
+
60
+
61
+ def task_enqueued(user_id: str, task_type: str, count: int = 1):
62
+ TASKS_ENQUEUED_TOTAL.labels(user_id=user_id, task_type=task_type).inc(count)
63
+
64
+
65
+ def task_dequeued(user_id: str, task_type: str, count: int = 1):
66
+ TASKS_DEQUEUED_TOTAL.labels(user_id=user_id, task_type=task_type).inc(count)
67
+
68
+
69
+ def observe_task_duration(duration: float, user_id: str, task_type: str):
70
+ TASK_DURATION_SECONDS.labels(user_id=user_id, task_type=task_type).observe(duration)
71
+
72
+
73
+ def observe_task_wait_duration(duration: float, user_id: str, task_type: str):
74
+ TASK_WAIT_DURATION_SECONDS.labels(user_id=user_id, task_type=task_type).observe(duration)
75
+
76
+
77
+ def task_failed(user_id: str, task_type: str, error_type: str):
78
+ TASKS_FAILED_TOTAL.labels(user_id=user_id, task_type=task_type, error_type=error_type).inc()
79
+
80
+
81
+ def task_completed(user_id: str, task_type: str, count: int = 1):
82
+ TASKS_COMPLETED_TOTAL.labels(user_id=user_id, task_type=task_type).inc(count)
83
+
84
+
85
+ def update_queue_length(length: int, user_id: str):
86
+ QUEUE_LENGTH.labels(user_id=user_id).set(length)
87
+
88
+
89
+ def observe_internal_span(duration: float, span_name: str, user_id: str, task_id: str):
90
+ INTERNAL_SPAN_DURATION.labels(span_name=span_name, user_id=user_id, task_id=task_id).observe(
91
+ duration
92
+ )
93
+
94
+
95
+ # --- TimingSpan Context Manager ---
96
+
97
+
98
+ class TimingSpan(ContextDecorator):
99
+ """
100
+ A context manager/decorator to measure the duration of a code block and record it
101
+ as a Prometheus histogram observation.
102
+
103
+ Usage as a decorator:
104
+ @TimingSpan("expensive_operation", user_id="user123")
105
+ def my_function():
106
+ time.sleep(2)
107
+
108
+ Usage as a context manager:
109
+ with TimingSpan("another_op", user_id="user456", task_id="t1"):
110
+ ...
111
+ """
112
+
113
+ def __init__(self, span_name: str, user_id: str = "unknown", task_id: str = "unknown"):
114
+ self.span_name = span_name
115
+ self.user_id = user_id
116
+ self.task_id = task_id
117
+ self.start_time = 0
118
+
119
+ def __enter__(self):
120
+ self.start_time = time.perf_counter()
121
+ return self
122
+
123
+ def __exit__(self, exc_type, exc_val, exc_tb):
124
+ duration = time.perf_counter() - self.start_time
125
+ observe_internal_span(duration, self.span_name, self.user_id, self.task_id)