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,92 @@
|
|
|
1
|
+
from abc import abstractmethod
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from memos.configs.memory import BaseTextMemoryConfig
|
|
5
|
+
from memos.memories.base import BaseMemory
|
|
6
|
+
from memos.memories.textual.item import TextualMemoryItem
|
|
7
|
+
from memos.types import MessageList
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BaseTextMemory(BaseMemory):
|
|
11
|
+
"""Base class for all textual memory implementations."""
|
|
12
|
+
|
|
13
|
+
# Default mode configuration - can be overridden by subclasses
|
|
14
|
+
mode: str = "sync" # Default mode: 'async' or 'sync'
|
|
15
|
+
|
|
16
|
+
@abstractmethod
|
|
17
|
+
def __init__(self, config: BaseTextMemoryConfig):
|
|
18
|
+
"""Initialize memory with the given configuration."""
|
|
19
|
+
|
|
20
|
+
@abstractmethod
|
|
21
|
+
def extract(self, messages: MessageList) -> list[TextualMemoryItem]:
|
|
22
|
+
"""Extract memories based on the messages.
|
|
23
|
+
Args:
|
|
24
|
+
messages (MessageList): The messages to extract memories from.
|
|
25
|
+
Returns:
|
|
26
|
+
list[TextualMemoryItem]: List of extracted memory items.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
@abstractmethod
|
|
30
|
+
def add(self, memories: list[TextualMemoryItem | dict[str, Any]], **kwargs) -> list[str]:
|
|
31
|
+
"""Add memories.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
memories: List of TextualMemoryItem objects or dictionaries to add.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
@abstractmethod
|
|
38
|
+
def update(self, memory_id: str, new_memory: TextualMemoryItem | dict[str, Any]) -> None:
|
|
39
|
+
"""Update a memory by memory_id."""
|
|
40
|
+
|
|
41
|
+
@abstractmethod
|
|
42
|
+
def search(self, query: str, top_k: int, info=None, **kwargs) -> list[TextualMemoryItem]:
|
|
43
|
+
"""Search for memories based on a query.
|
|
44
|
+
Args:
|
|
45
|
+
query (str): The query to search for.
|
|
46
|
+
top_k (int): The number of top results to return.
|
|
47
|
+
info (dict): Leave a record of memory consumption.
|
|
48
|
+
Returns:
|
|
49
|
+
list[TextualMemoryItem]: List of matching memories.
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
@abstractmethod
|
|
53
|
+
def get(self, memory_id: str, user_name: str | None = None) -> TextualMemoryItem:
|
|
54
|
+
"""Get a memory by its ID.
|
|
55
|
+
Args:
|
|
56
|
+
memory_id (str): The ID of the memory to retrieve.
|
|
57
|
+
Returns:
|
|
58
|
+
TextualMemoryItem: The memory with the given ID.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
@abstractmethod
|
|
62
|
+
def get_by_ids(self, memory_ids: list[str]) -> list[TextualMemoryItem]:
|
|
63
|
+
"""Get memories by their IDs.
|
|
64
|
+
Args:
|
|
65
|
+
memory_ids (list[str]): List of memory IDs to retrieve.
|
|
66
|
+
Returns:
|
|
67
|
+
list[TextualMemoryItem]: List of memories with the specified IDs.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
@abstractmethod
|
|
71
|
+
def get_all(self) -> list[TextualMemoryItem]:
|
|
72
|
+
"""Get all memories.
|
|
73
|
+
Returns:
|
|
74
|
+
list[TextualMemoryItem]: List of all memories.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
@abstractmethod
|
|
78
|
+
def delete(self, memory_ids: list[str]) -> None:
|
|
79
|
+
"""Delete memories.
|
|
80
|
+
Args:
|
|
81
|
+
memory_ids (list[str]): List of memory IDs to delete.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
@abstractmethod
|
|
85
|
+
def delete_all(self) -> None:
|
|
86
|
+
"""Delete all memories."""
|
|
87
|
+
|
|
88
|
+
@abstractmethod
|
|
89
|
+
def drop(
|
|
90
|
+
self,
|
|
91
|
+
) -> None:
|
|
92
|
+
"""Drop all databases."""
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from tenacity import retry, retry_if_exception_type, stop_after_attempt
|
|
8
|
+
|
|
9
|
+
from memos.configs.memory import GeneralTextMemoryConfig
|
|
10
|
+
from memos.embedders.factory import ArkEmbedder, EmbedderFactory, OllamaEmbedder
|
|
11
|
+
from memos.llms.factory import AzureLLM, LLMFactory, OllamaLLM, OpenAILLM
|
|
12
|
+
from memos.log import get_logger
|
|
13
|
+
from memos.memories.textual.base import BaseTextMemory
|
|
14
|
+
from memos.memories.textual.item import TextualMemoryItem
|
|
15
|
+
from memos.templates.mem_reader_prompts import SIMPLE_STRUCT_MEM_READER_PROMPT
|
|
16
|
+
from memos.types import MessageList
|
|
17
|
+
from memos.vec_dbs.factory import QdrantVecDB, VecDBFactory
|
|
18
|
+
from memos.vec_dbs.item import VecDBItem
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
logger = get_logger(__name__)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class GeneralTextMemory(BaseTextMemory):
|
|
25
|
+
"""General textual memory implementation for storing and retrieving memories."""
|
|
26
|
+
|
|
27
|
+
def __init__(self, config: GeneralTextMemoryConfig):
|
|
28
|
+
"""Initialize memory with the given configuration."""
|
|
29
|
+
# Set mode from class default or override if needed
|
|
30
|
+
self.mode = getattr(self.__class__, "mode", "sync")
|
|
31
|
+
self.config: GeneralTextMemoryConfig = config
|
|
32
|
+
self.extractor_llm: OpenAILLM | OllamaLLM | AzureLLM = LLMFactory.from_config(
|
|
33
|
+
config.extractor_llm
|
|
34
|
+
)
|
|
35
|
+
self.vector_db: QdrantVecDB = VecDBFactory.from_config(config.vector_db)
|
|
36
|
+
self.embedder: OllamaEmbedder | ArkEmbedder = EmbedderFactory.from_config(config.embedder)
|
|
37
|
+
|
|
38
|
+
@retry(
|
|
39
|
+
stop=stop_after_attempt(3),
|
|
40
|
+
retry=retry_if_exception_type(json.JSONDecodeError),
|
|
41
|
+
before_sleep=lambda retry_state: logger.warning(
|
|
42
|
+
f"Extracting memory failed due to JSON decode error: {retry_state.outcome.exception()}, Attempt retry: {retry_state.attempt_number} / {3}"
|
|
43
|
+
),
|
|
44
|
+
)
|
|
45
|
+
def extract(self, messages: MessageList) -> list[TextualMemoryItem]:
|
|
46
|
+
"""Extract memories based on the messages.
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
messages: List of message dictionaries to extract memories from.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
List of TextualMemoryItem objects representing the extracted memories.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
str_messages = "\n".join(
|
|
56
|
+
[message["role"] + ":" + message["content"] for message in messages]
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
prompt = SIMPLE_STRUCT_MEM_READER_PROMPT.replace("${conversation}", str_messages).replace(
|
|
60
|
+
"${custom_tags_prompt}", ""
|
|
61
|
+
)
|
|
62
|
+
messages = [{"role": "user", "content": prompt}]
|
|
63
|
+
response_text = self.extractor_llm.generate(messages)
|
|
64
|
+
response_json = self.parse_json_result(response_text)
|
|
65
|
+
|
|
66
|
+
extracted_memories = [
|
|
67
|
+
TextualMemoryItem(
|
|
68
|
+
memory=memory_dict["value"],
|
|
69
|
+
metadata={
|
|
70
|
+
"key": memory_dict["key"],
|
|
71
|
+
"source": "conversation",
|
|
72
|
+
"tags": memory_dict["tags"],
|
|
73
|
+
"updated_at": datetime.now().isoformat(),
|
|
74
|
+
},
|
|
75
|
+
)
|
|
76
|
+
for memory_dict in response_json["memory list"]
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
return extracted_memories
|
|
80
|
+
|
|
81
|
+
def add(self, memories: list[TextualMemoryItem | dict[str, Any]]) -> None:
|
|
82
|
+
"""Add memories.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
memories: List of TextualMemoryItem objects or dictionaries to add.
|
|
86
|
+
"""
|
|
87
|
+
memory_items = [TextualMemoryItem(**m) if isinstance(m, dict) else m for m in memories]
|
|
88
|
+
|
|
89
|
+
# Memory encode
|
|
90
|
+
embed_memories = self.embedder.embed([m.memory for m in memory_items])
|
|
91
|
+
|
|
92
|
+
# Create vector db items
|
|
93
|
+
vec_db_items = []
|
|
94
|
+
for item, emb in zip(memory_items, embed_memories, strict=True):
|
|
95
|
+
vec_db_items.append(
|
|
96
|
+
VecDBItem(
|
|
97
|
+
id=item.id,
|
|
98
|
+
payload=item.model_dump(),
|
|
99
|
+
vector=emb,
|
|
100
|
+
)
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# Add to vector db
|
|
104
|
+
self.vector_db.add(vec_db_items)
|
|
105
|
+
|
|
106
|
+
def update(self, memory_id: str, new_memory: TextualMemoryItem | dict[str, Any]) -> None:
|
|
107
|
+
"""Update a memory by memory_id."""
|
|
108
|
+
memory_item = (
|
|
109
|
+
TextualMemoryItem(**new_memory) if isinstance(new_memory, dict) else new_memory
|
|
110
|
+
)
|
|
111
|
+
memory_item.id = memory_id
|
|
112
|
+
|
|
113
|
+
vec_db_item = VecDBItem(
|
|
114
|
+
id=memory_item.id,
|
|
115
|
+
payload=memory_item.model_dump(),
|
|
116
|
+
vector=self._embed_one_sentence(memory_item.memory),
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
self.vector_db.update(memory_id, vec_db_item)
|
|
120
|
+
|
|
121
|
+
def search(self, query: str, top_k: int, info=None, **kwargs) -> list[TextualMemoryItem]:
|
|
122
|
+
"""Search for memories based on a query.
|
|
123
|
+
Args:
|
|
124
|
+
query (str): The query to search for.
|
|
125
|
+
top_k (int): The number of top results to return.
|
|
126
|
+
Returns:
|
|
127
|
+
list[TextualMemoryItem]: List of matching memories.
|
|
128
|
+
"""
|
|
129
|
+
query_vector = self._embed_one_sentence(query)
|
|
130
|
+
search_results = self.vector_db.search(query_vector, top_k)
|
|
131
|
+
search_results = sorted( # make higher score first
|
|
132
|
+
search_results, key=lambda x: x.score, reverse=True
|
|
133
|
+
)
|
|
134
|
+
result_memories = [
|
|
135
|
+
TextualMemoryItem(**search_item.payload) for search_item in search_results
|
|
136
|
+
]
|
|
137
|
+
return result_memories
|
|
138
|
+
|
|
139
|
+
def get(self, memory_id: str, user_name: str | None = None) -> TextualMemoryItem:
|
|
140
|
+
"""Get a memory by its ID."""
|
|
141
|
+
result = self.vector_db.get_by_id(memory_id)
|
|
142
|
+
if result is None:
|
|
143
|
+
raise ValueError(f"Memory with ID {memory_id} not found")
|
|
144
|
+
return TextualMemoryItem(**result.payload)
|
|
145
|
+
|
|
146
|
+
def get_by_ids(self, memory_ids: list[str]) -> list[TextualMemoryItem]:
|
|
147
|
+
"""Get memories by their IDs.
|
|
148
|
+
Args:
|
|
149
|
+
memory_ids (list[str]): List of memory IDs to retrieve.
|
|
150
|
+
Returns:
|
|
151
|
+
list[TextualMemoryItem]: List of memories with the specified IDs.
|
|
152
|
+
"""
|
|
153
|
+
db_items = self.vector_db.get_by_ids(memory_ids)
|
|
154
|
+
memories = [TextualMemoryItem(**db_item.payload) for db_item in db_items]
|
|
155
|
+
return memories
|
|
156
|
+
|
|
157
|
+
def get_all(self) -> list[TextualMemoryItem]:
|
|
158
|
+
"""Get all memories.
|
|
159
|
+
Returns:
|
|
160
|
+
list[TextualMemoryItem]: List of all memories.
|
|
161
|
+
"""
|
|
162
|
+
all_items = self.vector_db.get_all()
|
|
163
|
+
all_memories = [TextualMemoryItem(**memo.payload) for memo in all_items]
|
|
164
|
+
return all_memories
|
|
165
|
+
|
|
166
|
+
def delete(self, memory_ids: list[str]) -> None:
|
|
167
|
+
"""Delete a memory."""
|
|
168
|
+
self.vector_db.delete(memory_ids)
|
|
169
|
+
|
|
170
|
+
def delete_all(self) -> None:
|
|
171
|
+
"""Delete all memories."""
|
|
172
|
+
self.vector_db.delete_collection(self.vector_db.config.collection_name)
|
|
173
|
+
self.vector_db.create_collection()
|
|
174
|
+
|
|
175
|
+
def load(self, dir: str) -> None:
|
|
176
|
+
try:
|
|
177
|
+
memory_file = os.path.join(dir, self.config.memory_filename)
|
|
178
|
+
|
|
179
|
+
if not os.path.exists(memory_file):
|
|
180
|
+
logger.warning(f"Memory file not found: {memory_file}")
|
|
181
|
+
return
|
|
182
|
+
|
|
183
|
+
with open(memory_file, encoding="utf-8") as f:
|
|
184
|
+
memories = json.load(f)
|
|
185
|
+
|
|
186
|
+
vec_db_items = [VecDBItem.from_dict(m) for m in memories]
|
|
187
|
+
self.vector_db.add(vec_db_items)
|
|
188
|
+
logger.info(f"Loaded {len(memories)} memories from {memory_file}")
|
|
189
|
+
|
|
190
|
+
except FileNotFoundError:
|
|
191
|
+
logger.error(f"Memory file not found in directory: {dir}")
|
|
192
|
+
except json.JSONDecodeError as e:
|
|
193
|
+
logger.error(f"Error decoding JSON from memory file: {e}")
|
|
194
|
+
except Exception as e:
|
|
195
|
+
logger.error(f"An error occurred while loading memories: {e}")
|
|
196
|
+
|
|
197
|
+
def dump(self, dir: str) -> None:
|
|
198
|
+
"""Dump memories to os.path.join(dir, self.config.memory_filename)"""
|
|
199
|
+
try:
|
|
200
|
+
all_vec_db_items = self.vector_db.get_all()
|
|
201
|
+
json_memories = [memory.to_dict() for memory in all_vec_db_items]
|
|
202
|
+
|
|
203
|
+
os.makedirs(dir, exist_ok=True)
|
|
204
|
+
memory_file = os.path.join(dir, self.config.memory_filename)
|
|
205
|
+
with open(memory_file, "w", encoding="utf-8") as f:
|
|
206
|
+
json.dump(json_memories, f, indent=4, ensure_ascii=False)
|
|
207
|
+
|
|
208
|
+
logger.info(f"Dumped {len(all_vec_db_items)} memories to {memory_file}")
|
|
209
|
+
|
|
210
|
+
except Exception as e:
|
|
211
|
+
logger.error(f"An error occurred while dumping memories: {e}")
|
|
212
|
+
raise
|
|
213
|
+
|
|
214
|
+
def drop(
|
|
215
|
+
self,
|
|
216
|
+
) -> None:
|
|
217
|
+
pass
|
|
218
|
+
|
|
219
|
+
def _embed_one_sentence(self, sentence: str) -> list[float]:
|
|
220
|
+
"""Embed a single sentence."""
|
|
221
|
+
return self.embedder.embed([sentence])[0]
|
|
222
|
+
|
|
223
|
+
def parse_json_result(self, response_text):
|
|
224
|
+
try:
|
|
225
|
+
json_start = response_text.find("{")
|
|
226
|
+
response_text = response_text[json_start:]
|
|
227
|
+
response_text = response_text.replace("```", "").strip()
|
|
228
|
+
if response_text[-1] != "}":
|
|
229
|
+
response_text += "}"
|
|
230
|
+
response_json = json.loads(response_text)
|
|
231
|
+
return response_json
|
|
232
|
+
except json.JSONDecodeError as e:
|
|
233
|
+
logger.warning(
|
|
234
|
+
f"Failed to parse LLM response as JSON: {e}\nRaw response:\n{response_text}"
|
|
235
|
+
)
|
|
236
|
+
return {}
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
"""Defines memory item types for textual memory."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
import uuid
|
|
6
|
+
|
|
7
|
+
from datetime import datetime
|
|
8
|
+
from typing import Any, Literal
|
|
9
|
+
|
|
10
|
+
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
ALLOWED_ROLES = {"user", "assistant", "system"}
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SourceMessage(BaseModel):
|
|
17
|
+
"""
|
|
18
|
+
Purpose: **memory provenance / traceability**.
|
|
19
|
+
|
|
20
|
+
Capture the minimal, reproducible origin context of a memory item so it can be
|
|
21
|
+
audited, traced, rolled back, or de-duplicated later.
|
|
22
|
+
|
|
23
|
+
Fields & conventions:
|
|
24
|
+
- type: Source kind (e.g., "chat", "doc", "web", "file", "system", ...).
|
|
25
|
+
If not provided, upstream logic may infer it:
|
|
26
|
+
presence of `role` ⇒ "chat"; otherwise ⇒ "doc".
|
|
27
|
+
- role: Conversation role ("user" | "assistant" | "system" | "tool") when the
|
|
28
|
+
source is a chat turn.
|
|
29
|
+
- content: Minimal reproducible snippet from the source. If omitted,
|
|
30
|
+
upstream may fall back to `doc_path` / `url` / `message_id`.
|
|
31
|
+
- file_info: File information for file source.
|
|
32
|
+
- chat_time / message_id / doc_path: Locators for precisely pointing back
|
|
33
|
+
to the original record (timestamp, message id, document path).
|
|
34
|
+
- Extra fields: Allowed (`model_config.extra="allow"`) to carry arbitrary
|
|
35
|
+
provenance attributes (e.g., url, page, offset, span, local_confidence).
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
type: str | None = "chat"
|
|
39
|
+
role: Literal["user", "assistant", "system", "tool"] | None = None
|
|
40
|
+
chat_time: str | None = None
|
|
41
|
+
message_id: str | None = None
|
|
42
|
+
content: str | None = None
|
|
43
|
+
doc_path: str | None = None
|
|
44
|
+
file_info: dict | None = None
|
|
45
|
+
model_config = ConfigDict(extra="allow")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class TextualMemoryMetadata(BaseModel):
|
|
49
|
+
"""Metadata for a memory item.
|
|
50
|
+
|
|
51
|
+
This includes information such as the type of memory, when it occurred,
|
|
52
|
+
its source, and other relevant details.
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
user_id: str | None = Field(
|
|
56
|
+
default=None,
|
|
57
|
+
description="The ID of the user associated with the memory. Useful for multi-user systems.",
|
|
58
|
+
)
|
|
59
|
+
session_id: str | None = Field(
|
|
60
|
+
default=None,
|
|
61
|
+
description="The ID of the session during which the memory was created. Useful for tracking context in conversations.",
|
|
62
|
+
)
|
|
63
|
+
status: Literal["activated", "archived", "deleted"] | None = Field(
|
|
64
|
+
default="activated",
|
|
65
|
+
description="The status of the memory, e.g., 'activated', 'archived', 'deleted'.",
|
|
66
|
+
)
|
|
67
|
+
type: str | None = Field(default=None)
|
|
68
|
+
key: str | None = Field(default=None, description="Memory key or title.")
|
|
69
|
+
confidence: float | None = Field(
|
|
70
|
+
default=None,
|
|
71
|
+
description="A numeric score (float between 0 and 100) indicating how certain you are about the accuracy or reliability of the memory.",
|
|
72
|
+
)
|
|
73
|
+
source: Literal["conversation", "retrieved", "web", "file", "system"] | None = Field(
|
|
74
|
+
default=None, description="The origin of the memory"
|
|
75
|
+
)
|
|
76
|
+
tags: list[str] | None = Field(
|
|
77
|
+
default=None,
|
|
78
|
+
description='A list of keywords or thematic labels associated with the memory for categorization or retrieval, e.g., `["travel", "health", "project-x"]`.',
|
|
79
|
+
)
|
|
80
|
+
visibility: Literal["private", "public", "session"] | None = Field(
|
|
81
|
+
default=None, description="e.g., 'private', 'public', 'session'"
|
|
82
|
+
)
|
|
83
|
+
updated_at: str | None = Field(
|
|
84
|
+
default_factory=lambda: datetime.now().isoformat(),
|
|
85
|
+
description="The timestamp of the last modification to the memory. Useful for tracking memory freshness or change history. Format: ISO 8601.",
|
|
86
|
+
)
|
|
87
|
+
info: dict | None = Field(
|
|
88
|
+
default=None,
|
|
89
|
+
description="Arbitrary key-value pairs for additional metadata.",
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
model_config = ConfigDict(extra="allow")
|
|
93
|
+
|
|
94
|
+
covered_history: Any | None = Field(
|
|
95
|
+
default=None,
|
|
96
|
+
description="Record the memory id covered by the update",
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
def __str__(self) -> str:
|
|
100
|
+
"""Pretty string representation of the metadata."""
|
|
101
|
+
meta = self.model_dump(exclude_none=True)
|
|
102
|
+
return ", ".join(f"{k}={v}" for k, v in meta.items())
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class TreeNodeTextualMemoryMetadata(TextualMemoryMetadata):
|
|
106
|
+
"""Extended metadata for structured memory, layered retrieval, and lifecycle tracking."""
|
|
107
|
+
|
|
108
|
+
memory_type: Literal[
|
|
109
|
+
"WorkingMemory",
|
|
110
|
+
"LongTermMemory",
|
|
111
|
+
"UserMemory",
|
|
112
|
+
"OuterMemory",
|
|
113
|
+
"ToolSchemaMemory",
|
|
114
|
+
"ToolTrajectoryMemory",
|
|
115
|
+
] = Field(default="WorkingMemory", description="Memory lifecycle type.")
|
|
116
|
+
sources: list[SourceMessage] | None = Field(
|
|
117
|
+
default=None, description="Multiple origins of the memory (e.g., URLs, notes)."
|
|
118
|
+
)
|
|
119
|
+
embedding: list[float] | None = Field(
|
|
120
|
+
default=None,
|
|
121
|
+
description="The vector embedding of the memory content, used for semantic search or clustering.",
|
|
122
|
+
)
|
|
123
|
+
created_at: str | None = Field(
|
|
124
|
+
default_factory=lambda: datetime.now().isoformat(),
|
|
125
|
+
description="The timestamp of the first creation to the memory. Useful "
|
|
126
|
+
"for tracking memory initialization. Format: ISO 8601.",
|
|
127
|
+
)
|
|
128
|
+
usage: list[str] = Field(
|
|
129
|
+
default_factory=list,
|
|
130
|
+
description="Usage history of this node",
|
|
131
|
+
)
|
|
132
|
+
background: str | None = Field(
|
|
133
|
+
default="",
|
|
134
|
+
description="background of this node",
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
file_ids: list[str] | None = Field(
|
|
138
|
+
default_factory=list,
|
|
139
|
+
description="The ids of the files associated with the memory.",
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
@field_validator("sources", mode="before")
|
|
143
|
+
@classmethod
|
|
144
|
+
def coerce_sources(cls, v):
|
|
145
|
+
if v is None:
|
|
146
|
+
return v
|
|
147
|
+
# Handle string representation of sources (e.g., from PostgreSQL array or malformed data)
|
|
148
|
+
if isinstance(v, str):
|
|
149
|
+
logging.info(f"[coerce_sources] v: {v} type: {type(v)}")
|
|
150
|
+
# If it's a string that looks like a list representation, try to parse it
|
|
151
|
+
# This handles cases like: "[uuid1, uuid2, uuid3]" or "[item1, item2]"
|
|
152
|
+
v_stripped = v.strip()
|
|
153
|
+
if v_stripped.startswith("[") and v_stripped.endswith("]"):
|
|
154
|
+
# Remove brackets and split by comma
|
|
155
|
+
content = v_stripped[1:-1].strip()
|
|
156
|
+
if content:
|
|
157
|
+
# Split by comma and clean up each item
|
|
158
|
+
items = [item.strip() for item in content.split(",")]
|
|
159
|
+
# Convert to list of strings
|
|
160
|
+
v = items
|
|
161
|
+
else:
|
|
162
|
+
v = []
|
|
163
|
+
else:
|
|
164
|
+
# Single string, wrap in list
|
|
165
|
+
v = [v]
|
|
166
|
+
if not isinstance(v, list):
|
|
167
|
+
raise TypeError("sources must be a list")
|
|
168
|
+
out = []
|
|
169
|
+
for item in v:
|
|
170
|
+
if isinstance(item, SourceMessage):
|
|
171
|
+
out.append(item)
|
|
172
|
+
|
|
173
|
+
elif isinstance(item, dict):
|
|
174
|
+
d = dict(item)
|
|
175
|
+
if d.get("type") is None:
|
|
176
|
+
d["type"] = "chat" if d.get("role") in ALLOWED_ROLES else "doc"
|
|
177
|
+
out.append(SourceMessage(**d))
|
|
178
|
+
|
|
179
|
+
elif isinstance(item, str):
|
|
180
|
+
try:
|
|
181
|
+
parsed = json.loads(item)
|
|
182
|
+
except Exception:
|
|
183
|
+
parsed = None
|
|
184
|
+
|
|
185
|
+
if isinstance(parsed, dict):
|
|
186
|
+
if parsed.get("type") is None:
|
|
187
|
+
parsed["type"] = "chat" if parsed.get("role") in ALLOWED_ROLES else "doc"
|
|
188
|
+
out.append(SourceMessage(**parsed))
|
|
189
|
+
else:
|
|
190
|
+
out.append(SourceMessage(type="doc", content=item))
|
|
191
|
+
|
|
192
|
+
else:
|
|
193
|
+
out.append(SourceMessage(type="doc", content=str(item)))
|
|
194
|
+
return out
|
|
195
|
+
|
|
196
|
+
def __str__(self) -> str:
|
|
197
|
+
"""Pretty string representation of the metadata."""
|
|
198
|
+
meta = self.model_dump(exclude_none=True)
|
|
199
|
+
return ", ".join([f"{k}={v}" for k, v in meta.items() if k != "embedding"])
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class SearchedTreeNodeTextualMemoryMetadata(TreeNodeTextualMemoryMetadata):
|
|
203
|
+
"""Metadata for nodes returned by search, includes similarity info."""
|
|
204
|
+
|
|
205
|
+
relativity: float | None = Field(
|
|
206
|
+
default=None, description="Similarity score with respect to the query, 0 ~ 1."
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
class PreferenceTextualMemoryMetadata(TextualMemoryMetadata):
|
|
211
|
+
"""Metadata for preference memory item."""
|
|
212
|
+
|
|
213
|
+
preference_type: Literal["explicit_preference", "implicit_preference"] = Field(
|
|
214
|
+
default="explicit_preference", description="Type of preference."
|
|
215
|
+
)
|
|
216
|
+
dialog_id: str | None = Field(default=None, description="ID of the dialog.")
|
|
217
|
+
original_text: str | None = Field(default=None, description="String of the dialog.")
|
|
218
|
+
embedding: list[float] | None = Field(default=None, description="Vector of the dialog.")
|
|
219
|
+
preference: str | None = Field(default=None, description="Preference.")
|
|
220
|
+
created_at: str | None = Field(default=None, description="Timestamp of the dialog.")
|
|
221
|
+
mem_cube_id: str | None = Field(default=None, description="ID of the MemCube.")
|
|
222
|
+
score: float | None = Field(default=None, description="Score of the retrieval result.")
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
class TextualMemoryItem(BaseModel):
|
|
226
|
+
"""Represents a single memory item in the textual memory.
|
|
227
|
+
|
|
228
|
+
This serves as a standardized format for memory items across different
|
|
229
|
+
textual memory implementations.
|
|
230
|
+
"""
|
|
231
|
+
|
|
232
|
+
id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
|
233
|
+
memory: str
|
|
234
|
+
metadata: (
|
|
235
|
+
SearchedTreeNodeTextualMemoryMetadata
|
|
236
|
+
| TreeNodeTextualMemoryMetadata
|
|
237
|
+
| TextualMemoryMetadata
|
|
238
|
+
| PreferenceTextualMemoryMetadata
|
|
239
|
+
) = Field(default_factory=TextualMemoryMetadata)
|
|
240
|
+
|
|
241
|
+
model_config = ConfigDict(extra="forbid")
|
|
242
|
+
|
|
243
|
+
@field_validator("id")
|
|
244
|
+
@classmethod
|
|
245
|
+
def _validate_id(cls, v: str) -> str:
|
|
246
|
+
uuid.UUID(v)
|
|
247
|
+
return v
|
|
248
|
+
|
|
249
|
+
@classmethod
|
|
250
|
+
def from_dict(cls, data: dict) -> "TextualMemoryItem":
|
|
251
|
+
return cls(**data)
|
|
252
|
+
|
|
253
|
+
def to_dict(self) -> dict:
|
|
254
|
+
return self.model_dump(exclude_none=True)
|
|
255
|
+
|
|
256
|
+
@field_validator("metadata", mode="before")
|
|
257
|
+
@classmethod
|
|
258
|
+
def _coerce_metadata(cls, v: Any):
|
|
259
|
+
if isinstance(
|
|
260
|
+
v,
|
|
261
|
+
SearchedTreeNodeTextualMemoryMetadata
|
|
262
|
+
| TreeNodeTextualMemoryMetadata
|
|
263
|
+
| TextualMemoryMetadata
|
|
264
|
+
| PreferenceTextualMemoryMetadata,
|
|
265
|
+
):
|
|
266
|
+
return v
|
|
267
|
+
if isinstance(v, dict):
|
|
268
|
+
if "metadata" in v and isinstance(v["metadata"], dict):
|
|
269
|
+
nested_metadata = v["metadata"]
|
|
270
|
+
nested_metadata = nested_metadata.copy()
|
|
271
|
+
nested_metadata.pop("id", None)
|
|
272
|
+
nested_metadata.pop("memory", None)
|
|
273
|
+
v = nested_metadata
|
|
274
|
+
else:
|
|
275
|
+
v = v.copy()
|
|
276
|
+
v.pop("id", None)
|
|
277
|
+
v.pop("memory", None)
|
|
278
|
+
|
|
279
|
+
if v.get("relativity") is not None:
|
|
280
|
+
return SearchedTreeNodeTextualMemoryMetadata(**v)
|
|
281
|
+
if v.get("preference_type") is not None:
|
|
282
|
+
return PreferenceTextualMemoryMetadata(**v)
|
|
283
|
+
if any(k in v for k in ("sources", "memory_type", "embedding", "background", "usage")):
|
|
284
|
+
return TreeNodeTextualMemoryMetadata(**v)
|
|
285
|
+
return TextualMemoryMetadata(**v)
|
|
286
|
+
return v
|
|
287
|
+
|
|
288
|
+
def __str__(self) -> str:
|
|
289
|
+
"""Pretty string representation of the memory item."""
|
|
290
|
+
return f"<ID: {self.id} | Memory: {self.memory} | Metadata: {self.metadata!s}>"
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def list_all_fields() -> list[str]:
|
|
294
|
+
"""List all possible fields of the TextualMemoryItem model."""
|
|
295
|
+
top = list(TextualMemoryItem.model_fields.keys())
|
|
296
|
+
meta_models = [
|
|
297
|
+
TextualMemoryMetadata,
|
|
298
|
+
TreeNodeTextualMemoryMetadata,
|
|
299
|
+
SearchedTreeNodeTextualMemoryMetadata,
|
|
300
|
+
PreferenceTextualMemoryMetadata,
|
|
301
|
+
]
|
|
302
|
+
meta_all = sorted(set().union(*[set(m.model_fields.keys()) for m in meta_models]))
|
|
303
|
+
|
|
304
|
+
return top + meta_all
|