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,161 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from memos.embedders.factory import (
|
|
4
|
+
ArkEmbedder,
|
|
5
|
+
OllamaEmbedder,
|
|
6
|
+
SenTranEmbedder,
|
|
7
|
+
UniversalAPIEmbedder,
|
|
8
|
+
)
|
|
9
|
+
from memos.llms.factory import AzureLLM, OllamaLLM, OpenAILLM
|
|
10
|
+
from memos.log import get_logger
|
|
11
|
+
from memos.memories.textual.item import PreferenceTextualMemoryMetadata, TextualMemoryItem
|
|
12
|
+
from memos.memories.textual.preference import PreferenceTextMemory
|
|
13
|
+
from memos.types import MessageList
|
|
14
|
+
from memos.vec_dbs.factory import MilvusVecDB, QdrantVecDB
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
logger = get_logger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SimplePreferenceTextMemory(PreferenceTextMemory):
|
|
21
|
+
"""Preference textual memory implementation for storing and retrieving memories."""
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
extractor_llm: OpenAILLM | OllamaLLM | AzureLLM,
|
|
26
|
+
vector_db: MilvusVecDB | QdrantVecDB,
|
|
27
|
+
embedder: OllamaEmbedder | ArkEmbedder | SenTranEmbedder | UniversalAPIEmbedder,
|
|
28
|
+
reranker,
|
|
29
|
+
extractor,
|
|
30
|
+
adder,
|
|
31
|
+
retriever,
|
|
32
|
+
):
|
|
33
|
+
"""Initialize memory with the given configuration."""
|
|
34
|
+
self.extractor_llm = extractor_llm
|
|
35
|
+
self.vector_db = vector_db
|
|
36
|
+
self.embedder = embedder
|
|
37
|
+
self.reranker = reranker
|
|
38
|
+
self.extractor = extractor
|
|
39
|
+
self.adder = adder
|
|
40
|
+
self.retriever = retriever
|
|
41
|
+
|
|
42
|
+
def get_memory(
|
|
43
|
+
self, messages: list[MessageList], type: str, info: dict[str, Any]
|
|
44
|
+
) -> list[TextualMemoryItem]:
|
|
45
|
+
"""Get memory based on the messages.
|
|
46
|
+
Args:
|
|
47
|
+
messages (MessageList): The messages to get memory from.
|
|
48
|
+
type (str): The type of memory to get.
|
|
49
|
+
info (dict[str, Any]): The info to get memory.
|
|
50
|
+
"""
|
|
51
|
+
return self.extractor.extract(messages, type, info)
|
|
52
|
+
|
|
53
|
+
def search(
|
|
54
|
+
self, query: str, top_k: int, info=None, search_filter=None, **kwargs
|
|
55
|
+
) -> list[TextualMemoryItem]:
|
|
56
|
+
"""Search for memories based on a query.
|
|
57
|
+
Args:
|
|
58
|
+
query (str): The query to search for.
|
|
59
|
+
top_k (int): The number of top results to return.
|
|
60
|
+
info (dict): Leave a record of memory consumption.
|
|
61
|
+
Returns:
|
|
62
|
+
list[TextualMemoryItem]: List of matching memories.
|
|
63
|
+
"""
|
|
64
|
+
if not isinstance(search_filter, dict):
|
|
65
|
+
search_filter = {}
|
|
66
|
+
search_filter.update({"status": "activated"})
|
|
67
|
+
return self.retriever.retrieve(query, top_k, info, search_filter)
|
|
68
|
+
|
|
69
|
+
def add(self, memories: list[TextualMemoryItem | dict[str, Any]]) -> list[str]:
|
|
70
|
+
"""Add memories.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
memories: List of TextualMemoryItem objects or dictionaries to add.
|
|
74
|
+
"""
|
|
75
|
+
return self.adder.add(memories)
|
|
76
|
+
|
|
77
|
+
def get_with_collection_name(
|
|
78
|
+
self, collection_name: str, memory_id: str
|
|
79
|
+
) -> TextualMemoryItem | None:
|
|
80
|
+
"""Get a memory by its ID and collection name.
|
|
81
|
+
Args:
|
|
82
|
+
memory_id (str): The ID of the memory to retrieve.
|
|
83
|
+
collection_name (str): The name of the collection to retrieve the memory from.
|
|
84
|
+
Returns:
|
|
85
|
+
TextualMemoryItem: The memory with the given ID and collection name.
|
|
86
|
+
"""
|
|
87
|
+
try:
|
|
88
|
+
res = self.vector_db.get_by_id(collection_name, memory_id)
|
|
89
|
+
if res is None:
|
|
90
|
+
return None
|
|
91
|
+
return TextualMemoryItem(
|
|
92
|
+
id=res.id,
|
|
93
|
+
memory=res.memory,
|
|
94
|
+
metadata=PreferenceTextualMemoryMetadata(**res.payload),
|
|
95
|
+
)
|
|
96
|
+
except Exception as e:
|
|
97
|
+
# Convert any other exception to ValueError for consistent error handling
|
|
98
|
+
raise ValueError(
|
|
99
|
+
f"Memory with ID {memory_id} not found in collection {collection_name}: {e}"
|
|
100
|
+
) from e
|
|
101
|
+
|
|
102
|
+
def get_by_ids_with_collection_name(
|
|
103
|
+
self, collection_name: str, memory_ids: list[str]
|
|
104
|
+
) -> list[TextualMemoryItem]:
|
|
105
|
+
"""Get memories by their IDs and collection name.
|
|
106
|
+
Args:
|
|
107
|
+
collection_name (str): The name of the collection to retrieve the memory from.
|
|
108
|
+
memory_ids (list[str]): List of memory IDs to retrieve.
|
|
109
|
+
Returns:
|
|
110
|
+
list[TextualMemoryItem]: List of memories with the specified IDs and collection name.
|
|
111
|
+
"""
|
|
112
|
+
try:
|
|
113
|
+
res = self.vector_db.get_by_ids(collection_name, memory_ids)
|
|
114
|
+
if not res:
|
|
115
|
+
return []
|
|
116
|
+
return [
|
|
117
|
+
TextualMemoryItem(
|
|
118
|
+
id=memo.id,
|
|
119
|
+
memory=memo.memory,
|
|
120
|
+
metadata=PreferenceTextualMemoryMetadata(**memo.payload),
|
|
121
|
+
)
|
|
122
|
+
for memo in res
|
|
123
|
+
]
|
|
124
|
+
except Exception as e:
|
|
125
|
+
# Convert any other exception to ValueError for consistent error handling
|
|
126
|
+
raise ValueError(
|
|
127
|
+
f"Memory with IDs {memory_ids} not found in collection {collection_name}: {e}"
|
|
128
|
+
) from e
|
|
129
|
+
|
|
130
|
+
def get_all(self) -> list[TextualMemoryItem]:
|
|
131
|
+
"""Get all memories.
|
|
132
|
+
Returns:
|
|
133
|
+
list[TextualMemoryItem]: List of all memories.
|
|
134
|
+
"""
|
|
135
|
+
all_collections = ["explicit_preference", "implicit_preference"]
|
|
136
|
+
all_memories = {}
|
|
137
|
+
for collection_name in all_collections:
|
|
138
|
+
items = self.vector_db.get_all(collection_name)
|
|
139
|
+
all_memories[collection_name] = [
|
|
140
|
+
TextualMemoryItem(
|
|
141
|
+
id=memo.id,
|
|
142
|
+
memory=memo.memory,
|
|
143
|
+
metadata=PreferenceTextualMemoryMetadata(**memo.payload),
|
|
144
|
+
)
|
|
145
|
+
for memo in items
|
|
146
|
+
]
|
|
147
|
+
return all_memories
|
|
148
|
+
|
|
149
|
+
def delete_with_collection_name(self, collection_name: str, memory_ids: list[str]) -> None:
|
|
150
|
+
"""Delete memories by their IDs and collection name.
|
|
151
|
+
Args:
|
|
152
|
+
collection_name (str): The name of the collection to delete the memory from.
|
|
153
|
+
memory_ids (list[str]): List of memory IDs to delete.
|
|
154
|
+
"""
|
|
155
|
+
self.vector_db.delete(collection_name, memory_ids)
|
|
156
|
+
|
|
157
|
+
def delete_all(self) -> None:
|
|
158
|
+
"""Delete all memories."""
|
|
159
|
+
for collection_name in self.vector_db.config.collection_name:
|
|
160
|
+
self.vector_db.delete_collection(collection_name)
|
|
161
|
+
self.vector_db.create_collection()
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
from memos.configs.memory import TreeTextMemoryConfig
|
|
4
|
+
from memos.embedders.base import BaseEmbedder
|
|
5
|
+
from memos.graph_dbs.base import BaseGraphDB
|
|
6
|
+
from memos.llms.base import BaseLLM
|
|
7
|
+
from memos.log import get_logger
|
|
8
|
+
from memos.mem_reader.base import BaseMemReader
|
|
9
|
+
from memos.memories.textual.tree import TreeTextMemory
|
|
10
|
+
from memos.memories.textual.tree_text_memory.organize.manager import MemoryManager
|
|
11
|
+
from memos.memories.textual.tree_text_memory.retrieve.bm25_util import EnhancedBM25
|
|
12
|
+
from memos.memories.textual.tree_text_memory.retrieve.retrieve_utils import FastTokenizer
|
|
13
|
+
from memos.reranker.base import BaseReranker
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from memos.embedders.factory import OllamaEmbedder
|
|
18
|
+
from memos.graph_dbs.factory import Neo4jGraphDB
|
|
19
|
+
from memos.llms.factory import AzureLLM, OllamaLLM, OpenAILLM
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
logger = get_logger(__name__)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SimpleTreeTextMemory(TreeTextMemory):
|
|
26
|
+
"""General textual memory implementation for storing and retrieving memories."""
|
|
27
|
+
|
|
28
|
+
def __init__(
|
|
29
|
+
self,
|
|
30
|
+
llm: BaseLLM,
|
|
31
|
+
embedder: BaseEmbedder,
|
|
32
|
+
mem_reader: BaseMemReader,
|
|
33
|
+
graph_db: BaseGraphDB,
|
|
34
|
+
reranker: BaseReranker,
|
|
35
|
+
memory_manager: MemoryManager,
|
|
36
|
+
config: TreeTextMemoryConfig,
|
|
37
|
+
internet_retriever: None = None,
|
|
38
|
+
is_reorganize: bool = False,
|
|
39
|
+
tokenizer: FastTokenizer | None = None,
|
|
40
|
+
include_embedding: bool = False,
|
|
41
|
+
):
|
|
42
|
+
"""Initialize memory with the given configuration."""
|
|
43
|
+
self.config: TreeTextMemoryConfig = config
|
|
44
|
+
self.mode = self.config.mode
|
|
45
|
+
logger.info(f"Tree mode is {self.mode}")
|
|
46
|
+
|
|
47
|
+
self.extractor_llm: OpenAILLM | OllamaLLM | AzureLLM = llm
|
|
48
|
+
self.dispatcher_llm: OpenAILLM | OllamaLLM | AzureLLM = llm
|
|
49
|
+
self.embedder: OllamaEmbedder = embedder
|
|
50
|
+
self.graph_store: Neo4jGraphDB = graph_db
|
|
51
|
+
self.search_strategy = config.search_strategy
|
|
52
|
+
self.bm25_retriever = (
|
|
53
|
+
EnhancedBM25()
|
|
54
|
+
if self.search_strategy and self.search_strategy.get("bm25", False)
|
|
55
|
+
else None
|
|
56
|
+
)
|
|
57
|
+
self.tokenizer = tokenizer
|
|
58
|
+
self.reranker = reranker
|
|
59
|
+
self.memory_manager: MemoryManager = memory_manager
|
|
60
|
+
# Create internet retriever if configured
|
|
61
|
+
self.internet_retriever = None
|
|
62
|
+
if config.internet_retriever is not None:
|
|
63
|
+
self.internet_retriever = internet_retriever
|
|
64
|
+
logger.info(
|
|
65
|
+
f"Internet retriever initialized with backend: {config.internet_retriever.backend}"
|
|
66
|
+
)
|
|
67
|
+
else:
|
|
68
|
+
logger.info("No internet retriever configured")
|
|
69
|
+
self.include_embedding = include_embedding
|
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import shutil
|
|
4
|
+
import tempfile
|
|
5
|
+
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from memos.configs.memory import TreeTextMemoryConfig
|
|
11
|
+
from memos.configs.reranker import RerankerConfigFactory
|
|
12
|
+
from memos.embedders.factory import EmbedderFactory, OllamaEmbedder
|
|
13
|
+
from memos.graph_dbs.factory import GraphStoreFactory, Neo4jGraphDB
|
|
14
|
+
from memos.llms.factory import AzureLLM, LLMFactory, OllamaLLM, OpenAILLM
|
|
15
|
+
from memos.log import get_logger
|
|
16
|
+
from memos.memories.textual.base import BaseTextMemory
|
|
17
|
+
from memos.memories.textual.item import TextualMemoryItem, TreeNodeTextualMemoryMetadata
|
|
18
|
+
from memos.memories.textual.tree_text_memory.organize.manager import MemoryManager
|
|
19
|
+
from memos.memories.textual.tree_text_memory.retrieve.advanced_searcher import (
|
|
20
|
+
AdvancedSearcher as Searcher,
|
|
21
|
+
)
|
|
22
|
+
from memos.memories.textual.tree_text_memory.retrieve.bm25_util import EnhancedBM25
|
|
23
|
+
from memos.memories.textual.tree_text_memory.retrieve.internet_retriever_factory import (
|
|
24
|
+
InternetRetrieverFactory,
|
|
25
|
+
)
|
|
26
|
+
from memos.reranker.factory import RerankerFactory
|
|
27
|
+
from memos.types import MessageList
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
logger = get_logger(__name__)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class TreeTextMemory(BaseTextMemory):
|
|
34
|
+
"""General textual memory implementation for storing and retrieving memories."""
|
|
35
|
+
|
|
36
|
+
def __init__(self, config: TreeTextMemoryConfig):
|
|
37
|
+
"""Initialize memory with the given configuration."""
|
|
38
|
+
# Set mode from class default or override if needed
|
|
39
|
+
self.mode = config.mode
|
|
40
|
+
logger.info(f"Tree mode is {self.mode}")
|
|
41
|
+
|
|
42
|
+
self.config: TreeTextMemoryConfig = config
|
|
43
|
+
self.extractor_llm: OpenAILLM | OllamaLLM | AzureLLM = LLMFactory.from_config(
|
|
44
|
+
config.extractor_llm
|
|
45
|
+
)
|
|
46
|
+
self.dispatcher_llm: OpenAILLM | OllamaLLM | AzureLLM = LLMFactory.from_config(
|
|
47
|
+
config.dispatcher_llm
|
|
48
|
+
)
|
|
49
|
+
self.embedder: OllamaEmbedder = EmbedderFactory.from_config(config.embedder)
|
|
50
|
+
self.graph_store: Neo4jGraphDB = GraphStoreFactory.from_config(config.graph_db)
|
|
51
|
+
|
|
52
|
+
self.search_strategy = config.search_strategy
|
|
53
|
+
self.bm25_retriever = (
|
|
54
|
+
EnhancedBM25() if self.search_strategy and self.search_strategy["bm25"] else None
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
if config.reranker is None:
|
|
58
|
+
default_cfg = RerankerConfigFactory.model_validate(
|
|
59
|
+
{
|
|
60
|
+
"backend": "cosine_local",
|
|
61
|
+
"config": {
|
|
62
|
+
"level_weights": {"topic": 1.0, "concept": 1.0, "fact": 1.0},
|
|
63
|
+
"level_field": "background",
|
|
64
|
+
},
|
|
65
|
+
}
|
|
66
|
+
)
|
|
67
|
+
self.reranker = RerankerFactory.from_config(default_cfg)
|
|
68
|
+
else:
|
|
69
|
+
self.reranker = RerankerFactory.from_config(config.reranker)
|
|
70
|
+
self.is_reorganize = config.reorganize
|
|
71
|
+
self.memory_manager: MemoryManager = MemoryManager(
|
|
72
|
+
self.graph_store,
|
|
73
|
+
self.embedder,
|
|
74
|
+
self.extractor_llm,
|
|
75
|
+
memory_size=config.memory_size
|
|
76
|
+
or {
|
|
77
|
+
"WorkingMemory": 20,
|
|
78
|
+
"LongTermMemory": 1500,
|
|
79
|
+
"UserMemory": 480,
|
|
80
|
+
},
|
|
81
|
+
is_reorganize=self.is_reorganize,
|
|
82
|
+
)
|
|
83
|
+
# Create internet retriever if configured
|
|
84
|
+
self.internet_retriever = None
|
|
85
|
+
if config.internet_retriever is not None:
|
|
86
|
+
self.internet_retriever = InternetRetrieverFactory.from_config(
|
|
87
|
+
config.internet_retriever, self.embedder
|
|
88
|
+
)
|
|
89
|
+
logger.info(
|
|
90
|
+
f"Internet retriever initialized with backend: {config.internet_retriever.backend}"
|
|
91
|
+
)
|
|
92
|
+
else:
|
|
93
|
+
logger.info("No internet retriever configured")
|
|
94
|
+
self.tokenizer = None
|
|
95
|
+
self.include_embedding = config.include_embedding or False
|
|
96
|
+
|
|
97
|
+
def add(
|
|
98
|
+
self,
|
|
99
|
+
memories: list[TextualMemoryItem | dict[str, Any]],
|
|
100
|
+
user_name: str | None = None,
|
|
101
|
+
**kwargs,
|
|
102
|
+
) -> list[str]:
|
|
103
|
+
"""Add memories.
|
|
104
|
+
Args:
|
|
105
|
+
memories: List of TextualMemoryItem objects or dictionaries to add.
|
|
106
|
+
user_name: optional user_name
|
|
107
|
+
"""
|
|
108
|
+
return self.memory_manager.add(memories, user_name=user_name, mode=self.mode)
|
|
109
|
+
|
|
110
|
+
def replace_working_memory(
|
|
111
|
+
self, memories: list[TextualMemoryItem], user_name: str | None = None
|
|
112
|
+
) -> None:
|
|
113
|
+
self.memory_manager.replace_working_memory(memories, user_name=user_name)
|
|
114
|
+
|
|
115
|
+
def get_working_memory(self, user_name: str | None = None) -> list[TextualMemoryItem]:
|
|
116
|
+
working_memories = self.graph_store.get_all_memory_items(
|
|
117
|
+
scope="WorkingMemory", user_name=user_name
|
|
118
|
+
)
|
|
119
|
+
items = [TextualMemoryItem.from_dict(record) for record in (working_memories)]
|
|
120
|
+
# Sort by updated_at in descending order
|
|
121
|
+
sorted_items = sorted(
|
|
122
|
+
items, key=lambda x: x.metadata.updated_at or datetime.min, reverse=True
|
|
123
|
+
)
|
|
124
|
+
return sorted_items
|
|
125
|
+
|
|
126
|
+
def get_current_memory_size(self, user_name: str | None = None) -> dict[str, int]:
|
|
127
|
+
"""
|
|
128
|
+
Get the current size of each memory type.
|
|
129
|
+
This delegates to the MemoryManager.
|
|
130
|
+
"""
|
|
131
|
+
return self.memory_manager.get_current_memory_size(user_name=user_name)
|
|
132
|
+
|
|
133
|
+
def get_searcher(
|
|
134
|
+
self, manual_close_internet: bool = False, moscube: bool = False, process_llm=None
|
|
135
|
+
):
|
|
136
|
+
searcher = Searcher(
|
|
137
|
+
self.dispatcher_llm,
|
|
138
|
+
self.graph_store,
|
|
139
|
+
self.embedder,
|
|
140
|
+
self.reranker,
|
|
141
|
+
bm25_retriever=self.bm25_retriever,
|
|
142
|
+
internet_retriever=None,
|
|
143
|
+
search_strategy=self.search_strategy,
|
|
144
|
+
manual_close_internet=manual_close_internet,
|
|
145
|
+
process_llm=process_llm,
|
|
146
|
+
tokenizer=self.tokenizer,
|
|
147
|
+
include_embedding=self.include_embedding,
|
|
148
|
+
)
|
|
149
|
+
return searcher
|
|
150
|
+
|
|
151
|
+
def search(
|
|
152
|
+
self,
|
|
153
|
+
query: str,
|
|
154
|
+
top_k: int,
|
|
155
|
+
info=None,
|
|
156
|
+
mode: str = "fast",
|
|
157
|
+
memory_type: str = "All",
|
|
158
|
+
manual_close_internet: bool = True,
|
|
159
|
+
search_priority: dict | None = None,
|
|
160
|
+
search_filter: dict | None = None,
|
|
161
|
+
user_name: str | None = None,
|
|
162
|
+
search_tool_memory: bool = False,
|
|
163
|
+
tool_mem_top_k: int = 6,
|
|
164
|
+
dedup: str | None = None,
|
|
165
|
+
**kwargs,
|
|
166
|
+
) -> list[TextualMemoryItem]:
|
|
167
|
+
"""Search for memories based on a query.
|
|
168
|
+
User query -> TaskGoalParser -> MemoryPathResolver ->
|
|
169
|
+
GraphMemoryRetriever -> MemoryReranker -> MemoryReasoner -> Final output
|
|
170
|
+
Args:
|
|
171
|
+
query (str): The query to search for.
|
|
172
|
+
top_k (int): The number of top results to return.
|
|
173
|
+
info (dict): Leave a record of memory consumption.
|
|
174
|
+
mode (str, optional): The mode of the search.
|
|
175
|
+
- 'fast': Uses a faster search process, sacrificing some precision for speed.
|
|
176
|
+
- 'fine': Uses a more detailed search process, invoking large models for higher precision, but slower performance.
|
|
177
|
+
memory_type (str): Type restriction for search.
|
|
178
|
+
['All', 'WorkingMemory', 'LongTermMemory', 'UserMemory']
|
|
179
|
+
manual_close_internet (bool): If True, the internet retriever will be closed by this search, it high priority than config.
|
|
180
|
+
search_filter (dict, optional): Optional metadata filters for search results.
|
|
181
|
+
- Keys correspond to memory metadata fields (e.g., "user_id", "session_id").
|
|
182
|
+
- Values are exact-match conditions.
|
|
183
|
+
Example: {"user_id": "123", "session_id": "abc"}
|
|
184
|
+
If None, no additional filtering is applied.
|
|
185
|
+
Returns:
|
|
186
|
+
list[TextualMemoryItem]: List of matching memories.
|
|
187
|
+
"""
|
|
188
|
+
searcher = Searcher(
|
|
189
|
+
self.dispatcher_llm,
|
|
190
|
+
self.graph_store,
|
|
191
|
+
self.embedder,
|
|
192
|
+
self.reranker,
|
|
193
|
+
bm25_retriever=self.bm25_retriever,
|
|
194
|
+
internet_retriever=self.internet_retriever,
|
|
195
|
+
search_strategy=self.search_strategy,
|
|
196
|
+
manual_close_internet=manual_close_internet,
|
|
197
|
+
tokenizer=self.tokenizer,
|
|
198
|
+
include_embedding=self.include_embedding,
|
|
199
|
+
)
|
|
200
|
+
return searcher.search(
|
|
201
|
+
query,
|
|
202
|
+
top_k,
|
|
203
|
+
info,
|
|
204
|
+
mode,
|
|
205
|
+
memory_type,
|
|
206
|
+
search_filter,
|
|
207
|
+
search_priority,
|
|
208
|
+
user_name=user_name,
|
|
209
|
+
search_tool_memory=search_tool_memory,
|
|
210
|
+
tool_mem_top_k=tool_mem_top_k,
|
|
211
|
+
dedup=dedup,
|
|
212
|
+
**kwargs,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
def get_relevant_subgraph(
|
|
216
|
+
self,
|
|
217
|
+
query: str,
|
|
218
|
+
top_k: int = 20,
|
|
219
|
+
depth: int = 2,
|
|
220
|
+
center_status: str = "activated",
|
|
221
|
+
user_name: str | None = None,
|
|
222
|
+
) -> dict[str, Any]:
|
|
223
|
+
"""
|
|
224
|
+
Find and merge the local neighborhood sub-graphs of the top-k
|
|
225
|
+
nodes most relevant to the query.
|
|
226
|
+
Process:
|
|
227
|
+
1. Embed the user query into a vector representation.
|
|
228
|
+
2. Use vector similarity search to find the top-k similar nodes.
|
|
229
|
+
3. For each similar node:
|
|
230
|
+
- Ensure its status matches `center_status` (e.g., 'active').
|
|
231
|
+
- Retrieve its local subgraph up to `depth` hops.
|
|
232
|
+
- Collect the center node, its neighbors, and connecting edges.
|
|
233
|
+
4. Merge all retrieved subgraphs into a single unified subgraph.
|
|
234
|
+
5. Return the merged subgraph structure.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
query (str): The user input or concept to find relevant memories for.
|
|
238
|
+
top_k (int, optional): How many top similar nodes to retrieve. Default is 5.
|
|
239
|
+
depth (int, optional): The neighborhood depth (number of hops). Default is 2.
|
|
240
|
+
center_status (str, optional): Status condition the center node must satisfy (e.g., 'active').
|
|
241
|
+
|
|
242
|
+
Returns:
|
|
243
|
+
dict[str, Any]: A subgraph dict with:
|
|
244
|
+
- 'core_id': ID of the top matching core node, or None if none found.
|
|
245
|
+
- 'nodes': List of unique nodes (core + neighbors) in the merged subgraph.
|
|
246
|
+
- 'edges': List of unique edges (as dicts with 'from', 'to', 'type') in the merged subgraph.
|
|
247
|
+
"""
|
|
248
|
+
# Step 1: Embed query
|
|
249
|
+
query_embedding = self.embedder.embed([query])[0]
|
|
250
|
+
|
|
251
|
+
# Step 2: Get top-1 similar node
|
|
252
|
+
similar_nodes = self.graph_store.search_by_embedding(
|
|
253
|
+
query_embedding, top_k=top_k, user_name=user_name
|
|
254
|
+
)
|
|
255
|
+
if not similar_nodes:
|
|
256
|
+
logger.info("No similar nodes found for query embedding.")
|
|
257
|
+
return {"core_id": None, "nodes": [], "edges": []}
|
|
258
|
+
|
|
259
|
+
# Step 3: Fetch neighborhood
|
|
260
|
+
all_nodes = {}
|
|
261
|
+
all_edges = set()
|
|
262
|
+
cores = []
|
|
263
|
+
|
|
264
|
+
for node in similar_nodes:
|
|
265
|
+
core_id = node["id"]
|
|
266
|
+
score = node["score"]
|
|
267
|
+
|
|
268
|
+
subgraph = self.graph_store.get_subgraph(
|
|
269
|
+
center_id=core_id, depth=depth, center_status=center_status, user_name=user_name
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
if subgraph is None or not subgraph["core_node"]:
|
|
273
|
+
node = self.graph_store.get_node(core_id, user_name=user_name)
|
|
274
|
+
subgraph["neighbors"] = [node]
|
|
275
|
+
|
|
276
|
+
core_node = subgraph["core_node"]
|
|
277
|
+
neighbors = subgraph["neighbors"]
|
|
278
|
+
edges = subgraph["edges"]
|
|
279
|
+
|
|
280
|
+
# Collect nodes
|
|
281
|
+
if core_node:
|
|
282
|
+
all_nodes[core_node["id"]] = core_node
|
|
283
|
+
for n in neighbors:
|
|
284
|
+
all_nodes[n["id"]] = n
|
|
285
|
+
|
|
286
|
+
# Collect edges
|
|
287
|
+
for e in edges:
|
|
288
|
+
all_edges.add((e["source"], e["target"], e["type"]))
|
|
289
|
+
|
|
290
|
+
cores.append(
|
|
291
|
+
{"id": core_id, "score": score, "core_node": core_node, "neighbors": neighbors}
|
|
292
|
+
)
|
|
293
|
+
|
|
294
|
+
top_core = cores[0] if cores else None
|
|
295
|
+
return {
|
|
296
|
+
"core_id": top_core["id"] if top_core else None,
|
|
297
|
+
"nodes": list(all_nodes.values()),
|
|
298
|
+
"edges": [{"source": f, "target": t, "type": ty} for (f, t, ty) in all_edges],
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
def extract(self, messages: MessageList) -> list[TextualMemoryItem]:
|
|
302
|
+
raise NotImplementedError
|
|
303
|
+
|
|
304
|
+
def update(self, memory_id: str, new_memory: TextualMemoryItem | dict[str, Any]) -> None:
|
|
305
|
+
raise NotImplementedError
|
|
306
|
+
|
|
307
|
+
def get(self, memory_id: str, user_name: str | None = None) -> TextualMemoryItem:
|
|
308
|
+
"""Get a memory by its ID."""
|
|
309
|
+
result = self.graph_store.get_node(memory_id, user_name=user_name)
|
|
310
|
+
if result is None:
|
|
311
|
+
raise ValueError(f"Memory with ID {memory_id} not found")
|
|
312
|
+
metadata_dict = result.get("metadata", {})
|
|
313
|
+
return TextualMemoryItem(
|
|
314
|
+
id=result["id"],
|
|
315
|
+
memory=result["memory"],
|
|
316
|
+
metadata=TreeNodeTextualMemoryMetadata(**metadata_dict),
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
def get_by_ids(
|
|
320
|
+
self, memory_ids: list[str], user_name: str | None = None
|
|
321
|
+
) -> list[TextualMemoryItem]:
|
|
322
|
+
raise NotImplementedError
|
|
323
|
+
|
|
324
|
+
def get_all(
|
|
325
|
+
self,
|
|
326
|
+
user_name: str,
|
|
327
|
+
user_id: str | None = None,
|
|
328
|
+
page: int | None = None,
|
|
329
|
+
page_size: int | None = None,
|
|
330
|
+
filter: dict | None = None,
|
|
331
|
+
) -> dict:
|
|
332
|
+
"""Get all memories.
|
|
333
|
+
Returns:
|
|
334
|
+
list[TextualMemoryItem]: List of all memories.
|
|
335
|
+
"""
|
|
336
|
+
graph_output = self.graph_store.export_graph(
|
|
337
|
+
user_name=user_name, user_id=user_id, page=page, page_size=page_size, filter=filter
|
|
338
|
+
)
|
|
339
|
+
return graph_output
|
|
340
|
+
|
|
341
|
+
def delete(self, memory_ids: list[str], user_name: str | None = None) -> None:
|
|
342
|
+
"""Hard delete: permanently remove nodes and their edges from the graph."""
|
|
343
|
+
if not memory_ids:
|
|
344
|
+
return
|
|
345
|
+
for mid in memory_ids:
|
|
346
|
+
try:
|
|
347
|
+
self.graph_store.delete_node(mid, user_name=user_name)
|
|
348
|
+
except Exception as e:
|
|
349
|
+
logger.warning(f"TreeTextMemory.delete_hard: failed to delete {mid}: {e}")
|
|
350
|
+
|
|
351
|
+
def delete_by_memory_ids(self, memory_ids: list[str]) -> None:
|
|
352
|
+
"""Delete memories by memory_ids."""
|
|
353
|
+
try:
|
|
354
|
+
self.graph_store.delete_node_by_prams(memory_ids=memory_ids)
|
|
355
|
+
except Exception as e:
|
|
356
|
+
logger.error(f"An error occurred while deleting memories by memory_ids: {e}")
|
|
357
|
+
|
|
358
|
+
def delete_all(self) -> None:
|
|
359
|
+
"""Delete all memories and their relationships from the graph store."""
|
|
360
|
+
try:
|
|
361
|
+
self.graph_store.clear()
|
|
362
|
+
logger.info("All memories and edges have been deleted from the graph.")
|
|
363
|
+
except Exception as e:
|
|
364
|
+
logger.error(f"An error occurred while deleting all memories: {e}")
|
|
365
|
+
raise
|
|
366
|
+
|
|
367
|
+
def delete_by_filter(
|
|
368
|
+
self,
|
|
369
|
+
writable_cube_ids: list[str] | None = None,
|
|
370
|
+
file_ids: list[str] | None = None,
|
|
371
|
+
filter: dict | None = None,
|
|
372
|
+
) -> None:
|
|
373
|
+
"""Delete memories by filter."""
|
|
374
|
+
self.graph_store.delete_node_by_prams(
|
|
375
|
+
writable_cube_ids=writable_cube_ids, file_ids=file_ids, filter=filter
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
def load(self, dir: str) -> None:
|
|
379
|
+
try:
|
|
380
|
+
memory_file = os.path.join(dir, self.config.memory_filename)
|
|
381
|
+
|
|
382
|
+
if not os.path.exists(memory_file):
|
|
383
|
+
logger.warning(f"Memory file not found: {memory_file}")
|
|
384
|
+
return
|
|
385
|
+
|
|
386
|
+
with open(memory_file, encoding="utf-8") as f:
|
|
387
|
+
memories = json.load(f)
|
|
388
|
+
|
|
389
|
+
self.graph_store.import_graph(memories)
|
|
390
|
+
logger.info(f"Loaded {len(memories)} memories from {memory_file}")
|
|
391
|
+
|
|
392
|
+
except FileNotFoundError:
|
|
393
|
+
logger.error(f"Memory file not found in directory: {dir}")
|
|
394
|
+
except json.JSONDecodeError as e:
|
|
395
|
+
logger.error(f"Error decoding JSON from memory file: {e}")
|
|
396
|
+
except Exception as e:
|
|
397
|
+
logger.error(f"An error occurred while loading memories: {e}")
|
|
398
|
+
|
|
399
|
+
def dump(self, dir: str, include_embedding: bool = False) -> None:
|
|
400
|
+
"""Dump memories to os.path.join(dir, self.config.memory_filename)"""
|
|
401
|
+
try:
|
|
402
|
+
json_memories = self.graph_store.export_graph(include_embedding=include_embedding)
|
|
403
|
+
|
|
404
|
+
os.makedirs(dir, exist_ok=True)
|
|
405
|
+
memory_file = os.path.join(dir, self.config.memory_filename)
|
|
406
|
+
with open(memory_file, "w", encoding="utf-8") as f:
|
|
407
|
+
json.dump(json_memories, f, indent=4, ensure_ascii=False)
|
|
408
|
+
|
|
409
|
+
logger.info(f"Dumped {len(json_memories.get('nodes'))} memories to {memory_file}")
|
|
410
|
+
|
|
411
|
+
except Exception as e:
|
|
412
|
+
logger.error(f"An error occurred while dumping memories: {e}")
|
|
413
|
+
raise
|
|
414
|
+
|
|
415
|
+
def drop(self, keep_last_n: int = 30) -> None:
|
|
416
|
+
"""
|
|
417
|
+
Export all memory data to a versioned backup dir and drop the Neo4j database.
|
|
418
|
+
Only the latest `keep_last_n` backups will be retained.
|
|
419
|
+
"""
|
|
420
|
+
try:
|
|
421
|
+
backup_root = Path(tempfile.gettempdir()) / "memos_backups"
|
|
422
|
+
backup_root.mkdir(parents=True, exist_ok=True)
|
|
423
|
+
|
|
424
|
+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
425
|
+
backup_dir = backup_root / f"memos_backup_{timestamp}"
|
|
426
|
+
backup_dir.mkdir()
|
|
427
|
+
|
|
428
|
+
logger.info(f"Exporting memory to backup dir: {backup_dir}")
|
|
429
|
+
self.dump(str(backup_dir))
|
|
430
|
+
|
|
431
|
+
# Clean up old backups
|
|
432
|
+
self._cleanup_old_backups(backup_root, keep_last_n)
|
|
433
|
+
|
|
434
|
+
self.graph_store.drop_database()
|
|
435
|
+
logger.info(f"Database '{self.graph_store.db_name}' dropped after backup.")
|
|
436
|
+
|
|
437
|
+
except Exception as e:
|
|
438
|
+
logger.error(f"Error in drop(): {e}")
|
|
439
|
+
raise
|
|
440
|
+
|
|
441
|
+
@staticmethod
|
|
442
|
+
def _cleanup_old_backups(root_dir: Path, keep_last_n: int) -> None:
|
|
443
|
+
"""
|
|
444
|
+
Keep only the latest `keep_last_n` backup directories under `root_dir`.
|
|
445
|
+
Older ones will be deleted.
|
|
446
|
+
"""
|
|
447
|
+
backups = sorted(
|
|
448
|
+
[d for d in root_dir.iterdir() if d.is_dir() and d.name.startswith("memos_backup_")],
|
|
449
|
+
key=lambda p: p.name, # name includes timestamp
|
|
450
|
+
reverse=True,
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
to_delete = backups[keep_last_n:]
|
|
454
|
+
for old_dir in to_delete:
|
|
455
|
+
try:
|
|
456
|
+
shutil.rmtree(old_dir)
|
|
457
|
+
logger.info(f"Deleted old backup directory: {old_dir}")
|
|
458
|
+
except Exception as e:
|
|
459
|
+
logger.warning(f"Failed to delete backup {old_dir}: {e}")
|
|
File without changes
|
|
File without changes
|