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.
Files changed (315) hide show
  1. memoryos-2.0.3.dist-info/METADATA +418 -0
  2. memoryos-2.0.3.dist-info/RECORD +315 -0
  3. memoryos-2.0.3.dist-info/WHEEL +4 -0
  4. memoryos-2.0.3.dist-info/entry_points.txt +3 -0
  5. memoryos-2.0.3.dist-info/licenses/LICENSE +201 -0
  6. memos/__init__.py +20 -0
  7. memos/api/client.py +571 -0
  8. memos/api/config.py +1018 -0
  9. memos/api/context/dependencies.py +50 -0
  10. memos/api/exceptions.py +53 -0
  11. memos/api/handlers/__init__.py +62 -0
  12. memos/api/handlers/add_handler.py +158 -0
  13. memos/api/handlers/base_handler.py +194 -0
  14. memos/api/handlers/chat_handler.py +1401 -0
  15. memos/api/handlers/component_init.py +388 -0
  16. memos/api/handlers/config_builders.py +190 -0
  17. memos/api/handlers/feedback_handler.py +93 -0
  18. memos/api/handlers/formatters_handler.py +237 -0
  19. memos/api/handlers/memory_handler.py +316 -0
  20. memos/api/handlers/scheduler_handler.py +497 -0
  21. memos/api/handlers/search_handler.py +222 -0
  22. memos/api/handlers/suggestion_handler.py +117 -0
  23. memos/api/mcp_serve.py +614 -0
  24. memos/api/middleware/request_context.py +101 -0
  25. memos/api/product_api.py +38 -0
  26. memos/api/product_models.py +1206 -0
  27. memos/api/routers/__init__.py +1 -0
  28. memos/api/routers/product_router.py +477 -0
  29. memos/api/routers/server_router.py +394 -0
  30. memos/api/server_api.py +44 -0
  31. memos/api/start_api.py +433 -0
  32. memos/chunkers/__init__.py +4 -0
  33. memos/chunkers/base.py +24 -0
  34. memos/chunkers/charactertext_chunker.py +41 -0
  35. memos/chunkers/factory.py +24 -0
  36. memos/chunkers/markdown_chunker.py +62 -0
  37. memos/chunkers/sentence_chunker.py +54 -0
  38. memos/chunkers/simple_chunker.py +50 -0
  39. memos/cli.py +113 -0
  40. memos/configs/__init__.py +0 -0
  41. memos/configs/base.py +82 -0
  42. memos/configs/chunker.py +59 -0
  43. memos/configs/embedder.py +88 -0
  44. memos/configs/graph_db.py +236 -0
  45. memos/configs/internet_retriever.py +100 -0
  46. memos/configs/llm.py +151 -0
  47. memos/configs/mem_agent.py +54 -0
  48. memos/configs/mem_chat.py +81 -0
  49. memos/configs/mem_cube.py +105 -0
  50. memos/configs/mem_os.py +83 -0
  51. memos/configs/mem_reader.py +91 -0
  52. memos/configs/mem_scheduler.py +385 -0
  53. memos/configs/mem_user.py +70 -0
  54. memos/configs/memory.py +324 -0
  55. memos/configs/parser.py +38 -0
  56. memos/configs/reranker.py +18 -0
  57. memos/configs/utils.py +8 -0
  58. memos/configs/vec_db.py +80 -0
  59. memos/context/context.py +355 -0
  60. memos/dependency.py +52 -0
  61. memos/deprecation.py +262 -0
  62. memos/embedders/__init__.py +0 -0
  63. memos/embedders/ark.py +95 -0
  64. memos/embedders/base.py +106 -0
  65. memos/embedders/factory.py +29 -0
  66. memos/embedders/ollama.py +77 -0
  67. memos/embedders/sentence_transformer.py +49 -0
  68. memos/embedders/universal_api.py +51 -0
  69. memos/exceptions.py +30 -0
  70. memos/graph_dbs/__init__.py +0 -0
  71. memos/graph_dbs/base.py +274 -0
  72. memos/graph_dbs/factory.py +27 -0
  73. memos/graph_dbs/item.py +46 -0
  74. memos/graph_dbs/nebular.py +1794 -0
  75. memos/graph_dbs/neo4j.py +1942 -0
  76. memos/graph_dbs/neo4j_community.py +1058 -0
  77. memos/graph_dbs/polardb.py +5446 -0
  78. memos/hello_world.py +97 -0
  79. memos/llms/__init__.py +0 -0
  80. memos/llms/base.py +25 -0
  81. memos/llms/deepseek.py +13 -0
  82. memos/llms/factory.py +38 -0
  83. memos/llms/hf.py +443 -0
  84. memos/llms/hf_singleton.py +114 -0
  85. memos/llms/ollama.py +135 -0
  86. memos/llms/openai.py +222 -0
  87. memos/llms/openai_new.py +198 -0
  88. memos/llms/qwen.py +13 -0
  89. memos/llms/utils.py +14 -0
  90. memos/llms/vllm.py +218 -0
  91. memos/log.py +237 -0
  92. memos/mem_agent/base.py +19 -0
  93. memos/mem_agent/deepsearch_agent.py +391 -0
  94. memos/mem_agent/factory.py +36 -0
  95. memos/mem_chat/__init__.py +0 -0
  96. memos/mem_chat/base.py +30 -0
  97. memos/mem_chat/factory.py +21 -0
  98. memos/mem_chat/simple.py +200 -0
  99. memos/mem_cube/__init__.py +0 -0
  100. memos/mem_cube/base.py +30 -0
  101. memos/mem_cube/general.py +240 -0
  102. memos/mem_cube/navie.py +172 -0
  103. memos/mem_cube/utils.py +169 -0
  104. memos/mem_feedback/base.py +15 -0
  105. memos/mem_feedback/feedback.py +1192 -0
  106. memos/mem_feedback/simple_feedback.py +40 -0
  107. memos/mem_feedback/utils.py +230 -0
  108. memos/mem_os/client.py +5 -0
  109. memos/mem_os/core.py +1203 -0
  110. memos/mem_os/main.py +582 -0
  111. memos/mem_os/product.py +1608 -0
  112. memos/mem_os/product_server.py +455 -0
  113. memos/mem_os/utils/default_config.py +359 -0
  114. memos/mem_os/utils/format_utils.py +1403 -0
  115. memos/mem_os/utils/reference_utils.py +162 -0
  116. memos/mem_reader/__init__.py +0 -0
  117. memos/mem_reader/base.py +47 -0
  118. memos/mem_reader/factory.py +53 -0
  119. memos/mem_reader/memory.py +298 -0
  120. memos/mem_reader/multi_modal_struct.py +965 -0
  121. memos/mem_reader/read_multi_modal/__init__.py +43 -0
  122. memos/mem_reader/read_multi_modal/assistant_parser.py +311 -0
  123. memos/mem_reader/read_multi_modal/base.py +273 -0
  124. memos/mem_reader/read_multi_modal/file_content_parser.py +826 -0
  125. memos/mem_reader/read_multi_modal/image_parser.py +359 -0
  126. memos/mem_reader/read_multi_modal/multi_modal_parser.py +252 -0
  127. memos/mem_reader/read_multi_modal/string_parser.py +139 -0
  128. memos/mem_reader/read_multi_modal/system_parser.py +327 -0
  129. memos/mem_reader/read_multi_modal/text_content_parser.py +131 -0
  130. memos/mem_reader/read_multi_modal/tool_parser.py +210 -0
  131. memos/mem_reader/read_multi_modal/user_parser.py +218 -0
  132. memos/mem_reader/read_multi_modal/utils.py +358 -0
  133. memos/mem_reader/simple_struct.py +912 -0
  134. memos/mem_reader/strategy_struct.py +163 -0
  135. memos/mem_reader/utils.py +157 -0
  136. memos/mem_scheduler/__init__.py +0 -0
  137. memos/mem_scheduler/analyzer/__init__.py +0 -0
  138. memos/mem_scheduler/analyzer/api_analyzer.py +714 -0
  139. memos/mem_scheduler/analyzer/eval_analyzer.py +219 -0
  140. memos/mem_scheduler/analyzer/mos_for_test_scheduler.py +571 -0
  141. memos/mem_scheduler/analyzer/scheduler_for_eval.py +280 -0
  142. memos/mem_scheduler/base_scheduler.py +1319 -0
  143. memos/mem_scheduler/general_modules/__init__.py +0 -0
  144. memos/mem_scheduler/general_modules/api_misc.py +137 -0
  145. memos/mem_scheduler/general_modules/base.py +80 -0
  146. memos/mem_scheduler/general_modules/init_components_for_scheduler.py +425 -0
  147. memos/mem_scheduler/general_modules/misc.py +313 -0
  148. memos/mem_scheduler/general_modules/scheduler_logger.py +389 -0
  149. memos/mem_scheduler/general_modules/task_threads.py +315 -0
  150. memos/mem_scheduler/general_scheduler.py +1495 -0
  151. memos/mem_scheduler/memory_manage_modules/__init__.py +5 -0
  152. memos/mem_scheduler/memory_manage_modules/memory_filter.py +306 -0
  153. memos/mem_scheduler/memory_manage_modules/retriever.py +547 -0
  154. memos/mem_scheduler/monitors/__init__.py +0 -0
  155. memos/mem_scheduler/monitors/dispatcher_monitor.py +366 -0
  156. memos/mem_scheduler/monitors/general_monitor.py +394 -0
  157. memos/mem_scheduler/monitors/task_schedule_monitor.py +254 -0
  158. memos/mem_scheduler/optimized_scheduler.py +410 -0
  159. memos/mem_scheduler/orm_modules/__init__.py +0 -0
  160. memos/mem_scheduler/orm_modules/api_redis_model.py +518 -0
  161. memos/mem_scheduler/orm_modules/base_model.py +729 -0
  162. memos/mem_scheduler/orm_modules/monitor_models.py +261 -0
  163. memos/mem_scheduler/orm_modules/redis_model.py +699 -0
  164. memos/mem_scheduler/scheduler_factory.py +23 -0
  165. memos/mem_scheduler/schemas/__init__.py +0 -0
  166. memos/mem_scheduler/schemas/analyzer_schemas.py +52 -0
  167. memos/mem_scheduler/schemas/api_schemas.py +233 -0
  168. memos/mem_scheduler/schemas/general_schemas.py +55 -0
  169. memos/mem_scheduler/schemas/message_schemas.py +173 -0
  170. memos/mem_scheduler/schemas/monitor_schemas.py +406 -0
  171. memos/mem_scheduler/schemas/task_schemas.py +132 -0
  172. memos/mem_scheduler/task_schedule_modules/__init__.py +0 -0
  173. memos/mem_scheduler/task_schedule_modules/dispatcher.py +740 -0
  174. memos/mem_scheduler/task_schedule_modules/local_queue.py +247 -0
  175. memos/mem_scheduler/task_schedule_modules/orchestrator.py +74 -0
  176. memos/mem_scheduler/task_schedule_modules/redis_queue.py +1385 -0
  177. memos/mem_scheduler/task_schedule_modules/task_queue.py +162 -0
  178. memos/mem_scheduler/utils/__init__.py +0 -0
  179. memos/mem_scheduler/utils/api_utils.py +77 -0
  180. memos/mem_scheduler/utils/config_utils.py +100 -0
  181. memos/mem_scheduler/utils/db_utils.py +50 -0
  182. memos/mem_scheduler/utils/filter_utils.py +176 -0
  183. memos/mem_scheduler/utils/metrics.py +125 -0
  184. memos/mem_scheduler/utils/misc_utils.py +290 -0
  185. memos/mem_scheduler/utils/monitor_event_utils.py +67 -0
  186. memos/mem_scheduler/utils/status_tracker.py +229 -0
  187. memos/mem_scheduler/webservice_modules/__init__.py +0 -0
  188. memos/mem_scheduler/webservice_modules/rabbitmq_service.py +485 -0
  189. memos/mem_scheduler/webservice_modules/redis_service.py +380 -0
  190. memos/mem_user/factory.py +94 -0
  191. memos/mem_user/mysql_persistent_user_manager.py +271 -0
  192. memos/mem_user/mysql_user_manager.py +502 -0
  193. memos/mem_user/persistent_factory.py +98 -0
  194. memos/mem_user/persistent_user_manager.py +260 -0
  195. memos/mem_user/redis_persistent_user_manager.py +225 -0
  196. memos/mem_user/user_manager.py +488 -0
  197. memos/memories/__init__.py +0 -0
  198. memos/memories/activation/__init__.py +0 -0
  199. memos/memories/activation/base.py +42 -0
  200. memos/memories/activation/item.py +56 -0
  201. memos/memories/activation/kv.py +292 -0
  202. memos/memories/activation/vllmkv.py +219 -0
  203. memos/memories/base.py +19 -0
  204. memos/memories/factory.py +42 -0
  205. memos/memories/parametric/__init__.py +0 -0
  206. memos/memories/parametric/base.py +19 -0
  207. memos/memories/parametric/item.py +11 -0
  208. memos/memories/parametric/lora.py +41 -0
  209. memos/memories/textual/__init__.py +0 -0
  210. memos/memories/textual/base.py +92 -0
  211. memos/memories/textual/general.py +236 -0
  212. memos/memories/textual/item.py +304 -0
  213. memos/memories/textual/naive.py +187 -0
  214. memos/memories/textual/prefer_text_memory/__init__.py +0 -0
  215. memos/memories/textual/prefer_text_memory/adder.py +504 -0
  216. memos/memories/textual/prefer_text_memory/config.py +106 -0
  217. memos/memories/textual/prefer_text_memory/extractor.py +221 -0
  218. memos/memories/textual/prefer_text_memory/factory.py +85 -0
  219. memos/memories/textual/prefer_text_memory/retrievers.py +177 -0
  220. memos/memories/textual/prefer_text_memory/spliter.py +132 -0
  221. memos/memories/textual/prefer_text_memory/utils.py +93 -0
  222. memos/memories/textual/preference.py +344 -0
  223. memos/memories/textual/simple_preference.py +161 -0
  224. memos/memories/textual/simple_tree.py +69 -0
  225. memos/memories/textual/tree.py +459 -0
  226. memos/memories/textual/tree_text_memory/__init__.py +0 -0
  227. memos/memories/textual/tree_text_memory/organize/__init__.py +0 -0
  228. memos/memories/textual/tree_text_memory/organize/handler.py +184 -0
  229. memos/memories/textual/tree_text_memory/organize/manager.py +518 -0
  230. memos/memories/textual/tree_text_memory/organize/relation_reason_detector.py +238 -0
  231. memos/memories/textual/tree_text_memory/organize/reorganizer.py +622 -0
  232. memos/memories/textual/tree_text_memory/retrieve/__init__.py +0 -0
  233. memos/memories/textual/tree_text_memory/retrieve/advanced_searcher.py +364 -0
  234. memos/memories/textual/tree_text_memory/retrieve/bm25_util.py +186 -0
  235. memos/memories/textual/tree_text_memory/retrieve/bochasearch.py +419 -0
  236. memos/memories/textual/tree_text_memory/retrieve/internet_retriever.py +270 -0
  237. memos/memories/textual/tree_text_memory/retrieve/internet_retriever_factory.py +102 -0
  238. memos/memories/textual/tree_text_memory/retrieve/reasoner.py +61 -0
  239. memos/memories/textual/tree_text_memory/retrieve/recall.py +497 -0
  240. memos/memories/textual/tree_text_memory/retrieve/reranker.py +111 -0
  241. memos/memories/textual/tree_text_memory/retrieve/retrieval_mid_structs.py +16 -0
  242. memos/memories/textual/tree_text_memory/retrieve/retrieve_utils.py +472 -0
  243. memos/memories/textual/tree_text_memory/retrieve/searcher.py +848 -0
  244. memos/memories/textual/tree_text_memory/retrieve/task_goal_parser.py +135 -0
  245. memos/memories/textual/tree_text_memory/retrieve/utils.py +54 -0
  246. memos/memories/textual/tree_text_memory/retrieve/xinyusearch.py +387 -0
  247. memos/memos_tools/dinding_report_bot.py +453 -0
  248. memos/memos_tools/lockfree_dict.py +120 -0
  249. memos/memos_tools/notification_service.py +44 -0
  250. memos/memos_tools/notification_utils.py +142 -0
  251. memos/memos_tools/singleton.py +174 -0
  252. memos/memos_tools/thread_safe_dict.py +310 -0
  253. memos/memos_tools/thread_safe_dict_segment.py +382 -0
  254. memos/multi_mem_cube/__init__.py +0 -0
  255. memos/multi_mem_cube/composite_cube.py +86 -0
  256. memos/multi_mem_cube/single_cube.py +874 -0
  257. memos/multi_mem_cube/views.py +54 -0
  258. memos/parsers/__init__.py +0 -0
  259. memos/parsers/base.py +15 -0
  260. memos/parsers/factory.py +21 -0
  261. memos/parsers/markitdown.py +28 -0
  262. memos/reranker/__init__.py +4 -0
  263. memos/reranker/base.py +25 -0
  264. memos/reranker/concat.py +103 -0
  265. memos/reranker/cosine_local.py +102 -0
  266. memos/reranker/factory.py +72 -0
  267. memos/reranker/http_bge.py +324 -0
  268. memos/reranker/http_bge_strategy.py +327 -0
  269. memos/reranker/noop.py +19 -0
  270. memos/reranker/strategies/__init__.py +4 -0
  271. memos/reranker/strategies/base.py +61 -0
  272. memos/reranker/strategies/concat_background.py +94 -0
  273. memos/reranker/strategies/concat_docsource.py +110 -0
  274. memos/reranker/strategies/dialogue_common.py +109 -0
  275. memos/reranker/strategies/factory.py +31 -0
  276. memos/reranker/strategies/single_turn.py +107 -0
  277. memos/reranker/strategies/singleturn_outmem.py +98 -0
  278. memos/settings.py +10 -0
  279. memos/templates/__init__.py +0 -0
  280. memos/templates/advanced_search_prompts.py +211 -0
  281. memos/templates/cloud_service_prompt.py +107 -0
  282. memos/templates/instruction_completion.py +66 -0
  283. memos/templates/mem_agent_prompts.py +85 -0
  284. memos/templates/mem_feedback_prompts.py +822 -0
  285. memos/templates/mem_reader_prompts.py +1096 -0
  286. memos/templates/mem_reader_strategy_prompts.py +238 -0
  287. memos/templates/mem_scheduler_prompts.py +626 -0
  288. memos/templates/mem_search_prompts.py +93 -0
  289. memos/templates/mos_prompts.py +403 -0
  290. memos/templates/prefer_complete_prompt.py +735 -0
  291. memos/templates/tool_mem_prompts.py +139 -0
  292. memos/templates/tree_reorganize_prompts.py +230 -0
  293. memos/types/__init__.py +34 -0
  294. memos/types/general_types.py +151 -0
  295. memos/types/openai_chat_completion_types/__init__.py +15 -0
  296. memos/types/openai_chat_completion_types/chat_completion_assistant_message_param.py +56 -0
  297. memos/types/openai_chat_completion_types/chat_completion_content_part_image_param.py +27 -0
  298. memos/types/openai_chat_completion_types/chat_completion_content_part_input_audio_param.py +23 -0
  299. memos/types/openai_chat_completion_types/chat_completion_content_part_param.py +43 -0
  300. memos/types/openai_chat_completion_types/chat_completion_content_part_refusal_param.py +16 -0
  301. memos/types/openai_chat_completion_types/chat_completion_content_part_text_param.py +16 -0
  302. memos/types/openai_chat_completion_types/chat_completion_message_custom_tool_call_param.py +27 -0
  303. memos/types/openai_chat_completion_types/chat_completion_message_function_tool_call_param.py +32 -0
  304. memos/types/openai_chat_completion_types/chat_completion_message_param.py +18 -0
  305. memos/types/openai_chat_completion_types/chat_completion_message_tool_call_union_param.py +15 -0
  306. memos/types/openai_chat_completion_types/chat_completion_system_message_param.py +36 -0
  307. memos/types/openai_chat_completion_types/chat_completion_tool_message_param.py +30 -0
  308. memos/types/openai_chat_completion_types/chat_completion_user_message_param.py +34 -0
  309. memos/utils.py +123 -0
  310. memos/vec_dbs/__init__.py +0 -0
  311. memos/vec_dbs/base.py +117 -0
  312. memos/vec_dbs/factory.py +23 -0
  313. memos/vec_dbs/item.py +50 -0
  314. memos/vec_dbs/milvus.py +654 -0
  315. 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 []