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,210 @@
1
+ """Parser for tool messages."""
2
+
3
+ import json
4
+
5
+ from typing import Any
6
+
7
+ from memos.embedders.base import BaseEmbedder
8
+ from memos.llms.base import BaseLLM
9
+ from memos.log import get_logger
10
+ from memos.memories.textual.item import (
11
+ SourceMessage,
12
+ TextualMemoryItem,
13
+ TreeNodeTextualMemoryMetadata,
14
+ )
15
+ from memos.types.openai_chat_completion_types import ChatCompletionToolMessageParam
16
+
17
+ from .base import BaseMessageParser, _add_lang_to_source
18
+ from .utils import detect_lang
19
+
20
+
21
+ logger = get_logger(__name__)
22
+
23
+
24
+ class ToolParser(BaseMessageParser):
25
+ """Parser for tool messages."""
26
+
27
+ def __init__(self, embedder: BaseEmbedder, llm: BaseLLM | None = None):
28
+ """
29
+ Initialize ToolParser.
30
+
31
+ Args:
32
+ embedder: Embedder for generating embeddings
33
+ llm: Optional LLM for fine mode processing
34
+ """
35
+ super().__init__(embedder, llm)
36
+
37
+ def create_source(
38
+ self,
39
+ message: ChatCompletionToolMessageParam,
40
+ info: dict[str, Any],
41
+ ) -> SourceMessage | list[SourceMessage]:
42
+ """Create SourceMessage from tool message."""
43
+
44
+ if not isinstance(message, dict):
45
+ return []
46
+
47
+ role = message.get("role", "tool")
48
+ raw_content = message.get("content", "")
49
+ tool_call_id = message.get("tool_call_id", "")
50
+ chat_time = message.get("chat_time")
51
+ message_id = message.get("message_id")
52
+
53
+ sources = []
54
+
55
+ if isinstance(raw_content, list):
56
+ text_contents = []
57
+ for part in raw_content:
58
+ if isinstance(part, dict):
59
+ part_type = part.get("type", "")
60
+ if part_type == "text":
61
+ text_contents.append(part.get("text", ""))
62
+
63
+ # Detect overall language from all text content
64
+ overall_lang = "en"
65
+ if text_contents:
66
+ combined_text = " ".join(text_contents)
67
+ overall_lang = detect_lang(combined_text)
68
+
69
+ # Create one SourceMessage per part, all with the same detected language
70
+ for part in raw_content:
71
+ if isinstance(part, dict):
72
+ part_type = part.get("type", "")
73
+ if part_type == "text":
74
+ text_content = part.get("text", "")
75
+ source = SourceMessage(
76
+ type="text",
77
+ role=role,
78
+ chat_time=chat_time,
79
+ message_id=message_id,
80
+ content=text_content,
81
+ tool_call_id=tool_call_id,
82
+ )
83
+ source.lang = overall_lang
84
+ sources.append(source)
85
+ elif part_type == "file":
86
+ file_info = part.get("file", {})
87
+ file_content = file_info.get("file_data", "")
88
+ source = SourceMessage(
89
+ type="file",
90
+ role=role,
91
+ chat_time=chat_time,
92
+ message_id=message_id,
93
+ content=file_content,
94
+ filename=file_info.get("filename", ""),
95
+ file_id=file_info.get("file_id", ""),
96
+ tool_call_id=tool_call_id,
97
+ file_info=file_info,
98
+ )
99
+ source.lang = overall_lang
100
+ sources.append(source)
101
+ elif part_type == "image_url":
102
+ file_info = part.get("image_url", {})
103
+ source = SourceMessage(
104
+ type="image_url",
105
+ role=role,
106
+ chat_time=chat_time,
107
+ message_id=message_id,
108
+ content=file_info.get("url", ""),
109
+ detail=file_info.get("detail", "auto"),
110
+ tool_call_id=tool_call_id,
111
+ )
112
+ source.lang = overall_lang
113
+ sources.append(source)
114
+ elif part_type == "input_audio":
115
+ file_info = part.get("input_audio", {})
116
+ source = SourceMessage(
117
+ type="input_audio",
118
+ role=role,
119
+ chat_time=chat_time,
120
+ message_id=message_id,
121
+ content=file_info.get("data", ""),
122
+ format=file_info.get("format", "wav"),
123
+ tool_call_id=tool_call_id,
124
+ )
125
+ source.lang = overall_lang
126
+ sources.append(source)
127
+ else:
128
+ logger.warning(f"[ToolParser] Unsupported part type: {part_type}")
129
+ continue
130
+ else:
131
+ # Simple string content message: single SourceMessage
132
+ if raw_content:
133
+ source = SourceMessage(
134
+ type="chat",
135
+ role=role,
136
+ chat_time=chat_time,
137
+ message_id=message_id,
138
+ content=raw_content,
139
+ tool_call_id=tool_call_id,
140
+ )
141
+ sources.append(_add_lang_to_source(source, raw_content))
142
+
143
+ return sources
144
+
145
+ def rebuild_from_source(
146
+ self,
147
+ source: SourceMessage,
148
+ ) -> ChatCompletionToolMessageParam:
149
+ """Rebuild tool message from SourceMessage."""
150
+
151
+ def parse_fast(
152
+ self,
153
+ message: ChatCompletionToolMessageParam,
154
+ info: dict[str, Any],
155
+ **kwargs,
156
+ ) -> list[TextualMemoryItem]:
157
+ role = message.get("role", "")
158
+ content = message.get("content", "")
159
+ chat_time = message.get("chat_time", None)
160
+
161
+ if role != "tool":
162
+ logger.warning(f"[ToolParser] Expected role is `tool`, got {role}")
163
+ return []
164
+ parts = [f"{role}: "]
165
+ if chat_time:
166
+ parts.append(f"[{chat_time}]: ")
167
+ prefix = "".join(parts)
168
+ content = (
169
+ json.dumps(content, ensure_ascii=False) if isinstance(content, list | dict) else content
170
+ )
171
+ line = f"{prefix}{content}\n"
172
+ if not line:
173
+ return []
174
+
175
+ sources = self.create_source(message, info)
176
+
177
+ # Extract info fields
178
+ info_ = info.copy()
179
+ user_id = info_.pop("user_id", "")
180
+ session_id = info_.pop("session_id", "")
181
+
182
+ content_chunks = self._split_text(line)
183
+ memory_items = []
184
+ for _chunk_idx, chunk_text in enumerate(content_chunks):
185
+ if not chunk_text.strip():
186
+ continue
187
+
188
+ memory_item = TextualMemoryItem(
189
+ memory=chunk_text,
190
+ metadata=TreeNodeTextualMemoryMetadata(
191
+ user_id=user_id,
192
+ session_id=session_id,
193
+ memory_type="LongTermMemory", # only choce long term memory for tool messages as a placeholder
194
+ status="activated",
195
+ tags=["mode:fast"],
196
+ sources=sources,
197
+ info=info_,
198
+ ),
199
+ )
200
+ memory_items.append(memory_item)
201
+ return memory_items
202
+
203
+ def parse_fine(
204
+ self,
205
+ message: ChatCompletionToolMessageParam,
206
+ info: dict[str, Any],
207
+ **kwargs,
208
+ ) -> list[TextualMemoryItem]:
209
+ # tool message no special multimodal handling is required in fine mode.
210
+ return []
@@ -0,0 +1,218 @@
1
+ """Parser for user messages."""
2
+
3
+ from typing import Any
4
+
5
+ from memos.embedders.base import BaseEmbedder
6
+ from memos.llms.base import BaseLLM
7
+ from memos.log import get_logger
8
+ from memos.memories.textual.item import (
9
+ SourceMessage,
10
+ TextualMemoryItem,
11
+ TreeNodeTextualMemoryMetadata,
12
+ )
13
+ from memos.types.openai_chat_completion_types import ChatCompletionUserMessageParam
14
+
15
+ from .base import BaseMessageParser, _add_lang_to_source, _derive_key, _extract_text_from_content
16
+ from .utils import detect_lang
17
+
18
+
19
+ logger = get_logger(__name__)
20
+
21
+
22
+ class UserParser(BaseMessageParser):
23
+ """Parser for user messages.
24
+
25
+ Handles multimodal user messages by creating one SourceMessage per content part.
26
+ """
27
+
28
+ def __init__(self, embedder: BaseEmbedder, llm: BaseLLM | None = None):
29
+ """
30
+ Initialize UserParser.
31
+
32
+ Args:
33
+ embedder: Embedder for generating embeddings
34
+ llm: Optional LLM for fine mode processing
35
+ """
36
+ super().__init__(embedder, llm)
37
+
38
+ def create_source(
39
+ self,
40
+ message: ChatCompletionUserMessageParam,
41
+ info: dict[str, Any],
42
+ ) -> SourceMessage | list[SourceMessage]:
43
+ """
44
+ Create SourceMessage(s) from user message.
45
+
46
+ For multimodal messages (content is a list), creates one SourceMessage per part.
47
+ For simple messages (content is str), creates a single SourceMessage.
48
+ """
49
+ if not isinstance(message, dict):
50
+ return []
51
+
52
+ role = message.get("role", "user")
53
+ raw_content = message.get("content", "")
54
+ chat_time = message.get("chat_time")
55
+ message_id = message.get("message_id")
56
+
57
+ sources = []
58
+
59
+ if isinstance(raw_content, list):
60
+ # Multimodal: first collect all text content to detect overall language
61
+ text_contents = []
62
+ for part in raw_content:
63
+ if isinstance(part, dict):
64
+ part_type = part.get("type", "")
65
+ if part_type == "text":
66
+ text_contents.append(part.get("text", ""))
67
+
68
+ # Detect overall language from all text content
69
+ overall_lang = "en"
70
+ if text_contents:
71
+ combined_text = " ".join(text_contents)
72
+ overall_lang = detect_lang(combined_text)
73
+
74
+ # Create one SourceMessage per part, all with the same detected language
75
+ for part in raw_content:
76
+ if isinstance(part, dict):
77
+ part_type = part.get("type", "")
78
+ if part_type == "text":
79
+ source = SourceMessage(
80
+ type="chat",
81
+ role=role,
82
+ chat_time=chat_time,
83
+ message_id=message_id,
84
+ content=part.get("text", ""),
85
+ )
86
+ source.lang = overall_lang
87
+ sources.append(source)
88
+ elif part_type == "file":
89
+ file_info = part.get("file", {})
90
+ source = SourceMessage(
91
+ type="file",
92
+ role=role,
93
+ chat_time=chat_time,
94
+ message_id=message_id,
95
+ doc_path=file_info.get("filename") or file_info.get("file_id", ""),
96
+ content=file_info.get("file_data", ""),
97
+ file_info=file_info,
98
+ )
99
+ source.lang = overall_lang
100
+ sources.append(source)
101
+ elif part_type == "image_url":
102
+ image_info = part.get("image_url", {})
103
+ source = SourceMessage(
104
+ type="image",
105
+ role=role,
106
+ chat_time=chat_time,
107
+ message_id=message_id,
108
+ image_path=image_info.get("url"),
109
+ )
110
+ source.lang = overall_lang
111
+ sources.append(source)
112
+ else:
113
+ # input_audio, etc.
114
+ source = SourceMessage(
115
+ type=part_type,
116
+ role=role,
117
+ chat_time=chat_time,
118
+ message_id=message_id,
119
+ content=f"[{part_type}]",
120
+ )
121
+ source.lang = overall_lang
122
+ sources.append(source)
123
+ else:
124
+ # Simple message: single SourceMessage
125
+ content = _extract_text_from_content(raw_content)
126
+ if content:
127
+ source = SourceMessage(
128
+ type="chat",
129
+ role=role,
130
+ chat_time=chat_time,
131
+ message_id=message_id,
132
+ content=content,
133
+ )
134
+ sources.append(_add_lang_to_source(source, content))
135
+
136
+ if not sources:
137
+ return _add_lang_to_source(SourceMessage(type="chat", role=role), None)
138
+ if len(sources) > 1:
139
+ return sources
140
+ return sources[0]
141
+
142
+ def rebuild_from_source(
143
+ self,
144
+ source: SourceMessage,
145
+ ) -> ChatCompletionUserMessageParam:
146
+ """We only need rebuild from specific multimodal source"""
147
+
148
+ def parse_fast(
149
+ self,
150
+ message: ChatCompletionUserMessageParam,
151
+ info: dict[str, Any],
152
+ **kwargs,
153
+ ) -> list[TextualMemoryItem]:
154
+ if not isinstance(message, dict):
155
+ logger.warning(f"[UserParser] Expected dict, got {type(message)}")
156
+ return []
157
+
158
+ role = message.get("role", "")
159
+ content = message.get("content", "")
160
+ chat_time = message.get("chat_time", None)
161
+ if role != "user":
162
+ logger.warning(f"[UserParser] Expected role is `user`, got {role}")
163
+ return []
164
+ parts = [f"{role}: "]
165
+ if chat_time:
166
+ parts.append(f"[{chat_time}]: ")
167
+ prefix = "".join(parts)
168
+ line = f"{prefix}{content}\n"
169
+ if not line:
170
+ return []
171
+ memory_type = "UserMemory"
172
+
173
+ # Create source(s) using parser's create_source method
174
+ sources = self.create_source(message, info)
175
+ if isinstance(sources, SourceMessage):
176
+ sources = [sources]
177
+ elif not sources:
178
+ return []
179
+
180
+ # Extract info fields
181
+ info_ = info.copy()
182
+ user_id = info_.pop("user_id", "")
183
+ session_id = info_.pop("session_id", "")
184
+
185
+ # Create memory item (equivalent to _make_memory_item)
186
+ memory_item = TextualMemoryItem(
187
+ memory=line,
188
+ metadata=TreeNodeTextualMemoryMetadata(
189
+ user_id=user_id,
190
+ session_id=session_id,
191
+ memory_type=memory_type,
192
+ status="activated",
193
+ tags=["mode:fast"],
194
+ key=_derive_key(line),
195
+ embedding=self.embedder.embed([line])[0],
196
+ usage=[],
197
+ sources=sources,
198
+ background="",
199
+ confidence=0.99,
200
+ type="fact",
201
+ info=info_,
202
+ ),
203
+ )
204
+
205
+ return [memory_item]
206
+
207
+ def parse_fine(
208
+ self,
209
+ message: ChatCompletionUserMessageParam,
210
+ info: dict[str, Any],
211
+ **kwargs,
212
+ ) -> list[TextualMemoryItem]:
213
+ logger.info(
214
+ "ChatCompletionUserMessageParam is inherently a "
215
+ "text-only modality. No special multimodal handling"
216
+ " is required in fine mode."
217
+ )
218
+ return []