MemoryOS 2.0.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- memoryos-2.0.3.dist-info/METADATA +418 -0
- memoryos-2.0.3.dist-info/RECORD +315 -0
- memoryos-2.0.3.dist-info/WHEEL +4 -0
- memoryos-2.0.3.dist-info/entry_points.txt +3 -0
- memoryos-2.0.3.dist-info/licenses/LICENSE +201 -0
- memos/__init__.py +20 -0
- memos/api/client.py +571 -0
- memos/api/config.py +1018 -0
- memos/api/context/dependencies.py +50 -0
- memos/api/exceptions.py +53 -0
- memos/api/handlers/__init__.py +62 -0
- memos/api/handlers/add_handler.py +158 -0
- memos/api/handlers/base_handler.py +194 -0
- memos/api/handlers/chat_handler.py +1401 -0
- memos/api/handlers/component_init.py +388 -0
- memos/api/handlers/config_builders.py +190 -0
- memos/api/handlers/feedback_handler.py +93 -0
- memos/api/handlers/formatters_handler.py +237 -0
- memos/api/handlers/memory_handler.py +316 -0
- memos/api/handlers/scheduler_handler.py +497 -0
- memos/api/handlers/search_handler.py +222 -0
- memos/api/handlers/suggestion_handler.py +117 -0
- memos/api/mcp_serve.py +614 -0
- memos/api/middleware/request_context.py +101 -0
- memos/api/product_api.py +38 -0
- memos/api/product_models.py +1206 -0
- memos/api/routers/__init__.py +1 -0
- memos/api/routers/product_router.py +477 -0
- memos/api/routers/server_router.py +394 -0
- memos/api/server_api.py +44 -0
- memos/api/start_api.py +433 -0
- memos/chunkers/__init__.py +4 -0
- memos/chunkers/base.py +24 -0
- memos/chunkers/charactertext_chunker.py +41 -0
- memos/chunkers/factory.py +24 -0
- memos/chunkers/markdown_chunker.py +62 -0
- memos/chunkers/sentence_chunker.py +54 -0
- memos/chunkers/simple_chunker.py +50 -0
- memos/cli.py +113 -0
- memos/configs/__init__.py +0 -0
- memos/configs/base.py +82 -0
- memos/configs/chunker.py +59 -0
- memos/configs/embedder.py +88 -0
- memos/configs/graph_db.py +236 -0
- memos/configs/internet_retriever.py +100 -0
- memos/configs/llm.py +151 -0
- memos/configs/mem_agent.py +54 -0
- memos/configs/mem_chat.py +81 -0
- memos/configs/mem_cube.py +105 -0
- memos/configs/mem_os.py +83 -0
- memos/configs/mem_reader.py +91 -0
- memos/configs/mem_scheduler.py +385 -0
- memos/configs/mem_user.py +70 -0
- memos/configs/memory.py +324 -0
- memos/configs/parser.py +38 -0
- memos/configs/reranker.py +18 -0
- memos/configs/utils.py +8 -0
- memos/configs/vec_db.py +80 -0
- memos/context/context.py +355 -0
- memos/dependency.py +52 -0
- memos/deprecation.py +262 -0
- memos/embedders/__init__.py +0 -0
- memos/embedders/ark.py +95 -0
- memos/embedders/base.py +106 -0
- memos/embedders/factory.py +29 -0
- memos/embedders/ollama.py +77 -0
- memos/embedders/sentence_transformer.py +49 -0
- memos/embedders/universal_api.py +51 -0
- memos/exceptions.py +30 -0
- memos/graph_dbs/__init__.py +0 -0
- memos/graph_dbs/base.py +274 -0
- memos/graph_dbs/factory.py +27 -0
- memos/graph_dbs/item.py +46 -0
- memos/graph_dbs/nebular.py +1794 -0
- memos/graph_dbs/neo4j.py +1942 -0
- memos/graph_dbs/neo4j_community.py +1058 -0
- memos/graph_dbs/polardb.py +5446 -0
- memos/hello_world.py +97 -0
- memos/llms/__init__.py +0 -0
- memos/llms/base.py +25 -0
- memos/llms/deepseek.py +13 -0
- memos/llms/factory.py +38 -0
- memos/llms/hf.py +443 -0
- memos/llms/hf_singleton.py +114 -0
- memos/llms/ollama.py +135 -0
- memos/llms/openai.py +222 -0
- memos/llms/openai_new.py +198 -0
- memos/llms/qwen.py +13 -0
- memos/llms/utils.py +14 -0
- memos/llms/vllm.py +218 -0
- memos/log.py +237 -0
- memos/mem_agent/base.py +19 -0
- memos/mem_agent/deepsearch_agent.py +391 -0
- memos/mem_agent/factory.py +36 -0
- memos/mem_chat/__init__.py +0 -0
- memos/mem_chat/base.py +30 -0
- memos/mem_chat/factory.py +21 -0
- memos/mem_chat/simple.py +200 -0
- memos/mem_cube/__init__.py +0 -0
- memos/mem_cube/base.py +30 -0
- memos/mem_cube/general.py +240 -0
- memos/mem_cube/navie.py +172 -0
- memos/mem_cube/utils.py +169 -0
- memos/mem_feedback/base.py +15 -0
- memos/mem_feedback/feedback.py +1192 -0
- memos/mem_feedback/simple_feedback.py +40 -0
- memos/mem_feedback/utils.py +230 -0
- memos/mem_os/client.py +5 -0
- memos/mem_os/core.py +1203 -0
- memos/mem_os/main.py +582 -0
- memos/mem_os/product.py +1608 -0
- memos/mem_os/product_server.py +455 -0
- memos/mem_os/utils/default_config.py +359 -0
- memos/mem_os/utils/format_utils.py +1403 -0
- memos/mem_os/utils/reference_utils.py +162 -0
- memos/mem_reader/__init__.py +0 -0
- memos/mem_reader/base.py +47 -0
- memos/mem_reader/factory.py +53 -0
- memos/mem_reader/memory.py +298 -0
- memos/mem_reader/multi_modal_struct.py +965 -0
- memos/mem_reader/read_multi_modal/__init__.py +43 -0
- memos/mem_reader/read_multi_modal/assistant_parser.py +311 -0
- memos/mem_reader/read_multi_modal/base.py +273 -0
- memos/mem_reader/read_multi_modal/file_content_parser.py +826 -0
- memos/mem_reader/read_multi_modal/image_parser.py +359 -0
- memos/mem_reader/read_multi_modal/multi_modal_parser.py +252 -0
- memos/mem_reader/read_multi_modal/string_parser.py +139 -0
- memos/mem_reader/read_multi_modal/system_parser.py +327 -0
- memos/mem_reader/read_multi_modal/text_content_parser.py +131 -0
- memos/mem_reader/read_multi_modal/tool_parser.py +210 -0
- memos/mem_reader/read_multi_modal/user_parser.py +218 -0
- memos/mem_reader/read_multi_modal/utils.py +358 -0
- memos/mem_reader/simple_struct.py +912 -0
- memos/mem_reader/strategy_struct.py +163 -0
- memos/mem_reader/utils.py +157 -0
- memos/mem_scheduler/__init__.py +0 -0
- memos/mem_scheduler/analyzer/__init__.py +0 -0
- memos/mem_scheduler/analyzer/api_analyzer.py +714 -0
- memos/mem_scheduler/analyzer/eval_analyzer.py +219 -0
- memos/mem_scheduler/analyzer/mos_for_test_scheduler.py +571 -0
- memos/mem_scheduler/analyzer/scheduler_for_eval.py +280 -0
- memos/mem_scheduler/base_scheduler.py +1319 -0
- memos/mem_scheduler/general_modules/__init__.py +0 -0
- memos/mem_scheduler/general_modules/api_misc.py +137 -0
- memos/mem_scheduler/general_modules/base.py +80 -0
- memos/mem_scheduler/general_modules/init_components_for_scheduler.py +425 -0
- memos/mem_scheduler/general_modules/misc.py +313 -0
- memos/mem_scheduler/general_modules/scheduler_logger.py +389 -0
- memos/mem_scheduler/general_modules/task_threads.py +315 -0
- memos/mem_scheduler/general_scheduler.py +1495 -0
- memos/mem_scheduler/memory_manage_modules/__init__.py +5 -0
- memos/mem_scheduler/memory_manage_modules/memory_filter.py +306 -0
- memos/mem_scheduler/memory_manage_modules/retriever.py +547 -0
- memos/mem_scheduler/monitors/__init__.py +0 -0
- memos/mem_scheduler/monitors/dispatcher_monitor.py +366 -0
- memos/mem_scheduler/monitors/general_monitor.py +394 -0
- memos/mem_scheduler/monitors/task_schedule_monitor.py +254 -0
- memos/mem_scheduler/optimized_scheduler.py +410 -0
- memos/mem_scheduler/orm_modules/__init__.py +0 -0
- memos/mem_scheduler/orm_modules/api_redis_model.py +518 -0
- memos/mem_scheduler/orm_modules/base_model.py +729 -0
- memos/mem_scheduler/orm_modules/monitor_models.py +261 -0
- memos/mem_scheduler/orm_modules/redis_model.py +699 -0
- memos/mem_scheduler/scheduler_factory.py +23 -0
- memos/mem_scheduler/schemas/__init__.py +0 -0
- memos/mem_scheduler/schemas/analyzer_schemas.py +52 -0
- memos/mem_scheduler/schemas/api_schemas.py +233 -0
- memos/mem_scheduler/schemas/general_schemas.py +55 -0
- memos/mem_scheduler/schemas/message_schemas.py +173 -0
- memos/mem_scheduler/schemas/monitor_schemas.py +406 -0
- memos/mem_scheduler/schemas/task_schemas.py +132 -0
- memos/mem_scheduler/task_schedule_modules/__init__.py +0 -0
- memos/mem_scheduler/task_schedule_modules/dispatcher.py +740 -0
- memos/mem_scheduler/task_schedule_modules/local_queue.py +247 -0
- memos/mem_scheduler/task_schedule_modules/orchestrator.py +74 -0
- memos/mem_scheduler/task_schedule_modules/redis_queue.py +1385 -0
- memos/mem_scheduler/task_schedule_modules/task_queue.py +162 -0
- memos/mem_scheduler/utils/__init__.py +0 -0
- memos/mem_scheduler/utils/api_utils.py +77 -0
- memos/mem_scheduler/utils/config_utils.py +100 -0
- memos/mem_scheduler/utils/db_utils.py +50 -0
- memos/mem_scheduler/utils/filter_utils.py +176 -0
- memos/mem_scheduler/utils/metrics.py +125 -0
- memos/mem_scheduler/utils/misc_utils.py +290 -0
- memos/mem_scheduler/utils/monitor_event_utils.py +67 -0
- memos/mem_scheduler/utils/status_tracker.py +229 -0
- memos/mem_scheduler/webservice_modules/__init__.py +0 -0
- memos/mem_scheduler/webservice_modules/rabbitmq_service.py +485 -0
- memos/mem_scheduler/webservice_modules/redis_service.py +380 -0
- memos/mem_user/factory.py +94 -0
- memos/mem_user/mysql_persistent_user_manager.py +271 -0
- memos/mem_user/mysql_user_manager.py +502 -0
- memos/mem_user/persistent_factory.py +98 -0
- memos/mem_user/persistent_user_manager.py +260 -0
- memos/mem_user/redis_persistent_user_manager.py +225 -0
- memos/mem_user/user_manager.py +488 -0
- memos/memories/__init__.py +0 -0
- memos/memories/activation/__init__.py +0 -0
- memos/memories/activation/base.py +42 -0
- memos/memories/activation/item.py +56 -0
- memos/memories/activation/kv.py +292 -0
- memos/memories/activation/vllmkv.py +219 -0
- memos/memories/base.py +19 -0
- memos/memories/factory.py +42 -0
- memos/memories/parametric/__init__.py +0 -0
- memos/memories/parametric/base.py +19 -0
- memos/memories/parametric/item.py +11 -0
- memos/memories/parametric/lora.py +41 -0
- memos/memories/textual/__init__.py +0 -0
- memos/memories/textual/base.py +92 -0
- memos/memories/textual/general.py +236 -0
- memos/memories/textual/item.py +304 -0
- memos/memories/textual/naive.py +187 -0
- memos/memories/textual/prefer_text_memory/__init__.py +0 -0
- memos/memories/textual/prefer_text_memory/adder.py +504 -0
- memos/memories/textual/prefer_text_memory/config.py +106 -0
- memos/memories/textual/prefer_text_memory/extractor.py +221 -0
- memos/memories/textual/prefer_text_memory/factory.py +85 -0
- memos/memories/textual/prefer_text_memory/retrievers.py +177 -0
- memos/memories/textual/prefer_text_memory/spliter.py +132 -0
- memos/memories/textual/prefer_text_memory/utils.py +93 -0
- memos/memories/textual/preference.py +344 -0
- memos/memories/textual/simple_preference.py +161 -0
- memos/memories/textual/simple_tree.py +69 -0
- memos/memories/textual/tree.py +459 -0
- memos/memories/textual/tree_text_memory/__init__.py +0 -0
- memos/memories/textual/tree_text_memory/organize/__init__.py +0 -0
- memos/memories/textual/tree_text_memory/organize/handler.py +184 -0
- memos/memories/textual/tree_text_memory/organize/manager.py +518 -0
- memos/memories/textual/tree_text_memory/organize/relation_reason_detector.py +238 -0
- memos/memories/textual/tree_text_memory/organize/reorganizer.py +622 -0
- memos/memories/textual/tree_text_memory/retrieve/__init__.py +0 -0
- memos/memories/textual/tree_text_memory/retrieve/advanced_searcher.py +364 -0
- memos/memories/textual/tree_text_memory/retrieve/bm25_util.py +186 -0
- memos/memories/textual/tree_text_memory/retrieve/bochasearch.py +419 -0
- memos/memories/textual/tree_text_memory/retrieve/internet_retriever.py +270 -0
- memos/memories/textual/tree_text_memory/retrieve/internet_retriever_factory.py +102 -0
- memos/memories/textual/tree_text_memory/retrieve/reasoner.py +61 -0
- memos/memories/textual/tree_text_memory/retrieve/recall.py +497 -0
- memos/memories/textual/tree_text_memory/retrieve/reranker.py +111 -0
- memos/memories/textual/tree_text_memory/retrieve/retrieval_mid_structs.py +16 -0
- memos/memories/textual/tree_text_memory/retrieve/retrieve_utils.py +472 -0
- memos/memories/textual/tree_text_memory/retrieve/searcher.py +848 -0
- memos/memories/textual/tree_text_memory/retrieve/task_goal_parser.py +135 -0
- memos/memories/textual/tree_text_memory/retrieve/utils.py +54 -0
- memos/memories/textual/tree_text_memory/retrieve/xinyusearch.py +387 -0
- memos/memos_tools/dinding_report_bot.py +453 -0
- memos/memos_tools/lockfree_dict.py +120 -0
- memos/memos_tools/notification_service.py +44 -0
- memos/memos_tools/notification_utils.py +142 -0
- memos/memos_tools/singleton.py +174 -0
- memos/memos_tools/thread_safe_dict.py +310 -0
- memos/memos_tools/thread_safe_dict_segment.py +382 -0
- memos/multi_mem_cube/__init__.py +0 -0
- memos/multi_mem_cube/composite_cube.py +86 -0
- memos/multi_mem_cube/single_cube.py +874 -0
- memos/multi_mem_cube/views.py +54 -0
- memos/parsers/__init__.py +0 -0
- memos/parsers/base.py +15 -0
- memos/parsers/factory.py +21 -0
- memos/parsers/markitdown.py +28 -0
- memos/reranker/__init__.py +4 -0
- memos/reranker/base.py +25 -0
- memos/reranker/concat.py +103 -0
- memos/reranker/cosine_local.py +102 -0
- memos/reranker/factory.py +72 -0
- memos/reranker/http_bge.py +324 -0
- memos/reranker/http_bge_strategy.py +327 -0
- memos/reranker/noop.py +19 -0
- memos/reranker/strategies/__init__.py +4 -0
- memos/reranker/strategies/base.py +61 -0
- memos/reranker/strategies/concat_background.py +94 -0
- memos/reranker/strategies/concat_docsource.py +110 -0
- memos/reranker/strategies/dialogue_common.py +109 -0
- memos/reranker/strategies/factory.py +31 -0
- memos/reranker/strategies/single_turn.py +107 -0
- memos/reranker/strategies/singleturn_outmem.py +98 -0
- memos/settings.py +10 -0
- memos/templates/__init__.py +0 -0
- memos/templates/advanced_search_prompts.py +211 -0
- memos/templates/cloud_service_prompt.py +107 -0
- memos/templates/instruction_completion.py +66 -0
- memos/templates/mem_agent_prompts.py +85 -0
- memos/templates/mem_feedback_prompts.py +822 -0
- memos/templates/mem_reader_prompts.py +1096 -0
- memos/templates/mem_reader_strategy_prompts.py +238 -0
- memos/templates/mem_scheduler_prompts.py +626 -0
- memos/templates/mem_search_prompts.py +93 -0
- memos/templates/mos_prompts.py +403 -0
- memos/templates/prefer_complete_prompt.py +735 -0
- memos/templates/tool_mem_prompts.py +139 -0
- memos/templates/tree_reorganize_prompts.py +230 -0
- memos/types/__init__.py +34 -0
- memos/types/general_types.py +151 -0
- memos/types/openai_chat_completion_types/__init__.py +15 -0
- memos/types/openai_chat_completion_types/chat_completion_assistant_message_param.py +56 -0
- memos/types/openai_chat_completion_types/chat_completion_content_part_image_param.py +27 -0
- memos/types/openai_chat_completion_types/chat_completion_content_part_input_audio_param.py +23 -0
- memos/types/openai_chat_completion_types/chat_completion_content_part_param.py +43 -0
- memos/types/openai_chat_completion_types/chat_completion_content_part_refusal_param.py +16 -0
- memos/types/openai_chat_completion_types/chat_completion_content_part_text_param.py +16 -0
- memos/types/openai_chat_completion_types/chat_completion_message_custom_tool_call_param.py +27 -0
- memos/types/openai_chat_completion_types/chat_completion_message_function_tool_call_param.py +32 -0
- memos/types/openai_chat_completion_types/chat_completion_message_param.py +18 -0
- memos/types/openai_chat_completion_types/chat_completion_message_tool_call_union_param.py +15 -0
- memos/types/openai_chat_completion_types/chat_completion_system_message_param.py +36 -0
- memos/types/openai_chat_completion_types/chat_completion_tool_message_param.py +30 -0
- memos/types/openai_chat_completion_types/chat_completion_user_message_param.py +34 -0
- memos/utils.py +123 -0
- memos/vec_dbs/__init__.py +0 -0
- memos/vec_dbs/base.py +117 -0
- memos/vec_dbs/factory.py +23 -0
- memos/vec_dbs/item.py +50 -0
- memos/vec_dbs/milvus.py +654 -0
- memos/vec_dbs/qdrant.py +355 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Local Queue implementation for SchedulerMessageItem objects.
|
|
3
|
+
This module provides a local-based queue implementation that can replace
|
|
4
|
+
the local memos_message_queue functionality in BaseScheduler.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from collections.abc import Callable
|
|
12
|
+
|
|
13
|
+
from memos.log import get_logger
|
|
14
|
+
from memos.mem_scheduler.general_modules.misc import AutoDroppingQueue as Queue
|
|
15
|
+
from memos.mem_scheduler.schemas.message_schemas import ScheduleMessageItem
|
|
16
|
+
from memos.mem_scheduler.schemas.task_schemas import DEFAULT_STREAM_KEY_PREFIX
|
|
17
|
+
from memos.mem_scheduler.task_schedule_modules.orchestrator import SchedulerOrchestrator
|
|
18
|
+
from memos.mem_scheduler.utils.status_tracker import TaskStatusTracker
|
|
19
|
+
from memos.mem_scheduler.webservice_modules.redis_service import RedisSchedulerModule
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
logger = get_logger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SchedulerLocalQueue(RedisSchedulerModule):
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
maxsize: int = 0,
|
|
29
|
+
stream_key_prefix: str = DEFAULT_STREAM_KEY_PREFIX,
|
|
30
|
+
orchestrator: SchedulerOrchestrator | None = None,
|
|
31
|
+
status_tracker: TaskStatusTracker | None = None,
|
|
32
|
+
):
|
|
33
|
+
"""
|
|
34
|
+
Initialize the SchedulerLocalQueue with a maximum queue size limit.
|
|
35
|
+
Arguments match SchedulerRedisQueue for compatibility.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
maxsize (int): Maximum number of messages allowed in each individual queue.
|
|
39
|
+
stream_key_prefix (str): Prefix for stream keys (simulated).
|
|
40
|
+
orchestrator: SchedulerOrchestrator instance (ignored).
|
|
41
|
+
status_tracker: TaskStatusTracker instance (ignored).
|
|
42
|
+
"""
|
|
43
|
+
super().__init__()
|
|
44
|
+
|
|
45
|
+
self.stream_key_prefix = stream_key_prefix or "local_queue"
|
|
46
|
+
|
|
47
|
+
self.max_internal_message_queue_size = maxsize
|
|
48
|
+
|
|
49
|
+
# Dictionary to hold per-stream queues: key = stream_key, value = Queue[ScheduleMessageItem]
|
|
50
|
+
self.queue_streams: dict[str, Queue[ScheduleMessageItem]] = {}
|
|
51
|
+
|
|
52
|
+
self.orchestrator = orchestrator
|
|
53
|
+
self.status_tracker = status_tracker
|
|
54
|
+
|
|
55
|
+
self._is_listening = False
|
|
56
|
+
self._message_handler: Callable[[ScheduleMessageItem], None] | None = None
|
|
57
|
+
|
|
58
|
+
logger.info(
|
|
59
|
+
f"SchedulerLocalQueue initialized with max_internal_message_queue_size={self.max_internal_message_queue_size}"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
def get_stream_key(self, user_id: str, mem_cube_id: str, task_label: str) -> str:
|
|
63
|
+
stream_key = f"{self.stream_key_prefix}:{user_id}:{mem_cube_id}:{task_label}"
|
|
64
|
+
return stream_key
|
|
65
|
+
|
|
66
|
+
def put(
|
|
67
|
+
self, message: ScheduleMessageItem, block: bool = True, timeout: float | None = None
|
|
68
|
+
) -> None:
|
|
69
|
+
"""
|
|
70
|
+
Put a message into the appropriate internal queue based on user_id and mem_cube_id.
|
|
71
|
+
|
|
72
|
+
If the corresponding queue does not exist, it is created automatically.
|
|
73
|
+
This method uses a local in-memory queue (not Redis) for buffering messages.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
message (ScheduleMessageItem): The message to enqueue.
|
|
77
|
+
block (bool): If True, block if the queue is full; if False, raise Full immediately.
|
|
78
|
+
timeout (float | None): Maximum time to wait for the queue to become available.
|
|
79
|
+
If None, block indefinitely. Ignored if block=False.
|
|
80
|
+
|
|
81
|
+
Raises:
|
|
82
|
+
queue.Full: If the queue is full and block=False or timeout expires.
|
|
83
|
+
Exception: Any underlying error during queue.put() operation.
|
|
84
|
+
"""
|
|
85
|
+
stream_key = self.get_stream_key(
|
|
86
|
+
user_id=message.user_id, mem_cube_id=message.mem_cube_id, task_label=message.label
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
message.stream_key = stream_key
|
|
90
|
+
|
|
91
|
+
# Create the queue if it doesn't exist yet
|
|
92
|
+
if stream_key not in self.queue_streams:
|
|
93
|
+
logger.info(f"Creating new internal queue for stream: {stream_key}")
|
|
94
|
+
self.queue_streams[stream_key] = Queue(maxsize=self.max_internal_message_queue_size)
|
|
95
|
+
|
|
96
|
+
try:
|
|
97
|
+
self.queue_streams[stream_key].put(item=message, block=block, timeout=timeout)
|
|
98
|
+
logger.info(
|
|
99
|
+
f"Message successfully put into queue '{stream_key}'. Current size: {self.queue_streams[stream_key].qsize()}"
|
|
100
|
+
)
|
|
101
|
+
except Exception as e:
|
|
102
|
+
logger.error(f"Failed to put message into queue '{stream_key}': {e}", exc_info=True)
|
|
103
|
+
raise # Re-raise to maintain caller expectations
|
|
104
|
+
|
|
105
|
+
def get(
|
|
106
|
+
self,
|
|
107
|
+
stream_key: str,
|
|
108
|
+
block: bool = True,
|
|
109
|
+
timeout: float | None = None,
|
|
110
|
+
batch_size: int | None = 1,
|
|
111
|
+
) -> list[ScheduleMessageItem]:
|
|
112
|
+
if batch_size is not None and batch_size <= 0:
|
|
113
|
+
logger.warning(
|
|
114
|
+
f"get() called with invalid batch_size: {batch_size}. Returning empty list."
|
|
115
|
+
)
|
|
116
|
+
return []
|
|
117
|
+
|
|
118
|
+
# Return empty list if queue does not exist
|
|
119
|
+
if stream_key not in self.queue_streams:
|
|
120
|
+
logger.error(f"Stream {stream_key} does not exist when trying to get messages.")
|
|
121
|
+
return []
|
|
122
|
+
|
|
123
|
+
# Ensure we always request a batch so we get a list back
|
|
124
|
+
effective_batch_size = batch_size if batch_size is not None else 1
|
|
125
|
+
|
|
126
|
+
# Note: Assumes custom Queue implementation supports batch_size parameter
|
|
127
|
+
res = self.queue_streams[stream_key].get(
|
|
128
|
+
block=block, timeout=timeout, batch_size=effective_batch_size
|
|
129
|
+
)
|
|
130
|
+
logger.debug(
|
|
131
|
+
f"Retrieved {len(res)} messages from queue '{stream_key}'. Current size: {self.queue_streams[stream_key].qsize()}"
|
|
132
|
+
)
|
|
133
|
+
return res
|
|
134
|
+
|
|
135
|
+
def get_nowait(self, stream_key: str, batch_size: int | None = 1) -> list[ScheduleMessageItem]:
|
|
136
|
+
"""
|
|
137
|
+
Non-blocking version of get(). Equivalent to get(stream_key, block=False, batch_size=batch_size).
|
|
138
|
+
|
|
139
|
+
Returns immediately with available messages or an empty list if queue is empty.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
stream_key (str): The stream/queue identifier.
|
|
143
|
+
batch_size (int | None): Number of messages to retrieve in a batch.
|
|
144
|
+
If None, retrieves one message.
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
List[ScheduleMessageItem]: Retrieved messages or empty list if queue is empty.
|
|
148
|
+
"""
|
|
149
|
+
logger.debug(f"get_nowait() called for {stream_key} with batch_size: {batch_size}")
|
|
150
|
+
return self.get(stream_key=stream_key, block=False, batch_size=batch_size)
|
|
151
|
+
|
|
152
|
+
def get_messages(self, batch_size: int) -> list[ScheduleMessageItem]:
|
|
153
|
+
"""
|
|
154
|
+
Get messages from all streams in round-robin or sequential fashion.
|
|
155
|
+
Equivalent to SchedulerRedisQueue.get_messages.
|
|
156
|
+
"""
|
|
157
|
+
messages = []
|
|
158
|
+
# Snapshot keys to avoid runtime modification issues
|
|
159
|
+
stream_keys = list(self.queue_streams.keys())
|
|
160
|
+
|
|
161
|
+
# Simple strategy: try to get up to batch_size messages across all streams
|
|
162
|
+
# We can just iterate and collect.
|
|
163
|
+
|
|
164
|
+
# Calculate how many to get per stream to be fair?
|
|
165
|
+
# Or just greedy? Redis implementation uses a complex logic.
|
|
166
|
+
# For local, let's keep it simple: just iterate and take what's available (non-blocking)
|
|
167
|
+
|
|
168
|
+
for stream_key in stream_keys:
|
|
169
|
+
if len(messages) >= batch_size:
|
|
170
|
+
break
|
|
171
|
+
|
|
172
|
+
needed = batch_size - len(messages)
|
|
173
|
+
# Use get_nowait to avoid blocking
|
|
174
|
+
fetched = self.get_nowait(stream_key=stream_key, batch_size=needed)
|
|
175
|
+
messages.extend(fetched)
|
|
176
|
+
|
|
177
|
+
return messages
|
|
178
|
+
|
|
179
|
+
def qsize(self) -> dict:
|
|
180
|
+
"""
|
|
181
|
+
Return the current size of all internal queues as a dictionary.
|
|
182
|
+
|
|
183
|
+
Each key is the stream name, and each value is the number of messages in that queue.
|
|
184
|
+
Also includes 'total_size'.
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
Dict[str, int]: Mapping from stream name to current queue size.
|
|
188
|
+
"""
|
|
189
|
+
sizes = {stream: queue.qsize() for stream, queue in self.queue_streams.items()}
|
|
190
|
+
total_size = sum(sizes.values())
|
|
191
|
+
sizes["total_size"] = total_size
|
|
192
|
+
logger.debug(f"Current queue sizes: {sizes}")
|
|
193
|
+
return sizes
|
|
194
|
+
|
|
195
|
+
def clear(self, stream_key: str | None = None) -> None:
|
|
196
|
+
if stream_key:
|
|
197
|
+
if stream_key in self.queue_streams:
|
|
198
|
+
self.queue_streams[stream_key].clear()
|
|
199
|
+
else:
|
|
200
|
+
for queue in self.queue_streams.values():
|
|
201
|
+
queue.clear()
|
|
202
|
+
|
|
203
|
+
@property
|
|
204
|
+
def unfinished_tasks(self) -> int:
|
|
205
|
+
"""
|
|
206
|
+
Calculate the total number of unprocessed messages across all queues.
|
|
207
|
+
|
|
208
|
+
This is a convenience property for monitoring overall system load.
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
int: Sum of all message counts in all internal queues.
|
|
212
|
+
"""
|
|
213
|
+
# qsize() now includes "total_size", so we need to be careful not to double count if we use qsize() values
|
|
214
|
+
# But qsize() implementation above sums values from queue_streams, then adds total_size.
|
|
215
|
+
# So sum(self.queue_streams.values().qsize()) is safer.
|
|
216
|
+
total = sum(queue.qsize() for queue in self.queue_streams.values())
|
|
217
|
+
logger.debug(f"Total unfinished tasks across all queues: {total}")
|
|
218
|
+
return total
|
|
219
|
+
|
|
220
|
+
def get_stream_keys(self, stream_key_prefix: str | None = None) -> list[str]:
|
|
221
|
+
"""
|
|
222
|
+
Return list of active stream keys.
|
|
223
|
+
"""
|
|
224
|
+
prefix = stream_key_prefix or self.stream_key_prefix
|
|
225
|
+
return [k for k in self.queue_streams if k.startswith(prefix)]
|
|
226
|
+
|
|
227
|
+
def size(self) -> int:
|
|
228
|
+
"""
|
|
229
|
+
Total size of all queues.
|
|
230
|
+
"""
|
|
231
|
+
return sum(q.qsize() for q in self.queue_streams.values())
|
|
232
|
+
|
|
233
|
+
def empty(self) -> bool:
|
|
234
|
+
"""
|
|
235
|
+
Check if all queues are empty.
|
|
236
|
+
"""
|
|
237
|
+
return self.size() == 0
|
|
238
|
+
|
|
239
|
+
def full(self) -> bool:
|
|
240
|
+
"""
|
|
241
|
+
Check if any queue is full (approximate).
|
|
242
|
+
"""
|
|
243
|
+
if self.max_internal_message_queue_size <= 0:
|
|
244
|
+
return False
|
|
245
|
+
return any(
|
|
246
|
+
q.qsize() >= self.max_internal_message_queue_size for q in self.queue_streams.values()
|
|
247
|
+
)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Scheduler Orchestrator for Redis-backed task queues.
|
|
3
|
+
|
|
4
|
+
This module provides an orchestrator class that works with `SchedulerRedisQueue` to:
|
|
5
|
+
- Broker tasks from Redis streams according to per-user priority weights.
|
|
6
|
+
- Maintain a cache of fetched messages and assemble balanced batches across
|
|
7
|
+
`(user_id, mem_cube_id, task_label)` groups.
|
|
8
|
+
|
|
9
|
+
Stream format:
|
|
10
|
+
- Keys follow: `{prefix}:{user_id}:{mem_cube_id}:{task_label}`
|
|
11
|
+
|
|
12
|
+
Default behavior:
|
|
13
|
+
- All users have priority 1, so fetch sizes are equal per user.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
from memos.log import get_logger
|
|
19
|
+
from memos.mem_scheduler.schemas.task_schemas import (
|
|
20
|
+
ADD_TASK_LABEL,
|
|
21
|
+
ANSWER_TASK_LABEL,
|
|
22
|
+
DEFAULT_PENDING_CLAIM_MIN_IDLE_MS,
|
|
23
|
+
PREF_ADD_TASK_LABEL,
|
|
24
|
+
QUERY_TASK_LABEL,
|
|
25
|
+
TaskPriorityLevel,
|
|
26
|
+
)
|
|
27
|
+
from memos.mem_scheduler.webservice_modules.redis_service import RedisSchedulerModule
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
logger = get_logger(__name__)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class SchedulerOrchestrator(RedisSchedulerModule):
|
|
34
|
+
def __init__(self):
|
|
35
|
+
"""
|
|
36
|
+
Args:
|
|
37
|
+
queue: An instance of `SchedulerRedisQueue`.
|
|
38
|
+
"""
|
|
39
|
+
# Cache of fetched messages grouped by (user_id, mem_cube_id, task_label)
|
|
40
|
+
self._cache = None
|
|
41
|
+
self.tasks_priorities = {
|
|
42
|
+
ADD_TASK_LABEL: TaskPriorityLevel.LEVEL_1,
|
|
43
|
+
QUERY_TASK_LABEL: TaskPriorityLevel.LEVEL_1,
|
|
44
|
+
ANSWER_TASK_LABEL: TaskPriorityLevel.LEVEL_1,
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
# Per-task minimum idle time (ms) before claiming pending messages
|
|
48
|
+
# Default fallback handled in `get_task_idle_min`.
|
|
49
|
+
self.tasks_min_idle_ms = {
|
|
50
|
+
# Preferential add tasks: allow claiming pending sooner (10 minute)
|
|
51
|
+
PREF_ADD_TASK_LABEL: 600_000,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
def get_stream_priorities(self) -> None | dict:
|
|
55
|
+
return None
|
|
56
|
+
|
|
57
|
+
def get_task_priority(self, task_label: str):
|
|
58
|
+
return self.tasks_priorities.get(task_label, TaskPriorityLevel.LEVEL_3)
|
|
59
|
+
|
|
60
|
+
def get_task_idle_min(self, task_label: str) -> int:
|
|
61
|
+
idle_min = self.tasks_min_idle_ms.get(task_label, DEFAULT_PENDING_CLAIM_MIN_IDLE_MS)
|
|
62
|
+
return idle_min
|
|
63
|
+
|
|
64
|
+
def get_stream_quotas(self, stream_keys, consume_batch_size) -> dict:
|
|
65
|
+
stream_priorities = self.get_stream_priorities()
|
|
66
|
+
stream_quotas = {}
|
|
67
|
+
for stream_key in stream_keys:
|
|
68
|
+
if stream_priorities is None:
|
|
69
|
+
# Distribute per-stream evenly
|
|
70
|
+
stream_quotas[stream_key] = consume_batch_size
|
|
71
|
+
else:
|
|
72
|
+
# TODO: not implemented yet
|
|
73
|
+
stream_quotas[stream_key] = consume_batch_size
|
|
74
|
+
return stream_quotas
|