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,1495 @@
|
|
|
1
|
+
import concurrent.futures
|
|
2
|
+
import contextlib
|
|
3
|
+
import json
|
|
4
|
+
import traceback
|
|
5
|
+
|
|
6
|
+
from memos.configs.mem_scheduler import GeneralSchedulerConfig
|
|
7
|
+
from memos.context.context import ContextThreadPoolExecutor
|
|
8
|
+
from memos.log import get_logger
|
|
9
|
+
from memos.mem_cube.general import GeneralMemCube
|
|
10
|
+
from memos.mem_scheduler.base_scheduler import BaseScheduler
|
|
11
|
+
from memos.mem_scheduler.schemas.message_schemas import ScheduleMessageItem
|
|
12
|
+
from memos.mem_scheduler.schemas.monitor_schemas import QueryMonitorItem
|
|
13
|
+
from memos.mem_scheduler.schemas.task_schemas import (
|
|
14
|
+
ADD_TASK_LABEL,
|
|
15
|
+
ANSWER_TASK_LABEL,
|
|
16
|
+
DEFAULT_MAX_QUERY_KEY_WORDS,
|
|
17
|
+
LONG_TERM_MEMORY_TYPE,
|
|
18
|
+
MEM_FEEDBACK_TASK_LABEL,
|
|
19
|
+
MEM_ORGANIZE_TASK_LABEL,
|
|
20
|
+
MEM_READ_TASK_LABEL,
|
|
21
|
+
MEM_UPDATE_TASK_LABEL,
|
|
22
|
+
NOT_APPLICABLE_TYPE,
|
|
23
|
+
PREF_ADD_TASK_LABEL,
|
|
24
|
+
QUERY_TASK_LABEL,
|
|
25
|
+
USER_INPUT_TYPE,
|
|
26
|
+
)
|
|
27
|
+
from memos.mem_scheduler.utils.filter_utils import (
|
|
28
|
+
is_all_chinese,
|
|
29
|
+
is_all_english,
|
|
30
|
+
transform_name_to_key,
|
|
31
|
+
)
|
|
32
|
+
from memos.mem_scheduler.utils.misc_utils import (
|
|
33
|
+
group_messages_by_user_and_mem_cube,
|
|
34
|
+
is_cloud_env,
|
|
35
|
+
)
|
|
36
|
+
from memos.memories.textual.item import TextualMemoryItem
|
|
37
|
+
from memos.memories.textual.naive import NaiveTextMemory
|
|
38
|
+
from memos.memories.textual.preference import PreferenceTextMemory
|
|
39
|
+
from memos.memories.textual.tree import TreeTextMemory
|
|
40
|
+
from memos.types import (
|
|
41
|
+
MemCubeID,
|
|
42
|
+
UserID,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
logger = get_logger(__name__)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class GeneralScheduler(BaseScheduler):
|
|
50
|
+
def __init__(self, config: GeneralSchedulerConfig):
|
|
51
|
+
"""Initialize the scheduler with the given configuration."""
|
|
52
|
+
super().__init__(config)
|
|
53
|
+
|
|
54
|
+
self.query_key_words_limit = self.config.get("query_key_words_limit", 20)
|
|
55
|
+
|
|
56
|
+
# register handlers
|
|
57
|
+
handlers = {
|
|
58
|
+
QUERY_TASK_LABEL: self._query_message_consumer,
|
|
59
|
+
ANSWER_TASK_LABEL: self._answer_message_consumer,
|
|
60
|
+
MEM_UPDATE_TASK_LABEL: self._memory_update_consumer,
|
|
61
|
+
ADD_TASK_LABEL: self._add_message_consumer,
|
|
62
|
+
MEM_READ_TASK_LABEL: self._mem_read_message_consumer,
|
|
63
|
+
MEM_ORGANIZE_TASK_LABEL: self._mem_reorganize_message_consumer,
|
|
64
|
+
PREF_ADD_TASK_LABEL: self._pref_add_message_consumer,
|
|
65
|
+
MEM_FEEDBACK_TASK_LABEL: self._mem_feedback_message_consumer,
|
|
66
|
+
}
|
|
67
|
+
self.dispatcher.register_handlers(handlers)
|
|
68
|
+
|
|
69
|
+
def long_memory_update_process(
|
|
70
|
+
self, user_id: str, mem_cube_id: str, messages: list[ScheduleMessageItem]
|
|
71
|
+
):
|
|
72
|
+
mem_cube = self.mem_cube
|
|
73
|
+
|
|
74
|
+
# update query monitors
|
|
75
|
+
for msg in messages:
|
|
76
|
+
self.monitor.register_query_monitor_if_not_exists(
|
|
77
|
+
user_id=user_id, mem_cube_id=mem_cube_id
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
query = msg.content
|
|
81
|
+
query_keywords = self.monitor.extract_query_keywords(query=query)
|
|
82
|
+
logger.info(
|
|
83
|
+
f'Extracted keywords "{query_keywords}" from query "{query}" for user_id={user_id}'
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
if len(query_keywords) == 0:
|
|
87
|
+
stripped_query = query.strip()
|
|
88
|
+
# Determine measurement method based on language
|
|
89
|
+
if is_all_english(stripped_query):
|
|
90
|
+
words = stripped_query.split() # Word count for English
|
|
91
|
+
elif is_all_chinese(stripped_query):
|
|
92
|
+
words = stripped_query # Character count for Chinese
|
|
93
|
+
else:
|
|
94
|
+
logger.debug(
|
|
95
|
+
f"Mixed-language memory, using character count: {stripped_query[:50]}..."
|
|
96
|
+
)
|
|
97
|
+
words = stripped_query # Default to character count
|
|
98
|
+
|
|
99
|
+
query_keywords = list(set(words[: self.query_key_words_limit]))
|
|
100
|
+
logger.error(
|
|
101
|
+
f"Keyword extraction failed for query '{query}' (user_id={user_id}). Using fallback keywords: {query_keywords[:10]}... (truncated)",
|
|
102
|
+
exc_info=True,
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
item = QueryMonitorItem(
|
|
106
|
+
user_id=user_id,
|
|
107
|
+
mem_cube_id=mem_cube_id,
|
|
108
|
+
query_text=query,
|
|
109
|
+
keywords=query_keywords,
|
|
110
|
+
max_keywords=DEFAULT_MAX_QUERY_KEY_WORDS,
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
query_db_manager = self.monitor.query_monitors[user_id][mem_cube_id]
|
|
114
|
+
query_db_manager.obj.put(item=item)
|
|
115
|
+
# Sync with database after adding new item
|
|
116
|
+
query_db_manager.sync_with_orm()
|
|
117
|
+
logger.debug(
|
|
118
|
+
f"Queries in monitor for user_id={user_id}, mem_cube_id={mem_cube_id}: {query_db_manager.obj.get_queries_with_timesort()}"
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
queries = [msg.content for msg in messages]
|
|
122
|
+
|
|
123
|
+
# recall
|
|
124
|
+
cur_working_memory, new_candidates = self.process_session_turn(
|
|
125
|
+
queries=queries,
|
|
126
|
+
user_id=user_id,
|
|
127
|
+
mem_cube_id=mem_cube_id,
|
|
128
|
+
mem_cube=mem_cube,
|
|
129
|
+
top_k=self.top_k,
|
|
130
|
+
)
|
|
131
|
+
logger.info(
|
|
132
|
+
# Build the candidate preview string outside the f-string to avoid backslashes in expression
|
|
133
|
+
f"[long_memory_update_process] Processed {len(queries)} queries {queries} and retrieved {len(new_candidates)} "
|
|
134
|
+
f"new candidate memories for user_id={user_id}: "
|
|
135
|
+
+ ("\n- " + "\n- ".join([f"{one.id}: {one.memory}" for one in new_candidates]))
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
# rerank
|
|
139
|
+
new_order_working_memory = self.replace_working_memory(
|
|
140
|
+
user_id=user_id,
|
|
141
|
+
mem_cube_id=mem_cube_id,
|
|
142
|
+
mem_cube=mem_cube,
|
|
143
|
+
original_memory=cur_working_memory,
|
|
144
|
+
new_memory=new_candidates,
|
|
145
|
+
)
|
|
146
|
+
logger.debug(
|
|
147
|
+
f"[long_memory_update_process] Final working memory size: {len(new_order_working_memory)} memories for user_id={user_id}"
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
old_memory_texts = "\n- " + "\n- ".join(
|
|
151
|
+
[f"{one.id}: {one.memory}" for one in cur_working_memory]
|
|
152
|
+
)
|
|
153
|
+
new_memory_texts = "\n- " + "\n- ".join(
|
|
154
|
+
[f"{one.id}: {one.memory}" for one in new_order_working_memory]
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
logger.info(
|
|
158
|
+
f"[long_memory_update_process] For user_id='{user_id}', mem_cube_id='{mem_cube_id}': "
|
|
159
|
+
f"Scheduler replaced working memory based on query history {queries}. "
|
|
160
|
+
f"Old working memory ({len(cur_working_memory)} items): {old_memory_texts}. "
|
|
161
|
+
f"New working memory ({len(new_order_working_memory)} items): {new_memory_texts}."
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
# update activation memories
|
|
165
|
+
logger.debug(
|
|
166
|
+
f"Activation memory update {'enabled' if self.enable_activation_memory else 'disabled'} "
|
|
167
|
+
f"(interval: {self.monitor.act_mem_update_interval}s)"
|
|
168
|
+
)
|
|
169
|
+
if self.enable_activation_memory:
|
|
170
|
+
self.update_activation_memory_periodically(
|
|
171
|
+
interval_seconds=self.monitor.act_mem_update_interval,
|
|
172
|
+
label=QUERY_TASK_LABEL,
|
|
173
|
+
user_id=user_id,
|
|
174
|
+
mem_cube_id=mem_cube_id,
|
|
175
|
+
mem_cube=self.mem_cube,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
def _add_message_consumer(self, messages: list[ScheduleMessageItem]) -> None:
|
|
179
|
+
logger.info(f"Messages {messages} assigned to {ADD_TASK_LABEL} handler.")
|
|
180
|
+
# Process the query in a session turn
|
|
181
|
+
grouped_messages = group_messages_by_user_and_mem_cube(messages=messages)
|
|
182
|
+
|
|
183
|
+
self.validate_schedule_messages(messages=messages, label=ADD_TASK_LABEL)
|
|
184
|
+
try:
|
|
185
|
+
for user_id in grouped_messages:
|
|
186
|
+
for mem_cube_id in grouped_messages[user_id]:
|
|
187
|
+
batch = grouped_messages[user_id][mem_cube_id]
|
|
188
|
+
if not batch:
|
|
189
|
+
continue
|
|
190
|
+
|
|
191
|
+
# Process each message in the batch
|
|
192
|
+
for msg in batch:
|
|
193
|
+
prepared_add_items, prepared_update_items_with_original = (
|
|
194
|
+
self.log_add_messages(msg=msg)
|
|
195
|
+
)
|
|
196
|
+
logger.info(
|
|
197
|
+
f"prepared_add_items: {prepared_add_items};\n prepared_update_items_with_original: {prepared_update_items_with_original}"
|
|
198
|
+
)
|
|
199
|
+
# Conditional Logging: Knowledge Base (Cloud Service) vs. Playground/Default
|
|
200
|
+
cloud_env = is_cloud_env()
|
|
201
|
+
|
|
202
|
+
if cloud_env:
|
|
203
|
+
self.send_add_log_messages_to_cloud_env(
|
|
204
|
+
msg, prepared_add_items, prepared_update_items_with_original
|
|
205
|
+
)
|
|
206
|
+
else:
|
|
207
|
+
self.send_add_log_messages_to_local_env(
|
|
208
|
+
msg, prepared_add_items, prepared_update_items_with_original
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
except Exception as e:
|
|
212
|
+
logger.error(f"Error: {e}", exc_info=True)
|
|
213
|
+
|
|
214
|
+
def _memory_update_consumer(self, messages: list[ScheduleMessageItem]) -> None:
|
|
215
|
+
logger.info(f"Messages {messages} assigned to {MEM_UPDATE_TASK_LABEL} handler.")
|
|
216
|
+
|
|
217
|
+
grouped_messages = group_messages_by_user_and_mem_cube(messages=messages)
|
|
218
|
+
|
|
219
|
+
self.validate_schedule_messages(messages=messages, label=MEM_UPDATE_TASK_LABEL)
|
|
220
|
+
|
|
221
|
+
for user_id in grouped_messages:
|
|
222
|
+
for mem_cube_id in grouped_messages[user_id]:
|
|
223
|
+
batch = grouped_messages[user_id][mem_cube_id]
|
|
224
|
+
if not batch:
|
|
225
|
+
continue
|
|
226
|
+
# Process the whole batch once; no need to iterate per message
|
|
227
|
+
self.long_memory_update_process(
|
|
228
|
+
user_id=user_id, mem_cube_id=mem_cube_id, messages=batch
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
def _query_message_consumer(self, messages: list[ScheduleMessageItem]) -> None:
|
|
232
|
+
"""
|
|
233
|
+
Process and handle query trigger messages from the queue.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
messages: List of query messages to process
|
|
237
|
+
"""
|
|
238
|
+
logger.info(f"Messages {messages} assigned to {QUERY_TASK_LABEL} handler.")
|
|
239
|
+
|
|
240
|
+
grouped_messages = group_messages_by_user_and_mem_cube(messages=messages)
|
|
241
|
+
|
|
242
|
+
self.validate_schedule_messages(messages=messages, label=QUERY_TASK_LABEL)
|
|
243
|
+
|
|
244
|
+
mem_update_messages = []
|
|
245
|
+
for user_id in grouped_messages:
|
|
246
|
+
for mem_cube_id in grouped_messages[user_id]:
|
|
247
|
+
batch = grouped_messages[user_id][mem_cube_id]
|
|
248
|
+
if not batch:
|
|
249
|
+
continue
|
|
250
|
+
|
|
251
|
+
for msg in batch:
|
|
252
|
+
try:
|
|
253
|
+
event = self.create_event_log(
|
|
254
|
+
label="addMessage",
|
|
255
|
+
from_memory_type=USER_INPUT_TYPE,
|
|
256
|
+
to_memory_type=NOT_APPLICABLE_TYPE,
|
|
257
|
+
user_id=msg.user_id,
|
|
258
|
+
mem_cube_id=msg.mem_cube_id,
|
|
259
|
+
mem_cube=self.mem_cube,
|
|
260
|
+
memcube_log_content=[
|
|
261
|
+
{
|
|
262
|
+
"content": f"[User] {msg.content}",
|
|
263
|
+
"ref_id": msg.item_id,
|
|
264
|
+
"role": "user",
|
|
265
|
+
}
|
|
266
|
+
],
|
|
267
|
+
metadata=[],
|
|
268
|
+
memory_len=1,
|
|
269
|
+
memcube_name=self._map_memcube_name(msg.mem_cube_id),
|
|
270
|
+
)
|
|
271
|
+
event.task_id = msg.task_id
|
|
272
|
+
self._submit_web_logs([event])
|
|
273
|
+
except Exception:
|
|
274
|
+
logger.exception("Failed to record addMessage log for query")
|
|
275
|
+
# Re-submit the message with label changed to mem_update
|
|
276
|
+
update_msg = ScheduleMessageItem(
|
|
277
|
+
user_id=msg.user_id,
|
|
278
|
+
mem_cube_id=msg.mem_cube_id,
|
|
279
|
+
label=MEM_UPDATE_TASK_LABEL,
|
|
280
|
+
content=msg.content,
|
|
281
|
+
session_id=msg.session_id,
|
|
282
|
+
user_name=msg.user_name,
|
|
283
|
+
info=msg.info,
|
|
284
|
+
task_id=msg.task_id,
|
|
285
|
+
)
|
|
286
|
+
mem_update_messages.append(update_msg)
|
|
287
|
+
|
|
288
|
+
self.submit_messages(messages=mem_update_messages)
|
|
289
|
+
|
|
290
|
+
def _answer_message_consumer(self, messages: list[ScheduleMessageItem]) -> None:
|
|
291
|
+
"""
|
|
292
|
+
Process and handle answer trigger messages from the queue.
|
|
293
|
+
|
|
294
|
+
Args:
|
|
295
|
+
messages: List of answer messages to process
|
|
296
|
+
"""
|
|
297
|
+
logger.info(f"Messages {messages} assigned to {ANSWER_TASK_LABEL} handler.")
|
|
298
|
+
grouped_messages = group_messages_by_user_and_mem_cube(messages=messages)
|
|
299
|
+
|
|
300
|
+
self.validate_schedule_messages(messages=messages, label=ANSWER_TASK_LABEL)
|
|
301
|
+
|
|
302
|
+
for user_id in grouped_messages:
|
|
303
|
+
for mem_cube_id in grouped_messages[user_id]:
|
|
304
|
+
batch = grouped_messages[user_id][mem_cube_id]
|
|
305
|
+
if not batch:
|
|
306
|
+
continue
|
|
307
|
+
try:
|
|
308
|
+
for msg in batch:
|
|
309
|
+
event = self.create_event_log(
|
|
310
|
+
label="addMessage",
|
|
311
|
+
from_memory_type=USER_INPUT_TYPE,
|
|
312
|
+
to_memory_type=NOT_APPLICABLE_TYPE,
|
|
313
|
+
user_id=msg.user_id,
|
|
314
|
+
mem_cube_id=msg.mem_cube_id,
|
|
315
|
+
mem_cube=self.mem_cube,
|
|
316
|
+
memcube_log_content=[
|
|
317
|
+
{
|
|
318
|
+
"content": f"[Assistant] {msg.content}",
|
|
319
|
+
"ref_id": msg.item_id,
|
|
320
|
+
"role": "assistant",
|
|
321
|
+
}
|
|
322
|
+
],
|
|
323
|
+
metadata=[],
|
|
324
|
+
memory_len=1,
|
|
325
|
+
memcube_name=self._map_memcube_name(msg.mem_cube_id),
|
|
326
|
+
)
|
|
327
|
+
event.task_id = msg.task_id
|
|
328
|
+
self._submit_web_logs([event])
|
|
329
|
+
except Exception:
|
|
330
|
+
logger.exception("Failed to record addMessage log for answer")
|
|
331
|
+
|
|
332
|
+
def log_add_messages(self, msg: ScheduleMessageItem):
|
|
333
|
+
try:
|
|
334
|
+
userinput_memory_ids = json.loads(msg.content)
|
|
335
|
+
except Exception as e:
|
|
336
|
+
logger.error(f"Error: {e}. Content: {msg.content}", exc_info=True)
|
|
337
|
+
userinput_memory_ids = []
|
|
338
|
+
|
|
339
|
+
# Prepare data for both logging paths, fetching original content for updates
|
|
340
|
+
prepared_add_items = []
|
|
341
|
+
prepared_update_items_with_original = []
|
|
342
|
+
missing_ids: list[str] = []
|
|
343
|
+
|
|
344
|
+
for memory_id in userinput_memory_ids:
|
|
345
|
+
try:
|
|
346
|
+
# This mem_item represents the NEW content that was just added/processed
|
|
347
|
+
mem_item: TextualMemoryItem | None = None
|
|
348
|
+
mem_item = self.mem_cube.text_mem.get(
|
|
349
|
+
memory_id=memory_id, user_name=msg.mem_cube_id
|
|
350
|
+
)
|
|
351
|
+
if mem_item is None:
|
|
352
|
+
raise ValueError(f"Memory {memory_id} not found after retries")
|
|
353
|
+
# Check if a memory with the same key already exists (determining if it's an update)
|
|
354
|
+
key = getattr(mem_item.metadata, "key", None) or transform_name_to_key(
|
|
355
|
+
name=mem_item.memory
|
|
356
|
+
)
|
|
357
|
+
exists = False
|
|
358
|
+
original_content = None
|
|
359
|
+
original_item_id = None
|
|
360
|
+
|
|
361
|
+
# Only check graph_store if a key exists and the text_mem has a graph_store
|
|
362
|
+
if key and hasattr(self.mem_cube.text_mem, "graph_store"):
|
|
363
|
+
candidates = self.mem_cube.text_mem.graph_store.get_by_metadata(
|
|
364
|
+
[
|
|
365
|
+
{"field": "key", "op": "=", "value": key},
|
|
366
|
+
{
|
|
367
|
+
"field": "memory_type",
|
|
368
|
+
"op": "=",
|
|
369
|
+
"value": mem_item.metadata.memory_type,
|
|
370
|
+
},
|
|
371
|
+
]
|
|
372
|
+
)
|
|
373
|
+
if candidates:
|
|
374
|
+
exists = True
|
|
375
|
+
original_item_id = candidates[0]
|
|
376
|
+
# Crucial step: Fetch the original content for updates
|
|
377
|
+
# This `get` is for the *existing* memory that will be updated
|
|
378
|
+
original_mem_item = self.mem_cube.text_mem.get(
|
|
379
|
+
memory_id=original_item_id, user_name=msg.mem_cube_id
|
|
380
|
+
)
|
|
381
|
+
original_content = original_mem_item.memory
|
|
382
|
+
|
|
383
|
+
if exists:
|
|
384
|
+
prepared_update_items_with_original.append(
|
|
385
|
+
{
|
|
386
|
+
"new_item": mem_item,
|
|
387
|
+
"original_content": original_content,
|
|
388
|
+
"original_item_id": original_item_id,
|
|
389
|
+
}
|
|
390
|
+
)
|
|
391
|
+
else:
|
|
392
|
+
prepared_add_items.append(mem_item)
|
|
393
|
+
|
|
394
|
+
except Exception:
|
|
395
|
+
missing_ids.append(memory_id)
|
|
396
|
+
logger.debug(
|
|
397
|
+
f"This MemoryItem {memory_id} has already been deleted or an error occurred during preparation."
|
|
398
|
+
)
|
|
399
|
+
|
|
400
|
+
if missing_ids:
|
|
401
|
+
content_preview = (
|
|
402
|
+
msg.content[:200] + "..."
|
|
403
|
+
if isinstance(msg.content, str) and len(msg.content) > 200
|
|
404
|
+
else msg.content
|
|
405
|
+
)
|
|
406
|
+
logger.warning(
|
|
407
|
+
"Missing TextualMemoryItem(s) during add log preparation. "
|
|
408
|
+
"memory_ids=%s user_id=%s mem_cube_id=%s task_id=%s item_id=%s redis_msg_id=%s label=%s stream_key=%s content_preview=%s",
|
|
409
|
+
missing_ids,
|
|
410
|
+
msg.user_id,
|
|
411
|
+
msg.mem_cube_id,
|
|
412
|
+
msg.task_id,
|
|
413
|
+
msg.item_id,
|
|
414
|
+
getattr(msg, "redis_message_id", ""),
|
|
415
|
+
msg.label,
|
|
416
|
+
getattr(msg, "stream_key", ""),
|
|
417
|
+
content_preview,
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
if not prepared_add_items and not prepared_update_items_with_original:
|
|
421
|
+
logger.warning(
|
|
422
|
+
"No add/update items prepared; skipping addMemory/knowledgeBaseUpdate logs. "
|
|
423
|
+
"user_id=%s mem_cube_id=%s task_id=%s item_id=%s redis_msg_id=%s label=%s stream_key=%s missing_ids=%s",
|
|
424
|
+
msg.user_id,
|
|
425
|
+
msg.mem_cube_id,
|
|
426
|
+
msg.task_id,
|
|
427
|
+
msg.item_id,
|
|
428
|
+
getattr(msg, "redis_message_id", ""),
|
|
429
|
+
msg.label,
|
|
430
|
+
getattr(msg, "stream_key", ""),
|
|
431
|
+
missing_ids,
|
|
432
|
+
)
|
|
433
|
+
return prepared_add_items, prepared_update_items_with_original
|
|
434
|
+
|
|
435
|
+
def send_add_log_messages_to_local_env(
|
|
436
|
+
self, msg: ScheduleMessageItem, prepared_add_items, prepared_update_items_with_original
|
|
437
|
+
):
|
|
438
|
+
# Existing: Playground/Default Logging
|
|
439
|
+
# Reconstruct add_content/add_meta/update_content/update_meta from prepared_items
|
|
440
|
+
# This ensures existing logging path continues to work with pre-existing data structures
|
|
441
|
+
add_content_legacy: list[dict] = []
|
|
442
|
+
add_meta_legacy: list[dict] = []
|
|
443
|
+
update_content_legacy: list[dict] = []
|
|
444
|
+
update_meta_legacy: list[dict] = []
|
|
445
|
+
|
|
446
|
+
for item in prepared_add_items:
|
|
447
|
+
key = getattr(item.metadata, "key", None) or transform_name_to_key(name=item.memory)
|
|
448
|
+
add_content_legacy.append({"content": f"{key}: {item.memory}", "ref_id": item.id})
|
|
449
|
+
add_meta_legacy.append(
|
|
450
|
+
{
|
|
451
|
+
"ref_id": item.id,
|
|
452
|
+
"id": item.id,
|
|
453
|
+
"key": item.metadata.key,
|
|
454
|
+
"memory": item.memory,
|
|
455
|
+
"memory_type": item.metadata.memory_type,
|
|
456
|
+
"status": item.metadata.status,
|
|
457
|
+
"confidence": item.metadata.confidence,
|
|
458
|
+
"tags": item.metadata.tags,
|
|
459
|
+
"updated_at": getattr(item.metadata, "updated_at", None)
|
|
460
|
+
or getattr(item.metadata, "update_at", None),
|
|
461
|
+
}
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
for item_data in prepared_update_items_with_original:
|
|
465
|
+
item = item_data["new_item"]
|
|
466
|
+
key = getattr(item.metadata, "key", None) or transform_name_to_key(name=item.memory)
|
|
467
|
+
update_content_legacy.append({"content": f"{key}: {item.memory}", "ref_id": item.id})
|
|
468
|
+
update_meta_legacy.append(
|
|
469
|
+
{
|
|
470
|
+
"ref_id": item.id,
|
|
471
|
+
"id": item.id,
|
|
472
|
+
"key": item.metadata.key,
|
|
473
|
+
"memory": item.memory,
|
|
474
|
+
"memory_type": item.metadata.memory_type,
|
|
475
|
+
"status": item.metadata.status,
|
|
476
|
+
"confidence": item.metadata.confidence,
|
|
477
|
+
"tags": item.metadata.tags,
|
|
478
|
+
"updated_at": getattr(item.metadata, "updated_at", None)
|
|
479
|
+
or getattr(item.metadata, "update_at", None),
|
|
480
|
+
}
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
events = []
|
|
484
|
+
if add_content_legacy:
|
|
485
|
+
event = self.create_event_log(
|
|
486
|
+
label="addMemory",
|
|
487
|
+
from_memory_type=USER_INPUT_TYPE,
|
|
488
|
+
to_memory_type=LONG_TERM_MEMORY_TYPE,
|
|
489
|
+
user_id=msg.user_id,
|
|
490
|
+
mem_cube_id=msg.mem_cube_id,
|
|
491
|
+
mem_cube=self.mem_cube,
|
|
492
|
+
memcube_log_content=add_content_legacy,
|
|
493
|
+
metadata=add_meta_legacy,
|
|
494
|
+
memory_len=len(add_content_legacy),
|
|
495
|
+
memcube_name=self._map_memcube_name(msg.mem_cube_id),
|
|
496
|
+
)
|
|
497
|
+
event.task_id = msg.task_id
|
|
498
|
+
events.append(event)
|
|
499
|
+
if update_content_legacy:
|
|
500
|
+
event = self.create_event_log(
|
|
501
|
+
label="updateMemory",
|
|
502
|
+
from_memory_type=LONG_TERM_MEMORY_TYPE,
|
|
503
|
+
to_memory_type=LONG_TERM_MEMORY_TYPE,
|
|
504
|
+
user_id=msg.user_id,
|
|
505
|
+
mem_cube_id=msg.mem_cube_id,
|
|
506
|
+
mem_cube=self.mem_cube,
|
|
507
|
+
memcube_log_content=update_content_legacy,
|
|
508
|
+
metadata=update_meta_legacy,
|
|
509
|
+
memory_len=len(update_content_legacy),
|
|
510
|
+
memcube_name=self._map_memcube_name(msg.mem_cube_id),
|
|
511
|
+
)
|
|
512
|
+
event.task_id = msg.task_id
|
|
513
|
+
events.append(event)
|
|
514
|
+
logger.info(f"send_add_log_messages_to_local_env: {len(events)}")
|
|
515
|
+
if events:
|
|
516
|
+
self._submit_web_logs(events, additional_log_info="send_add_log_messages_to_cloud_env")
|
|
517
|
+
|
|
518
|
+
def send_add_log_messages_to_cloud_env(
|
|
519
|
+
self, msg: ScheduleMessageItem, prepared_add_items, prepared_update_items_with_original
|
|
520
|
+
):
|
|
521
|
+
"""
|
|
522
|
+
Cloud logging path for add/update events.
|
|
523
|
+
"""
|
|
524
|
+
kb_log_content: list[dict] = []
|
|
525
|
+
info = msg.info or {}
|
|
526
|
+
|
|
527
|
+
# Process added items
|
|
528
|
+
for item in prepared_add_items:
|
|
529
|
+
metadata = getattr(item, "metadata", None)
|
|
530
|
+
file_ids = getattr(metadata, "file_ids", None) if metadata else None
|
|
531
|
+
source_doc_id = file_ids[0] if isinstance(file_ids, list) and file_ids else None
|
|
532
|
+
kb_log_content.append(
|
|
533
|
+
{
|
|
534
|
+
"log_source": "KNOWLEDGE_BASE_LOG",
|
|
535
|
+
"trigger_source": info.get("trigger_source", "Messages"),
|
|
536
|
+
"operation": "ADD",
|
|
537
|
+
"memory_id": item.id,
|
|
538
|
+
"content": item.memory,
|
|
539
|
+
"original_content": None,
|
|
540
|
+
"source_doc_id": source_doc_id,
|
|
541
|
+
}
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
# Process updated items
|
|
545
|
+
for item_data in prepared_update_items_with_original:
|
|
546
|
+
item = item_data["new_item"]
|
|
547
|
+
metadata = getattr(item, "metadata", None)
|
|
548
|
+
file_ids = getattr(metadata, "file_ids", None) if metadata else None
|
|
549
|
+
source_doc_id = file_ids[0] if isinstance(file_ids, list) and file_ids else None
|
|
550
|
+
kb_log_content.append(
|
|
551
|
+
{
|
|
552
|
+
"log_source": "KNOWLEDGE_BASE_LOG",
|
|
553
|
+
"trigger_source": info.get("trigger_source", "Messages"),
|
|
554
|
+
"operation": "UPDATE",
|
|
555
|
+
"memory_id": item.id,
|
|
556
|
+
"content": item.memory,
|
|
557
|
+
"original_content": item_data.get("original_content"),
|
|
558
|
+
"source_doc_id": source_doc_id,
|
|
559
|
+
}
|
|
560
|
+
)
|
|
561
|
+
|
|
562
|
+
if kb_log_content:
|
|
563
|
+
logger.info(
|
|
564
|
+
f"[DIAGNOSTIC] general_scheduler.send_add_log_messages_to_cloud_env: Creating event log for KB update. Label: knowledgeBaseUpdate, user_id: {msg.user_id}, mem_cube_id: {msg.mem_cube_id}, task_id: {msg.task_id}. KB content: {json.dumps(kb_log_content, indent=2)}"
|
|
565
|
+
)
|
|
566
|
+
event = self.create_event_log(
|
|
567
|
+
label="knowledgeBaseUpdate",
|
|
568
|
+
from_memory_type=USER_INPUT_TYPE,
|
|
569
|
+
to_memory_type=LONG_TERM_MEMORY_TYPE,
|
|
570
|
+
user_id=msg.user_id,
|
|
571
|
+
mem_cube_id=msg.mem_cube_id,
|
|
572
|
+
mem_cube=self.mem_cube,
|
|
573
|
+
memcube_log_content=kb_log_content,
|
|
574
|
+
metadata=None,
|
|
575
|
+
memory_len=len(kb_log_content),
|
|
576
|
+
memcube_name=self._map_memcube_name(msg.mem_cube_id),
|
|
577
|
+
)
|
|
578
|
+
event.log_content = f"Knowledge Base Memory Update: {len(kb_log_content)} changes."
|
|
579
|
+
event.task_id = msg.task_id
|
|
580
|
+
self._submit_web_logs([event])
|
|
581
|
+
|
|
582
|
+
def _mem_feedback_message_consumer(self, messages: list[ScheduleMessageItem]) -> None:
|
|
583
|
+
try:
|
|
584
|
+
if not messages:
|
|
585
|
+
return
|
|
586
|
+
message = messages[0]
|
|
587
|
+
mem_cube = self.mem_cube
|
|
588
|
+
|
|
589
|
+
user_id = message.user_id
|
|
590
|
+
mem_cube_id = message.mem_cube_id
|
|
591
|
+
content = message.content
|
|
592
|
+
|
|
593
|
+
try:
|
|
594
|
+
feedback_data = json.loads(content) if isinstance(content, str) else content
|
|
595
|
+
if not isinstance(feedback_data, dict):
|
|
596
|
+
logger.error(
|
|
597
|
+
f"Failed to decode feedback_data or it is not a dict: {feedback_data}"
|
|
598
|
+
)
|
|
599
|
+
return
|
|
600
|
+
except json.JSONDecodeError:
|
|
601
|
+
logger.error(f"Invalid JSON content for feedback message: {content}", exc_info=True)
|
|
602
|
+
return
|
|
603
|
+
|
|
604
|
+
task_id = feedback_data.get("task_id") or message.task_id
|
|
605
|
+
feedback_result = self.feedback_server.process_feedback(
|
|
606
|
+
user_id=user_id,
|
|
607
|
+
user_name=mem_cube_id,
|
|
608
|
+
session_id=feedback_data.get("session_id"),
|
|
609
|
+
chat_history=feedback_data.get("history", []),
|
|
610
|
+
retrieved_memory_ids=feedback_data.get("retrieved_memory_ids", []),
|
|
611
|
+
feedback_content=feedback_data.get("feedback_content"),
|
|
612
|
+
feedback_time=feedback_data.get("feedback_time"),
|
|
613
|
+
task_id=task_id,
|
|
614
|
+
info=feedback_data.get("info", None),
|
|
615
|
+
)
|
|
616
|
+
|
|
617
|
+
logger.info(
|
|
618
|
+
f"Successfully processed feedback for user_id={user_id}, mem_cube_id={mem_cube_id}"
|
|
619
|
+
)
|
|
620
|
+
|
|
621
|
+
cloud_env = is_cloud_env()
|
|
622
|
+
if cloud_env:
|
|
623
|
+
record = feedback_result.get("record") if isinstance(feedback_result, dict) else {}
|
|
624
|
+
add_records = record.get("add") if isinstance(record, dict) else []
|
|
625
|
+
update_records = record.get("update") if isinstance(record, dict) else []
|
|
626
|
+
|
|
627
|
+
def _extract_fields(mem_item):
|
|
628
|
+
mem_id = (
|
|
629
|
+
getattr(mem_item, "id", None)
|
|
630
|
+
if not isinstance(mem_item, dict)
|
|
631
|
+
else mem_item.get("id")
|
|
632
|
+
)
|
|
633
|
+
mem_memory = (
|
|
634
|
+
getattr(mem_item, "memory", None)
|
|
635
|
+
if not isinstance(mem_item, dict)
|
|
636
|
+
else mem_item.get("memory") or mem_item.get("text")
|
|
637
|
+
)
|
|
638
|
+
if mem_memory is None and isinstance(mem_item, dict):
|
|
639
|
+
mem_memory = mem_item.get("text")
|
|
640
|
+
original_content = (
|
|
641
|
+
getattr(mem_item, "origin_memory", None)
|
|
642
|
+
if not isinstance(mem_item, dict)
|
|
643
|
+
else mem_item.get("origin_memory")
|
|
644
|
+
or mem_item.get("old_memory")
|
|
645
|
+
or mem_item.get("original_content")
|
|
646
|
+
)
|
|
647
|
+
source_doc_id = None
|
|
648
|
+
if isinstance(mem_item, dict):
|
|
649
|
+
source_doc_id = mem_item.get("source_doc_id", None)
|
|
650
|
+
|
|
651
|
+
return mem_id, mem_memory, original_content, source_doc_id
|
|
652
|
+
|
|
653
|
+
kb_log_content: list[dict] = []
|
|
654
|
+
|
|
655
|
+
for mem_item in add_records or []:
|
|
656
|
+
mem_id, mem_memory, _, source_doc_id = _extract_fields(mem_item)
|
|
657
|
+
if mem_id and mem_memory:
|
|
658
|
+
kb_log_content.append(
|
|
659
|
+
{
|
|
660
|
+
"log_source": "KNOWLEDGE_BASE_LOG",
|
|
661
|
+
"trigger_source": "Feedback",
|
|
662
|
+
"operation": "ADD",
|
|
663
|
+
"memory_id": mem_id,
|
|
664
|
+
"content": mem_memory,
|
|
665
|
+
"original_content": None,
|
|
666
|
+
"source_doc_id": source_doc_id,
|
|
667
|
+
}
|
|
668
|
+
)
|
|
669
|
+
else:
|
|
670
|
+
logger.warning(
|
|
671
|
+
"Skipping malformed feedback add item. user_id=%s mem_cube_id=%s task_id=%s item=%s",
|
|
672
|
+
user_id,
|
|
673
|
+
mem_cube_id,
|
|
674
|
+
task_id,
|
|
675
|
+
mem_item,
|
|
676
|
+
stack_info=True,
|
|
677
|
+
)
|
|
678
|
+
|
|
679
|
+
for mem_item in update_records or []:
|
|
680
|
+
mem_id, mem_memory, original_content, source_doc_id = _extract_fields(mem_item)
|
|
681
|
+
if mem_id and mem_memory:
|
|
682
|
+
kb_log_content.append(
|
|
683
|
+
{
|
|
684
|
+
"log_source": "KNOWLEDGE_BASE_LOG",
|
|
685
|
+
"trigger_source": "Feedback",
|
|
686
|
+
"operation": "UPDATE",
|
|
687
|
+
"memory_id": mem_id,
|
|
688
|
+
"content": mem_memory,
|
|
689
|
+
"original_content": original_content,
|
|
690
|
+
"source_doc_id": source_doc_id,
|
|
691
|
+
}
|
|
692
|
+
)
|
|
693
|
+
else:
|
|
694
|
+
logger.warning(
|
|
695
|
+
"Skipping malformed feedback update item. user_id=%s mem_cube_id=%s task_id=%s item=%s",
|
|
696
|
+
user_id,
|
|
697
|
+
mem_cube_id,
|
|
698
|
+
task_id,
|
|
699
|
+
mem_item,
|
|
700
|
+
stack_info=True,
|
|
701
|
+
)
|
|
702
|
+
|
|
703
|
+
logger.info(f"[Feedback Scheduler] kb_log_content: {kb_log_content!s}")
|
|
704
|
+
if kb_log_content:
|
|
705
|
+
logger.info(
|
|
706
|
+
"[DIAGNOSTIC] general_scheduler._mem_feedback_message_consumer: Creating knowledgeBaseUpdate event for feedback. user_id=%s mem_cube_id=%s task_id=%s items=%s",
|
|
707
|
+
user_id,
|
|
708
|
+
mem_cube_id,
|
|
709
|
+
task_id,
|
|
710
|
+
len(kb_log_content),
|
|
711
|
+
)
|
|
712
|
+
event = self.create_event_log(
|
|
713
|
+
label="knowledgeBaseUpdate",
|
|
714
|
+
from_memory_type=USER_INPUT_TYPE,
|
|
715
|
+
to_memory_type=LONG_TERM_MEMORY_TYPE,
|
|
716
|
+
user_id=user_id,
|
|
717
|
+
mem_cube_id=mem_cube_id,
|
|
718
|
+
mem_cube=mem_cube,
|
|
719
|
+
memcube_log_content=kb_log_content,
|
|
720
|
+
metadata=None,
|
|
721
|
+
memory_len=len(kb_log_content),
|
|
722
|
+
memcube_name=self._map_memcube_name(mem_cube_id),
|
|
723
|
+
)
|
|
724
|
+
event.log_content = (
|
|
725
|
+
f"Knowledge Base Memory Update: {len(kb_log_content)} changes."
|
|
726
|
+
)
|
|
727
|
+
event.task_id = task_id
|
|
728
|
+
self._submit_web_logs([event])
|
|
729
|
+
else:
|
|
730
|
+
logger.warning(
|
|
731
|
+
"No valid feedback content generated for web log. user_id=%s mem_cube_id=%s task_id=%s",
|
|
732
|
+
user_id,
|
|
733
|
+
mem_cube_id,
|
|
734
|
+
task_id,
|
|
735
|
+
stack_info=True,
|
|
736
|
+
)
|
|
737
|
+
else:
|
|
738
|
+
logger.info(
|
|
739
|
+
"Skipping web log for feedback. Not in a cloud environment (is_cloud_env=%s)",
|
|
740
|
+
cloud_env,
|
|
741
|
+
)
|
|
742
|
+
|
|
743
|
+
except Exception as e:
|
|
744
|
+
logger.error(f"Error processing feedbackMemory message: {e}", exc_info=True)
|
|
745
|
+
|
|
746
|
+
def _mem_read_message_consumer(self, messages: list[ScheduleMessageItem]) -> None:
|
|
747
|
+
logger.info(
|
|
748
|
+
f"[DIAGNOSTIC] general_scheduler._mem_read_message_consumer called. Received messages: {[msg.model_dump_json(indent=2) for msg in messages]}"
|
|
749
|
+
)
|
|
750
|
+
logger.info(f"Messages {messages} assigned to {MEM_READ_TASK_LABEL} handler.")
|
|
751
|
+
|
|
752
|
+
def process_message(message: ScheduleMessageItem):
|
|
753
|
+
try:
|
|
754
|
+
user_id = message.user_id
|
|
755
|
+
mem_cube_id = message.mem_cube_id
|
|
756
|
+
mem_cube = self.mem_cube
|
|
757
|
+
if mem_cube is None:
|
|
758
|
+
logger.error(
|
|
759
|
+
f"mem_cube is None for user_id={user_id}, mem_cube_id={mem_cube_id}, skipping processing",
|
|
760
|
+
stack_info=True,
|
|
761
|
+
)
|
|
762
|
+
return
|
|
763
|
+
|
|
764
|
+
content = message.content
|
|
765
|
+
user_name = message.user_name
|
|
766
|
+
info = message.info or {}
|
|
767
|
+
|
|
768
|
+
# Parse the memory IDs from content
|
|
769
|
+
mem_ids = json.loads(content) if isinstance(content, str) else content
|
|
770
|
+
if not mem_ids:
|
|
771
|
+
return
|
|
772
|
+
|
|
773
|
+
logger.info(
|
|
774
|
+
f"Processing mem_read for user_id={user_id}, mem_cube_id={mem_cube_id}, mem_ids={mem_ids}"
|
|
775
|
+
)
|
|
776
|
+
|
|
777
|
+
# Get the text memory from the mem_cube
|
|
778
|
+
text_mem = mem_cube.text_mem
|
|
779
|
+
if not isinstance(text_mem, TreeTextMemory):
|
|
780
|
+
logger.error(f"Expected TreeTextMemory but got {type(text_mem).__name__}")
|
|
781
|
+
return
|
|
782
|
+
|
|
783
|
+
# Use mem_reader to process the memories
|
|
784
|
+
self._process_memories_with_reader(
|
|
785
|
+
mem_ids=mem_ids,
|
|
786
|
+
user_id=user_id,
|
|
787
|
+
mem_cube_id=mem_cube_id,
|
|
788
|
+
text_mem=text_mem,
|
|
789
|
+
user_name=user_name,
|
|
790
|
+
custom_tags=info.get("custom_tags", None),
|
|
791
|
+
task_id=message.task_id,
|
|
792
|
+
info=info,
|
|
793
|
+
)
|
|
794
|
+
|
|
795
|
+
logger.info(
|
|
796
|
+
f"Successfully processed mem_read for user_id={user_id}, mem_cube_id={mem_cube_id}"
|
|
797
|
+
)
|
|
798
|
+
|
|
799
|
+
except Exception as e:
|
|
800
|
+
logger.error(f"Error processing mem_read message: {e}", stack_info=True)
|
|
801
|
+
|
|
802
|
+
with ContextThreadPoolExecutor(max_workers=min(8, len(messages))) as executor:
|
|
803
|
+
futures = [executor.submit(process_message, msg) for msg in messages]
|
|
804
|
+
for future in concurrent.futures.as_completed(futures):
|
|
805
|
+
try:
|
|
806
|
+
future.result()
|
|
807
|
+
except Exception as e:
|
|
808
|
+
logger.error(f"Thread task failed: {e}", stack_info=True)
|
|
809
|
+
|
|
810
|
+
def _process_memories_with_reader(
|
|
811
|
+
self,
|
|
812
|
+
mem_ids: list[str],
|
|
813
|
+
user_id: str,
|
|
814
|
+
mem_cube_id: str,
|
|
815
|
+
text_mem: TreeTextMemory,
|
|
816
|
+
user_name: str,
|
|
817
|
+
custom_tags: list[str] | None = None,
|
|
818
|
+
task_id: str | None = None,
|
|
819
|
+
info: dict | None = None,
|
|
820
|
+
) -> None:
|
|
821
|
+
logger.info(
|
|
822
|
+
f"[DIAGNOSTIC] general_scheduler._process_memories_with_reader called. mem_ids: {mem_ids}, user_id: {user_id}, mem_cube_id: {mem_cube_id}, task_id: {task_id}"
|
|
823
|
+
)
|
|
824
|
+
"""
|
|
825
|
+
Process memories using mem_reader for enhanced memory processing.
|
|
826
|
+
|
|
827
|
+
Args:
|
|
828
|
+
mem_ids: List of memory IDs to process
|
|
829
|
+
user_id: User ID
|
|
830
|
+
mem_cube_id: Memory cube ID
|
|
831
|
+
text_mem: Text memory instance
|
|
832
|
+
custom_tags: Optional list of custom tags for memory processing
|
|
833
|
+
"""
|
|
834
|
+
kb_log_content: list[dict] = []
|
|
835
|
+
try:
|
|
836
|
+
# Get the mem_reader from the parent MOSCore
|
|
837
|
+
if not hasattr(self, "mem_reader") or self.mem_reader is None:
|
|
838
|
+
logger.warning(
|
|
839
|
+
"mem_reader not available in scheduler, skipping enhanced processing"
|
|
840
|
+
)
|
|
841
|
+
return
|
|
842
|
+
|
|
843
|
+
# Get the original memory items
|
|
844
|
+
memory_items = []
|
|
845
|
+
for mem_id in mem_ids:
|
|
846
|
+
try:
|
|
847
|
+
memory_item = text_mem.get(mem_id, user_name=user_name)
|
|
848
|
+
memory_items.append(memory_item)
|
|
849
|
+
except Exception as e:
|
|
850
|
+
logger.warning(
|
|
851
|
+
f"[_process_memories_with_reader] Failed to get memory {mem_id}: {e}"
|
|
852
|
+
)
|
|
853
|
+
continue
|
|
854
|
+
|
|
855
|
+
if not memory_items:
|
|
856
|
+
logger.warning("No valid memory items found for processing")
|
|
857
|
+
return
|
|
858
|
+
|
|
859
|
+
# parse working_binding ids from the *original* memory_items (the raw items created in /add)
|
|
860
|
+
# these still carry metadata.background with "[working_binding:...]" so we can know
|
|
861
|
+
# which WorkingMemory clones should be cleaned up later.
|
|
862
|
+
from memos.memories.textual.tree_text_memory.organize.manager import (
|
|
863
|
+
extract_working_binding_ids,
|
|
864
|
+
)
|
|
865
|
+
|
|
866
|
+
bindings_to_delete = extract_working_binding_ids(memory_items)
|
|
867
|
+
logger.info(
|
|
868
|
+
f"Extracted {len(bindings_to_delete)} working_binding ids to cleanup: {list(bindings_to_delete)}"
|
|
869
|
+
)
|
|
870
|
+
|
|
871
|
+
# Use mem_reader to process the memories
|
|
872
|
+
logger.info(f"Processing {len(memory_items)} memories with mem_reader")
|
|
873
|
+
|
|
874
|
+
# Extract memories using mem_reader
|
|
875
|
+
try:
|
|
876
|
+
processed_memories = self.mem_reader.fine_transfer_simple_mem(
|
|
877
|
+
memory_items,
|
|
878
|
+
type="chat",
|
|
879
|
+
custom_tags=custom_tags,
|
|
880
|
+
user_name=user_name,
|
|
881
|
+
)
|
|
882
|
+
except Exception as e:
|
|
883
|
+
logger.warning(f"{e}: Fail to transfer mem: {memory_items}")
|
|
884
|
+
processed_memories = []
|
|
885
|
+
|
|
886
|
+
if processed_memories and len(processed_memories) > 0:
|
|
887
|
+
# Flatten the results (mem_reader returns list of lists)
|
|
888
|
+
flattened_memories = []
|
|
889
|
+
for memory_list in processed_memories:
|
|
890
|
+
flattened_memories.extend(memory_list)
|
|
891
|
+
|
|
892
|
+
logger.info(f"mem_reader processed {len(flattened_memories)} enhanced memories")
|
|
893
|
+
|
|
894
|
+
# Add the enhanced memories back to the memory system
|
|
895
|
+
if flattened_memories:
|
|
896
|
+
enhanced_mem_ids = text_mem.add(flattened_memories, user_name=user_name)
|
|
897
|
+
logger.info(
|
|
898
|
+
f"Added {len(enhanced_mem_ids)} enhanced memories: {enhanced_mem_ids}"
|
|
899
|
+
)
|
|
900
|
+
|
|
901
|
+
# Mark merged_from memories as archived when provided in memory metadata
|
|
902
|
+
if self.mem_reader.graph_db:
|
|
903
|
+
for memory in flattened_memories:
|
|
904
|
+
merged_from = (memory.metadata.info or {}).get("merged_from")
|
|
905
|
+
if merged_from:
|
|
906
|
+
old_ids = (
|
|
907
|
+
merged_from
|
|
908
|
+
if isinstance(merged_from, (list | tuple | set))
|
|
909
|
+
else [merged_from]
|
|
910
|
+
)
|
|
911
|
+
for old_id in old_ids:
|
|
912
|
+
try:
|
|
913
|
+
self.mem_reader.graph_db.update_node(
|
|
914
|
+
str(old_id), {"status": "archived"}, user_name=user_name
|
|
915
|
+
)
|
|
916
|
+
logger.info(
|
|
917
|
+
f"[Scheduler] Archived merged_from memory: {old_id}"
|
|
918
|
+
)
|
|
919
|
+
except Exception as e:
|
|
920
|
+
logger.warning(
|
|
921
|
+
f"[Scheduler] Failed to archive merged_from memory {old_id}: {e}"
|
|
922
|
+
)
|
|
923
|
+
else:
|
|
924
|
+
# Check if any memory has merged_from but graph_db is unavailable
|
|
925
|
+
has_merged_from = any(
|
|
926
|
+
(m.metadata.info or {}).get("merged_from") for m in flattened_memories
|
|
927
|
+
)
|
|
928
|
+
if has_merged_from:
|
|
929
|
+
logger.warning(
|
|
930
|
+
"[Scheduler] merged_from provided but graph_db is unavailable; skip archiving."
|
|
931
|
+
)
|
|
932
|
+
|
|
933
|
+
# LOGGING BLOCK START
|
|
934
|
+
# This block is replicated from _add_message_consumer to ensure consistent logging
|
|
935
|
+
cloud_env = is_cloud_env()
|
|
936
|
+
if cloud_env:
|
|
937
|
+
# New: Knowledge Base Logging (Cloud Service)
|
|
938
|
+
kb_log_content = []
|
|
939
|
+
for item in flattened_memories:
|
|
940
|
+
metadata = getattr(item, "metadata", None)
|
|
941
|
+
file_ids = getattr(metadata, "file_ids", None) if metadata else None
|
|
942
|
+
source_doc_id = (
|
|
943
|
+
file_ids[0] if isinstance(file_ids, list) and file_ids else None
|
|
944
|
+
)
|
|
945
|
+
kb_log_content.append(
|
|
946
|
+
{
|
|
947
|
+
"log_source": "KNOWLEDGE_BASE_LOG",
|
|
948
|
+
"trigger_source": info.get("trigger_source", "Messages")
|
|
949
|
+
if info
|
|
950
|
+
else "Messages",
|
|
951
|
+
"operation": "ADD",
|
|
952
|
+
"memory_id": item.id,
|
|
953
|
+
"content": item.memory,
|
|
954
|
+
"original_content": None,
|
|
955
|
+
"source_doc_id": source_doc_id,
|
|
956
|
+
}
|
|
957
|
+
)
|
|
958
|
+
if kb_log_content:
|
|
959
|
+
logger.info(
|
|
960
|
+
f"[DIAGNOSTIC] general_scheduler._process_memories_with_reader: Creating event log for KB update. Label: knowledgeBaseUpdate, user_id: {user_id}, mem_cube_id: {mem_cube_id}, task_id: {task_id}. KB content: {json.dumps(kb_log_content, indent=2)}"
|
|
961
|
+
)
|
|
962
|
+
event = self.create_event_log(
|
|
963
|
+
label="knowledgeBaseUpdate",
|
|
964
|
+
from_memory_type=USER_INPUT_TYPE,
|
|
965
|
+
to_memory_type=LONG_TERM_MEMORY_TYPE,
|
|
966
|
+
user_id=user_id,
|
|
967
|
+
mem_cube_id=mem_cube_id,
|
|
968
|
+
mem_cube=self.mem_cube,
|
|
969
|
+
memcube_log_content=kb_log_content,
|
|
970
|
+
metadata=None,
|
|
971
|
+
memory_len=len(kb_log_content),
|
|
972
|
+
memcube_name=self._map_memcube_name(mem_cube_id),
|
|
973
|
+
)
|
|
974
|
+
event.log_content = (
|
|
975
|
+
f"Knowledge Base Memory Update: {len(kb_log_content)} changes."
|
|
976
|
+
)
|
|
977
|
+
event.task_id = task_id
|
|
978
|
+
self._submit_web_logs([event])
|
|
979
|
+
else:
|
|
980
|
+
# Existing: Playground/Default Logging
|
|
981
|
+
add_content_legacy: list[dict] = []
|
|
982
|
+
add_meta_legacy: list[dict] = []
|
|
983
|
+
for item_id, item in zip(
|
|
984
|
+
enhanced_mem_ids, flattened_memories, strict=False
|
|
985
|
+
):
|
|
986
|
+
key = getattr(item.metadata, "key", None) or transform_name_to_key(
|
|
987
|
+
name=item.memory
|
|
988
|
+
)
|
|
989
|
+
add_content_legacy.append(
|
|
990
|
+
{"content": f"{key}: {item.memory}", "ref_id": item_id}
|
|
991
|
+
)
|
|
992
|
+
add_meta_legacy.append(
|
|
993
|
+
{
|
|
994
|
+
"ref_id": item_id,
|
|
995
|
+
"id": item_id,
|
|
996
|
+
"key": item.metadata.key,
|
|
997
|
+
"memory": item.memory,
|
|
998
|
+
"memory_type": item.metadata.memory_type,
|
|
999
|
+
"status": item.metadata.status,
|
|
1000
|
+
"confidence": item.metadata.confidence,
|
|
1001
|
+
"tags": item.metadata.tags,
|
|
1002
|
+
"updated_at": getattr(item.metadata, "updated_at", None)
|
|
1003
|
+
or getattr(item.metadata, "update_at", None),
|
|
1004
|
+
}
|
|
1005
|
+
)
|
|
1006
|
+
if add_content_legacy:
|
|
1007
|
+
event = self.create_event_log(
|
|
1008
|
+
label="addMemory",
|
|
1009
|
+
from_memory_type=USER_INPUT_TYPE,
|
|
1010
|
+
to_memory_type=LONG_TERM_MEMORY_TYPE,
|
|
1011
|
+
user_id=user_id,
|
|
1012
|
+
mem_cube_id=mem_cube_id,
|
|
1013
|
+
mem_cube=self.mem_cube,
|
|
1014
|
+
memcube_log_content=add_content_legacy,
|
|
1015
|
+
metadata=add_meta_legacy,
|
|
1016
|
+
memory_len=len(add_content_legacy),
|
|
1017
|
+
memcube_name=self._map_memcube_name(mem_cube_id),
|
|
1018
|
+
)
|
|
1019
|
+
event.task_id = task_id
|
|
1020
|
+
self._submit_web_logs([event])
|
|
1021
|
+
# LOGGING BLOCK END
|
|
1022
|
+
else:
|
|
1023
|
+
logger.info("No enhanced memories generated by mem_reader")
|
|
1024
|
+
else:
|
|
1025
|
+
logger.info("mem_reader returned no processed memories")
|
|
1026
|
+
|
|
1027
|
+
# build full delete list:
|
|
1028
|
+
# - original raw mem_ids (temporary fast memories)
|
|
1029
|
+
# - any bound working memories referenced by the enhanced memories
|
|
1030
|
+
delete_ids = list(mem_ids)
|
|
1031
|
+
if bindings_to_delete:
|
|
1032
|
+
delete_ids.extend(list(bindings_to_delete))
|
|
1033
|
+
# deduplicate
|
|
1034
|
+
delete_ids = list(dict.fromkeys(delete_ids))
|
|
1035
|
+
if delete_ids:
|
|
1036
|
+
try:
|
|
1037
|
+
text_mem.delete(delete_ids, user_name=user_name)
|
|
1038
|
+
logger.info(
|
|
1039
|
+
f"Delete raw/working mem_ids: {delete_ids} for user_name: {user_name}"
|
|
1040
|
+
)
|
|
1041
|
+
except Exception as e:
|
|
1042
|
+
logger.warning(f"Failed to delete some mem_ids {delete_ids}: {e}")
|
|
1043
|
+
else:
|
|
1044
|
+
logger.info("No mem_ids to delete (nothing to cleanup)")
|
|
1045
|
+
|
|
1046
|
+
text_mem.memory_manager.remove_and_refresh_memory(user_name=user_name)
|
|
1047
|
+
logger.info("Remove and Refresh Memories")
|
|
1048
|
+
logger.debug(f"Finished add {user_id} memory: {mem_ids}")
|
|
1049
|
+
|
|
1050
|
+
except Exception as exc:
|
|
1051
|
+
logger.error(
|
|
1052
|
+
f"Error in _process_memories_with_reader: {traceback.format_exc()}", exc_info=True
|
|
1053
|
+
)
|
|
1054
|
+
with contextlib.suppress(Exception):
|
|
1055
|
+
cloud_env = is_cloud_env()
|
|
1056
|
+
if cloud_env:
|
|
1057
|
+
if not kb_log_content:
|
|
1058
|
+
trigger_source = (
|
|
1059
|
+
info.get("trigger_source", "Messages") if info else "Messages"
|
|
1060
|
+
)
|
|
1061
|
+
kb_log_content = [
|
|
1062
|
+
{
|
|
1063
|
+
"log_source": "KNOWLEDGE_BASE_LOG",
|
|
1064
|
+
"trigger_source": trigger_source,
|
|
1065
|
+
"operation": "ADD",
|
|
1066
|
+
"memory_id": mem_id,
|
|
1067
|
+
"content": None,
|
|
1068
|
+
"original_content": None,
|
|
1069
|
+
"source_doc_id": None,
|
|
1070
|
+
}
|
|
1071
|
+
for mem_id in mem_ids
|
|
1072
|
+
]
|
|
1073
|
+
event = self.create_event_log(
|
|
1074
|
+
label="knowledgeBaseUpdate",
|
|
1075
|
+
from_memory_type=USER_INPUT_TYPE,
|
|
1076
|
+
to_memory_type=LONG_TERM_MEMORY_TYPE,
|
|
1077
|
+
user_id=user_id,
|
|
1078
|
+
mem_cube_id=mem_cube_id,
|
|
1079
|
+
mem_cube=self.mem_cube,
|
|
1080
|
+
memcube_log_content=kb_log_content,
|
|
1081
|
+
metadata=None,
|
|
1082
|
+
memory_len=len(kb_log_content),
|
|
1083
|
+
memcube_name=self._map_memcube_name(mem_cube_id),
|
|
1084
|
+
)
|
|
1085
|
+
event.log_content = f"Knowledge Base Memory Update failed: {exc!s}"
|
|
1086
|
+
event.task_id = task_id
|
|
1087
|
+
event.status = "failed"
|
|
1088
|
+
self._submit_web_logs([event])
|
|
1089
|
+
|
|
1090
|
+
def _mem_reorganize_message_consumer(self, messages: list[ScheduleMessageItem]) -> None:
|
|
1091
|
+
logger.info(f"Messages {messages} assigned to {MEM_ORGANIZE_TASK_LABEL} handler.")
|
|
1092
|
+
|
|
1093
|
+
def process_message(message: ScheduleMessageItem):
|
|
1094
|
+
try:
|
|
1095
|
+
user_id = message.user_id
|
|
1096
|
+
mem_cube_id = message.mem_cube_id
|
|
1097
|
+
mem_cube = self.mem_cube
|
|
1098
|
+
if mem_cube is None:
|
|
1099
|
+
logger.warning(
|
|
1100
|
+
f"mem_cube is None for user_id={user_id}, mem_cube_id={mem_cube_id}, skipping processing"
|
|
1101
|
+
)
|
|
1102
|
+
return
|
|
1103
|
+
content = message.content
|
|
1104
|
+
user_name = message.user_name
|
|
1105
|
+
|
|
1106
|
+
# Parse the memory IDs from content
|
|
1107
|
+
mem_ids = json.loads(content) if isinstance(content, str) else content
|
|
1108
|
+
if not mem_ids:
|
|
1109
|
+
return
|
|
1110
|
+
|
|
1111
|
+
logger.info(
|
|
1112
|
+
f"Processing mem_reorganize for user_id={user_id}, mem_cube_id={mem_cube_id}, mem_ids={mem_ids}"
|
|
1113
|
+
)
|
|
1114
|
+
|
|
1115
|
+
# Get the text memory from the mem_cube
|
|
1116
|
+
text_mem = mem_cube.text_mem
|
|
1117
|
+
if not isinstance(text_mem, TreeTextMemory):
|
|
1118
|
+
logger.error(f"Expected TreeTextMemory but got {type(text_mem).__name__}")
|
|
1119
|
+
return
|
|
1120
|
+
|
|
1121
|
+
# Use mem_reader to process the memories
|
|
1122
|
+
self._process_memories_with_reorganize(
|
|
1123
|
+
mem_ids=mem_ids,
|
|
1124
|
+
user_id=user_id,
|
|
1125
|
+
mem_cube_id=mem_cube_id,
|
|
1126
|
+
mem_cube=mem_cube,
|
|
1127
|
+
text_mem=text_mem,
|
|
1128
|
+
user_name=user_name,
|
|
1129
|
+
)
|
|
1130
|
+
|
|
1131
|
+
with contextlib.suppress(Exception):
|
|
1132
|
+
mem_items: list[TextualMemoryItem] = []
|
|
1133
|
+
for mid in mem_ids:
|
|
1134
|
+
with contextlib.suppress(Exception):
|
|
1135
|
+
mem_items.append(text_mem.get(mid, user_name=user_name))
|
|
1136
|
+
if len(mem_items) > 1:
|
|
1137
|
+
keys: list[str] = []
|
|
1138
|
+
memcube_content: list[dict] = []
|
|
1139
|
+
meta: list[dict] = []
|
|
1140
|
+
merged_target_ids: set[str] = set()
|
|
1141
|
+
with contextlib.suppress(Exception):
|
|
1142
|
+
if hasattr(text_mem, "graph_store"):
|
|
1143
|
+
for mid in mem_ids:
|
|
1144
|
+
edges = text_mem.graph_store.get_edges(
|
|
1145
|
+
mid, type="MERGED_TO", direction="OUT"
|
|
1146
|
+
)
|
|
1147
|
+
for edge in edges:
|
|
1148
|
+
target = (
|
|
1149
|
+
edge.get("to") or edge.get("dst") or edge.get("target")
|
|
1150
|
+
)
|
|
1151
|
+
if target:
|
|
1152
|
+
merged_target_ids.add(target)
|
|
1153
|
+
for item in mem_items:
|
|
1154
|
+
key = getattr(
|
|
1155
|
+
getattr(item, "metadata", {}), "key", None
|
|
1156
|
+
) or transform_name_to_key(getattr(item, "memory", ""))
|
|
1157
|
+
keys.append(key)
|
|
1158
|
+
memcube_content.append(
|
|
1159
|
+
{"content": key or "(no key)", "ref_id": item.id, "type": "merged"}
|
|
1160
|
+
)
|
|
1161
|
+
meta.append(
|
|
1162
|
+
{
|
|
1163
|
+
"ref_id": item.id,
|
|
1164
|
+
"id": item.id,
|
|
1165
|
+
"key": key,
|
|
1166
|
+
"memory": item.memory,
|
|
1167
|
+
"memory_type": item.metadata.memory_type,
|
|
1168
|
+
"status": item.metadata.status,
|
|
1169
|
+
"confidence": item.metadata.confidence,
|
|
1170
|
+
"tags": item.metadata.tags,
|
|
1171
|
+
"updated_at": getattr(item.metadata, "updated_at", None)
|
|
1172
|
+
or getattr(item.metadata, "update_at", None),
|
|
1173
|
+
}
|
|
1174
|
+
)
|
|
1175
|
+
combined_key = keys[0] if keys else ""
|
|
1176
|
+
post_ref_id = None
|
|
1177
|
+
post_meta = {
|
|
1178
|
+
"ref_id": None,
|
|
1179
|
+
"id": None,
|
|
1180
|
+
"key": None,
|
|
1181
|
+
"memory": None,
|
|
1182
|
+
"memory_type": None,
|
|
1183
|
+
"status": None,
|
|
1184
|
+
"confidence": None,
|
|
1185
|
+
"tags": None,
|
|
1186
|
+
"updated_at": None,
|
|
1187
|
+
}
|
|
1188
|
+
if merged_target_ids:
|
|
1189
|
+
post_ref_id = next(iter(merged_target_ids))
|
|
1190
|
+
with contextlib.suppress(Exception):
|
|
1191
|
+
merged_item = text_mem.get(post_ref_id, user_name=user_name)
|
|
1192
|
+
combined_key = (
|
|
1193
|
+
getattr(getattr(merged_item, "metadata", {}), "key", None)
|
|
1194
|
+
or combined_key
|
|
1195
|
+
)
|
|
1196
|
+
post_meta = {
|
|
1197
|
+
"ref_id": post_ref_id,
|
|
1198
|
+
"id": post_ref_id,
|
|
1199
|
+
"key": getattr(
|
|
1200
|
+
getattr(merged_item, "metadata", {}), "key", None
|
|
1201
|
+
),
|
|
1202
|
+
"memory": getattr(merged_item, "memory", None),
|
|
1203
|
+
"memory_type": getattr(
|
|
1204
|
+
getattr(merged_item, "metadata", {}), "memory_type", None
|
|
1205
|
+
),
|
|
1206
|
+
"status": getattr(
|
|
1207
|
+
getattr(merged_item, "metadata", {}), "status", None
|
|
1208
|
+
),
|
|
1209
|
+
"confidence": getattr(
|
|
1210
|
+
getattr(merged_item, "metadata", {}), "confidence", None
|
|
1211
|
+
),
|
|
1212
|
+
"tags": getattr(
|
|
1213
|
+
getattr(merged_item, "metadata", {}), "tags", None
|
|
1214
|
+
),
|
|
1215
|
+
"updated_at": getattr(
|
|
1216
|
+
getattr(merged_item, "metadata", {}), "updated_at", None
|
|
1217
|
+
)
|
|
1218
|
+
or getattr(
|
|
1219
|
+
getattr(merged_item, "metadata", {}), "update_at", None
|
|
1220
|
+
),
|
|
1221
|
+
}
|
|
1222
|
+
if not post_ref_id:
|
|
1223
|
+
import hashlib
|
|
1224
|
+
|
|
1225
|
+
post_ref_id = f"merge-{hashlib.md5(''.join(sorted(mem_ids)).encode()).hexdigest()}"
|
|
1226
|
+
post_meta["ref_id"] = post_ref_id
|
|
1227
|
+
post_meta["id"] = post_ref_id
|
|
1228
|
+
if not post_meta.get("key"):
|
|
1229
|
+
post_meta["key"] = combined_key
|
|
1230
|
+
if not keys:
|
|
1231
|
+
keys = [item.id for item in mem_items]
|
|
1232
|
+
memcube_content.append(
|
|
1233
|
+
{
|
|
1234
|
+
"content": combined_key if combined_key else "(no key)",
|
|
1235
|
+
"ref_id": post_ref_id,
|
|
1236
|
+
"type": "postMerge",
|
|
1237
|
+
}
|
|
1238
|
+
)
|
|
1239
|
+
meta.append(post_meta)
|
|
1240
|
+
event = self.create_event_log(
|
|
1241
|
+
label="mergeMemory",
|
|
1242
|
+
from_memory_type=LONG_TERM_MEMORY_TYPE,
|
|
1243
|
+
to_memory_type=LONG_TERM_MEMORY_TYPE,
|
|
1244
|
+
user_id=user_id,
|
|
1245
|
+
mem_cube_id=mem_cube_id,
|
|
1246
|
+
mem_cube=mem_cube,
|
|
1247
|
+
memcube_log_content=memcube_content,
|
|
1248
|
+
metadata=meta,
|
|
1249
|
+
memory_len=len(keys),
|
|
1250
|
+
memcube_name=self._map_memcube_name(mem_cube_id),
|
|
1251
|
+
)
|
|
1252
|
+
self._submit_web_logs([event])
|
|
1253
|
+
|
|
1254
|
+
logger.info(
|
|
1255
|
+
f"Successfully processed mem_reorganize for user_id={user_id}, mem_cube_id={mem_cube_id}"
|
|
1256
|
+
)
|
|
1257
|
+
|
|
1258
|
+
except Exception as e:
|
|
1259
|
+
logger.error(f"Error processing mem_reorganize message: {e}", exc_info=True)
|
|
1260
|
+
|
|
1261
|
+
with ContextThreadPoolExecutor(max_workers=min(8, len(messages))) as executor:
|
|
1262
|
+
futures = [executor.submit(process_message, msg) for msg in messages]
|
|
1263
|
+
for future in concurrent.futures.as_completed(futures):
|
|
1264
|
+
try:
|
|
1265
|
+
future.result()
|
|
1266
|
+
except Exception as e:
|
|
1267
|
+
logger.error(f"Thread task failed: {e}", exc_info=True)
|
|
1268
|
+
|
|
1269
|
+
def _process_memories_with_reorganize(
|
|
1270
|
+
self,
|
|
1271
|
+
mem_ids: list[str],
|
|
1272
|
+
user_id: str,
|
|
1273
|
+
mem_cube_id: str,
|
|
1274
|
+
mem_cube: GeneralMemCube,
|
|
1275
|
+
text_mem: TreeTextMemory,
|
|
1276
|
+
user_name: str,
|
|
1277
|
+
) -> None:
|
|
1278
|
+
"""
|
|
1279
|
+
Process memories using mem_reorganize for enhanced memory processing.
|
|
1280
|
+
|
|
1281
|
+
Args:
|
|
1282
|
+
mem_ids: List of memory IDs to process
|
|
1283
|
+
user_id: User ID
|
|
1284
|
+
mem_cube_id: Memory cube ID
|
|
1285
|
+
mem_cube: Memory cube instance
|
|
1286
|
+
text_mem: Text memory instance
|
|
1287
|
+
"""
|
|
1288
|
+
try:
|
|
1289
|
+
# Get the mem_reader from the parent MOSCore
|
|
1290
|
+
if not hasattr(self, "mem_reader") or self.mem_reader is None:
|
|
1291
|
+
logger.warning(
|
|
1292
|
+
"mem_reader not available in scheduler, skipping enhanced processing"
|
|
1293
|
+
)
|
|
1294
|
+
return
|
|
1295
|
+
|
|
1296
|
+
# Get the original memory items
|
|
1297
|
+
memory_items = []
|
|
1298
|
+
for mem_id in mem_ids:
|
|
1299
|
+
try:
|
|
1300
|
+
memory_item = text_mem.get(mem_id, user_name=user_name)
|
|
1301
|
+
memory_items.append(memory_item)
|
|
1302
|
+
except Exception as e:
|
|
1303
|
+
logger.warning(f"Failed to get memory {mem_id}: {e}|{traceback.format_exc()}")
|
|
1304
|
+
continue
|
|
1305
|
+
|
|
1306
|
+
if not memory_items:
|
|
1307
|
+
logger.warning("No valid memory items found for processing")
|
|
1308
|
+
return
|
|
1309
|
+
|
|
1310
|
+
# Use mem_reader to process the memories
|
|
1311
|
+
logger.info(f"Processing {len(memory_items)} memories with mem_reader")
|
|
1312
|
+
text_mem.memory_manager.remove_and_refresh_memory(user_name=user_name)
|
|
1313
|
+
logger.info("Remove and Refresh Memories")
|
|
1314
|
+
logger.debug(f"Finished add {user_id} memory: {mem_ids}")
|
|
1315
|
+
|
|
1316
|
+
except Exception:
|
|
1317
|
+
logger.error(
|
|
1318
|
+
f"Error in _process_memories_with_reorganize: {traceback.format_exc()}",
|
|
1319
|
+
exc_info=True,
|
|
1320
|
+
)
|
|
1321
|
+
|
|
1322
|
+
def _pref_add_message_consumer(self, messages: list[ScheduleMessageItem]) -> None:
|
|
1323
|
+
logger.info(f"Messages {messages} assigned to {PREF_ADD_TASK_LABEL} handler.")
|
|
1324
|
+
|
|
1325
|
+
def process_message(message: ScheduleMessageItem):
|
|
1326
|
+
try:
|
|
1327
|
+
mem_cube = self.mem_cube
|
|
1328
|
+
if mem_cube is None:
|
|
1329
|
+
logger.warning(
|
|
1330
|
+
f"mem_cube is None for user_id={message.user_id}, mem_cube_id={message.mem_cube_id}, skipping processing"
|
|
1331
|
+
)
|
|
1332
|
+
return
|
|
1333
|
+
|
|
1334
|
+
user_id = message.user_id
|
|
1335
|
+
session_id = message.session_id
|
|
1336
|
+
mem_cube_id = message.mem_cube_id
|
|
1337
|
+
content = message.content
|
|
1338
|
+
messages_list = json.loads(content)
|
|
1339
|
+
info = message.info or {}
|
|
1340
|
+
|
|
1341
|
+
logger.info(f"Processing pref_add for user_id={user_id}, mem_cube_id={mem_cube_id}")
|
|
1342
|
+
|
|
1343
|
+
# Get the preference memory from the mem_cube
|
|
1344
|
+
pref_mem = mem_cube.pref_mem
|
|
1345
|
+
if pref_mem is None:
|
|
1346
|
+
logger.warning(
|
|
1347
|
+
f"Preference memory not initialized for mem_cube_id={mem_cube_id}, "
|
|
1348
|
+
f"skipping pref_add processing"
|
|
1349
|
+
)
|
|
1350
|
+
return
|
|
1351
|
+
if not isinstance(pref_mem, PreferenceTextMemory):
|
|
1352
|
+
logger.error(
|
|
1353
|
+
f"Expected PreferenceTextMemory but got {type(pref_mem).__name__} "
|
|
1354
|
+
f"for mem_cube_id={mem_cube_id}"
|
|
1355
|
+
)
|
|
1356
|
+
return
|
|
1357
|
+
|
|
1358
|
+
# Use pref_mem.get_memory to process the memories
|
|
1359
|
+
pref_memories = pref_mem.get_memory(
|
|
1360
|
+
messages_list,
|
|
1361
|
+
type="chat",
|
|
1362
|
+
info={
|
|
1363
|
+
**info,
|
|
1364
|
+
"user_id": user_id,
|
|
1365
|
+
"session_id": session_id,
|
|
1366
|
+
"mem_cube_id": mem_cube_id,
|
|
1367
|
+
},
|
|
1368
|
+
)
|
|
1369
|
+
# Add pref_mem to vector db
|
|
1370
|
+
pref_ids = pref_mem.add(pref_memories)
|
|
1371
|
+
|
|
1372
|
+
logger.info(
|
|
1373
|
+
f"Successfully processed and add preferences for user_id={user_id}, mem_cube_id={mem_cube_id}, pref_ids={pref_ids}"
|
|
1374
|
+
)
|
|
1375
|
+
|
|
1376
|
+
except Exception as e:
|
|
1377
|
+
logger.error(f"Error processing pref_add message: {e}", exc_info=True)
|
|
1378
|
+
|
|
1379
|
+
with ContextThreadPoolExecutor(max_workers=min(8, len(messages))) as executor:
|
|
1380
|
+
futures = [executor.submit(process_message, msg) for msg in messages]
|
|
1381
|
+
for future in concurrent.futures.as_completed(futures):
|
|
1382
|
+
try:
|
|
1383
|
+
future.result()
|
|
1384
|
+
except Exception as e:
|
|
1385
|
+
logger.error(f"Thread task failed: {e}", exc_info=True)
|
|
1386
|
+
|
|
1387
|
+
def process_session_turn(
|
|
1388
|
+
self,
|
|
1389
|
+
queries: str | list[str],
|
|
1390
|
+
user_id: UserID | str,
|
|
1391
|
+
mem_cube_id: MemCubeID | str,
|
|
1392
|
+
mem_cube: GeneralMemCube,
|
|
1393
|
+
top_k: int = 10,
|
|
1394
|
+
) -> tuple[list[TextualMemoryItem], list[TextualMemoryItem]] | None:
|
|
1395
|
+
"""
|
|
1396
|
+
Process a dialog turn:
|
|
1397
|
+
- If q_list reaches window size, trigger retrieval;
|
|
1398
|
+
- Immediately switch to the new memory if retrieval is triggered.
|
|
1399
|
+
"""
|
|
1400
|
+
|
|
1401
|
+
text_mem_base = mem_cube.text_mem
|
|
1402
|
+
if not isinstance(text_mem_base, TreeTextMemory):
|
|
1403
|
+
if isinstance(text_mem_base, NaiveTextMemory):
|
|
1404
|
+
logger.debug(
|
|
1405
|
+
f"NaiveTextMemory used for mem_cube_id={mem_cube_id}, processing session turn with simple search."
|
|
1406
|
+
)
|
|
1407
|
+
# Treat NaiveTextMemory similar to TreeTextMemory but with simpler logic
|
|
1408
|
+
# We will perform retrieval to get "working memory" candidates for activation memory
|
|
1409
|
+
# But we won't have a distinct "current working memory"
|
|
1410
|
+
cur_working_memory = []
|
|
1411
|
+
else:
|
|
1412
|
+
logger.warning(
|
|
1413
|
+
f"Not implemented! Expected TreeTextMemory but got {type(text_mem_base).__name__} "
|
|
1414
|
+
f"for mem_cube_id={mem_cube_id}, user_id={user_id}. "
|
|
1415
|
+
f"text_mem_base value: {text_mem_base}"
|
|
1416
|
+
)
|
|
1417
|
+
return [], []
|
|
1418
|
+
else:
|
|
1419
|
+
cur_working_memory: list[TextualMemoryItem] = text_mem_base.get_working_memory(
|
|
1420
|
+
user_name=mem_cube_id
|
|
1421
|
+
)
|
|
1422
|
+
cur_working_memory = cur_working_memory[:top_k]
|
|
1423
|
+
|
|
1424
|
+
logger.info(
|
|
1425
|
+
f"[process_session_turn] Processing {len(queries)} queries for user_id={user_id}, mem_cube_id={mem_cube_id}"
|
|
1426
|
+
)
|
|
1427
|
+
|
|
1428
|
+
text_working_memory: list[str] = [w_m.memory for w_m in cur_working_memory]
|
|
1429
|
+
intent_result = self.monitor.detect_intent(
|
|
1430
|
+
q_list=queries, text_working_memory=text_working_memory
|
|
1431
|
+
)
|
|
1432
|
+
|
|
1433
|
+
time_trigger_flag = False
|
|
1434
|
+
if self.monitor.timed_trigger(
|
|
1435
|
+
last_time=self.monitor.last_query_consume_time,
|
|
1436
|
+
interval_seconds=self.monitor.query_trigger_interval,
|
|
1437
|
+
):
|
|
1438
|
+
time_trigger_flag = True
|
|
1439
|
+
|
|
1440
|
+
if (not intent_result["trigger_retrieval"]) and (not time_trigger_flag):
|
|
1441
|
+
logger.info(
|
|
1442
|
+
f"[process_session_turn] Query schedule not triggered for user_id={user_id}, mem_cube_id={mem_cube_id}. Intent_result: {intent_result}"
|
|
1443
|
+
)
|
|
1444
|
+
return
|
|
1445
|
+
elif (not intent_result["trigger_retrieval"]) and time_trigger_flag:
|
|
1446
|
+
logger.info(
|
|
1447
|
+
f"[process_session_turn] Query schedule forced to trigger due to time ticker for user_id={user_id}, mem_cube_id={mem_cube_id}"
|
|
1448
|
+
)
|
|
1449
|
+
intent_result["trigger_retrieval"] = True
|
|
1450
|
+
intent_result["missing_evidences"] = queries
|
|
1451
|
+
else:
|
|
1452
|
+
logger.info(
|
|
1453
|
+
f"[process_session_turn] Query schedule triggered for user_id={user_id}, mem_cube_id={mem_cube_id}. "
|
|
1454
|
+
f"Missing evidences: {intent_result['missing_evidences']}"
|
|
1455
|
+
)
|
|
1456
|
+
|
|
1457
|
+
missing_evidences = intent_result["missing_evidences"]
|
|
1458
|
+
num_evidence = len(missing_evidences)
|
|
1459
|
+
k_per_evidence = max(1, top_k // max(1, num_evidence))
|
|
1460
|
+
new_candidates = []
|
|
1461
|
+
for item in missing_evidences:
|
|
1462
|
+
logger.info(
|
|
1463
|
+
f"[process_session_turn] Searching for missing evidence: '{item}' with top_k={k_per_evidence} for user_id={user_id}"
|
|
1464
|
+
)
|
|
1465
|
+
|
|
1466
|
+
search_args = {}
|
|
1467
|
+
if isinstance(text_mem_base, NaiveTextMemory):
|
|
1468
|
+
# NaiveTextMemory doesn't support complex search args usually, but let's see
|
|
1469
|
+
# self.retriever.search calls mem_cube.text_mem.search
|
|
1470
|
+
# NaiveTextMemory.search takes query and top_k
|
|
1471
|
+
# SchedulerRetriever.search handles method dispatch
|
|
1472
|
+
# For NaiveTextMemory, we might need to bypass retriever or extend it
|
|
1473
|
+
# But let's try calling naive memory directly if retriever fails or doesn't support it
|
|
1474
|
+
try:
|
|
1475
|
+
results = text_mem_base.search(query=item, top_k=k_per_evidence)
|
|
1476
|
+
except Exception as e:
|
|
1477
|
+
logger.warning(f"NaiveTextMemory search failed: {e}")
|
|
1478
|
+
results = []
|
|
1479
|
+
else:
|
|
1480
|
+
results: list[TextualMemoryItem] = self.retriever.search(
|
|
1481
|
+
query=item,
|
|
1482
|
+
user_id=user_id,
|
|
1483
|
+
mem_cube_id=mem_cube_id,
|
|
1484
|
+
mem_cube=mem_cube,
|
|
1485
|
+
top_k=k_per_evidence,
|
|
1486
|
+
method=self.search_method,
|
|
1487
|
+
search_args=search_args,
|
|
1488
|
+
)
|
|
1489
|
+
|
|
1490
|
+
logger.info(
|
|
1491
|
+
f"[process_session_turn] Search results for missing evidence '{item}': "
|
|
1492
|
+
+ ("\n- " + "\n- ".join([f"{one.id}: {one.memory}" for one in results]))
|
|
1493
|
+
)
|
|
1494
|
+
new_candidates.extend(results)
|
|
1495
|
+
return cur_working_memory, new_candidates
|