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,313 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
from contextlib import suppress
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from queue import Empty, Full, Queue
|
|
7
|
+
from typing import TYPE_CHECKING, Any, Generic, TypeVar
|
|
8
|
+
|
|
9
|
+
from dotenv import load_dotenv
|
|
10
|
+
from pydantic import field_serializer
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from pydantic import BaseModel
|
|
15
|
+
|
|
16
|
+
T = TypeVar("T")
|
|
17
|
+
|
|
18
|
+
BaseModelType = TypeVar("T", bound="BaseModel")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class EnvConfigMixin(Generic[T]):
|
|
22
|
+
"""Abstract base class for environment variable configuration."""
|
|
23
|
+
|
|
24
|
+
ENV_PREFIX = "MEMSCHEDULER_"
|
|
25
|
+
|
|
26
|
+
@classmethod
|
|
27
|
+
def get_env_prefix(cls) -> str:
|
|
28
|
+
"""Automatically generates environment variable prefix from class name.
|
|
29
|
+
|
|
30
|
+
Converts the class name to uppercase and appends an underscore.
|
|
31
|
+
If the class name ends with 'Config', that suffix is removed first.
|
|
32
|
+
|
|
33
|
+
Examples:
|
|
34
|
+
RabbitMQConfig -> "RABBITMQ_"
|
|
35
|
+
OpenAIConfig -> "OPENAI_"
|
|
36
|
+
GraphDBAuthConfig -> "GRAPHDBAUTH_"
|
|
37
|
+
"""
|
|
38
|
+
class_name = cls.__name__
|
|
39
|
+
# Remove 'Config' suffix if present
|
|
40
|
+
if class_name.endswith("Config"):
|
|
41
|
+
class_name = class_name[:-6]
|
|
42
|
+
# Convert to uppercase and add trailing underscore
|
|
43
|
+
|
|
44
|
+
return f"{cls.ENV_PREFIX}{class_name.upper()}_"
|
|
45
|
+
|
|
46
|
+
@classmethod
|
|
47
|
+
def from_env(cls: type[T]) -> T:
|
|
48
|
+
"""Creates a config instance from environment variables.
|
|
49
|
+
|
|
50
|
+
Reads all environment variables with the class-specific prefix and maps them
|
|
51
|
+
to corresponding configuration fields (converting to the appropriate types).
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
An instance of the config class populated from environment variables.
|
|
55
|
+
|
|
56
|
+
Raises:
|
|
57
|
+
ValueError: If required environment variables are missing.
|
|
58
|
+
"""
|
|
59
|
+
load_dotenv()
|
|
60
|
+
|
|
61
|
+
prefix = cls.get_env_prefix()
|
|
62
|
+
field_values = {}
|
|
63
|
+
|
|
64
|
+
for field_name, field_info in cls.model_fields.items():
|
|
65
|
+
env_var = f"{prefix}{field_name.upper()}"
|
|
66
|
+
field_type = field_info.annotation
|
|
67
|
+
|
|
68
|
+
if field_info.is_required() and env_var not in os.environ:
|
|
69
|
+
raise ValueError(f"Required environment variable {env_var} is missing")
|
|
70
|
+
|
|
71
|
+
if env_var in os.environ:
|
|
72
|
+
raw_value = os.environ[env_var]
|
|
73
|
+
field_values[field_name] = cls._parse_env_value(raw_value, field_type)
|
|
74
|
+
elif field_info.default is not None:
|
|
75
|
+
field_values[field_name] = field_info.default
|
|
76
|
+
else:
|
|
77
|
+
raise ValueError()
|
|
78
|
+
return cls(**field_values)
|
|
79
|
+
|
|
80
|
+
@classmethod
|
|
81
|
+
def _parse_env_value(cls, value: str, target_type: type) -> Any:
|
|
82
|
+
"""Converts environment variable string to appropriate type."""
|
|
83
|
+
if target_type is bool:
|
|
84
|
+
return value.lower() in ("true", "1", "t", "y", "yes")
|
|
85
|
+
if target_type is int:
|
|
86
|
+
return int(value)
|
|
87
|
+
if target_type is float:
|
|
88
|
+
return float(value)
|
|
89
|
+
return value
|
|
90
|
+
|
|
91
|
+
@classmethod
|
|
92
|
+
def print_env_mapping(cls) -> None:
|
|
93
|
+
"""Print the mapping between class fields and their corresponding environment variable names.
|
|
94
|
+
|
|
95
|
+
Displays each field's name, type, whether it's required, default value, and corresponding environment variable name.
|
|
96
|
+
"""
|
|
97
|
+
prefix = cls.get_env_prefix()
|
|
98
|
+
print(f"\n=== {cls.__name__} Environment Variable Mapping ===")
|
|
99
|
+
print(f"Environment Variable Prefix: {prefix}")
|
|
100
|
+
print("-" * 60)
|
|
101
|
+
|
|
102
|
+
if not hasattr(cls, "model_fields"):
|
|
103
|
+
print("This class does not define model_fields, may not be a Pydantic model")
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
for field_name, field_info in cls.model_fields.items():
|
|
107
|
+
env_var = f"{prefix}{field_name.upper()}"
|
|
108
|
+
field_type = field_info.annotation
|
|
109
|
+
is_required = field_info.is_required()
|
|
110
|
+
default_value = field_info.default if field_info.default is not None else "None"
|
|
111
|
+
|
|
112
|
+
print(f"Field Name: {field_name}")
|
|
113
|
+
print(f" Environment Variable: {env_var}")
|
|
114
|
+
print(f" Type: {field_type}")
|
|
115
|
+
print(f" Required: {'Yes' if is_required else 'No'}")
|
|
116
|
+
print(f" Default Value: {default_value}")
|
|
117
|
+
print(f" Current Environment Value: {os.environ.get(env_var, 'Not Set')}")
|
|
118
|
+
print("-" * 40)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
class DictConversionMixin:
|
|
122
|
+
"""
|
|
123
|
+
Provides conversion functionality between Pydantic models and dictionaries,
|
|
124
|
+
including datetime serialization handling.
|
|
125
|
+
"""
|
|
126
|
+
|
|
127
|
+
@field_serializer("timestamp", check_fields=False)
|
|
128
|
+
def serialize_datetime(self, dt: datetime | None, _info) -> str | None:
|
|
129
|
+
"""
|
|
130
|
+
Custom timestamp serialization logic.
|
|
131
|
+
- Supports timezone-aware datetime objects
|
|
132
|
+
- Compatible with models without timestamp field (via check_fields=False)
|
|
133
|
+
"""
|
|
134
|
+
if dt is None:
|
|
135
|
+
return None
|
|
136
|
+
return dt.isoformat()
|
|
137
|
+
|
|
138
|
+
def to_dict(self) -> dict:
|
|
139
|
+
"""
|
|
140
|
+
Convert model instance to dictionary.
|
|
141
|
+
- Uses model_dump to ensure field consistency
|
|
142
|
+
- Prioritizes custom serializer for timestamp handling
|
|
143
|
+
"""
|
|
144
|
+
dump_data = self.model_dump()
|
|
145
|
+
if hasattr(self, "timestamp") and self.timestamp is not None:
|
|
146
|
+
dump_data["timestamp"] = self.serialize_datetime(self.timestamp, None)
|
|
147
|
+
return dump_data
|
|
148
|
+
|
|
149
|
+
def to_json(self, **kwargs) -> str:
|
|
150
|
+
"""
|
|
151
|
+
Convert model instance to a JSON string.
|
|
152
|
+
- Accepts the same kwargs as json.dumps (e.g., indent, ensure_ascii)
|
|
153
|
+
- Default settings make JSON human-readable and UTF-8 safe
|
|
154
|
+
"""
|
|
155
|
+
return json.dumps(self.to_dict(), ensure_ascii=False, default=lambda o: str(o), **kwargs)
|
|
156
|
+
|
|
157
|
+
@classmethod
|
|
158
|
+
def from_json(cls: type[BaseModelType], json_str: str) -> BaseModelType:
|
|
159
|
+
"""
|
|
160
|
+
Create model instance from a JSON string.
|
|
161
|
+
- Parses JSON into a dictionary and delegates to from_dict
|
|
162
|
+
"""
|
|
163
|
+
try:
|
|
164
|
+
data = json.loads(json_str)
|
|
165
|
+
except json.JSONDecodeError as e:
|
|
166
|
+
raise ValueError(f"Invalid JSON string: {e}") from e
|
|
167
|
+
return cls.from_dict(data)
|
|
168
|
+
|
|
169
|
+
@classmethod
|
|
170
|
+
def from_dict(cls: type[BaseModelType], data: dict) -> BaseModelType:
|
|
171
|
+
"""
|
|
172
|
+
Create model instance from dictionary.
|
|
173
|
+
- Automatically converts timestamp strings to datetime objects
|
|
174
|
+
"""
|
|
175
|
+
data_copy = data.copy() # Avoid modifying original dictionary
|
|
176
|
+
if "timestamp" in data_copy and isinstance(data_copy["timestamp"], str):
|
|
177
|
+
try:
|
|
178
|
+
data_copy["timestamp"] = datetime.fromisoformat(data_copy["timestamp"])
|
|
179
|
+
except ValueError:
|
|
180
|
+
# Handle invalid time formats - adjust as needed (e.g., log warning or set to None)
|
|
181
|
+
data_copy["timestamp"] = None
|
|
182
|
+
|
|
183
|
+
return cls(**data_copy)
|
|
184
|
+
|
|
185
|
+
def __str__(self) -> str:
|
|
186
|
+
"""
|
|
187
|
+
Convert to formatted JSON string.
|
|
188
|
+
- Used for user-friendly display in print() or str() calls
|
|
189
|
+
"""
|
|
190
|
+
return json.dumps(
|
|
191
|
+
self.to_dict(),
|
|
192
|
+
indent=4,
|
|
193
|
+
ensure_ascii=False,
|
|
194
|
+
default=lambda o: str(o), # Handle other non-serializable objects
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class AutoDroppingQueue(Queue[T]):
|
|
199
|
+
"""A thread-safe queue that automatically drops the oldest item when full."""
|
|
200
|
+
|
|
201
|
+
def __init__(self, maxsize: int = 0):
|
|
202
|
+
# If maxsize <= 0, set to 0 (unlimited queue size)
|
|
203
|
+
if maxsize <= 0:
|
|
204
|
+
maxsize = 0
|
|
205
|
+
super().__init__(maxsize=maxsize)
|
|
206
|
+
|
|
207
|
+
def put(self, item: T, block: bool = False, timeout: float | None = None) -> None:
|
|
208
|
+
"""Put an item into the queue.
|
|
209
|
+
|
|
210
|
+
If the queue is full, the oldest item will be automatically removed to make space.
|
|
211
|
+
IMPORTANT: When we drop an item we also call `task_done()` to keep
|
|
212
|
+
the internal `unfinished_tasks` counter consistent (the dropped task
|
|
213
|
+
will never be processed).
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
item: The item to be put into the queue
|
|
217
|
+
block: Ignored (kept for compatibility with Queue interface)
|
|
218
|
+
timeout: Ignored (kept for compatibility with Queue interface)
|
|
219
|
+
"""
|
|
220
|
+
while True:
|
|
221
|
+
try:
|
|
222
|
+
# First try non-blocking put
|
|
223
|
+
super().put(item, block=block, timeout=timeout)
|
|
224
|
+
return
|
|
225
|
+
except Full:
|
|
226
|
+
# Remove the oldest item and mark it done to avoid leaking unfinished_tasks
|
|
227
|
+
with suppress(Empty):
|
|
228
|
+
_ = self.get_nowait()
|
|
229
|
+
# If the removed item had previously incremented unfinished_tasks,
|
|
230
|
+
# we must decrement here since it will never be processed.
|
|
231
|
+
with suppress(ValueError):
|
|
232
|
+
self.task_done()
|
|
233
|
+
# Continue loop to retry putting the item
|
|
234
|
+
|
|
235
|
+
def get(
|
|
236
|
+
self, block: bool = True, timeout: float | None = None, batch_size: int | None = None
|
|
237
|
+
) -> list[T]:
|
|
238
|
+
"""Get items from the queue.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
block: Whether to block if no items are available (default: True)
|
|
242
|
+
timeout: Timeout in seconds for blocking operations (default: None)
|
|
243
|
+
batch_size: Number of items to retrieve (default: 1)
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
List of items (always returns a list for consistency)
|
|
247
|
+
|
|
248
|
+
Raises:
|
|
249
|
+
Empty: If no items are available and block=False or timeout expires
|
|
250
|
+
"""
|
|
251
|
+
|
|
252
|
+
if batch_size is None:
|
|
253
|
+
return super().get(block=block, timeout=timeout)
|
|
254
|
+
items = []
|
|
255
|
+
for _ in range(batch_size):
|
|
256
|
+
try:
|
|
257
|
+
items.append(super().get(block=block, timeout=timeout))
|
|
258
|
+
except Empty:
|
|
259
|
+
if not items and block:
|
|
260
|
+
# If we haven't gotten any items and we're blocking, re-raise Empty
|
|
261
|
+
raise
|
|
262
|
+
break
|
|
263
|
+
return items
|
|
264
|
+
|
|
265
|
+
def get_nowait(self, batch_size: int | None = None) -> list[T]:
|
|
266
|
+
"""Get items from the queue without blocking.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
batch_size: Number of items to retrieve (default: 1)
|
|
270
|
+
|
|
271
|
+
Returns:
|
|
272
|
+
List of items (always returns a list for consistency)
|
|
273
|
+
"""
|
|
274
|
+
if batch_size is None:
|
|
275
|
+
return super().get_nowait()
|
|
276
|
+
|
|
277
|
+
items = []
|
|
278
|
+
for _ in range(batch_size):
|
|
279
|
+
try:
|
|
280
|
+
items.append(super().get_nowait())
|
|
281
|
+
except Empty:
|
|
282
|
+
break
|
|
283
|
+
return items
|
|
284
|
+
|
|
285
|
+
def get_queue_content_without_pop(self) -> list[T]:
|
|
286
|
+
"""Return a copy of the queue's contents without modifying it."""
|
|
287
|
+
# Ensure a consistent snapshot by holding the mutex
|
|
288
|
+
with self.mutex:
|
|
289
|
+
return list(self.queue)
|
|
290
|
+
|
|
291
|
+
def qsize(self) -> int:
|
|
292
|
+
"""Return the approximate size of the queue.
|
|
293
|
+
|
|
294
|
+
Returns:
|
|
295
|
+
Number of items currently in the queue
|
|
296
|
+
"""
|
|
297
|
+
return super().qsize()
|
|
298
|
+
|
|
299
|
+
def clear(self) -> None:
|
|
300
|
+
"""Remove all items from the queue.
|
|
301
|
+
|
|
302
|
+
This operation is thread-safe.
|
|
303
|
+
IMPORTANT: We also decrement `unfinished_tasks` by the number of
|
|
304
|
+
items cleared, since those tasks will never be processed.
|
|
305
|
+
"""
|
|
306
|
+
with self.mutex:
|
|
307
|
+
dropped = len(self.queue)
|
|
308
|
+
self.queue.clear()
|
|
309
|
+
# Call task_done() outside of the mutex to avoid deadlocks because
|
|
310
|
+
# Queue.task_done() acquires the same condition bound to `self.mutex`.
|
|
311
|
+
for _ in range(dropped):
|
|
312
|
+
with suppress(ValueError):
|
|
313
|
+
self.task_done()
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
|
|
3
|
+
from collections.abc import Callable
|
|
4
|
+
|
|
5
|
+
from memos.log import get_logger
|
|
6
|
+
from memos.mem_cube.general import GeneralMemCube
|
|
7
|
+
from memos.mem_scheduler.general_modules.base import BaseSchedulerModule
|
|
8
|
+
from memos.mem_scheduler.schemas.general_schemas import (
|
|
9
|
+
ACTIVATION_MEMORY_TYPE,
|
|
10
|
+
NOT_INITIALIZED,
|
|
11
|
+
PARAMETER_MEMORY_TYPE,
|
|
12
|
+
TEXT_MEMORY_TYPE,
|
|
13
|
+
WORKING_MEMORY_TYPE,
|
|
14
|
+
)
|
|
15
|
+
from memos.mem_scheduler.schemas.message_schemas import (
|
|
16
|
+
ScheduleLogForWebItem,
|
|
17
|
+
ScheduleMessageItem,
|
|
18
|
+
)
|
|
19
|
+
from memos.mem_scheduler.schemas.task_schemas import (
|
|
20
|
+
ADD_TASK_LABEL,
|
|
21
|
+
MEM_ARCHIVE_TASK_LABEL,
|
|
22
|
+
MEM_UPDATE_TASK_LABEL,
|
|
23
|
+
USER_INPUT_TYPE,
|
|
24
|
+
)
|
|
25
|
+
from memos.mem_scheduler.utils.filter_utils import (
|
|
26
|
+
transform_name_to_key,
|
|
27
|
+
)
|
|
28
|
+
from memos.mem_scheduler.utils.misc_utils import log_exceptions
|
|
29
|
+
from memos.memories.textual.tree import TextualMemoryItem, TreeTextMemory
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
logger = get_logger(__name__)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class SchedulerLoggerModule(BaseSchedulerModule):
|
|
36
|
+
def __init__(self):
|
|
37
|
+
"""
|
|
38
|
+
Initialize RabbitMQ connection settings.
|
|
39
|
+
"""
|
|
40
|
+
super().__init__()
|
|
41
|
+
|
|
42
|
+
@log_exceptions(logger=logger)
|
|
43
|
+
def create_autofilled_log_item(
|
|
44
|
+
self,
|
|
45
|
+
log_content: str,
|
|
46
|
+
label: str,
|
|
47
|
+
from_memory_type: str,
|
|
48
|
+
to_memory_type: str,
|
|
49
|
+
user_id: str,
|
|
50
|
+
mem_cube_id: str,
|
|
51
|
+
mem_cube: GeneralMemCube,
|
|
52
|
+
) -> ScheduleLogForWebItem:
|
|
53
|
+
if mem_cube is None:
|
|
54
|
+
logger.error(
|
|
55
|
+
"mem_cube is None — this should not happen in production!", stack_info=True
|
|
56
|
+
)
|
|
57
|
+
text_mem_base: TreeTextMemory = mem_cube.text_mem
|
|
58
|
+
|
|
59
|
+
current_memory_sizes = {}
|
|
60
|
+
if hasattr(text_mem_base, "get_current_memory_size"):
|
|
61
|
+
current_memory_sizes = text_mem_base.get_current_memory_size(user_name=mem_cube_id)
|
|
62
|
+
|
|
63
|
+
current_memory_sizes = {
|
|
64
|
+
"long_term_memory_size": current_memory_sizes.get("LongTermMemory", 0),
|
|
65
|
+
"user_memory_size": current_memory_sizes.get("UserMemory", 0),
|
|
66
|
+
"working_memory_size": current_memory_sizes.get("WorkingMemory", 0),
|
|
67
|
+
"transformed_act_memory_size": NOT_INITIALIZED,
|
|
68
|
+
"parameter_memory_size": NOT_INITIALIZED,
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
memory_capacities = {
|
|
72
|
+
"long_term_memory_capacity": 0,
|
|
73
|
+
"user_memory_capacity": 0,
|
|
74
|
+
"working_memory_capacity": 0,
|
|
75
|
+
"transformed_act_memory_capacity": NOT_INITIALIZED,
|
|
76
|
+
"parameter_memory_capacity": NOT_INITIALIZED,
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if hasattr(text_mem_base, "memory_manager") and hasattr(
|
|
80
|
+
text_mem_base.memory_manager, "memory_size"
|
|
81
|
+
):
|
|
82
|
+
memory_capacities.update(
|
|
83
|
+
{
|
|
84
|
+
"long_term_memory_capacity": text_mem_base.memory_manager.memory_size.get(
|
|
85
|
+
"LongTermMemory", 0
|
|
86
|
+
),
|
|
87
|
+
"user_memory_capacity": text_mem_base.memory_manager.memory_size.get(
|
|
88
|
+
"UserMemory", 0
|
|
89
|
+
),
|
|
90
|
+
"working_memory_capacity": text_mem_base.memory_manager.memory_size.get(
|
|
91
|
+
"WorkingMemory", 0
|
|
92
|
+
),
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
if hasattr(self, "monitor"):
|
|
97
|
+
if (
|
|
98
|
+
user_id in self.monitor.activation_memory_monitors
|
|
99
|
+
and mem_cube_id in self.monitor.activation_memory_monitors[user_id]
|
|
100
|
+
):
|
|
101
|
+
activation_monitor = self.monitor.activation_memory_monitors[user_id][mem_cube_id]
|
|
102
|
+
transformed_act_memory_size = len(activation_monitor.obj.memories)
|
|
103
|
+
logger.info(
|
|
104
|
+
f'activation_memory_monitors currently has "{transformed_act_memory_size}" transformed memory size'
|
|
105
|
+
)
|
|
106
|
+
else:
|
|
107
|
+
transformed_act_memory_size = 0
|
|
108
|
+
logger.info(
|
|
109
|
+
f'activation_memory_monitors is not initialized for user "{user_id}" and mem_cube "{mem_cube_id}'
|
|
110
|
+
)
|
|
111
|
+
current_memory_sizes["transformed_act_memory_size"] = transformed_act_memory_size
|
|
112
|
+
current_memory_sizes["parameter_memory_size"] = 1
|
|
113
|
+
|
|
114
|
+
memory_capacities["transformed_act_memory_capacity"] = (
|
|
115
|
+
self.monitor.activation_mem_monitor_capacity
|
|
116
|
+
)
|
|
117
|
+
memory_capacities["parameter_memory_capacity"] = 1
|
|
118
|
+
|
|
119
|
+
log_message = ScheduleLogForWebItem(
|
|
120
|
+
user_id=user_id,
|
|
121
|
+
mem_cube_id=mem_cube_id,
|
|
122
|
+
label=label,
|
|
123
|
+
from_memory_type=from_memory_type,
|
|
124
|
+
to_memory_type=to_memory_type,
|
|
125
|
+
log_content=log_content,
|
|
126
|
+
current_memory_sizes=current_memory_sizes,
|
|
127
|
+
memory_capacities=memory_capacities,
|
|
128
|
+
)
|
|
129
|
+
return log_message
|
|
130
|
+
|
|
131
|
+
@log_exceptions(logger=logger)
|
|
132
|
+
def create_event_log(
|
|
133
|
+
self,
|
|
134
|
+
label: str,
|
|
135
|
+
from_memory_type: str,
|
|
136
|
+
to_memory_type: str,
|
|
137
|
+
user_id: str,
|
|
138
|
+
mem_cube_id: str,
|
|
139
|
+
mem_cube: GeneralMemCube,
|
|
140
|
+
memcube_log_content: list[dict],
|
|
141
|
+
metadata: list[dict],
|
|
142
|
+
memory_len: int,
|
|
143
|
+
memcube_name: str | None = None,
|
|
144
|
+
log_content: str | None = None,
|
|
145
|
+
) -> ScheduleLogForWebItem:
|
|
146
|
+
item = self.create_autofilled_log_item(
|
|
147
|
+
log_content=log_content or "",
|
|
148
|
+
label=label,
|
|
149
|
+
from_memory_type=from_memory_type,
|
|
150
|
+
to_memory_type=to_memory_type,
|
|
151
|
+
user_id=user_id,
|
|
152
|
+
mem_cube_id=mem_cube_id,
|
|
153
|
+
mem_cube=mem_cube,
|
|
154
|
+
)
|
|
155
|
+
item.memcube_log_content = memcube_log_content
|
|
156
|
+
item.metadata = metadata
|
|
157
|
+
item.memory_len = memory_len
|
|
158
|
+
item.memcube_name = memcube_name or self._map_memcube_name(mem_cube_id)
|
|
159
|
+
return item
|
|
160
|
+
|
|
161
|
+
def _map_memcube_name(self, mem_cube_id: str) -> str:
|
|
162
|
+
x = mem_cube_id or ""
|
|
163
|
+
if "public" in x.lower():
|
|
164
|
+
return "PublicMemCube"
|
|
165
|
+
return "UserMemCube"
|
|
166
|
+
|
|
167
|
+
# TODO: Log output count is incorrect
|
|
168
|
+
@log_exceptions(logger=logger)
|
|
169
|
+
def log_working_memory_replacement(
|
|
170
|
+
self,
|
|
171
|
+
original_memory: list[TextualMemoryItem],
|
|
172
|
+
new_memory: list[TextualMemoryItem],
|
|
173
|
+
user_id: str,
|
|
174
|
+
mem_cube_id: str,
|
|
175
|
+
mem_cube: GeneralMemCube,
|
|
176
|
+
log_func_callback: Callable[[list[ScheduleLogForWebItem]], None],
|
|
177
|
+
):
|
|
178
|
+
"""Log changes when working memory is replaced."""
|
|
179
|
+
original_text_memories = [m.memory for m in original_memory]
|
|
180
|
+
new_text_memories = [m.memory for m in new_memory]
|
|
181
|
+
original_set = set(original_text_memories)
|
|
182
|
+
new_set = set(new_text_memories)
|
|
183
|
+
added_texts = []
|
|
184
|
+
for new_mem in new_set:
|
|
185
|
+
if new_mem not in original_set:
|
|
186
|
+
added_texts.append(new_mem)
|
|
187
|
+
memcube_content = []
|
|
188
|
+
meta = []
|
|
189
|
+
by_text = {m.memory: m for m in new_memory}
|
|
190
|
+
for t in added_texts:
|
|
191
|
+
itm = by_text.get(t)
|
|
192
|
+
if not itm:
|
|
193
|
+
continue
|
|
194
|
+
key_name = getattr(itm.metadata, "key", None) or itm.memory
|
|
195
|
+
k = transform_name_to_key(name=key_name)
|
|
196
|
+
memcube_content.append(
|
|
197
|
+
{
|
|
198
|
+
"content": f"[{itm.metadata.memory_type}→{WORKING_MEMORY_TYPE}] {k}: {itm.memory}",
|
|
199
|
+
"ref_id": itm.id,
|
|
200
|
+
}
|
|
201
|
+
)
|
|
202
|
+
meta.append(
|
|
203
|
+
{
|
|
204
|
+
"ref_id": itm.id,
|
|
205
|
+
"id": itm.id,
|
|
206
|
+
"key": itm.metadata.key,
|
|
207
|
+
"memory": itm.memory,
|
|
208
|
+
"memory_type": itm.metadata.memory_type,
|
|
209
|
+
"status": itm.metadata.status,
|
|
210
|
+
"confidence": itm.metadata.confidence,
|
|
211
|
+
"tags": itm.metadata.tags,
|
|
212
|
+
"updated_at": getattr(itm.metadata, "updated_at", None)
|
|
213
|
+
or getattr(itm.metadata, "update_at", None),
|
|
214
|
+
}
|
|
215
|
+
)
|
|
216
|
+
# Only create log if there are actual memory changes
|
|
217
|
+
if memcube_content:
|
|
218
|
+
ev = self.create_event_log(
|
|
219
|
+
label="scheduleMemory",
|
|
220
|
+
from_memory_type=TEXT_MEMORY_TYPE,
|
|
221
|
+
to_memory_type=WORKING_MEMORY_TYPE,
|
|
222
|
+
user_id=user_id,
|
|
223
|
+
mem_cube_id=mem_cube_id,
|
|
224
|
+
mem_cube=mem_cube,
|
|
225
|
+
memcube_log_content=memcube_content,
|
|
226
|
+
metadata=meta,
|
|
227
|
+
memory_len=len(memcube_content),
|
|
228
|
+
memcube_name=self._map_memcube_name(mem_cube_id),
|
|
229
|
+
)
|
|
230
|
+
log_func_callback([ev])
|
|
231
|
+
|
|
232
|
+
@log_exceptions(logger=logger)
|
|
233
|
+
def log_activation_memory_update(
|
|
234
|
+
self,
|
|
235
|
+
original_text_memories: list[str],
|
|
236
|
+
new_text_memories: list[str],
|
|
237
|
+
label: str,
|
|
238
|
+
user_id: str,
|
|
239
|
+
mem_cube_id: str,
|
|
240
|
+
mem_cube: GeneralMemCube,
|
|
241
|
+
log_func_callback: Callable[[list[ScheduleLogForWebItem]], None],
|
|
242
|
+
):
|
|
243
|
+
"""Log changes when activation memory is updated."""
|
|
244
|
+
original_set = set(original_text_memories)
|
|
245
|
+
new_set = set(new_text_memories)
|
|
246
|
+
|
|
247
|
+
added_memories = list(new_set - original_set)
|
|
248
|
+
memcube_content = []
|
|
249
|
+
meta = []
|
|
250
|
+
for mem in added_memories:
|
|
251
|
+
key = transform_name_to_key(mem)
|
|
252
|
+
ref_id = f"actparam-{hashlib.md5(mem.encode()).hexdigest()}"
|
|
253
|
+
memcube_content.append(
|
|
254
|
+
{
|
|
255
|
+
"content": f"[{ACTIVATION_MEMORY_TYPE}→{PARAMETER_MEMORY_TYPE}] {key}: {mem}",
|
|
256
|
+
"ref_id": ref_id,
|
|
257
|
+
}
|
|
258
|
+
)
|
|
259
|
+
meta.append(
|
|
260
|
+
{
|
|
261
|
+
"ref_id": ref_id,
|
|
262
|
+
"id": ref_id,
|
|
263
|
+
"key": key,
|
|
264
|
+
"memory": mem,
|
|
265
|
+
"memory_type": ACTIVATION_MEMORY_TYPE,
|
|
266
|
+
"status": None,
|
|
267
|
+
"confidence": None,
|
|
268
|
+
"tags": None,
|
|
269
|
+
"updated_at": None,
|
|
270
|
+
}
|
|
271
|
+
)
|
|
272
|
+
# Only create log if there are actual memory changes
|
|
273
|
+
if memcube_content:
|
|
274
|
+
ev = self.create_event_log(
|
|
275
|
+
label="scheduleMemory",
|
|
276
|
+
from_memory_type=ACTIVATION_MEMORY_TYPE,
|
|
277
|
+
to_memory_type=PARAMETER_MEMORY_TYPE,
|
|
278
|
+
user_id=user_id,
|
|
279
|
+
mem_cube_id=mem_cube_id,
|
|
280
|
+
mem_cube=mem_cube,
|
|
281
|
+
memcube_log_content=memcube_content,
|
|
282
|
+
metadata=meta,
|
|
283
|
+
memory_len=len(added_memories),
|
|
284
|
+
memcube_name=self._map_memcube_name(mem_cube_id),
|
|
285
|
+
)
|
|
286
|
+
log_func_callback([ev])
|
|
287
|
+
|
|
288
|
+
@log_exceptions(logger=logger)
|
|
289
|
+
def log_adding_memory(
|
|
290
|
+
self,
|
|
291
|
+
memory: str,
|
|
292
|
+
memory_type: str,
|
|
293
|
+
user_id: str,
|
|
294
|
+
mem_cube_id: str,
|
|
295
|
+
mem_cube: GeneralMemCube,
|
|
296
|
+
log_func_callback: Callable[[list[ScheduleLogForWebItem]], None],
|
|
297
|
+
):
|
|
298
|
+
"""Deprecated: legacy text log. Use create_event_log with structured fields instead."""
|
|
299
|
+
log_message = self.create_autofilled_log_item(
|
|
300
|
+
log_content=memory,
|
|
301
|
+
label=ADD_TASK_LABEL,
|
|
302
|
+
from_memory_type=USER_INPUT_TYPE,
|
|
303
|
+
to_memory_type=memory_type,
|
|
304
|
+
user_id=user_id,
|
|
305
|
+
mem_cube_id=mem_cube_id,
|
|
306
|
+
mem_cube=mem_cube,
|
|
307
|
+
)
|
|
308
|
+
log_func_callback([log_message])
|
|
309
|
+
logger.info(
|
|
310
|
+
f"{USER_INPUT_TYPE} memory for user {user_id} "
|
|
311
|
+
f"converted to {memory_type} memory in mem_cube {mem_cube_id}: {memory}"
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
@log_exceptions(logger=logger)
|
|
315
|
+
def log_updating_memory(
|
|
316
|
+
self,
|
|
317
|
+
memory: str,
|
|
318
|
+
memory_type: str,
|
|
319
|
+
user_id: str,
|
|
320
|
+
mem_cube_id: str,
|
|
321
|
+
mem_cube: GeneralMemCube,
|
|
322
|
+
log_func_callback: Callable[[list[ScheduleLogForWebItem]], None],
|
|
323
|
+
):
|
|
324
|
+
"""Deprecated: legacy text log. Use create_event_log with structured fields instead."""
|
|
325
|
+
log_message = self.create_autofilled_log_item(
|
|
326
|
+
log_content=memory,
|
|
327
|
+
label=MEM_UPDATE_TASK_LABEL,
|
|
328
|
+
from_memory_type=memory_type,
|
|
329
|
+
to_memory_type=memory_type,
|
|
330
|
+
user_id=user_id,
|
|
331
|
+
mem_cube_id=mem_cube_id,
|
|
332
|
+
mem_cube=mem_cube,
|
|
333
|
+
)
|
|
334
|
+
log_func_callback([log_message])
|
|
335
|
+
|
|
336
|
+
@log_exceptions(logger=logger)
|
|
337
|
+
def log_archiving_memory(
|
|
338
|
+
self,
|
|
339
|
+
memory: str,
|
|
340
|
+
memory_type: str,
|
|
341
|
+
user_id: str,
|
|
342
|
+
mem_cube_id: str,
|
|
343
|
+
mem_cube: GeneralMemCube,
|
|
344
|
+
log_func_callback: Callable[[list[ScheduleLogForWebItem]], None],
|
|
345
|
+
):
|
|
346
|
+
"""Deprecated: legacy text log. Use create_event_log with structured fields instead."""
|
|
347
|
+
log_message = self.create_autofilled_log_item(
|
|
348
|
+
log_content=memory,
|
|
349
|
+
label=MEM_ARCHIVE_TASK_LABEL,
|
|
350
|
+
from_memory_type=memory_type,
|
|
351
|
+
to_memory_type=memory_type,
|
|
352
|
+
user_id=user_id,
|
|
353
|
+
mem_cube_id=mem_cube_id,
|
|
354
|
+
mem_cube=mem_cube,
|
|
355
|
+
)
|
|
356
|
+
log_func_callback([log_message])
|
|
357
|
+
|
|
358
|
+
@log_exceptions(logger=logger)
|
|
359
|
+
def validate_schedule_message(self, message: ScheduleMessageItem, label: str):
|
|
360
|
+
"""Validate if the message matches the expected label.
|
|
361
|
+
|
|
362
|
+
Args:
|
|
363
|
+
message: Incoming message item to validate.
|
|
364
|
+
label: Expected message label (e.g., QUERY_LABEL/ANSWER_LABEL).
|
|
365
|
+
|
|
366
|
+
Returns:
|
|
367
|
+
bool: True if validation passed, False otherwise.
|
|
368
|
+
"""
|
|
369
|
+
if message.label != label:
|
|
370
|
+
logger.error(f"Handler validation failed: expected={label}, actual={message.label}")
|
|
371
|
+
return False
|
|
372
|
+
return True
|
|
373
|
+
|
|
374
|
+
@log_exceptions(logger=logger)
|
|
375
|
+
def validate_schedule_messages(self, messages: list[ScheduleMessageItem], label: str):
|
|
376
|
+
"""Validate if all messages match the expected label.
|
|
377
|
+
|
|
378
|
+
Args:
|
|
379
|
+
messages: List of message items to validate.
|
|
380
|
+
label: Expected message label (e.g., QUERY_LABEL/ANSWER_LABEL).
|
|
381
|
+
|
|
382
|
+
Returns:
|
|
383
|
+
bool: True if all messages passed validation, False if any failed.
|
|
384
|
+
"""
|
|
385
|
+
for message in messages:
|
|
386
|
+
if not self.validate_schedule_message(message, label):
|
|
387
|
+
logger.error("Message batch contains invalid labels, aborting processing")
|
|
388
|
+
return False
|
|
389
|
+
return True
|