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,359 @@
|
|
|
1
|
+
"""Parser for image_url content parts."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import re
|
|
5
|
+
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from memos.embedders.base import BaseEmbedder
|
|
9
|
+
from memos.llms.base import BaseLLM
|
|
10
|
+
from memos.log import get_logger
|
|
11
|
+
from memos.memories.textual.item import (
|
|
12
|
+
SourceMessage,
|
|
13
|
+
TextualMemoryItem,
|
|
14
|
+
TreeNodeTextualMemoryMetadata,
|
|
15
|
+
)
|
|
16
|
+
from memos.templates.mem_reader_prompts import IMAGE_ANALYSIS_PROMPT_EN, IMAGE_ANALYSIS_PROMPT_ZH
|
|
17
|
+
from memos.types.openai_chat_completion_types import ChatCompletionContentPartImageParam
|
|
18
|
+
|
|
19
|
+
from .base import BaseMessageParser, _derive_key
|
|
20
|
+
from .utils import detect_lang
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
logger = get_logger(__name__)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ImageParser(BaseMessageParser):
|
|
27
|
+
"""Parser for image_url content parts."""
|
|
28
|
+
|
|
29
|
+
def __init__(self, embedder: BaseEmbedder, llm: BaseLLM | None = None):
|
|
30
|
+
"""
|
|
31
|
+
Initialize ImageParser.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
embedder: Embedder for generating embeddings
|
|
35
|
+
llm: Optional LLM for fine mode processing
|
|
36
|
+
"""
|
|
37
|
+
super().__init__(embedder, llm)
|
|
38
|
+
|
|
39
|
+
def create_source(
|
|
40
|
+
self,
|
|
41
|
+
message: ChatCompletionContentPartImageParam,
|
|
42
|
+
info: dict[str, Any],
|
|
43
|
+
) -> SourceMessage:
|
|
44
|
+
"""Create SourceMessage from image_url content part."""
|
|
45
|
+
if isinstance(message, dict):
|
|
46
|
+
image_url = message.get("image_url", {})
|
|
47
|
+
if isinstance(image_url, dict):
|
|
48
|
+
url = image_url.get("url", "")
|
|
49
|
+
detail = image_url.get("detail", "auto")
|
|
50
|
+
else:
|
|
51
|
+
url = str(image_url)
|
|
52
|
+
detail = "auto"
|
|
53
|
+
return SourceMessage(
|
|
54
|
+
type="image",
|
|
55
|
+
content=url,
|
|
56
|
+
url=url,
|
|
57
|
+
detail=detail,
|
|
58
|
+
)
|
|
59
|
+
return SourceMessage(type="image", content=str(message))
|
|
60
|
+
|
|
61
|
+
def rebuild_from_source(
|
|
62
|
+
self,
|
|
63
|
+
source: SourceMessage,
|
|
64
|
+
) -> ChatCompletionContentPartImageParam:
|
|
65
|
+
"""Rebuild image_url content part from SourceMessage."""
|
|
66
|
+
# Rebuild from source fields
|
|
67
|
+
url = (
|
|
68
|
+
getattr(source, "url", "")
|
|
69
|
+
or getattr(source, "image_path", "")
|
|
70
|
+
or (source.content or "").replace("[image_url]: ", "")
|
|
71
|
+
)
|
|
72
|
+
detail = getattr(source, "detail", "auto")
|
|
73
|
+
return {
|
|
74
|
+
"type": "image_url",
|
|
75
|
+
"image_url": {
|
|
76
|
+
"url": url,
|
|
77
|
+
"detail": detail,
|
|
78
|
+
},
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
def parse_fast(
|
|
82
|
+
self,
|
|
83
|
+
message: ChatCompletionContentPartImageParam,
|
|
84
|
+
info: dict[str, Any],
|
|
85
|
+
**kwargs,
|
|
86
|
+
) -> list[TextualMemoryItem]:
|
|
87
|
+
"""Parse image_url in fast mode - returns empty list as images need fine mode processing."""
|
|
88
|
+
# In fast mode, images are not processed (they need vision models)
|
|
89
|
+
# They will be processed in fine mode via process_transfer
|
|
90
|
+
return []
|
|
91
|
+
|
|
92
|
+
def parse_fine(
|
|
93
|
+
self,
|
|
94
|
+
message: ChatCompletionContentPartImageParam,
|
|
95
|
+
info: dict[str, Any],
|
|
96
|
+
**kwargs,
|
|
97
|
+
) -> list[TextualMemoryItem]:
|
|
98
|
+
"""
|
|
99
|
+
Parse image_url in fine mode using vision models to extract information from images.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
message: Image message to parse
|
|
103
|
+
info: Dictionary containing user_id and session_id
|
|
104
|
+
**kwargs: Additional parameters (e.g., context_items, custom_tags)
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
List of TextualMemoryItem objects extracted from the image
|
|
108
|
+
"""
|
|
109
|
+
if not self.llm:
|
|
110
|
+
logger.warning("[ImageParser] LLM not available for fine mode processing")
|
|
111
|
+
return []
|
|
112
|
+
|
|
113
|
+
# Extract image information
|
|
114
|
+
if not isinstance(message, dict):
|
|
115
|
+
logger.warning(f"[ImageParser] Expected dict, got {type(message)}")
|
|
116
|
+
return []
|
|
117
|
+
|
|
118
|
+
image_url = message.get("image_url", {})
|
|
119
|
+
if isinstance(image_url, dict):
|
|
120
|
+
url = image_url.get("url", "")
|
|
121
|
+
detail = image_url.get("detail", "auto")
|
|
122
|
+
else:
|
|
123
|
+
url = str(image_url)
|
|
124
|
+
detail = "auto"
|
|
125
|
+
|
|
126
|
+
if not url:
|
|
127
|
+
logger.warning("[ImageParser] No image URL found in message")
|
|
128
|
+
return []
|
|
129
|
+
|
|
130
|
+
# Create source for this image
|
|
131
|
+
source = self.create_source(message, info)
|
|
132
|
+
|
|
133
|
+
# Get context items if available
|
|
134
|
+
context_items = kwargs.get("context_items")
|
|
135
|
+
|
|
136
|
+
# Determine language: prioritize lang from source (passed via kwargs),
|
|
137
|
+
# fallback to detecting from context_items if lang not provided
|
|
138
|
+
lang = kwargs.get("lang")
|
|
139
|
+
if lang is None and context_items:
|
|
140
|
+
for item in context_items:
|
|
141
|
+
if hasattr(item, "memory") and item.memory:
|
|
142
|
+
lang = detect_lang(item.memory)
|
|
143
|
+
break
|
|
144
|
+
if not lang:
|
|
145
|
+
lang = "en"
|
|
146
|
+
if not hasattr(source, "lang") or source.lang is None:
|
|
147
|
+
source.lang = lang
|
|
148
|
+
|
|
149
|
+
# Select prompt based on language
|
|
150
|
+
image_analysis_prompt = (
|
|
151
|
+
IMAGE_ANALYSIS_PROMPT_ZH if lang == "zh" else IMAGE_ANALYSIS_PROMPT_EN
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# Add context if available
|
|
155
|
+
context_text = ""
|
|
156
|
+
if context_items:
|
|
157
|
+
for item in context_items:
|
|
158
|
+
if hasattr(item, "memory") and item.memory:
|
|
159
|
+
context_text += f"{item.memory}\n"
|
|
160
|
+
context_text = context_text.strip()
|
|
161
|
+
|
|
162
|
+
# Inject context into prompt when possible
|
|
163
|
+
image_analysis_prompt = image_analysis_prompt.replace("{context}", context_text)
|
|
164
|
+
|
|
165
|
+
# Build messages with image content
|
|
166
|
+
messages = [
|
|
167
|
+
{
|
|
168
|
+
"role": "user",
|
|
169
|
+
"content": [
|
|
170
|
+
{"type": "text", "text": image_analysis_prompt},
|
|
171
|
+
{
|
|
172
|
+
"type": "image_url",
|
|
173
|
+
"image_url": {
|
|
174
|
+
"url": url,
|
|
175
|
+
"detail": detail,
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
],
|
|
179
|
+
}
|
|
180
|
+
]
|
|
181
|
+
|
|
182
|
+
try:
|
|
183
|
+
# Call LLM with vision model
|
|
184
|
+
response_text = self.llm.generate(messages)
|
|
185
|
+
if not response_text:
|
|
186
|
+
logger.warning("[ImageParser] Empty response from LLM")
|
|
187
|
+
return []
|
|
188
|
+
|
|
189
|
+
# Parse JSON response
|
|
190
|
+
response_json = self._parse_json_result(response_text)
|
|
191
|
+
if not response_json:
|
|
192
|
+
logger.warning(f"[ImageParser] Fail to parse response from LLM: {response_text}")
|
|
193
|
+
return []
|
|
194
|
+
|
|
195
|
+
# Extract memory items from response
|
|
196
|
+
memory_items = []
|
|
197
|
+
memory_list = response_json.get("memory list", [])
|
|
198
|
+
|
|
199
|
+
if not memory_list:
|
|
200
|
+
logger.warning("[ImageParser] No memory items extracted from image")
|
|
201
|
+
# Fallback: create a simple memory item with the summary
|
|
202
|
+
summary = response_json.get(
|
|
203
|
+
"summary", "Image analyzed but no specific memories extracted."
|
|
204
|
+
)
|
|
205
|
+
if summary:
|
|
206
|
+
memory_items.append(
|
|
207
|
+
self._create_memory_item(
|
|
208
|
+
value=summary,
|
|
209
|
+
info=info,
|
|
210
|
+
memory_type="LongTermMemory",
|
|
211
|
+
tags=["image", "visual"],
|
|
212
|
+
key=_derive_key(summary),
|
|
213
|
+
sources=[source],
|
|
214
|
+
background=summary,
|
|
215
|
+
)
|
|
216
|
+
)
|
|
217
|
+
return memory_items
|
|
218
|
+
|
|
219
|
+
# Create memory items from parsed response
|
|
220
|
+
for mem_data in memory_list:
|
|
221
|
+
try:
|
|
222
|
+
# Normalize memory_type
|
|
223
|
+
memory_type = (
|
|
224
|
+
mem_data.get("memory_type", "LongTermMemory")
|
|
225
|
+
.replace("长期记忆", "LongTermMemory")
|
|
226
|
+
.replace("用户记忆", "UserMemory")
|
|
227
|
+
)
|
|
228
|
+
if memory_type not in ["LongTermMemory", "UserMemory"]:
|
|
229
|
+
memory_type = "LongTermMemory"
|
|
230
|
+
|
|
231
|
+
value = mem_data.get("value", "").strip()
|
|
232
|
+
if not value:
|
|
233
|
+
continue
|
|
234
|
+
|
|
235
|
+
tags = mem_data.get("tags", [])
|
|
236
|
+
if not isinstance(tags, list):
|
|
237
|
+
tags = []
|
|
238
|
+
# Add image-related tags
|
|
239
|
+
if "image" not in [t.lower() for t in tags]:
|
|
240
|
+
tags.append("image")
|
|
241
|
+
if "visual" not in [t.lower() for t in tags]:
|
|
242
|
+
tags.append("visual")
|
|
243
|
+
|
|
244
|
+
key = mem_data.get("key", "")
|
|
245
|
+
background = response_json.get("summary", "")
|
|
246
|
+
|
|
247
|
+
memory_item = self._create_memory_item(
|
|
248
|
+
value=value,
|
|
249
|
+
info=info,
|
|
250
|
+
memory_type=memory_type,
|
|
251
|
+
tags=tags,
|
|
252
|
+
key=key if key else _derive_key(value),
|
|
253
|
+
sources=[source],
|
|
254
|
+
background=background,
|
|
255
|
+
)
|
|
256
|
+
memory_items.append(memory_item)
|
|
257
|
+
except Exception as e:
|
|
258
|
+
logger.error(f"[ImageParser] Error creating memory item: {e}")
|
|
259
|
+
continue
|
|
260
|
+
|
|
261
|
+
return memory_items
|
|
262
|
+
|
|
263
|
+
except Exception as e:
|
|
264
|
+
logger.error(f"[ImageParser] Error processing image in fine mode: {e}")
|
|
265
|
+
# Fallback: create a simple memory item
|
|
266
|
+
fallback_value = f"Image analyzed: {url}"
|
|
267
|
+
return [
|
|
268
|
+
self._create_memory_item(
|
|
269
|
+
value=fallback_value,
|
|
270
|
+
info=info,
|
|
271
|
+
memory_type="LongTermMemory",
|
|
272
|
+
tags=["image", "visual"],
|
|
273
|
+
key=_derive_key(fallback_value),
|
|
274
|
+
sources=[source],
|
|
275
|
+
background="Image processing encountered an error.",
|
|
276
|
+
)
|
|
277
|
+
]
|
|
278
|
+
|
|
279
|
+
def _parse_json_result(self, response_text: str) -> dict:
|
|
280
|
+
"""
|
|
281
|
+
Parse JSON result from LLM response.
|
|
282
|
+
Similar to SimpleStructMemReader.parse_json_result.
|
|
283
|
+
"""
|
|
284
|
+
s = (response_text or "").strip()
|
|
285
|
+
|
|
286
|
+
# Try to extract JSON from code blocks
|
|
287
|
+
m = re.search(r"```(?:json)?\s*([\s\S]*?)```", s, flags=re.I)
|
|
288
|
+
s = (m.group(1) if m else s.replace("```", "")).strip()
|
|
289
|
+
|
|
290
|
+
# Find first {
|
|
291
|
+
i = s.find("{")
|
|
292
|
+
if i == -1:
|
|
293
|
+
return {}
|
|
294
|
+
s = s[i:].strip()
|
|
295
|
+
|
|
296
|
+
try:
|
|
297
|
+
return json.loads(s)
|
|
298
|
+
except json.JSONDecodeError:
|
|
299
|
+
pass
|
|
300
|
+
|
|
301
|
+
# Try to find the last } or ]
|
|
302
|
+
j = max(s.rfind("}"), s.rfind("]"))
|
|
303
|
+
if j != -1:
|
|
304
|
+
try:
|
|
305
|
+
return json.loads(s[: j + 1])
|
|
306
|
+
except json.JSONDecodeError:
|
|
307
|
+
pass
|
|
308
|
+
|
|
309
|
+
# Try to close brackets
|
|
310
|
+
def _cheap_close(t: str) -> str:
|
|
311
|
+
t += "}" * max(0, t.count("{") - t.count("}"))
|
|
312
|
+
t += "]" * max(0, t.count("[") - t.count("]"))
|
|
313
|
+
return t
|
|
314
|
+
|
|
315
|
+
t = _cheap_close(s)
|
|
316
|
+
try:
|
|
317
|
+
return json.loads(t)
|
|
318
|
+
except json.JSONDecodeError as e:
|
|
319
|
+
if "Invalid \\escape" in str(e):
|
|
320
|
+
s = s.replace("\\", "\\\\")
|
|
321
|
+
try:
|
|
322
|
+
return json.loads(s)
|
|
323
|
+
except json.JSONDecodeError:
|
|
324
|
+
pass
|
|
325
|
+
logger.warning(f"[ImageParser] Failed to parse JSON: {e}\nResponse: {response_text}")
|
|
326
|
+
|
|
327
|
+
def _create_memory_item(
|
|
328
|
+
self,
|
|
329
|
+
value: str,
|
|
330
|
+
info: dict[str, Any],
|
|
331
|
+
memory_type: str,
|
|
332
|
+
tags: list[str],
|
|
333
|
+
key: str,
|
|
334
|
+
sources: list[SourceMessage],
|
|
335
|
+
background: str = "",
|
|
336
|
+
) -> TextualMemoryItem:
|
|
337
|
+
"""Create a TextualMemoryItem with the given parameters."""
|
|
338
|
+
info_ = info.copy()
|
|
339
|
+
user_id = info_.pop("user_id", "")
|
|
340
|
+
session_id = info_.pop("session_id", "")
|
|
341
|
+
|
|
342
|
+
return TextualMemoryItem(
|
|
343
|
+
memory=value,
|
|
344
|
+
metadata=TreeNodeTextualMemoryMetadata(
|
|
345
|
+
user_id=user_id,
|
|
346
|
+
session_id=session_id,
|
|
347
|
+
memory_type=memory_type,
|
|
348
|
+
status="activated",
|
|
349
|
+
tags=tags,
|
|
350
|
+
key=key,
|
|
351
|
+
embedding=self.embedder.embed([value])[0],
|
|
352
|
+
usage=[],
|
|
353
|
+
sources=sources,
|
|
354
|
+
background=background,
|
|
355
|
+
confidence=0.99,
|
|
356
|
+
type="fact",
|
|
357
|
+
info=info_,
|
|
358
|
+
),
|
|
359
|
+
)
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"""Unified multimodal parser for different message types.
|
|
2
|
+
|
|
3
|
+
This module provides a unified interface to parse different message types
|
|
4
|
+
in both fast and fine modes.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from memos.embedders.base import BaseEmbedder
|
|
10
|
+
from memos.llms.base import BaseLLM
|
|
11
|
+
from memos.log import get_logger
|
|
12
|
+
from memos.memories.textual.item import SourceMessage, TextualMemoryItem
|
|
13
|
+
from memos.types import MessagesType
|
|
14
|
+
|
|
15
|
+
from .assistant_parser import AssistantParser
|
|
16
|
+
from .base import BaseMessageParser
|
|
17
|
+
from .file_content_parser import FileContentParser
|
|
18
|
+
from .image_parser import ImageParser
|
|
19
|
+
from .string_parser import StringParser
|
|
20
|
+
from .system_parser import SystemParser
|
|
21
|
+
from .text_content_parser import TextContentParser
|
|
22
|
+
from .tool_parser import ToolParser
|
|
23
|
+
from .user_parser import UserParser
|
|
24
|
+
from .utils import extract_role
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
logger = get_logger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class MultiModalParser:
|
|
31
|
+
"""Unified parser for different message types."""
|
|
32
|
+
|
|
33
|
+
def __init__(
|
|
34
|
+
self,
|
|
35
|
+
embedder: BaseEmbedder,
|
|
36
|
+
llm: BaseLLM | None = None,
|
|
37
|
+
parser: Any | None = None,
|
|
38
|
+
direct_markdown_hostnames: list[str] | None = None,
|
|
39
|
+
):
|
|
40
|
+
"""
|
|
41
|
+
Initialize MultiModalParser.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
embedder: Embedder for generating embeddings
|
|
45
|
+
llm: Optional LLM for fine mode processing
|
|
46
|
+
parser: Optional parser for parsing file contents
|
|
47
|
+
direct_markdown_hostnames: List of hostnames that should return markdown directly
|
|
48
|
+
without parsing. If None, reads from FILE_PARSER_DIRECT_MARKDOWN_HOSTNAMES
|
|
49
|
+
environment variable (comma-separated). Default: ["139.196.232.20"]
|
|
50
|
+
"""
|
|
51
|
+
self.embedder = embedder
|
|
52
|
+
self.llm = llm
|
|
53
|
+
self.parser = parser
|
|
54
|
+
|
|
55
|
+
# Initialize parsers for different message types
|
|
56
|
+
self.string_parser = StringParser(embedder, llm)
|
|
57
|
+
self.system_parser = SystemParser(embedder, llm)
|
|
58
|
+
self.user_parser = UserParser(embedder, llm)
|
|
59
|
+
self.assistant_parser = AssistantParser(embedder, llm)
|
|
60
|
+
self.tool_parser = ToolParser(embedder, llm)
|
|
61
|
+
self.text_content_parser = TextContentParser(embedder, llm)
|
|
62
|
+
self.file_content_parser = FileContentParser(
|
|
63
|
+
embedder, llm, parser, direct_markdown_hostnames=direct_markdown_hostnames
|
|
64
|
+
)
|
|
65
|
+
self.image_parser = ImageParser(embedder, llm)
|
|
66
|
+
self.audio_parser = None # future
|
|
67
|
+
|
|
68
|
+
self.role_parsers = {
|
|
69
|
+
"system": SystemParser(embedder, llm),
|
|
70
|
+
"user": UserParser(embedder, llm),
|
|
71
|
+
"assistant": AssistantParser(embedder, llm),
|
|
72
|
+
"tool": ToolParser(embedder, llm),
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
self.type_parsers = {
|
|
76
|
+
"text": self.text_content_parser,
|
|
77
|
+
"file": self.file_content_parser,
|
|
78
|
+
"image": self.image_parser,
|
|
79
|
+
"image_url": self.image_parser, # Support both "image" and "image_url"
|
|
80
|
+
"audio": self.audio_parser,
|
|
81
|
+
# Custom tool formats
|
|
82
|
+
"tool_description": self.tool_parser,
|
|
83
|
+
"tool_input": self.tool_parser,
|
|
84
|
+
"tool_output": self.tool_parser,
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
def _get_parser(self, message: Any) -> BaseMessageParser | None:
|
|
88
|
+
"""
|
|
89
|
+
Get appropriate parser for the message type.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
message: Message to parse
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
Appropriate parser or None
|
|
96
|
+
"""
|
|
97
|
+
# Handle string messages
|
|
98
|
+
if isinstance(message, str):
|
|
99
|
+
return self.string_parser
|
|
100
|
+
|
|
101
|
+
# Handle dict messages
|
|
102
|
+
if not isinstance(message, dict):
|
|
103
|
+
logger.warning(f"[MultiModalParser] Unknown message type: {type(message)}")
|
|
104
|
+
return None
|
|
105
|
+
|
|
106
|
+
# Check if it's a RawMessageList item (text or file)
|
|
107
|
+
if "type" in message:
|
|
108
|
+
msg_type = message.get("type")
|
|
109
|
+
parser = self.type_parsers.get(msg_type)
|
|
110
|
+
if parser:
|
|
111
|
+
return parser
|
|
112
|
+
|
|
113
|
+
# Check if it's a MessageList item (system, user, assistant, tool)
|
|
114
|
+
role = extract_role(message)
|
|
115
|
+
if role:
|
|
116
|
+
parser = self.role_parsers.get(role)
|
|
117
|
+
if parser:
|
|
118
|
+
return parser
|
|
119
|
+
|
|
120
|
+
logger.warning(f"[MultiModalParser] Could not determine parser for message: {message}")
|
|
121
|
+
return None
|
|
122
|
+
|
|
123
|
+
def parse(
|
|
124
|
+
self,
|
|
125
|
+
message: MessagesType,
|
|
126
|
+
info: dict[str, Any],
|
|
127
|
+
mode: str = "fast",
|
|
128
|
+
**kwargs,
|
|
129
|
+
) -> list[TextualMemoryItem]:
|
|
130
|
+
"""
|
|
131
|
+
Parse a single message in the specified mode.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
message: Message to parse (can be str, MessageList item, or RawMessageList item)
|
|
135
|
+
info: Dictionary containing user_id and session_id
|
|
136
|
+
mode: "fast" or "fine"
|
|
137
|
+
**kwargs: Additional parameters
|
|
138
|
+
|
|
139
|
+
Returns:
|
|
140
|
+
List of TextualMemoryItem objects
|
|
141
|
+
"""
|
|
142
|
+
# Handle list of messages (MessageList or RawMessageList)
|
|
143
|
+
if isinstance(message, list):
|
|
144
|
+
return [item for msg in message for item in self.parse(msg, info, mode, **kwargs)]
|
|
145
|
+
|
|
146
|
+
# Get appropriate parser
|
|
147
|
+
parser = self._get_parser(message)
|
|
148
|
+
if not parser:
|
|
149
|
+
logger.warning(f"[MultiModalParser] No parser found for message: {message}")
|
|
150
|
+
return []
|
|
151
|
+
|
|
152
|
+
# Parse using the appropriate parser
|
|
153
|
+
try:
|
|
154
|
+
return parser.parse(message, info, mode=mode, **kwargs)
|
|
155
|
+
except Exception as e:
|
|
156
|
+
logger.error(f"[MultiModalParser] Error parsing message: {e}")
|
|
157
|
+
return []
|
|
158
|
+
|
|
159
|
+
def parse_batch(
|
|
160
|
+
self,
|
|
161
|
+
messages: list[MessagesType],
|
|
162
|
+
info: dict[str, Any],
|
|
163
|
+
mode: str = "fast",
|
|
164
|
+
**kwargs,
|
|
165
|
+
) -> list[list[TextualMemoryItem]]:
|
|
166
|
+
"""
|
|
167
|
+
Parse a batch of messages.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
messages: List of messages to parse
|
|
171
|
+
info: Dictionary containing user_id and session_id
|
|
172
|
+
mode: "fast" or "fine"
|
|
173
|
+
**kwargs: Additional parameters
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
List of lists of TextualMemoryItem objects (one list per message)
|
|
177
|
+
"""
|
|
178
|
+
results = []
|
|
179
|
+
for message in messages:
|
|
180
|
+
items = self.parse(message, info, mode, **kwargs)
|
|
181
|
+
results.append(items)
|
|
182
|
+
return results
|
|
183
|
+
|
|
184
|
+
def process_transfer(
|
|
185
|
+
self,
|
|
186
|
+
source: SourceMessage,
|
|
187
|
+
context_items: list[TextualMemoryItem] | None = None,
|
|
188
|
+
**kwargs,
|
|
189
|
+
) -> list[TextualMemoryItem]:
|
|
190
|
+
"""
|
|
191
|
+
Process transfer from SourceMessage to fine memory items.
|
|
192
|
+
|
|
193
|
+
This method:
|
|
194
|
+
1. Determines which parser to use based on source type
|
|
195
|
+
2. Rebuilds message from source using parser's rebuild_from_source
|
|
196
|
+
3. Calls parse_fine on the appropriate parser
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
source: SourceMessage to process
|
|
200
|
+
context_items: Optional list of TextualMemoryItem for context
|
|
201
|
+
**kwargs: Additional parameters (e.g., info dict with user_id, session_id, custom_tags)
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
List of TextualMemoryItem objects from fine mode parsing
|
|
205
|
+
"""
|
|
206
|
+
if not self.llm:
|
|
207
|
+
logger.warning("[MultiModalParser] LLM not available for process_transfer")
|
|
208
|
+
return []
|
|
209
|
+
|
|
210
|
+
# Extract info from context_items if available
|
|
211
|
+
info = kwargs.get("info", {})
|
|
212
|
+
if context_items and len(context_items) > 0:
|
|
213
|
+
first_item = context_items[0]
|
|
214
|
+
if not info:
|
|
215
|
+
info = {
|
|
216
|
+
"user_id": first_item.metadata.user_id,
|
|
217
|
+
"session_id": first_item.metadata.session_id,
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
# Try to determine parser from source.type
|
|
221
|
+
parser = None
|
|
222
|
+
if source.type == "file":
|
|
223
|
+
parser = self.file_content_parser
|
|
224
|
+
elif source.type == "text":
|
|
225
|
+
parser = self.text_content_parser
|
|
226
|
+
elif source.type in ["image", "image_url"]:
|
|
227
|
+
parser = self.image_parser
|
|
228
|
+
elif source.role:
|
|
229
|
+
# Chat message, use role parser
|
|
230
|
+
parser = self.role_parsers.get(source.role)
|
|
231
|
+
|
|
232
|
+
if not parser:
|
|
233
|
+
logger.warning(f"[MultiModalParser] Could not determine parser for source: {source}")
|
|
234
|
+
return []
|
|
235
|
+
|
|
236
|
+
# Rebuild message from source using parser's method
|
|
237
|
+
try:
|
|
238
|
+
message = parser.rebuild_from_source(source)
|
|
239
|
+
except Exception as e:
|
|
240
|
+
logger.error(f"[MultiModalParser] Error rebuilding message from source: {e}")
|
|
241
|
+
return []
|
|
242
|
+
|
|
243
|
+
# Parse in fine mode (pass context_items and custom_tags to parse_fine)
|
|
244
|
+
try:
|
|
245
|
+
custom_tags = kwargs.pop("custom_tags", None)
|
|
246
|
+
info = kwargs.pop("info", None)
|
|
247
|
+
return parser.parse_fine(
|
|
248
|
+
message, info, context_items=context_items, custom_tags=custom_tags, **kwargs
|
|
249
|
+
)
|
|
250
|
+
except Exception as e:
|
|
251
|
+
logger.error(f"[MultiModalParser] Error parsing in fine mode: {e}")
|
|
252
|
+
return []
|