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,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)
|