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,547 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
3
|
+
from concurrent.futures import as_completed
|
|
4
|
+
|
|
5
|
+
from memos.configs.mem_scheduler import BaseSchedulerConfig
|
|
6
|
+
from memos.context.context import ContextThreadPoolExecutor
|
|
7
|
+
from memos.llms.base import BaseLLM
|
|
8
|
+
from memos.log import get_logger
|
|
9
|
+
from memos.mem_cube.general import GeneralMemCube
|
|
10
|
+
from memos.mem_scheduler.general_modules.base import BaseSchedulerModule
|
|
11
|
+
from memos.mem_scheduler.schemas.general_schemas import (
|
|
12
|
+
DEFAULT_SCHEDULER_RETRIEVER_BATCH_SIZE,
|
|
13
|
+
DEFAULT_SCHEDULER_RETRIEVER_RETRIES,
|
|
14
|
+
TreeTextMemory_FINE_SEARCH_METHOD,
|
|
15
|
+
TreeTextMemory_SEARCH_METHOD,
|
|
16
|
+
)
|
|
17
|
+
from memos.mem_scheduler.utils.filter_utils import (
|
|
18
|
+
filter_too_short_memories,
|
|
19
|
+
filter_vector_based_similar_memories,
|
|
20
|
+
transform_name_to_key,
|
|
21
|
+
)
|
|
22
|
+
from memos.mem_scheduler.utils.misc_utils import extract_json_obj, extract_list_items_in_answer
|
|
23
|
+
from memos.memories.textual.item import TextualMemoryMetadata
|
|
24
|
+
from memos.memories.textual.tree import TextualMemoryItem, TreeTextMemory
|
|
25
|
+
from memos.types.general_types import (
|
|
26
|
+
FINE_STRATEGY,
|
|
27
|
+
FineStrategy,
|
|
28
|
+
SearchMode,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# Extract JSON response
|
|
32
|
+
from .memory_filter import MemoryFilter
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
logger = get_logger(__name__)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class SchedulerRetriever(BaseSchedulerModule):
|
|
39
|
+
def __init__(self, process_llm: BaseLLM, config: BaseSchedulerConfig):
|
|
40
|
+
super().__init__()
|
|
41
|
+
|
|
42
|
+
# hyper-parameters
|
|
43
|
+
self.filter_similarity_threshold = 0.75
|
|
44
|
+
self.filter_min_length_threshold = 6
|
|
45
|
+
self.memory_filter = MemoryFilter(process_llm=process_llm, config=config)
|
|
46
|
+
self.process_llm = process_llm
|
|
47
|
+
self.config = config
|
|
48
|
+
|
|
49
|
+
# Configure enhancement batching & retries from config with safe defaults
|
|
50
|
+
self.batch_size: int | None = getattr(
|
|
51
|
+
config, "scheduler_retriever_batch_size", DEFAULT_SCHEDULER_RETRIEVER_BATCH_SIZE
|
|
52
|
+
)
|
|
53
|
+
self.retries: int = getattr(
|
|
54
|
+
config, "scheduler_retriever_enhance_retries", DEFAULT_SCHEDULER_RETRIEVER_RETRIES
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
def evaluate_memory_answer_ability(
|
|
58
|
+
self, query: str, memory_texts: list[str], top_k: int | None = None
|
|
59
|
+
) -> bool:
|
|
60
|
+
limited_memories = memory_texts[:top_k] if top_k is not None else memory_texts
|
|
61
|
+
# Build prompt using the template
|
|
62
|
+
prompt = self.build_prompt(
|
|
63
|
+
template_name="memory_answer_ability_evaluation",
|
|
64
|
+
query=query,
|
|
65
|
+
memory_list="\n".join([f"- {memory}" for memory in limited_memories])
|
|
66
|
+
if limited_memories
|
|
67
|
+
else "No memories available",
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# Use the process LLM to generate response
|
|
71
|
+
response = self.process_llm.generate([{"role": "user", "content": prompt}])
|
|
72
|
+
|
|
73
|
+
try:
|
|
74
|
+
result = extract_json_obj(response)
|
|
75
|
+
|
|
76
|
+
# Validate response structure
|
|
77
|
+
if "result" in result:
|
|
78
|
+
logger.info(
|
|
79
|
+
f"Answerability: result={result['result']}; reason={result.get('reason', 'n/a')}; evaluated={len(limited_memories)}"
|
|
80
|
+
)
|
|
81
|
+
return result["result"]
|
|
82
|
+
else:
|
|
83
|
+
logger.warning(f"Answerability: invalid LLM JSON structure; payload={result}")
|
|
84
|
+
return False
|
|
85
|
+
|
|
86
|
+
except Exception as e:
|
|
87
|
+
logger.error(f"Answerability: parse failed; err={e}; raw={str(response)[:200]}...")
|
|
88
|
+
# Fallback: return False if we can't determine answer ability
|
|
89
|
+
return False
|
|
90
|
+
|
|
91
|
+
# ---------------------- Enhancement helpers ----------------------
|
|
92
|
+
def _build_enhancement_prompt(self, query_history: list[str], batch_texts: list[str]) -> str:
|
|
93
|
+
if len(query_history) == 1:
|
|
94
|
+
query_history = query_history[0]
|
|
95
|
+
else:
|
|
96
|
+
query_history = (
|
|
97
|
+
[f"[{i}] {query}" for i, query in enumerate(query_history)]
|
|
98
|
+
if len(query_history) > 1
|
|
99
|
+
else query_history[0]
|
|
100
|
+
)
|
|
101
|
+
# Include numbering for rewrite mode to help LLM reference original memory IDs
|
|
102
|
+
if FINE_STRATEGY == FineStrategy.REWRITE:
|
|
103
|
+
text_memories = "\n".join([f"- [{i}] {mem}" for i, mem in enumerate(batch_texts)])
|
|
104
|
+
prompt_name = "memory_rewrite_enhancement"
|
|
105
|
+
else:
|
|
106
|
+
text_memories = "\n".join([f"- {mem}" for i, mem in enumerate(batch_texts)])
|
|
107
|
+
prompt_name = "memory_recreate_enhancement"
|
|
108
|
+
return self.build_prompt(
|
|
109
|
+
prompt_name,
|
|
110
|
+
query_history=query_history,
|
|
111
|
+
memories=text_memories,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
def _process_enhancement_batch(
|
|
115
|
+
self,
|
|
116
|
+
batch_index: int,
|
|
117
|
+
query_history: list[str],
|
|
118
|
+
memories: list[TextualMemoryItem],
|
|
119
|
+
retries: int,
|
|
120
|
+
) -> tuple[list[TextualMemoryItem], bool]:
|
|
121
|
+
attempt = 0
|
|
122
|
+
text_memories = [one.memory for one in memories]
|
|
123
|
+
|
|
124
|
+
prompt = self._build_enhancement_prompt(
|
|
125
|
+
query_history=query_history, batch_texts=text_memories
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
llm_response = None
|
|
129
|
+
while attempt <= max(0, retries) + 1:
|
|
130
|
+
try:
|
|
131
|
+
llm_response = self.process_llm.generate([{"role": "user", "content": prompt}])
|
|
132
|
+
processed_text_memories = extract_list_items_in_answer(llm_response)
|
|
133
|
+
if len(processed_text_memories) > 0:
|
|
134
|
+
# create new
|
|
135
|
+
enhanced_memories = []
|
|
136
|
+
user_id = memories[0].metadata.user_id
|
|
137
|
+
if FINE_STRATEGY == FineStrategy.RECREATE:
|
|
138
|
+
for new_mem in processed_text_memories:
|
|
139
|
+
enhanced_memories.append(
|
|
140
|
+
TextualMemoryItem(
|
|
141
|
+
memory=new_mem,
|
|
142
|
+
metadata=TextualMemoryMetadata(
|
|
143
|
+
user_id=user_id, memory_type="LongTermMemory"
|
|
144
|
+
), # TODO add memory_type
|
|
145
|
+
)
|
|
146
|
+
)
|
|
147
|
+
elif FINE_STRATEGY == FineStrategy.REWRITE:
|
|
148
|
+
# Parse index from each processed line and rewrite corresponding original memory
|
|
149
|
+
def _parse_index_and_text(s: str) -> tuple[int | None, str]:
|
|
150
|
+
import re
|
|
151
|
+
|
|
152
|
+
s = (s or "").strip()
|
|
153
|
+
# Preferred: [index] text
|
|
154
|
+
m = re.match(r"^\s*\[(\d+)\]\s*(.+)$", s)
|
|
155
|
+
if m:
|
|
156
|
+
return int(m.group(1)), m.group(2).strip()
|
|
157
|
+
# Fallback: index: text or index - text
|
|
158
|
+
m = re.match(r"^\s*(\d+)\s*[:\-\)]\s*(.+)$", s)
|
|
159
|
+
if m:
|
|
160
|
+
return int(m.group(1)), m.group(2).strip()
|
|
161
|
+
return None, s
|
|
162
|
+
|
|
163
|
+
idx_to_original = dict(enumerate(memories))
|
|
164
|
+
for j, item in enumerate(processed_text_memories):
|
|
165
|
+
idx, new_text = _parse_index_and_text(item)
|
|
166
|
+
if idx is not None and idx in idx_to_original:
|
|
167
|
+
orig = idx_to_original[idx]
|
|
168
|
+
else:
|
|
169
|
+
# Fallback: align by order if index missing/invalid
|
|
170
|
+
orig = memories[j] if j < len(memories) else None
|
|
171
|
+
if not orig:
|
|
172
|
+
continue
|
|
173
|
+
enhanced_memories.append(
|
|
174
|
+
TextualMemoryItem(
|
|
175
|
+
id=orig.id,
|
|
176
|
+
memory=new_text,
|
|
177
|
+
metadata=orig.metadata,
|
|
178
|
+
)
|
|
179
|
+
)
|
|
180
|
+
else:
|
|
181
|
+
logger.error(f"Fine search strategy {FINE_STRATEGY} not exists")
|
|
182
|
+
|
|
183
|
+
logger.info(
|
|
184
|
+
f"[enhance_memories_with_query] ✅ done | Strategy={FINE_STRATEGY} | prompt={prompt} | llm_response={llm_response}"
|
|
185
|
+
)
|
|
186
|
+
return enhanced_memories, True
|
|
187
|
+
else:
|
|
188
|
+
raise ValueError(
|
|
189
|
+
f"Fail to run memory enhancement; retry {attempt}/{max(1, retries) + 1}; processed_text_memories: {processed_text_memories}"
|
|
190
|
+
)
|
|
191
|
+
except Exception as e:
|
|
192
|
+
attempt += 1
|
|
193
|
+
time.sleep(1)
|
|
194
|
+
logger.debug(
|
|
195
|
+
f"[enhance_memories_with_query][batch={batch_index}] 🔁 retry {attempt}/{max(1, retries) + 1} failed: {e}"
|
|
196
|
+
)
|
|
197
|
+
logger.error(
|
|
198
|
+
f"Fail to run memory enhancement; prompt: {prompt};\n llm_response: {llm_response}",
|
|
199
|
+
exc_info=True,
|
|
200
|
+
)
|
|
201
|
+
return memories, False
|
|
202
|
+
|
|
203
|
+
@staticmethod
|
|
204
|
+
def _split_batches(
|
|
205
|
+
memories: list[TextualMemoryItem], batch_size: int
|
|
206
|
+
) -> list[tuple[int, int, list[TextualMemoryItem]]]:
|
|
207
|
+
batches: list[tuple[int, int, list[TextualMemoryItem]]] = []
|
|
208
|
+
start = 0
|
|
209
|
+
n = len(memories)
|
|
210
|
+
while start < n:
|
|
211
|
+
end = min(start + batch_size, n)
|
|
212
|
+
batches.append((start, end, memories[start:end]))
|
|
213
|
+
start = end
|
|
214
|
+
return batches
|
|
215
|
+
|
|
216
|
+
def recall_for_missing_memories(
|
|
217
|
+
self,
|
|
218
|
+
query: str,
|
|
219
|
+
memories: list[str],
|
|
220
|
+
) -> tuple[str, bool]:
|
|
221
|
+
text_memories = "\n".join([f"- {mem}" for i, mem in enumerate(memories)])
|
|
222
|
+
|
|
223
|
+
prompt = self.build_prompt(
|
|
224
|
+
template_name="enlarge_recall",
|
|
225
|
+
query=query,
|
|
226
|
+
memories_inline=text_memories,
|
|
227
|
+
)
|
|
228
|
+
llm_response = self.process_llm.generate([{"role": "user", "content": prompt}])
|
|
229
|
+
|
|
230
|
+
json_result: dict = extract_json_obj(llm_response)
|
|
231
|
+
|
|
232
|
+
logger.info(
|
|
233
|
+
f"[recall_for_missing_memories] ✅ done | prompt={prompt} | llm_response={llm_response}"
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
hint = json_result.get("hint", "")
|
|
237
|
+
if len(hint) == 0:
|
|
238
|
+
return hint, False
|
|
239
|
+
return hint, json_result.get("trigger_recall", False)
|
|
240
|
+
|
|
241
|
+
def search(
|
|
242
|
+
self,
|
|
243
|
+
query: str,
|
|
244
|
+
user_id: str,
|
|
245
|
+
mem_cube_id: str,
|
|
246
|
+
mem_cube: GeneralMemCube,
|
|
247
|
+
top_k: int,
|
|
248
|
+
method: str = TreeTextMemory_SEARCH_METHOD,
|
|
249
|
+
search_args: dict | None = None,
|
|
250
|
+
) -> list[TextualMemoryItem]:
|
|
251
|
+
"""Search in text memory with the given query.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
query: The search query string
|
|
255
|
+
top_k: Number of top results to return
|
|
256
|
+
method: Search method to use
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
Search results or None if not implemented
|
|
260
|
+
"""
|
|
261
|
+
text_mem_base = mem_cube.text_mem
|
|
262
|
+
# Normalize default for mutable argument
|
|
263
|
+
search_args = search_args or {}
|
|
264
|
+
try:
|
|
265
|
+
if method in [TreeTextMemory_SEARCH_METHOD, TreeTextMemory_FINE_SEARCH_METHOD]:
|
|
266
|
+
assert isinstance(text_mem_base, TreeTextMemory)
|
|
267
|
+
session_id = search_args.get("session_id", "default_session")
|
|
268
|
+
target_session_id = session_id
|
|
269
|
+
search_priority = (
|
|
270
|
+
{"session_id": target_session_id} if "session_id" in search_args else None
|
|
271
|
+
)
|
|
272
|
+
search_filter = search_args.get("filter")
|
|
273
|
+
search_source = search_args.get("source")
|
|
274
|
+
plugin = bool(search_source is not None and search_source == "plugin")
|
|
275
|
+
user_name = search_args.get("user_name", mem_cube_id)
|
|
276
|
+
internet_search = search_args.get("internet_search", False)
|
|
277
|
+
chat_history = search_args.get("chat_history")
|
|
278
|
+
search_tool_memory = search_args.get("search_tool_memory", False)
|
|
279
|
+
tool_mem_top_k = search_args.get("tool_mem_top_k", 6)
|
|
280
|
+
playground_search_goal_parser = search_args.get(
|
|
281
|
+
"playground_search_goal_parser", False
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
info = search_args.get(
|
|
285
|
+
"info",
|
|
286
|
+
{
|
|
287
|
+
"user_id": user_id,
|
|
288
|
+
"session_id": target_session_id,
|
|
289
|
+
"chat_history": chat_history,
|
|
290
|
+
},
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
results_long_term = mem_cube.text_mem.search(
|
|
294
|
+
query=query,
|
|
295
|
+
user_name=user_name,
|
|
296
|
+
top_k=top_k,
|
|
297
|
+
mode=SearchMode.FAST,
|
|
298
|
+
manual_close_internet=not internet_search,
|
|
299
|
+
memory_type="LongTermMemory",
|
|
300
|
+
search_filter=search_filter,
|
|
301
|
+
search_priority=search_priority,
|
|
302
|
+
info=info,
|
|
303
|
+
plugin=plugin,
|
|
304
|
+
search_tool_memory=search_tool_memory,
|
|
305
|
+
tool_mem_top_k=tool_mem_top_k,
|
|
306
|
+
playground_search_goal_parser=playground_search_goal_parser,
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
results_user = mem_cube.text_mem.search(
|
|
310
|
+
query=query,
|
|
311
|
+
user_name=user_name,
|
|
312
|
+
top_k=top_k,
|
|
313
|
+
mode=SearchMode.FAST,
|
|
314
|
+
manual_close_internet=not internet_search,
|
|
315
|
+
memory_type="UserMemory",
|
|
316
|
+
search_filter=search_filter,
|
|
317
|
+
search_priority=search_priority,
|
|
318
|
+
info=info,
|
|
319
|
+
plugin=plugin,
|
|
320
|
+
search_tool_memory=search_tool_memory,
|
|
321
|
+
tool_mem_top_k=tool_mem_top_k,
|
|
322
|
+
playground_search_goal_parser=playground_search_goal_parser,
|
|
323
|
+
)
|
|
324
|
+
results = results_long_term + results_user
|
|
325
|
+
else:
|
|
326
|
+
raise NotImplementedError(str(type(text_mem_base)))
|
|
327
|
+
except Exception as e:
|
|
328
|
+
logger.error(f"Fail to search. The exeption is {e}.", exc_info=True)
|
|
329
|
+
results = []
|
|
330
|
+
return results
|
|
331
|
+
|
|
332
|
+
def enhance_memories_with_query(
|
|
333
|
+
self,
|
|
334
|
+
query_history: list[str],
|
|
335
|
+
memories: list[TextualMemoryItem],
|
|
336
|
+
) -> (list[TextualMemoryItem], bool):
|
|
337
|
+
"""
|
|
338
|
+
Enhance memories by adding context and making connections to better answer queries.
|
|
339
|
+
|
|
340
|
+
Args:
|
|
341
|
+
query_history: List of user queries in chronological order
|
|
342
|
+
memories: List of memory items to enhance
|
|
343
|
+
|
|
344
|
+
Returns:
|
|
345
|
+
Tuple of (enhanced_memories, success_flag)
|
|
346
|
+
"""
|
|
347
|
+
if not memories:
|
|
348
|
+
logger.warning("[Enhance] ⚠️ skipped (no memories to process)")
|
|
349
|
+
return memories, True
|
|
350
|
+
|
|
351
|
+
batch_size = self.batch_size
|
|
352
|
+
retries = self.retries
|
|
353
|
+
num_of_memories = len(memories)
|
|
354
|
+
try:
|
|
355
|
+
# no parallel
|
|
356
|
+
if batch_size is None or num_of_memories <= batch_size:
|
|
357
|
+
# Single batch path with retry
|
|
358
|
+
enhanced_memories, success_flag = self._process_enhancement_batch(
|
|
359
|
+
batch_index=0,
|
|
360
|
+
query_history=query_history,
|
|
361
|
+
memories=memories,
|
|
362
|
+
retries=retries,
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
all_success = success_flag
|
|
366
|
+
else:
|
|
367
|
+
# parallel running batches
|
|
368
|
+
# Split into batches preserving order
|
|
369
|
+
batches = self._split_batches(memories=memories, batch_size=batch_size)
|
|
370
|
+
|
|
371
|
+
# Process batches concurrently
|
|
372
|
+
all_success = True
|
|
373
|
+
failed_batches = 0
|
|
374
|
+
with ContextThreadPoolExecutor(max_workers=len(batches)) as executor:
|
|
375
|
+
future_map = {
|
|
376
|
+
executor.submit(
|
|
377
|
+
self._process_enhancement_batch, bi, query_history, texts, retries
|
|
378
|
+
): (bi, s, e)
|
|
379
|
+
for bi, (s, e, texts) in enumerate(batches)
|
|
380
|
+
}
|
|
381
|
+
enhanced_memories = []
|
|
382
|
+
for fut in as_completed(future_map):
|
|
383
|
+
bi, s, e = future_map[fut]
|
|
384
|
+
|
|
385
|
+
batch_memories, ok = fut.result()
|
|
386
|
+
enhanced_memories.extend(batch_memories)
|
|
387
|
+
if not ok:
|
|
388
|
+
all_success = False
|
|
389
|
+
failed_batches += 1
|
|
390
|
+
logger.info(
|
|
391
|
+
f"[Enhance] ✅ multi-batch done | batches={len(batches)} | enhanced={len(enhanced_memories)} |"
|
|
392
|
+
f" failed_batches={failed_batches} | success={all_success}"
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
except Exception as e:
|
|
396
|
+
logger.error(f"[Enhance] ❌ fatal error: {e}", exc_info=True)
|
|
397
|
+
all_success = False
|
|
398
|
+
enhanced_memories = memories
|
|
399
|
+
|
|
400
|
+
if len(enhanced_memories) == 0:
|
|
401
|
+
enhanced_memories = []
|
|
402
|
+
logger.error("[Enhance] ❌ fatal error: enhanced_memories is empty", exc_info=True)
|
|
403
|
+
return enhanced_memories, all_success
|
|
404
|
+
|
|
405
|
+
def rerank_memories(
|
|
406
|
+
self, queries: list[str], original_memories: list[str], top_k: int
|
|
407
|
+
) -> (list[str], bool):
|
|
408
|
+
"""
|
|
409
|
+
Rerank memories based on relevance to given queries using LLM.
|
|
410
|
+
|
|
411
|
+
Args:
|
|
412
|
+
queries: List of query strings to determine relevance
|
|
413
|
+
original_memories: List of memory strings to be reranked
|
|
414
|
+
top_k: Number of top memories to return after reranking
|
|
415
|
+
|
|
416
|
+
Returns:
|
|
417
|
+
List of reranked memory strings (length <= top_k)
|
|
418
|
+
|
|
419
|
+
Note:
|
|
420
|
+
If LLM reranking fails, falls back to original order (truncated to top_k)
|
|
421
|
+
"""
|
|
422
|
+
|
|
423
|
+
logger.info(f"Starting memory reranking for {len(original_memories)} memories")
|
|
424
|
+
|
|
425
|
+
# Build LLM prompt for memory reranking
|
|
426
|
+
prompt = self.build_prompt(
|
|
427
|
+
"memory_reranking",
|
|
428
|
+
queries=[f"[0] {queries[0]}"],
|
|
429
|
+
current_order=[f"[{i}] {mem}" for i, mem in enumerate(original_memories)],
|
|
430
|
+
)
|
|
431
|
+
logger.debug(f"Generated reranking prompt: {prompt[:200]}...") # Log first 200 chars
|
|
432
|
+
|
|
433
|
+
# Get LLM response
|
|
434
|
+
response = self.process_llm.generate([{"role": "user", "content": prompt}])
|
|
435
|
+
logger.debug(f"Received LLM response: {response[:200]}...") # Log first 200 chars
|
|
436
|
+
|
|
437
|
+
try:
|
|
438
|
+
# Parse JSON response
|
|
439
|
+
response = extract_json_obj(response)
|
|
440
|
+
new_order = response["new_order"][:top_k]
|
|
441
|
+
text_memories_with_new_order = [original_memories[idx] for idx in new_order]
|
|
442
|
+
logger.info(
|
|
443
|
+
f"Successfully reranked memories. Returning top {len(text_memories_with_new_order)} items;"
|
|
444
|
+
f"Ranking reasoning: {response['reasoning']}"
|
|
445
|
+
)
|
|
446
|
+
success_flag = True
|
|
447
|
+
except Exception as e:
|
|
448
|
+
logger.error(
|
|
449
|
+
f"Failed to rerank memories with LLM. Exception: {e}. Raw response: {response} ",
|
|
450
|
+
exc_info=True,
|
|
451
|
+
)
|
|
452
|
+
text_memories_with_new_order = original_memories[:top_k]
|
|
453
|
+
success_flag = False
|
|
454
|
+
return text_memories_with_new_order, success_flag
|
|
455
|
+
|
|
456
|
+
def process_and_rerank_memories(
|
|
457
|
+
self,
|
|
458
|
+
queries: list[str],
|
|
459
|
+
original_memory: list[TextualMemoryItem],
|
|
460
|
+
new_memory: list[TextualMemoryItem],
|
|
461
|
+
top_k: int = 10,
|
|
462
|
+
) -> list[TextualMemoryItem] | None:
|
|
463
|
+
"""
|
|
464
|
+
Process and rerank memory items by combining original and new memories,
|
|
465
|
+
applying filters, and then reranking based on relevance to queries.
|
|
466
|
+
|
|
467
|
+
Args:
|
|
468
|
+
queries: List of query strings to rerank memories against
|
|
469
|
+
original_memory: List of original TextualMemoryItem objects
|
|
470
|
+
new_memory: List of new TextualMemoryItem objects to merge
|
|
471
|
+
top_k: Maximum number of memories to return after reranking
|
|
472
|
+
|
|
473
|
+
Returns:
|
|
474
|
+
List of reranked TextualMemoryItem objects, or None if processing fails
|
|
475
|
+
"""
|
|
476
|
+
# Combine original and new memories into a single list
|
|
477
|
+
combined_memory = original_memory + new_memory
|
|
478
|
+
|
|
479
|
+
# Create a mapping from normalized text to memory objects
|
|
480
|
+
memory_map = {
|
|
481
|
+
transform_name_to_key(name=mem_obj.memory): mem_obj for mem_obj in combined_memory
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
# Extract normalized text representations from all memory items
|
|
485
|
+
combined_text_memory = [m.memory for m in combined_memory]
|
|
486
|
+
|
|
487
|
+
# Apply similarity filter to remove overly similar memories
|
|
488
|
+
filtered_combined_text_memory = filter_vector_based_similar_memories(
|
|
489
|
+
text_memories=combined_text_memory,
|
|
490
|
+
similarity_threshold=self.filter_similarity_threshold,
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
# Apply length filter to remove memories that are too short
|
|
494
|
+
filtered_combined_text_memory = filter_too_short_memories(
|
|
495
|
+
text_memories=filtered_combined_text_memory,
|
|
496
|
+
min_length_threshold=self.filter_min_length_threshold,
|
|
497
|
+
)
|
|
498
|
+
|
|
499
|
+
# Ensure uniqueness of memory texts using dictionary keys (preserves order)
|
|
500
|
+
unique_memory = list(dict.fromkeys(filtered_combined_text_memory))
|
|
501
|
+
|
|
502
|
+
# Rerank the filtered memories based on relevance to the queries
|
|
503
|
+
text_memories_with_new_order, success_flag = self.rerank_memories(
|
|
504
|
+
queries=queries,
|
|
505
|
+
original_memories=unique_memory,
|
|
506
|
+
top_k=top_k,
|
|
507
|
+
)
|
|
508
|
+
|
|
509
|
+
# Map reranked text entries back to their original memory objects
|
|
510
|
+
memories_with_new_order = []
|
|
511
|
+
for text in text_memories_with_new_order:
|
|
512
|
+
normalized_text = transform_name_to_key(name=text)
|
|
513
|
+
if normalized_text in memory_map: # Ensure correct key matching
|
|
514
|
+
memories_with_new_order.append(memory_map[normalized_text])
|
|
515
|
+
else:
|
|
516
|
+
logger.warning(
|
|
517
|
+
f"Memory text not found in memory map. text: {text};\n"
|
|
518
|
+
f"Keys of memory_map: {memory_map.keys()}"
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
return memories_with_new_order, success_flag
|
|
522
|
+
|
|
523
|
+
def filter_unrelated_memories(
|
|
524
|
+
self,
|
|
525
|
+
query_history: list[str],
|
|
526
|
+
memories: list[TextualMemoryItem],
|
|
527
|
+
) -> (list[TextualMemoryItem], bool):
|
|
528
|
+
return self.memory_filter.filter_unrelated_memories(query_history, memories)
|
|
529
|
+
|
|
530
|
+
def filter_redundant_memories(
|
|
531
|
+
self,
|
|
532
|
+
query_history: list[str],
|
|
533
|
+
memories: list[TextualMemoryItem],
|
|
534
|
+
) -> (list[TextualMemoryItem], bool):
|
|
535
|
+
return self.memory_filter.filter_redundant_memories(query_history, memories)
|
|
536
|
+
|
|
537
|
+
def filter_unrelated_and_redundant_memories(
|
|
538
|
+
self,
|
|
539
|
+
query_history: list[str],
|
|
540
|
+
memories: list[TextualMemoryItem],
|
|
541
|
+
) -> (list[TextualMemoryItem], bool):
|
|
542
|
+
"""
|
|
543
|
+
Filter out both unrelated and redundant memories using LLM analysis.
|
|
544
|
+
|
|
545
|
+
This method delegates to the MemoryFilter class.
|
|
546
|
+
"""
|
|
547
|
+
return self.memory_filter.filter_unrelated_and_redundant_memories(query_history, memories)
|
|
File without changes
|