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,23 @@
|
|
|
1
|
+
from typing import Any, ClassVar
|
|
2
|
+
|
|
3
|
+
from memos.configs.mem_scheduler import SchedulerConfigFactory
|
|
4
|
+
from memos.mem_scheduler.base_scheduler import BaseScheduler
|
|
5
|
+
from memos.mem_scheduler.general_scheduler import GeneralScheduler
|
|
6
|
+
from memos.mem_scheduler.optimized_scheduler import OptimizedScheduler
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SchedulerFactory(BaseScheduler):
|
|
10
|
+
"""Factory class for creating scheduler instances."""
|
|
11
|
+
|
|
12
|
+
backend_to_class: ClassVar[dict[str, Any]] = {
|
|
13
|
+
"general_scheduler": GeneralScheduler,
|
|
14
|
+
"optimized_scheduler": OptimizedScheduler,
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
@classmethod
|
|
18
|
+
def from_config(cls, config_factory: SchedulerConfigFactory) -> GeneralScheduler:
|
|
19
|
+
backend = config_factory.backend
|
|
20
|
+
if backend not in cls.backend_to_class:
|
|
21
|
+
raise ValueError(f"Invalid backend: {backend}")
|
|
22
|
+
mem_scheduler_class = cls.backend_to_class[backend]
|
|
23
|
+
return mem_scheduler_class(config_factory.config)
|
|
File without changes
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
7
|
+
|
|
8
|
+
from memos.log import get_logger
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
logger = get_logger(__name__)
|
|
12
|
+
|
|
13
|
+
FILE_PATH = Path(__file__).absolute()
|
|
14
|
+
BASE_DIR = FILE_PATH.parent.parent.parent.parent.parent
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class BasicRecordingCase(BaseModel):
|
|
18
|
+
# Conversation identification
|
|
19
|
+
conv_id: str = Field(description="Conversation identifier for this evaluation case")
|
|
20
|
+
user_id: str = Field(description="User identifier for this evaluation case")
|
|
21
|
+
memcube_id: str = Field(description="Memcube identifier for this evaluation case")
|
|
22
|
+
|
|
23
|
+
# Query and answer information
|
|
24
|
+
query: str = Field(description="The current question/query being evaluated")
|
|
25
|
+
|
|
26
|
+
answer: str = Field(description="The generated answer for the query")
|
|
27
|
+
|
|
28
|
+
golden_answer: str | None = Field(
|
|
29
|
+
default=None, description="Ground truth answer for evaluation"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
def to_dict(self) -> dict[str, Any]:
|
|
33
|
+
return self.dict()
|
|
34
|
+
|
|
35
|
+
def to_json(self, indent: int = 2) -> str:
|
|
36
|
+
return self.json(indent=indent, ensure_ascii=False)
|
|
37
|
+
|
|
38
|
+
@classmethod
|
|
39
|
+
def from_dict(cls, data: dict[str, Any]) -> "BasicRecordingCase":
|
|
40
|
+
return cls(**data)
|
|
41
|
+
|
|
42
|
+
@classmethod
|
|
43
|
+
def from_json(cls, json_str: str) -> "BasicRecordingCase":
|
|
44
|
+
data = json.loads(json_str)
|
|
45
|
+
return cls.from_dict(data)
|
|
46
|
+
|
|
47
|
+
class Config:
|
|
48
|
+
"""Pydantic configuration"""
|
|
49
|
+
|
|
50
|
+
extra = "allow" # Allow additional fields not defined in the schema
|
|
51
|
+
validate_assignment = True # Validate on assignment
|
|
52
|
+
use_enum_values = True # Use enum values instead of enum names
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from enum import Enum
|
|
3
|
+
from typing import Any
|
|
4
|
+
from uuid import uuid4
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, ConfigDict, Field, field_serializer
|
|
7
|
+
|
|
8
|
+
from memos.log import get_logger
|
|
9
|
+
from memos.mem_scheduler.general_modules.misc import DictConversionMixin
|
|
10
|
+
from memos.mem_scheduler.utils.db_utils import get_utc_now
|
|
11
|
+
from memos.memories.textual.item import TextualMemoryItem
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
logger = get_logger(__name__)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class TaskRunningStatus(str, Enum):
|
|
18
|
+
"""Enumeration for task running status values."""
|
|
19
|
+
|
|
20
|
+
RUNNING = "running"
|
|
21
|
+
COMPLETED = "completed"
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class APIMemoryHistoryEntryItem(BaseModel, DictConversionMixin):
|
|
25
|
+
"""Data class for search entry items stored in Redis."""
|
|
26
|
+
|
|
27
|
+
item_id: str = Field(
|
|
28
|
+
description="Unique identifier for the task", default_factory=lambda: str(uuid4())
|
|
29
|
+
)
|
|
30
|
+
query: str = Field(..., description="Search query string")
|
|
31
|
+
formatted_memories: Any = Field(..., description="Formatted search results")
|
|
32
|
+
memories: list[TextualMemoryItem] = Field(
|
|
33
|
+
default_factory=list, description="List of TextualMemoryItem objects"
|
|
34
|
+
)
|
|
35
|
+
task_status: str = Field(
|
|
36
|
+
default="running", description="Task status: running, completed, failed"
|
|
37
|
+
)
|
|
38
|
+
session_id: str | None = Field(default=None, description="Optional conversation identifier")
|
|
39
|
+
created_time: datetime = Field(description="Entry creation time", default_factory=get_utc_now)
|
|
40
|
+
timestamp: datetime | None = Field(default=None, description="Timestamp for the entry")
|
|
41
|
+
conversation_turn: int = Field(default=0, description="Turn count for the same session_id")
|
|
42
|
+
|
|
43
|
+
model_config = ConfigDict(
|
|
44
|
+
arbitrary_types_allowed=True,
|
|
45
|
+
validate_assignment=True,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
@field_serializer("created_time")
|
|
49
|
+
def serialize_created_time(self, value: datetime) -> str:
|
|
50
|
+
"""Serialize datetime to ISO format string."""
|
|
51
|
+
return value.isoformat()
|
|
52
|
+
|
|
53
|
+
def get(self, key: str, default: Any | None = None) -> Any:
|
|
54
|
+
"""
|
|
55
|
+
Get attribute value by key name, similar to dict.get().
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
key: The attribute name to retrieve
|
|
59
|
+
default: Default value to return if attribute doesn't exist
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
The attribute value or default if not found
|
|
63
|
+
"""
|
|
64
|
+
return getattr(self, key, default)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class APISearchHistoryManager(BaseModel, DictConversionMixin):
|
|
68
|
+
"""
|
|
69
|
+
Data structure for managing search history with separate completed and running entries.
|
|
70
|
+
Supports window_size to limit the number of completed entries.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
window_size: int = Field(default=5, description="Maximum number of completed entries to keep")
|
|
74
|
+
completed_entries: list[APIMemoryHistoryEntryItem] = Field(
|
|
75
|
+
default_factory=list, description="List of completed search entries"
|
|
76
|
+
)
|
|
77
|
+
running_item_ids: list[str] = Field(
|
|
78
|
+
default_factory=list, description="List of running task ids"
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
model_config = ConfigDict(
|
|
82
|
+
arbitrary_types_allowed=True,
|
|
83
|
+
validate_assignment=True,
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
def complete_entry(self, task_id: str) -> bool:
|
|
87
|
+
"""
|
|
88
|
+
Remove task_id from running list when completed.
|
|
89
|
+
Note: The actual entry data should be managed separately.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
task_id: The task ID to complete
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
True if task_id was found and removed, False otherwise
|
|
96
|
+
"""
|
|
97
|
+
if task_id in self.running_item_ids:
|
|
98
|
+
self.running_item_ids.remove(task_id)
|
|
99
|
+
logger.debug(f"Completed task_id: {task_id}")
|
|
100
|
+
return True
|
|
101
|
+
|
|
102
|
+
logger.warning(f"Task ID {task_id} not found in running task ids")
|
|
103
|
+
return False
|
|
104
|
+
|
|
105
|
+
def get_running_item_ids(self) -> list[str]:
|
|
106
|
+
"""Get all running task IDs"""
|
|
107
|
+
return self.running_item_ids.copy()
|
|
108
|
+
|
|
109
|
+
def get_completed_entries(self) -> list[APIMemoryHistoryEntryItem]:
|
|
110
|
+
"""Get all completed entries"""
|
|
111
|
+
return self.completed_entries.copy()
|
|
112
|
+
|
|
113
|
+
def get_history_memory_entries(
|
|
114
|
+
self, turns: int | None = None
|
|
115
|
+
) -> list[APIMemoryHistoryEntryItem]:
|
|
116
|
+
"""
|
|
117
|
+
Get the most recent n completed search entries, sorted by created_time.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
turns: Number of entries to return. If None, returns all completed entries.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
List of completed search entries, sorted by created_time (newest first)
|
|
124
|
+
"""
|
|
125
|
+
if not self.completed_entries:
|
|
126
|
+
return []
|
|
127
|
+
|
|
128
|
+
# Sort by created_time (newest first)
|
|
129
|
+
sorted_entries = sorted(self.completed_entries, key=lambda x: x.created_time, reverse=True)
|
|
130
|
+
|
|
131
|
+
if turns is None:
|
|
132
|
+
return sorted_entries
|
|
133
|
+
|
|
134
|
+
return sorted_entries[:turns]
|
|
135
|
+
|
|
136
|
+
def get_history_memories(self, turns: int | None = None) -> list[TextualMemoryItem]:
|
|
137
|
+
"""
|
|
138
|
+
Get the most recent n completed search entries, sorted by created_time.
|
|
139
|
+
|
|
140
|
+
Args:
|
|
141
|
+
turns: Number of entries to return. If None, returns all completed entries.
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
List of TextualMemoryItem objects from completed entries, sorted by created_time (newest first)
|
|
145
|
+
"""
|
|
146
|
+
sorted_entries = self.get_history_memory_entries(turns=turns)
|
|
147
|
+
|
|
148
|
+
memories = []
|
|
149
|
+
for one in sorted_entries:
|
|
150
|
+
memories.extend(one.memories)
|
|
151
|
+
return memories
|
|
152
|
+
|
|
153
|
+
def find_entry_by_item_id(self, item_id: str) -> tuple[dict[str, Any] | None, str]:
|
|
154
|
+
"""
|
|
155
|
+
Find an entry by item_id in completed list only.
|
|
156
|
+
Running entries are now just task IDs, so we can only search completed entries.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
item_id: The item ID to search for
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
Tuple of (entry_dict, location) where location is 'completed' or 'not_found'
|
|
163
|
+
"""
|
|
164
|
+
# Check completed entries
|
|
165
|
+
for entry in self.completed_entries:
|
|
166
|
+
try:
|
|
167
|
+
if hasattr(entry, "item_id") and entry.item_id == item_id:
|
|
168
|
+
return entry.to_dict(), "completed"
|
|
169
|
+
elif isinstance(entry, dict) and entry.get("item_id") == item_id:
|
|
170
|
+
return entry, "completed"
|
|
171
|
+
except AttributeError as e:
|
|
172
|
+
logger.warning(f"Entry missing item_id attribute: {e}, entry type: {type(entry)}")
|
|
173
|
+
continue
|
|
174
|
+
|
|
175
|
+
return None, "not_found"
|
|
176
|
+
|
|
177
|
+
def update_entry_by_item_id(
|
|
178
|
+
self,
|
|
179
|
+
item_id: str,
|
|
180
|
+
query: str,
|
|
181
|
+
formatted_memories: Any,
|
|
182
|
+
task_status: TaskRunningStatus,
|
|
183
|
+
session_id: str | None = None,
|
|
184
|
+
memories: list[TextualMemoryItem] | None = None,
|
|
185
|
+
) -> bool:
|
|
186
|
+
"""
|
|
187
|
+
Update an existing entry by item_id. Since running entries are now just IDs,
|
|
188
|
+
this method can only update completed entries.
|
|
189
|
+
|
|
190
|
+
Args:
|
|
191
|
+
item_id: The item ID to update
|
|
192
|
+
query: New query string
|
|
193
|
+
formatted_memories: New formatted memories
|
|
194
|
+
task_status: New task status
|
|
195
|
+
session_id: New conversation ID
|
|
196
|
+
memories: List of TextualMemoryItem objects
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
True if entry was found and updated, False otherwise
|
|
200
|
+
"""
|
|
201
|
+
# Find the entry in completed list
|
|
202
|
+
for entry in self.completed_entries:
|
|
203
|
+
if entry.item_id == item_id:
|
|
204
|
+
# Update the entry content
|
|
205
|
+
entry.query = query
|
|
206
|
+
entry.formatted_memories = formatted_memories
|
|
207
|
+
entry.task_status = task_status
|
|
208
|
+
if session_id is not None:
|
|
209
|
+
entry.session_id = session_id
|
|
210
|
+
if memories is not None:
|
|
211
|
+
entry.memories = memories
|
|
212
|
+
|
|
213
|
+
logger.debug(f"Updated entry with item_id: {item_id}, new status: {task_status}")
|
|
214
|
+
return True
|
|
215
|
+
|
|
216
|
+
logger.warning(f"Entry with item_id: {item_id} not found in completed entries")
|
|
217
|
+
return False
|
|
218
|
+
|
|
219
|
+
def get_total_count(self) -> dict[str, int]:
|
|
220
|
+
"""Get count of entries by status"""
|
|
221
|
+
return {
|
|
222
|
+
"completed": len(self.completed_entries),
|
|
223
|
+
"running": len(self.running_item_ids),
|
|
224
|
+
"total": len(self.completed_entries) + len(self.running_item_ids),
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
def __len__(self) -> int:
|
|
228
|
+
"""Return total number of entries (completed + running)"""
|
|
229
|
+
return len(self.completed_entries) + len(self.running_item_ids)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
# Alias for easier usage
|
|
233
|
+
SearchHistoryManager = APISearchHistoryManager
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
FILE_PATH = Path(__file__).absolute()
|
|
7
|
+
BASE_DIR = FILE_PATH.parent.parent.parent.parent.parent
|
|
8
|
+
|
|
9
|
+
TreeTextMemory_SEARCH_METHOD = "tree_text_memory_search"
|
|
10
|
+
TreeTextMemory_FINE_SEARCH_METHOD = "tree_text_memory_fine_search"
|
|
11
|
+
TextMemory_SEARCH_METHOD = "text_memory_search"
|
|
12
|
+
DIRECT_EXCHANGE_TYPE = "direct"
|
|
13
|
+
FANOUT_EXCHANGE_TYPE = "fanout"
|
|
14
|
+
DEFAULT_WORKING_MEM_MONITOR_SIZE_LIMIT = 30
|
|
15
|
+
DEFAULT_ACTIVATION_MEM_MONITOR_SIZE_LIMIT = 20
|
|
16
|
+
DEFAULT_ACT_MEM_DUMP_PATH = f"{BASE_DIR}/outputs/mem_scheduler/mem_cube_scheduler_test.kv_cache"
|
|
17
|
+
DEFAULT_THREAD_POOL_MAX_WORKERS = 50
|
|
18
|
+
DEFAULT_CONSUME_INTERVAL_SECONDS = 0.01
|
|
19
|
+
DEFAULT_CONSUME_BATCH = 3
|
|
20
|
+
DEFAULT_DISPATCHER_MONITOR_CHECK_INTERVAL = 300
|
|
21
|
+
DEFAULT_DISPATCHER_MONITOR_MAX_FAILURES = 2
|
|
22
|
+
DEFAULT_STUCK_THREAD_TOLERANCE = 10
|
|
23
|
+
DEFAULT_MAX_INTERNAL_MESSAGE_QUEUE_SIZE = -1
|
|
24
|
+
DEFAULT_TOP_K = 5
|
|
25
|
+
DEFAULT_CONTEXT_WINDOW_SIZE = 5
|
|
26
|
+
DEFAULT_USE_REDIS_QUEUE = os.getenv("MEMSCHEDULER_USE_REDIS_QUEUE", "False").lower() == "true"
|
|
27
|
+
DEFAULT_MULTI_TASK_RUNNING_TIMEOUT = 30
|
|
28
|
+
DEFAULT_SCHEDULER_RETRIEVER_BATCH_SIZE = 20
|
|
29
|
+
DEFAULT_SCHEDULER_RETRIEVER_RETRIES = 1
|
|
30
|
+
DEFAULT_STOP_WAIT = False
|
|
31
|
+
|
|
32
|
+
# startup mode configuration
|
|
33
|
+
STARTUP_BY_THREAD = "thread"
|
|
34
|
+
STARTUP_BY_PROCESS = "process"
|
|
35
|
+
DEFAULT_STARTUP_MODE = STARTUP_BY_THREAD # default to thread mode
|
|
36
|
+
|
|
37
|
+
NOT_INITIALIZED = -1
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# web log
|
|
41
|
+
LONG_TERM_MEMORY_TYPE = "LongTermMemory"
|
|
42
|
+
USER_MEMORY_TYPE = "UserMemory"
|
|
43
|
+
WORKING_MEMORY_TYPE = "WorkingMemory"
|
|
44
|
+
TEXT_MEMORY_TYPE = "TextMemory"
|
|
45
|
+
ACTIVATION_MEMORY_TYPE = "ActivationMemory"
|
|
46
|
+
PARAMETER_MEMORY_TYPE = "ParameterMemory"
|
|
47
|
+
USER_INPUT_TYPE = "UserInput"
|
|
48
|
+
NOT_APPLICABLE_TYPE = "NotApplicable"
|
|
49
|
+
|
|
50
|
+
# monitors
|
|
51
|
+
MONITOR_WORKING_MEMORY_TYPE = "MonitorWorkingMemoryType"
|
|
52
|
+
MONITOR_ACTIVATION_MEMORY_TYPE = "MonitorActivationMemoryType"
|
|
53
|
+
DEFAULT_MAX_QUERY_KEY_WORDS = 1000
|
|
54
|
+
DEFAULT_WEIGHT_VECTOR_FOR_RANKING = [0.9, 0.05, 0.05]
|
|
55
|
+
DEFAULT_MAX_WEB_LOG_QUEUE_SIZE = 50
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import Any
|
|
3
|
+
from uuid import uuid4
|
|
4
|
+
|
|
5
|
+
from pydantic import BaseModel, ConfigDict, Field
|
|
6
|
+
from typing_extensions import TypedDict
|
|
7
|
+
|
|
8
|
+
from memos.context.context import generate_trace_id
|
|
9
|
+
from memos.log import get_logger
|
|
10
|
+
from memos.mem_scheduler.general_modules.misc import DictConversionMixin
|
|
11
|
+
from memos.mem_scheduler.utils.db_utils import get_utc_now
|
|
12
|
+
|
|
13
|
+
from .general_schemas import NOT_INITIALIZED
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
logger = get_logger(__name__)
|
|
17
|
+
|
|
18
|
+
DEFAULT_MEMORY_SIZES = {
|
|
19
|
+
"long_term_memory_size": NOT_INITIALIZED,
|
|
20
|
+
"user_memory_size": NOT_INITIALIZED,
|
|
21
|
+
"working_memory_size": NOT_INITIALIZED,
|
|
22
|
+
"transformed_act_memory_size": NOT_INITIALIZED,
|
|
23
|
+
"parameter_memory_size": NOT_INITIALIZED,
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
DEFAULT_MEMORY_CAPACITIES = {
|
|
27
|
+
"long_term_memory_capacity": 10000,
|
|
28
|
+
"user_memory_capacity": 10000,
|
|
29
|
+
"working_memory_capacity": 20,
|
|
30
|
+
"transformed_act_memory_capacity": NOT_INITIALIZED,
|
|
31
|
+
"parameter_memory_capacity": NOT_INITIALIZED,
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class ScheduleMessageItem(BaseModel, DictConversionMixin):
|
|
36
|
+
item_id: str = Field(description="uuid", default_factory=lambda: str(uuid4()))
|
|
37
|
+
redis_message_id: str = Field(default="", description="the message get from redis stream")
|
|
38
|
+
stream_key: str = Field("", description="stream_key for identifying the queue in line")
|
|
39
|
+
user_id: str = Field(..., description="user id")
|
|
40
|
+
trace_id: str = Field(default_factory=generate_trace_id, description="trace id for logging")
|
|
41
|
+
mem_cube_id: str = Field(..., description="memcube id")
|
|
42
|
+
session_id: str = Field(default="", description="Session ID for soft-filtering memories")
|
|
43
|
+
label: str = Field(..., description="Label of the schedule message")
|
|
44
|
+
content: str = Field(..., description="Content of the schedule message")
|
|
45
|
+
timestamp: datetime = Field(
|
|
46
|
+
default_factory=get_utc_now, description="submit time for schedule_messages"
|
|
47
|
+
)
|
|
48
|
+
user_name: str = Field(
|
|
49
|
+
default="",
|
|
50
|
+
description="user name / display name (optional)",
|
|
51
|
+
)
|
|
52
|
+
info: dict | None = Field(default=None, description="user custom info")
|
|
53
|
+
task_id: str | None = Field(
|
|
54
|
+
default=None,
|
|
55
|
+
description="Optional business-level task ID. Multiple items can share the same task_id.",
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
# Pydantic V2 model configuration
|
|
59
|
+
model_config = ConfigDict(
|
|
60
|
+
# Allows arbitrary Python types as model fields without validation
|
|
61
|
+
# Required when using custom types like GeneralMemCube that aren't Pydantic models
|
|
62
|
+
arbitrary_types_allowed=True,
|
|
63
|
+
# Additional metadata for JSON Schema generation
|
|
64
|
+
json_schema_extra={
|
|
65
|
+
# Example payload demonstrating the expected structure and sample values
|
|
66
|
+
# Used for API documentation, testing, and developer reference
|
|
67
|
+
"example": {
|
|
68
|
+
"item_id": "123e4567-e89b-12d3-a456-426614174000", # Sample UUID
|
|
69
|
+
"user_id": "user123", # Example user identifier
|
|
70
|
+
"mem_cube_id": "cube456", # Sample memory cube ID
|
|
71
|
+
"label": "sample_label", # Demonstration label value
|
|
72
|
+
"content": "sample content", # Example message content
|
|
73
|
+
"timestamp": "2024-07-22T12:00:00Z", # Added timestamp example
|
|
74
|
+
"user_name": "Alice", # Added username example
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
def to_dict(self) -> dict:
|
|
80
|
+
"""Convert model to dictionary suitable for Redis Stream"""
|
|
81
|
+
return {
|
|
82
|
+
"item_id": self.item_id,
|
|
83
|
+
"user_id": self.user_id,
|
|
84
|
+
"cube_id": self.mem_cube_id,
|
|
85
|
+
"trace_id": self.trace_id,
|
|
86
|
+
"label": self.label,
|
|
87
|
+
"cube": "Not Applicable", # Custom cube serialization
|
|
88
|
+
"content": self.content,
|
|
89
|
+
"timestamp": self.timestamp.isoformat(),
|
|
90
|
+
"user_name": self.user_name,
|
|
91
|
+
"task_id": self.task_id if self.task_id is not None else "",
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
@classmethod
|
|
95
|
+
def from_dict(cls, data: dict) -> "ScheduleMessageItem":
|
|
96
|
+
"""Create model from Redis Stream dictionary"""
|
|
97
|
+
return cls(
|
|
98
|
+
item_id=data.get("item_id", str(uuid4())),
|
|
99
|
+
user_id=data["user_id"],
|
|
100
|
+
mem_cube_id=data["cube_id"],
|
|
101
|
+
trace_id=data.get("trace_id", generate_trace_id()),
|
|
102
|
+
label=data["label"],
|
|
103
|
+
content=data["content"],
|
|
104
|
+
timestamp=datetime.fromisoformat(data["timestamp"]),
|
|
105
|
+
user_name=data.get("user_name"),
|
|
106
|
+
task_id=data.get("task_id"),
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
class MemorySizes(TypedDict):
|
|
111
|
+
long_term_memory_size: int
|
|
112
|
+
user_memory_size: int
|
|
113
|
+
working_memory_size: int
|
|
114
|
+
transformed_act_memory_size: int
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
class MemoryCapacities(TypedDict):
|
|
118
|
+
long_term_memory_capacity: int
|
|
119
|
+
user_memory_capacity: int
|
|
120
|
+
working_memory_capacity: int
|
|
121
|
+
transformed_act_memory_capacity: int
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class ScheduleLogForWebItem(BaseModel, DictConversionMixin):
|
|
125
|
+
item_id: str = Field(
|
|
126
|
+
description="Unique identifier for the log entry", default_factory=lambda: str(uuid4())
|
|
127
|
+
)
|
|
128
|
+
task_id: str | None = Field(default=None, description="Identifier for the parent task")
|
|
129
|
+
user_id: str = Field(..., description="Identifier for the user associated with the log")
|
|
130
|
+
mem_cube_id: str = Field(
|
|
131
|
+
..., description="Identifier for the memcube associated with this log entry"
|
|
132
|
+
)
|
|
133
|
+
label: str = Field(..., description="Label categorizing the type of log")
|
|
134
|
+
from_memory_type: str | None = Field(None, description="Source memory type")
|
|
135
|
+
to_memory_type: str | None = Field(None, description="Destination memory type")
|
|
136
|
+
log_content: str = Field(..., description="Detailed content of the log entry")
|
|
137
|
+
current_memory_sizes: MemorySizes = Field(
|
|
138
|
+
default_factory=lambda: dict(DEFAULT_MEMORY_SIZES),
|
|
139
|
+
description="Current utilization of memory partitions",
|
|
140
|
+
)
|
|
141
|
+
memory_capacities: MemoryCapacities = Field(
|
|
142
|
+
default_factory=lambda: dict(DEFAULT_MEMORY_CAPACITIES),
|
|
143
|
+
description="Maximum capacities of memory partitions",
|
|
144
|
+
)
|
|
145
|
+
timestamp: datetime = Field(
|
|
146
|
+
default_factory=get_utc_now,
|
|
147
|
+
description="Timestamp indicating when the log entry was created",
|
|
148
|
+
)
|
|
149
|
+
memcube_log_content: list[dict] | None = Field(
|
|
150
|
+
default=None, description="Structured memcube log content list"
|
|
151
|
+
)
|
|
152
|
+
metadata: list[dict] | None = Field(
|
|
153
|
+
default=None, description="Structured metadata list for each log item"
|
|
154
|
+
)
|
|
155
|
+
memcube_name: str | None = Field(default=None, description="Display name for memcube")
|
|
156
|
+
memory_len: int | None = Field(default=None, description="Count of items involved in the event")
|
|
157
|
+
status: str | None = Field(
|
|
158
|
+
default=None, description="Completion status of the task (e.g., 'completed', 'failed')"
|
|
159
|
+
)
|
|
160
|
+
source_doc_id: str | None = Field(default=None, description="Source document ID")
|
|
161
|
+
|
|
162
|
+
def debug_info(self) -> dict[str, Any]:
|
|
163
|
+
"""Return structured debug information for logging purposes."""
|
|
164
|
+
return {
|
|
165
|
+
"content_preview:": self.log_content[:50],
|
|
166
|
+
"item_id": self.item_id,
|
|
167
|
+
"user_id": self.user_id,
|
|
168
|
+
"mem_cube_id": self.mem_cube_id,
|
|
169
|
+
"operation": f"{self.from_memory_type} → {self.to_memory_type}",
|
|
170
|
+
"label": self.label,
|
|
171
|
+
"content_length": len(self.log_content),
|
|
172
|
+
"timestamp": self.timestamp.isoformat(),
|
|
173
|
+
}
|