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,874 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import traceback
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from datetime import datetime
|
|
9
|
+
from typing import TYPE_CHECKING, Any
|
|
10
|
+
|
|
11
|
+
from memos.api.handlers.formatters_handler import (
|
|
12
|
+
format_memory_item,
|
|
13
|
+
post_process_pref_mem,
|
|
14
|
+
post_process_textual_mem,
|
|
15
|
+
)
|
|
16
|
+
from memos.context.context import ContextThreadPoolExecutor
|
|
17
|
+
from memos.log import get_logger
|
|
18
|
+
from memos.mem_reader.utils import parse_keep_filter_response
|
|
19
|
+
from memos.mem_scheduler.schemas.message_schemas import ScheduleMessageItem
|
|
20
|
+
from memos.mem_scheduler.schemas.task_schemas import (
|
|
21
|
+
ADD_TASK_LABEL,
|
|
22
|
+
MEM_FEEDBACK_TASK_LABEL,
|
|
23
|
+
MEM_READ_TASK_LABEL,
|
|
24
|
+
PREF_ADD_TASK_LABEL,
|
|
25
|
+
)
|
|
26
|
+
from memos.multi_mem_cube.views import MemCubeView
|
|
27
|
+
from memos.templates.mem_reader_prompts import PROMPT_MAPPING
|
|
28
|
+
from memos.types.general_types import (
|
|
29
|
+
FINE_STRATEGY,
|
|
30
|
+
FineStrategy,
|
|
31
|
+
MOSSearchResult,
|
|
32
|
+
SearchMode,
|
|
33
|
+
UserContext,
|
|
34
|
+
)
|
|
35
|
+
from memos.utils import timed
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
logger = get_logger(__name__)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
if TYPE_CHECKING:
|
|
42
|
+
from memos.api.product_models import APIADDRequest, APIFeedbackRequest, APISearchRequest
|
|
43
|
+
from memos.mem_cube.navie import NaiveMemCube
|
|
44
|
+
from memos.mem_reader.simple_struct import SimpleStructMemReader
|
|
45
|
+
from memos.mem_scheduler.optimized_scheduler import OptimizedScheduler
|
|
46
|
+
from memos.memories.textual.item import TextualMemoryItem
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass
|
|
50
|
+
class SingleCubeView(MemCubeView):
|
|
51
|
+
cube_id: str
|
|
52
|
+
naive_mem_cube: NaiveMemCube
|
|
53
|
+
mem_reader: SimpleStructMemReader
|
|
54
|
+
mem_scheduler: OptimizedScheduler
|
|
55
|
+
logger: Any
|
|
56
|
+
searcher: Any
|
|
57
|
+
feedback_server: Any | None = None
|
|
58
|
+
deepsearch_agent: Any | None = None
|
|
59
|
+
|
|
60
|
+
@timed
|
|
61
|
+
def add_memories(self, add_req: APIADDRequest) -> list[dict[str, Any]]:
|
|
62
|
+
"""
|
|
63
|
+
This is basically your current handle_add_memories logic,
|
|
64
|
+
but scoped to a single cube_id.
|
|
65
|
+
"""
|
|
66
|
+
sync_mode = add_req.async_mode or self._get_sync_mode()
|
|
67
|
+
self.logger.info(
|
|
68
|
+
f"[DIAGNOSTIC] single_cube.add_memories called for cube_id: {self.cube_id}. sync_mode: {sync_mode}. Request: {add_req.model_dump_json(indent=2)}"
|
|
69
|
+
)
|
|
70
|
+
user_context = UserContext(
|
|
71
|
+
user_id=add_req.user_id,
|
|
72
|
+
mem_cube_id=self.cube_id,
|
|
73
|
+
session_id=add_req.session_id or "default_session",
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
target_session_id = add_req.session_id or "default_session"
|
|
77
|
+
sync_mode = add_req.async_mode or self._get_sync_mode()
|
|
78
|
+
|
|
79
|
+
self.logger.info(
|
|
80
|
+
f"[SingleCubeView] cube={self.cube_id} "
|
|
81
|
+
f"Processing add with mode={sync_mode}, session={target_session_id}"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
with ContextThreadPoolExecutor(max_workers=2) as executor:
|
|
85
|
+
text_future = executor.submit(self._process_text_mem, add_req, user_context, sync_mode)
|
|
86
|
+
pref_future = executor.submit(self._process_pref_mem, add_req, user_context, sync_mode)
|
|
87
|
+
|
|
88
|
+
text_results = text_future.result()
|
|
89
|
+
pref_results = pref_future.result()
|
|
90
|
+
|
|
91
|
+
self.logger.info(
|
|
92
|
+
f"[SingleCubeView] cube={self.cube_id} text_results={len(text_results)}, "
|
|
93
|
+
f"pref_results={len(pref_results)}"
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
for item in text_results:
|
|
97
|
+
item["cube_id"] = self.cube_id
|
|
98
|
+
for item in pref_results:
|
|
99
|
+
item["cube_id"] = self.cube_id
|
|
100
|
+
|
|
101
|
+
all_memories = text_results + pref_results
|
|
102
|
+
|
|
103
|
+
# TODO: search existing memories and compare
|
|
104
|
+
|
|
105
|
+
return all_memories
|
|
106
|
+
|
|
107
|
+
@timed
|
|
108
|
+
def search_memories(self, search_req: APISearchRequest) -> dict[str, Any]:
|
|
109
|
+
# Create UserContext object
|
|
110
|
+
user_context = UserContext(
|
|
111
|
+
user_id=search_req.user_id,
|
|
112
|
+
mem_cube_id=self.cube_id,
|
|
113
|
+
session_id=search_req.session_id or "default_session",
|
|
114
|
+
)
|
|
115
|
+
self.logger.info(f"Search Req is: {search_req}")
|
|
116
|
+
|
|
117
|
+
memories_result: MOSSearchResult = {
|
|
118
|
+
"text_mem": [],
|
|
119
|
+
"act_mem": [],
|
|
120
|
+
"para_mem": [],
|
|
121
|
+
"pref_mem": [],
|
|
122
|
+
"pref_note": "",
|
|
123
|
+
"tool_mem": [],
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
# Determine search mode
|
|
127
|
+
search_mode = self._get_search_mode(search_req.mode)
|
|
128
|
+
|
|
129
|
+
# Execute search in parallel for text and preference memories
|
|
130
|
+
with ContextThreadPoolExecutor(max_workers=2) as executor:
|
|
131
|
+
text_future = executor.submit(self._search_text, search_req, user_context, search_mode)
|
|
132
|
+
pref_future = executor.submit(self._search_pref, search_req, user_context)
|
|
133
|
+
|
|
134
|
+
text_formatted_memories = text_future.result()
|
|
135
|
+
pref_formatted_memories = pref_future.result()
|
|
136
|
+
|
|
137
|
+
# Build result
|
|
138
|
+
memories_result = post_process_textual_mem(
|
|
139
|
+
memories_result,
|
|
140
|
+
text_formatted_memories,
|
|
141
|
+
self.cube_id,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
memories_result = post_process_pref_mem(
|
|
145
|
+
memories_result,
|
|
146
|
+
pref_formatted_memories,
|
|
147
|
+
self.cube_id,
|
|
148
|
+
search_req.include_preference,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
self.logger.info(f"Search memories result: {memories_result}")
|
|
152
|
+
self.logger.info(f"Search {len(memories_result)} memories.")
|
|
153
|
+
return memories_result
|
|
154
|
+
|
|
155
|
+
@timed
|
|
156
|
+
def feedback_memories(self, feedback_req: APIFeedbackRequest) -> dict[str, Any]:
|
|
157
|
+
target_session_id = feedback_req.session_id or "default_session"
|
|
158
|
+
if feedback_req.async_mode == "async":
|
|
159
|
+
try:
|
|
160
|
+
feedback_req_str = json.dumps(feedback_req.model_dump())
|
|
161
|
+
message_item_feedback = ScheduleMessageItem(
|
|
162
|
+
user_id=feedback_req.user_id,
|
|
163
|
+
task_id=feedback_req.task_id,
|
|
164
|
+
session_id=target_session_id,
|
|
165
|
+
mem_cube_id=self.cube_id,
|
|
166
|
+
mem_cube=self.naive_mem_cube,
|
|
167
|
+
label=MEM_FEEDBACK_TASK_LABEL,
|
|
168
|
+
content=feedback_req_str,
|
|
169
|
+
timestamp=datetime.utcnow(),
|
|
170
|
+
)
|
|
171
|
+
# Use scheduler submission to ensure tracking and metrics
|
|
172
|
+
self.mem_scheduler.submit_messages(messages=[message_item_feedback])
|
|
173
|
+
self.logger.info(f"[SingleCubeView] cube={self.cube_id} Submitted FEEDBACK async")
|
|
174
|
+
except Exception as e:
|
|
175
|
+
self.logger.error(
|
|
176
|
+
f"[SingleCubeView] cube={self.cube_id} Failed to submit FEEDBACK: {e}",
|
|
177
|
+
exc_info=True,
|
|
178
|
+
)
|
|
179
|
+
return []
|
|
180
|
+
else:
|
|
181
|
+
feedback_result = self.feedback_server.process_feedback(
|
|
182
|
+
user_id=feedback_req.user_id,
|
|
183
|
+
user_name=self.cube_id,
|
|
184
|
+
session_id=feedback_req.session_id,
|
|
185
|
+
chat_history=feedback_req.history,
|
|
186
|
+
retrieved_memory_ids=feedback_req.retrieved_memory_ids,
|
|
187
|
+
feedback_content=feedback_req.feedback_content,
|
|
188
|
+
feedback_time=feedback_req.feedback_time,
|
|
189
|
+
async_mode=feedback_req.async_mode,
|
|
190
|
+
corrected_answer=feedback_req.corrected_answer,
|
|
191
|
+
task_id=feedback_req.task_id,
|
|
192
|
+
info=feedback_req.info,
|
|
193
|
+
)
|
|
194
|
+
self.logger.info(f"[Feedback memories result:] {feedback_result}")
|
|
195
|
+
return feedback_result
|
|
196
|
+
|
|
197
|
+
def _get_search_mode(self, mode: str) -> str:
|
|
198
|
+
"""
|
|
199
|
+
Get search mode with environment variable fallback.
|
|
200
|
+
|
|
201
|
+
Args:
|
|
202
|
+
mode: Requested search mode
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
Search mode string
|
|
206
|
+
"""
|
|
207
|
+
return mode
|
|
208
|
+
|
|
209
|
+
@timed
|
|
210
|
+
def _search_text(
|
|
211
|
+
self,
|
|
212
|
+
search_req: APISearchRequest,
|
|
213
|
+
user_context: UserContext,
|
|
214
|
+
search_mode: str,
|
|
215
|
+
) -> list[dict[str, Any]]:
|
|
216
|
+
"""
|
|
217
|
+
Search text memories based on mode.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
search_req: Search request
|
|
221
|
+
user_context: User context
|
|
222
|
+
search_mode: Search mode (fast, fine, or mixture)
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
List of formatted memory items
|
|
226
|
+
"""
|
|
227
|
+
try:
|
|
228
|
+
if search_mode == SearchMode.FAST:
|
|
229
|
+
text_memories = self._fast_search(search_req, user_context)
|
|
230
|
+
elif search_mode == SearchMode.FINE:
|
|
231
|
+
text_memories = self._fine_search(search_req, user_context)
|
|
232
|
+
elif search_mode == SearchMode.MIXTURE:
|
|
233
|
+
text_memories = self._mix_search(search_req, user_context)
|
|
234
|
+
else:
|
|
235
|
+
self.logger.error(f"Unsupported search mode: {search_mode}")
|
|
236
|
+
return []
|
|
237
|
+
return text_memories
|
|
238
|
+
|
|
239
|
+
except Exception as e:
|
|
240
|
+
self.logger.error("Error in search_text: %s; traceback: %s", e, traceback.format_exc())
|
|
241
|
+
return []
|
|
242
|
+
|
|
243
|
+
def _deep_search(
|
|
244
|
+
self,
|
|
245
|
+
search_req: APISearchRequest,
|
|
246
|
+
user_context: UserContext,
|
|
247
|
+
) -> list:
|
|
248
|
+
target_session_id = search_req.session_id or "default_session"
|
|
249
|
+
search_filter = {"session_id": search_req.session_id} if search_req.session_id else None
|
|
250
|
+
|
|
251
|
+
info = {
|
|
252
|
+
"user_id": search_req.user_id,
|
|
253
|
+
"session_id": target_session_id,
|
|
254
|
+
"chat_history": search_req.chat_history,
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
enhanced_memories = self.searcher.deep_search(
|
|
258
|
+
query=search_req.query,
|
|
259
|
+
user_name=user_context.mem_cube_id,
|
|
260
|
+
top_k=search_req.top_k,
|
|
261
|
+
mode=SearchMode.FINE,
|
|
262
|
+
manual_close_internet=not search_req.internet_search,
|
|
263
|
+
moscube=search_req.moscube,
|
|
264
|
+
search_filter=search_filter,
|
|
265
|
+
info=info,
|
|
266
|
+
)
|
|
267
|
+
formatted_memories = [
|
|
268
|
+
format_memory_item(data, include_embedding=search_req.dedup == "sim")
|
|
269
|
+
for data in enhanced_memories
|
|
270
|
+
]
|
|
271
|
+
return formatted_memories
|
|
272
|
+
|
|
273
|
+
def _agentic_search(
|
|
274
|
+
self, search_req: APISearchRequest, user_context: UserContext, max_thinking_depth: int
|
|
275
|
+
) -> list:
|
|
276
|
+
deepsearch_results = self.deepsearch_agent.run(
|
|
277
|
+
search_req.query, user_id=user_context.mem_cube_id
|
|
278
|
+
)
|
|
279
|
+
formatted_memories = [
|
|
280
|
+
format_memory_item(data, include_embedding=search_req.dedup == "sim")
|
|
281
|
+
for data in deepsearch_results
|
|
282
|
+
]
|
|
283
|
+
return formatted_memories
|
|
284
|
+
|
|
285
|
+
def _fine_search(
|
|
286
|
+
self,
|
|
287
|
+
search_req: APISearchRequest,
|
|
288
|
+
user_context: UserContext,
|
|
289
|
+
) -> list:
|
|
290
|
+
"""
|
|
291
|
+
Fine-grained search with query enhancement.
|
|
292
|
+
|
|
293
|
+
Args:
|
|
294
|
+
search_req: Search request
|
|
295
|
+
user_context: User context
|
|
296
|
+
|
|
297
|
+
Returns:
|
|
298
|
+
List of enhanced search results
|
|
299
|
+
"""
|
|
300
|
+
# TODO: support tool memory search in future
|
|
301
|
+
|
|
302
|
+
logger.info(f"Fine strategy: {FINE_STRATEGY}")
|
|
303
|
+
if FINE_STRATEGY == FineStrategy.DEEP_SEARCH:
|
|
304
|
+
return self._deep_search(search_req=search_req, user_context=user_context)
|
|
305
|
+
elif FINE_STRATEGY == FineStrategy.AGENTIC_SEARCH:
|
|
306
|
+
return self._agentic_search(search_req=search_req, user_context=user_context)
|
|
307
|
+
|
|
308
|
+
target_session_id = search_req.session_id or "default_session"
|
|
309
|
+
search_priority = {"session_id": search_req.session_id} if search_req.session_id else None
|
|
310
|
+
search_filter = search_req.filter
|
|
311
|
+
|
|
312
|
+
info = {
|
|
313
|
+
"user_id": search_req.user_id,
|
|
314
|
+
"session_id": target_session_id,
|
|
315
|
+
"chat_history": search_req.chat_history,
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
# Fine retrieve
|
|
319
|
+
raw_retrieved_memories = self.searcher.retrieve(
|
|
320
|
+
query=search_req.query,
|
|
321
|
+
user_name=user_context.mem_cube_id,
|
|
322
|
+
top_k=search_req.top_k,
|
|
323
|
+
mode=SearchMode.FINE,
|
|
324
|
+
manual_close_internet=not search_req.internet_search,
|
|
325
|
+
moscube=search_req.moscube,
|
|
326
|
+
search_filter=search_filter,
|
|
327
|
+
search_priority=search_priority,
|
|
328
|
+
info=info,
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
# Post retrieve
|
|
332
|
+
raw_memories = self.searcher.post_retrieve(
|
|
333
|
+
retrieved_results=raw_retrieved_memories,
|
|
334
|
+
top_k=search_req.top_k,
|
|
335
|
+
user_name=user_context.mem_cube_id,
|
|
336
|
+
info=info,
|
|
337
|
+
dedup=search_req.dedup,
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
# Enhance with query
|
|
341
|
+
enhanced_memories, _ = self.mem_scheduler.retriever.enhance_memories_with_query(
|
|
342
|
+
query_history=[search_req.query],
|
|
343
|
+
memories=raw_memories,
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
if len(enhanced_memories) < len(raw_memories):
|
|
347
|
+
logger.info(
|
|
348
|
+
f"Enhanced memories ({len(enhanced_memories)}) are less than raw memories ({len(raw_memories)}). Recalling for more."
|
|
349
|
+
)
|
|
350
|
+
missing_info_hint, trigger = self.mem_scheduler.retriever.recall_for_missing_memories(
|
|
351
|
+
query=search_req.query,
|
|
352
|
+
memories=[mem.memory for mem in enhanced_memories],
|
|
353
|
+
)
|
|
354
|
+
retrieval_size = len(raw_memories) - len(enhanced_memories)
|
|
355
|
+
logger.info(f"Retrieval size: {retrieval_size}")
|
|
356
|
+
if trigger:
|
|
357
|
+
logger.info(f"Triggering additional search with hint: {missing_info_hint}")
|
|
358
|
+
additional_memories = self.searcher.search(
|
|
359
|
+
query=missing_info_hint,
|
|
360
|
+
user_name=user_context.mem_cube_id,
|
|
361
|
+
top_k=retrieval_size,
|
|
362
|
+
mode=SearchMode.FAST,
|
|
363
|
+
memory_type="All",
|
|
364
|
+
search_priority=search_priority,
|
|
365
|
+
search_filter=search_filter,
|
|
366
|
+
info=info,
|
|
367
|
+
)
|
|
368
|
+
else:
|
|
369
|
+
logger.info("Not triggering additional search, using fast memories.")
|
|
370
|
+
additional_memories = raw_memories[:retrieval_size]
|
|
371
|
+
|
|
372
|
+
enhanced_memories += additional_memories
|
|
373
|
+
logger.info(
|
|
374
|
+
f"Added {len(additional_memories)} more memories. Total enhanced memories: {len(enhanced_memories)}"
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
def _dedup_by_content(memories: list) -> list:
|
|
378
|
+
seen = set()
|
|
379
|
+
unique_memories = []
|
|
380
|
+
for mem in memories:
|
|
381
|
+
key = " ".join(mem.memory.split())
|
|
382
|
+
if key in seen:
|
|
383
|
+
continue
|
|
384
|
+
seen.add(key)
|
|
385
|
+
unique_memories.append(mem)
|
|
386
|
+
return unique_memories
|
|
387
|
+
|
|
388
|
+
deduped_memories = (
|
|
389
|
+
enhanced_memories if search_req.dedup == "no" else _dedup_by_content(enhanced_memories)
|
|
390
|
+
)
|
|
391
|
+
formatted_memories = [
|
|
392
|
+
format_memory_item(data, include_embedding=search_req.dedup == "sim")
|
|
393
|
+
for data in deduped_memories
|
|
394
|
+
]
|
|
395
|
+
|
|
396
|
+
logger.info(f"Found {len(formatted_memories)} memories for user {search_req.user_id}")
|
|
397
|
+
|
|
398
|
+
return formatted_memories
|
|
399
|
+
|
|
400
|
+
@timed
|
|
401
|
+
def _search_pref(
|
|
402
|
+
self,
|
|
403
|
+
search_req: APISearchRequest,
|
|
404
|
+
user_context: UserContext,
|
|
405
|
+
) -> list[dict[str, Any]]:
|
|
406
|
+
"""
|
|
407
|
+
Search preference memories.
|
|
408
|
+
|
|
409
|
+
Args:
|
|
410
|
+
search_req: Search request
|
|
411
|
+
user_context: User context
|
|
412
|
+
|
|
413
|
+
Returns:
|
|
414
|
+
List of formatted preference memory items
|
|
415
|
+
TODO: ADD CUBE ID IN PREFERENCE MEMORY
|
|
416
|
+
"""
|
|
417
|
+
if os.getenv("ENABLE_PREFERENCE_MEMORY", "false").lower() != "true":
|
|
418
|
+
return []
|
|
419
|
+
if not search_req.include_preference:
|
|
420
|
+
return []
|
|
421
|
+
|
|
422
|
+
logger.info(f"search_req.filter for preference memory: {search_req.filter}")
|
|
423
|
+
logger.info(f"type of pref_mem: {type(self.naive_mem_cube.pref_mem)}")
|
|
424
|
+
try:
|
|
425
|
+
results = self.naive_mem_cube.pref_mem.search(
|
|
426
|
+
query=search_req.query,
|
|
427
|
+
top_k=search_req.pref_top_k,
|
|
428
|
+
info={
|
|
429
|
+
"user_id": search_req.user_id,
|
|
430
|
+
"mem_cube_id": user_context.mem_cube_id,
|
|
431
|
+
"session_id": search_req.session_id,
|
|
432
|
+
"chat_history": search_req.chat_history,
|
|
433
|
+
},
|
|
434
|
+
search_filter=search_req.filter,
|
|
435
|
+
)
|
|
436
|
+
return [format_memory_item(data) for data in results]
|
|
437
|
+
except Exception as e:
|
|
438
|
+
self.logger.error("Error in _search_pref: %s; traceback: %s", e, traceback.format_exc())
|
|
439
|
+
return []
|
|
440
|
+
|
|
441
|
+
def _fast_search(
|
|
442
|
+
self,
|
|
443
|
+
search_req: APISearchRequest,
|
|
444
|
+
user_context: UserContext,
|
|
445
|
+
) -> list:
|
|
446
|
+
"""
|
|
447
|
+
Fast search using vector database.
|
|
448
|
+
|
|
449
|
+
Args:
|
|
450
|
+
search_req: Search request
|
|
451
|
+
user_context: User context
|
|
452
|
+
|
|
453
|
+
Returns:
|
|
454
|
+
List of search results
|
|
455
|
+
"""
|
|
456
|
+
target_session_id = search_req.session_id or "default_session"
|
|
457
|
+
search_priority = {"session_id": search_req.session_id} if search_req.session_id else None
|
|
458
|
+
search_filter = search_req.filter or None
|
|
459
|
+
plugin = bool(search_req.source is not None and search_req.source == "plugin")
|
|
460
|
+
|
|
461
|
+
search_results = self.naive_mem_cube.text_mem.search(
|
|
462
|
+
query=search_req.query,
|
|
463
|
+
user_name=user_context.mem_cube_id,
|
|
464
|
+
top_k=search_req.top_k,
|
|
465
|
+
mode=SearchMode.FAST,
|
|
466
|
+
manual_close_internet=not search_req.internet_search,
|
|
467
|
+
memory_type=search_req.search_memory_type,
|
|
468
|
+
search_filter=search_filter,
|
|
469
|
+
search_priority=search_priority,
|
|
470
|
+
info={
|
|
471
|
+
"user_id": search_req.user_id,
|
|
472
|
+
"session_id": target_session_id,
|
|
473
|
+
"chat_history": search_req.chat_history,
|
|
474
|
+
},
|
|
475
|
+
plugin=plugin,
|
|
476
|
+
search_tool_memory=search_req.search_tool_memory,
|
|
477
|
+
tool_mem_top_k=search_req.tool_mem_top_k,
|
|
478
|
+
dedup=search_req.dedup,
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
formatted_memories = [
|
|
482
|
+
format_memory_item(data, include_embedding=search_req.dedup == "sim")
|
|
483
|
+
for data in search_results
|
|
484
|
+
]
|
|
485
|
+
|
|
486
|
+
return formatted_memories
|
|
487
|
+
|
|
488
|
+
def _mix_search(
|
|
489
|
+
self,
|
|
490
|
+
search_req: APISearchRequest,
|
|
491
|
+
user_context: UserContext,
|
|
492
|
+
) -> list:
|
|
493
|
+
"""
|
|
494
|
+
Mix search combining fast and fine-grained approaches.
|
|
495
|
+
|
|
496
|
+
Args:
|
|
497
|
+
search_req: Search request
|
|
498
|
+
user_context: User context
|
|
499
|
+
|
|
500
|
+
Returns:
|
|
501
|
+
List of formatted search results
|
|
502
|
+
"""
|
|
503
|
+
return self.mem_scheduler.mix_search_memories(
|
|
504
|
+
search_req=search_req,
|
|
505
|
+
user_context=user_context,
|
|
506
|
+
)
|
|
507
|
+
|
|
508
|
+
def _get_sync_mode(self) -> str:
|
|
509
|
+
"""
|
|
510
|
+
Get synchronization mode from memory cube.
|
|
511
|
+
|
|
512
|
+
Returns:
|
|
513
|
+
Sync mode string ("sync" or "async")
|
|
514
|
+
"""
|
|
515
|
+
try:
|
|
516
|
+
return getattr(self.naive_mem_cube.text_mem, "mode", "sync")
|
|
517
|
+
except Exception:
|
|
518
|
+
return "sync"
|
|
519
|
+
|
|
520
|
+
def _schedule_memory_tasks(
|
|
521
|
+
self,
|
|
522
|
+
add_req: APIADDRequest,
|
|
523
|
+
user_context: UserContext,
|
|
524
|
+
mem_ids: list[str],
|
|
525
|
+
sync_mode: str,
|
|
526
|
+
) -> None:
|
|
527
|
+
"""
|
|
528
|
+
Schedule memory processing tasks based on sync mode.
|
|
529
|
+
|
|
530
|
+
Args:
|
|
531
|
+
add_req: Add memory request
|
|
532
|
+
user_context: User context
|
|
533
|
+
mem_ids: List of memory IDs
|
|
534
|
+
sync_mode: Synchronization mode
|
|
535
|
+
"""
|
|
536
|
+
target_session_id = add_req.session_id or "default_session"
|
|
537
|
+
|
|
538
|
+
if sync_mode == "async":
|
|
539
|
+
# Async mode: submit MEM_READ_LABEL task
|
|
540
|
+
try:
|
|
541
|
+
message_item_read = ScheduleMessageItem(
|
|
542
|
+
user_id=add_req.user_id,
|
|
543
|
+
task_id=add_req.task_id,
|
|
544
|
+
session_id=target_session_id,
|
|
545
|
+
mem_cube_id=self.cube_id,
|
|
546
|
+
mem_cube=self.naive_mem_cube,
|
|
547
|
+
label=MEM_READ_TASK_LABEL,
|
|
548
|
+
content=json.dumps(mem_ids),
|
|
549
|
+
timestamp=datetime.utcnow(),
|
|
550
|
+
user_name=self.cube_id,
|
|
551
|
+
info=add_req.info,
|
|
552
|
+
)
|
|
553
|
+
self.mem_scheduler.submit_messages(messages=[message_item_read])
|
|
554
|
+
self.logger.info(
|
|
555
|
+
f"[SingleCubeView] cube={self.cube_id} Submitted async MEM_READ: {json.dumps(mem_ids)}"
|
|
556
|
+
)
|
|
557
|
+
except Exception as e:
|
|
558
|
+
self.logger.error(
|
|
559
|
+
f"[SingleCubeView] cube={self.cube_id} Failed to submit async memory tasks: {e}",
|
|
560
|
+
exc_info=True,
|
|
561
|
+
)
|
|
562
|
+
else:
|
|
563
|
+
message_item_add = ScheduleMessageItem(
|
|
564
|
+
user_id=add_req.user_id,
|
|
565
|
+
task_id=add_req.task_id,
|
|
566
|
+
session_id=target_session_id,
|
|
567
|
+
mem_cube_id=self.cube_id,
|
|
568
|
+
mem_cube=self.naive_mem_cube,
|
|
569
|
+
label=ADD_TASK_LABEL,
|
|
570
|
+
content=json.dumps(mem_ids),
|
|
571
|
+
timestamp=datetime.utcnow(),
|
|
572
|
+
user_name=self.cube_id,
|
|
573
|
+
)
|
|
574
|
+
self.mem_scheduler.submit_messages(messages=[message_item_add])
|
|
575
|
+
|
|
576
|
+
@timed
|
|
577
|
+
def _process_pref_mem(
|
|
578
|
+
self,
|
|
579
|
+
add_req: APIADDRequest,
|
|
580
|
+
user_context: UserContext,
|
|
581
|
+
sync_mode: str,
|
|
582
|
+
) -> list[dict[str, Any]]:
|
|
583
|
+
"""
|
|
584
|
+
Process and add preference memories.
|
|
585
|
+
|
|
586
|
+
Extracts preferences from messages and adds them to the preference memory system.
|
|
587
|
+
Handles both sync and async modes.
|
|
588
|
+
|
|
589
|
+
Args:
|
|
590
|
+
add_req: Add memory request
|
|
591
|
+
user_context: User context with IDs
|
|
592
|
+
|
|
593
|
+
Returns:
|
|
594
|
+
List of formatted preference responses
|
|
595
|
+
"""
|
|
596
|
+
if os.getenv("ENABLE_PREFERENCE_MEMORY", "false").lower() != "true":
|
|
597
|
+
return []
|
|
598
|
+
|
|
599
|
+
if add_req.messages is None or isinstance(add_req.messages, str):
|
|
600
|
+
return []
|
|
601
|
+
|
|
602
|
+
for message in add_req.messages:
|
|
603
|
+
if isinstance(message, dict) and message.get("role", None) is None:
|
|
604
|
+
return []
|
|
605
|
+
|
|
606
|
+
target_session_id = add_req.session_id or "default_session"
|
|
607
|
+
|
|
608
|
+
if sync_mode == "async":
|
|
609
|
+
try:
|
|
610
|
+
messages_list = [add_req.messages]
|
|
611
|
+
message_item_pref = ScheduleMessageItem(
|
|
612
|
+
user_id=add_req.user_id,
|
|
613
|
+
session_id=target_session_id,
|
|
614
|
+
mem_cube_id=user_context.mem_cube_id,
|
|
615
|
+
mem_cube=self.naive_mem_cube,
|
|
616
|
+
label=PREF_ADD_TASK_LABEL,
|
|
617
|
+
content=json.dumps(messages_list),
|
|
618
|
+
timestamp=datetime.utcnow(),
|
|
619
|
+
info=add_req.info,
|
|
620
|
+
user_name=self.cube_id,
|
|
621
|
+
task_id=add_req.task_id,
|
|
622
|
+
)
|
|
623
|
+
self.mem_scheduler.submit_messages(messages=[message_item_pref])
|
|
624
|
+
self.logger.info(f"[SingleCubeView] cube={self.cube_id} Submitted PREF_ADD async")
|
|
625
|
+
except Exception as e:
|
|
626
|
+
self.logger.error(
|
|
627
|
+
f"[SingleCubeView] cube={self.cube_id} Failed to submit PREF_ADD: {e}",
|
|
628
|
+
exc_info=True,
|
|
629
|
+
)
|
|
630
|
+
return []
|
|
631
|
+
else:
|
|
632
|
+
pref_memories_local = self.naive_mem_cube.pref_mem.get_memory(
|
|
633
|
+
[add_req.messages],
|
|
634
|
+
type="chat",
|
|
635
|
+
info={
|
|
636
|
+
**(add_req.info or {}),
|
|
637
|
+
"user_id": add_req.user_id,
|
|
638
|
+
"session_id": target_session_id,
|
|
639
|
+
"mem_cube_id": user_context.mem_cube_id,
|
|
640
|
+
},
|
|
641
|
+
)
|
|
642
|
+
pref_ids_local: list[str] = self.naive_mem_cube.pref_mem.add(pref_memories_local)
|
|
643
|
+
self.logger.info(
|
|
644
|
+
f"[SingleCubeView] cube={self.cube_id} "
|
|
645
|
+
f"added {len(pref_ids_local)} preferences for user {add_req.user_id}: {pref_ids_local}"
|
|
646
|
+
)
|
|
647
|
+
|
|
648
|
+
return [
|
|
649
|
+
{
|
|
650
|
+
"memory": memory.metadata.preference,
|
|
651
|
+
"memory_id": memory_id,
|
|
652
|
+
"memory_type": memory.metadata.preference_type,
|
|
653
|
+
}
|
|
654
|
+
for memory_id, memory in zip(pref_ids_local, pref_memories_local, strict=False)
|
|
655
|
+
]
|
|
656
|
+
|
|
657
|
+
def add_before_search(
|
|
658
|
+
self,
|
|
659
|
+
messages: list[dict],
|
|
660
|
+
memory_list: list[TextualMemoryItem],
|
|
661
|
+
user_name: str,
|
|
662
|
+
info: dict[str, Any],
|
|
663
|
+
) -> list[TextualMemoryItem]:
|
|
664
|
+
# Build input objects with memory text and metadata (timestamps, sources, etc.)
|
|
665
|
+
template = PROMPT_MAPPING["add_before_search"]
|
|
666
|
+
|
|
667
|
+
if not self.searcher:
|
|
668
|
+
self.logger.warning("[add_before_search] Searcher is not initialized, skipping check.")
|
|
669
|
+
return memory_list
|
|
670
|
+
|
|
671
|
+
# 1. Gather candidates and search for related memories
|
|
672
|
+
candidates_data = []
|
|
673
|
+
for idx, mem in enumerate(memory_list):
|
|
674
|
+
try:
|
|
675
|
+
related_memories = self.searcher.search(
|
|
676
|
+
query=mem.memory, top_k=3, mode="fast", user_name=user_name, info=info
|
|
677
|
+
)
|
|
678
|
+
related_text = "None"
|
|
679
|
+
if related_memories:
|
|
680
|
+
related_text = "\n".join([f"- {r.memory}" for r in related_memories])
|
|
681
|
+
|
|
682
|
+
candidates_data.append(
|
|
683
|
+
{"idx": idx, "new_memory": mem.memory, "related_memories": related_text}
|
|
684
|
+
)
|
|
685
|
+
except Exception as e:
|
|
686
|
+
self.logger.error(
|
|
687
|
+
f"[add_before_search] Search error for memory '{mem.memory}': {e}"
|
|
688
|
+
)
|
|
689
|
+
# If search fails, we can either skip this check or treat related as empty
|
|
690
|
+
candidates_data.append(
|
|
691
|
+
{
|
|
692
|
+
"idx": idx,
|
|
693
|
+
"new_memory": mem.memory,
|
|
694
|
+
"related_memories": "None (Search Failed)",
|
|
695
|
+
}
|
|
696
|
+
)
|
|
697
|
+
|
|
698
|
+
if not candidates_data:
|
|
699
|
+
return memory_list
|
|
700
|
+
|
|
701
|
+
# 2. Build Prompt
|
|
702
|
+
messages_inline = "\n".join(
|
|
703
|
+
[
|
|
704
|
+
f"- [{message.get('role', 'unknown')}]: {message.get('content', '')}"
|
|
705
|
+
for message in messages
|
|
706
|
+
]
|
|
707
|
+
)
|
|
708
|
+
|
|
709
|
+
candidates_inline_dict = {
|
|
710
|
+
str(item["idx"]): {
|
|
711
|
+
"new_memory": item["new_memory"],
|
|
712
|
+
"related_memories": item["related_memories"],
|
|
713
|
+
}
|
|
714
|
+
for item in candidates_data
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
candidates_inline = json.dumps(candidates_inline_dict, ensure_ascii=False, indent=2)
|
|
718
|
+
|
|
719
|
+
prompt = template.format(
|
|
720
|
+
messages_inline=messages_inline, candidates_inline=candidates_inline
|
|
721
|
+
)
|
|
722
|
+
|
|
723
|
+
# 3. Call LLM
|
|
724
|
+
try:
|
|
725
|
+
raw = self.mem_reader.llm.generate([{"role": "user", "content": prompt}])
|
|
726
|
+
success, parsed_result = parse_keep_filter_response(raw)
|
|
727
|
+
|
|
728
|
+
if not success:
|
|
729
|
+
self.logger.warning(
|
|
730
|
+
"[add_before_search] Failed to parse LLM response, keeping all."
|
|
731
|
+
)
|
|
732
|
+
return memory_list
|
|
733
|
+
|
|
734
|
+
# 4. Filter
|
|
735
|
+
filtered_list = []
|
|
736
|
+
for idx, mem in enumerate(memory_list):
|
|
737
|
+
res = parsed_result.get(idx)
|
|
738
|
+
if not res:
|
|
739
|
+
filtered_list.append(mem)
|
|
740
|
+
continue
|
|
741
|
+
|
|
742
|
+
if res.get("keep", True):
|
|
743
|
+
filtered_list.append(mem)
|
|
744
|
+
else:
|
|
745
|
+
self.logger.info(
|
|
746
|
+
f"[add_before_search] Dropping memory: '{mem.memory}', reason: '{res.get('reason')}'"
|
|
747
|
+
)
|
|
748
|
+
|
|
749
|
+
return filtered_list
|
|
750
|
+
|
|
751
|
+
except Exception as e:
|
|
752
|
+
self.logger.error(f"[add_before_search] LLM execution error: {e}")
|
|
753
|
+
return memory_list
|
|
754
|
+
|
|
755
|
+
@timed
|
|
756
|
+
def _process_text_mem(
|
|
757
|
+
self,
|
|
758
|
+
add_req: APIADDRequest,
|
|
759
|
+
user_context: UserContext,
|
|
760
|
+
sync_mode: str,
|
|
761
|
+
) -> list[dict[str, Any]]:
|
|
762
|
+
"""
|
|
763
|
+
Process and add text memories.
|
|
764
|
+
|
|
765
|
+
Extracts memories from messages and adds them to the text memory system.
|
|
766
|
+
Handles both sync and async modes.
|
|
767
|
+
|
|
768
|
+
Args:
|
|
769
|
+
add_req: Add memory request
|
|
770
|
+
user_context: User context with IDs
|
|
771
|
+
|
|
772
|
+
Returns:
|
|
773
|
+
List of formatted memory responses
|
|
774
|
+
"""
|
|
775
|
+
target_session_id = add_req.session_id or "default_session"
|
|
776
|
+
|
|
777
|
+
# Decide extraction mode:
|
|
778
|
+
# - async: always fast (ignore add_req.mode)
|
|
779
|
+
# - sync: use add_req.mode == "fast" to switch to fast pipeline, otherwise fine
|
|
780
|
+
if sync_mode == "async":
|
|
781
|
+
extract_mode = "fast"
|
|
782
|
+
else: # sync
|
|
783
|
+
extract_mode = "fast" if add_req.mode == "fast" else "fine"
|
|
784
|
+
|
|
785
|
+
self.logger.info(
|
|
786
|
+
"[SingleCubeView] cube=%s Processing text memory "
|
|
787
|
+
"with sync_mode=%s, extract_mode=%s, add_mode=%s",
|
|
788
|
+
user_context.mem_cube_id,
|
|
789
|
+
sync_mode,
|
|
790
|
+
extract_mode,
|
|
791
|
+
add_req.mode,
|
|
792
|
+
)
|
|
793
|
+
|
|
794
|
+
# Extract memories
|
|
795
|
+
memories_local = self.mem_reader.get_memory(
|
|
796
|
+
[add_req.messages],
|
|
797
|
+
type="chat",
|
|
798
|
+
info={
|
|
799
|
+
**(add_req.info or {}),
|
|
800
|
+
"custom_tags": add_req.custom_tags,
|
|
801
|
+
"user_id": add_req.user_id,
|
|
802
|
+
"session_id": target_session_id,
|
|
803
|
+
},
|
|
804
|
+
mode=extract_mode,
|
|
805
|
+
user_name=user_context.mem_cube_id,
|
|
806
|
+
)
|
|
807
|
+
flattened_local = [mm for m in memories_local for mm in m]
|
|
808
|
+
|
|
809
|
+
# Explicitly set source_doc_id to metadata if present in info
|
|
810
|
+
source_doc_id = (add_req.info or {}).get("source_doc_id")
|
|
811
|
+
if source_doc_id:
|
|
812
|
+
for memory in flattened_local:
|
|
813
|
+
memory.metadata.source_doc_id = source_doc_id
|
|
814
|
+
|
|
815
|
+
self.logger.info(f"Memory extraction completed for user {add_req.user_id}")
|
|
816
|
+
|
|
817
|
+
# Add memories to text_mem
|
|
818
|
+
mem_ids_local: list[str] = self.naive_mem_cube.text_mem.add(
|
|
819
|
+
flattened_local,
|
|
820
|
+
user_name=user_context.mem_cube_id,
|
|
821
|
+
)
|
|
822
|
+
self.logger.info(
|
|
823
|
+
f"Added {len(mem_ids_local)} memories for user {add_req.user_id} "
|
|
824
|
+
f"in session {add_req.session_id}: {mem_ids_local}"
|
|
825
|
+
)
|
|
826
|
+
|
|
827
|
+
# Schedule async/sync tasks
|
|
828
|
+
self._schedule_memory_tasks(
|
|
829
|
+
add_req=add_req,
|
|
830
|
+
user_context=user_context,
|
|
831
|
+
mem_ids=mem_ids_local,
|
|
832
|
+
sync_mode=sync_mode,
|
|
833
|
+
)
|
|
834
|
+
|
|
835
|
+
# Mark merged_from memories as archived when provided in add_req.info
|
|
836
|
+
if sync_mode == "sync" and extract_mode == "fine":
|
|
837
|
+
for memory in flattened_local:
|
|
838
|
+
merged_from = (memory.metadata.info or {}).get("merged_from")
|
|
839
|
+
if merged_from:
|
|
840
|
+
old_ids = (
|
|
841
|
+
merged_from
|
|
842
|
+
if isinstance(merged_from, (list | tuple | set))
|
|
843
|
+
else [merged_from]
|
|
844
|
+
)
|
|
845
|
+
if self.mem_reader and self.mem_reader.graph_db:
|
|
846
|
+
for old_id in old_ids:
|
|
847
|
+
try:
|
|
848
|
+
self.mem_reader.graph_db.update_node(
|
|
849
|
+
str(old_id),
|
|
850
|
+
{"status": "archived"},
|
|
851
|
+
user_name=user_context.mem_cube_id,
|
|
852
|
+
)
|
|
853
|
+
self.logger.info(
|
|
854
|
+
f"[SingleCubeView] Archived merged_from memory: {old_id}"
|
|
855
|
+
)
|
|
856
|
+
except Exception as e:
|
|
857
|
+
self.logger.warning(
|
|
858
|
+
f"[SingleCubeView] Failed to archive merged_from memory {old_id}: {e}"
|
|
859
|
+
)
|
|
860
|
+
else:
|
|
861
|
+
self.logger.warning(
|
|
862
|
+
"[SingleCubeView] merged_from provided but graph_db is unavailable; skip archiving."
|
|
863
|
+
)
|
|
864
|
+
|
|
865
|
+
text_memories = [
|
|
866
|
+
{
|
|
867
|
+
"memory": memory.memory,
|
|
868
|
+
"memory_id": memory_id,
|
|
869
|
+
"memory_type": memory.metadata.memory_type,
|
|
870
|
+
}
|
|
871
|
+
for memory_id, memory in zip(mem_ids_local, flattened_local, strict=False)
|
|
872
|
+
]
|
|
873
|
+
|
|
874
|
+
return text_memories
|