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,92 @@
1
+ from abc import abstractmethod
2
+ from typing import Any
3
+
4
+ from memos.configs.memory import BaseTextMemoryConfig
5
+ from memos.memories.base import BaseMemory
6
+ from memos.memories.textual.item import TextualMemoryItem
7
+ from memos.types import MessageList
8
+
9
+
10
+ class BaseTextMemory(BaseMemory):
11
+ """Base class for all textual memory implementations."""
12
+
13
+ # Default mode configuration - can be overridden by subclasses
14
+ mode: str = "sync" # Default mode: 'async' or 'sync'
15
+
16
+ @abstractmethod
17
+ def __init__(self, config: BaseTextMemoryConfig):
18
+ """Initialize memory with the given configuration."""
19
+
20
+ @abstractmethod
21
+ def extract(self, messages: MessageList) -> list[TextualMemoryItem]:
22
+ """Extract memories based on the messages.
23
+ Args:
24
+ messages (MessageList): The messages to extract memories from.
25
+ Returns:
26
+ list[TextualMemoryItem]: List of extracted memory items.
27
+ """
28
+
29
+ @abstractmethod
30
+ def add(self, memories: list[TextualMemoryItem | dict[str, Any]], **kwargs) -> list[str]:
31
+ """Add memories.
32
+
33
+ Args:
34
+ memories: List of TextualMemoryItem objects or dictionaries to add.
35
+ """
36
+
37
+ @abstractmethod
38
+ def update(self, memory_id: str, new_memory: TextualMemoryItem | dict[str, Any]) -> None:
39
+ """Update a memory by memory_id."""
40
+
41
+ @abstractmethod
42
+ def search(self, query: str, top_k: int, info=None, **kwargs) -> list[TextualMemoryItem]:
43
+ """Search for memories based on a query.
44
+ Args:
45
+ query (str): The query to search for.
46
+ top_k (int): The number of top results to return.
47
+ info (dict): Leave a record of memory consumption.
48
+ Returns:
49
+ list[TextualMemoryItem]: List of matching memories.
50
+ """
51
+
52
+ @abstractmethod
53
+ def get(self, memory_id: str, user_name: str | None = None) -> TextualMemoryItem:
54
+ """Get a memory by its ID.
55
+ Args:
56
+ memory_id (str): The ID of the memory to retrieve.
57
+ Returns:
58
+ TextualMemoryItem: The memory with the given ID.
59
+ """
60
+
61
+ @abstractmethod
62
+ def get_by_ids(self, memory_ids: list[str]) -> list[TextualMemoryItem]:
63
+ """Get memories by their IDs.
64
+ Args:
65
+ memory_ids (list[str]): List of memory IDs to retrieve.
66
+ Returns:
67
+ list[TextualMemoryItem]: List of memories with the specified IDs.
68
+ """
69
+
70
+ @abstractmethod
71
+ def get_all(self) -> list[TextualMemoryItem]:
72
+ """Get all memories.
73
+ Returns:
74
+ list[TextualMemoryItem]: List of all memories.
75
+ """
76
+
77
+ @abstractmethod
78
+ def delete(self, memory_ids: list[str]) -> None:
79
+ """Delete memories.
80
+ Args:
81
+ memory_ids (list[str]): List of memory IDs to delete.
82
+ """
83
+
84
+ @abstractmethod
85
+ def delete_all(self) -> None:
86
+ """Delete all memories."""
87
+
88
+ @abstractmethod
89
+ def drop(
90
+ self,
91
+ ) -> None:
92
+ """Drop all databases."""
@@ -0,0 +1,236 @@
1
+ import json
2
+ import os
3
+
4
+ from datetime import datetime
5
+ from typing import Any
6
+
7
+ from tenacity import retry, retry_if_exception_type, stop_after_attempt
8
+
9
+ from memos.configs.memory import GeneralTextMemoryConfig
10
+ from memos.embedders.factory import ArkEmbedder, EmbedderFactory, OllamaEmbedder
11
+ from memos.llms.factory import AzureLLM, LLMFactory, OllamaLLM, OpenAILLM
12
+ from memos.log import get_logger
13
+ from memos.memories.textual.base import BaseTextMemory
14
+ from memos.memories.textual.item import TextualMemoryItem
15
+ from memos.templates.mem_reader_prompts import SIMPLE_STRUCT_MEM_READER_PROMPT
16
+ from memos.types import MessageList
17
+ from memos.vec_dbs.factory import QdrantVecDB, VecDBFactory
18
+ from memos.vec_dbs.item import VecDBItem
19
+
20
+
21
+ logger = get_logger(__name__)
22
+
23
+
24
+ class GeneralTextMemory(BaseTextMemory):
25
+ """General textual memory implementation for storing and retrieving memories."""
26
+
27
+ def __init__(self, config: GeneralTextMemoryConfig):
28
+ """Initialize memory with the given configuration."""
29
+ # Set mode from class default or override if needed
30
+ self.mode = getattr(self.__class__, "mode", "sync")
31
+ self.config: GeneralTextMemoryConfig = config
32
+ self.extractor_llm: OpenAILLM | OllamaLLM | AzureLLM = LLMFactory.from_config(
33
+ config.extractor_llm
34
+ )
35
+ self.vector_db: QdrantVecDB = VecDBFactory.from_config(config.vector_db)
36
+ self.embedder: OllamaEmbedder | ArkEmbedder = EmbedderFactory.from_config(config.embedder)
37
+
38
+ @retry(
39
+ stop=stop_after_attempt(3),
40
+ retry=retry_if_exception_type(json.JSONDecodeError),
41
+ before_sleep=lambda retry_state: logger.warning(
42
+ f"Extracting memory failed due to JSON decode error: {retry_state.outcome.exception()}, Attempt retry: {retry_state.attempt_number} / {3}"
43
+ ),
44
+ )
45
+ def extract(self, messages: MessageList) -> list[TextualMemoryItem]:
46
+ """Extract memories based on the messages.
47
+
48
+ Args:
49
+ messages: List of message dictionaries to extract memories from.
50
+
51
+ Returns:
52
+ List of TextualMemoryItem objects representing the extracted memories.
53
+ """
54
+
55
+ str_messages = "\n".join(
56
+ [message["role"] + ":" + message["content"] for message in messages]
57
+ )
58
+
59
+ prompt = SIMPLE_STRUCT_MEM_READER_PROMPT.replace("${conversation}", str_messages).replace(
60
+ "${custom_tags_prompt}", ""
61
+ )
62
+ messages = [{"role": "user", "content": prompt}]
63
+ response_text = self.extractor_llm.generate(messages)
64
+ response_json = self.parse_json_result(response_text)
65
+
66
+ extracted_memories = [
67
+ TextualMemoryItem(
68
+ memory=memory_dict["value"],
69
+ metadata={
70
+ "key": memory_dict["key"],
71
+ "source": "conversation",
72
+ "tags": memory_dict["tags"],
73
+ "updated_at": datetime.now().isoformat(),
74
+ },
75
+ )
76
+ for memory_dict in response_json["memory list"]
77
+ ]
78
+
79
+ return extracted_memories
80
+
81
+ def add(self, memories: list[TextualMemoryItem | dict[str, Any]]) -> None:
82
+ """Add memories.
83
+
84
+ Args:
85
+ memories: List of TextualMemoryItem objects or dictionaries to add.
86
+ """
87
+ memory_items = [TextualMemoryItem(**m) if isinstance(m, dict) else m for m in memories]
88
+
89
+ # Memory encode
90
+ embed_memories = self.embedder.embed([m.memory for m in memory_items])
91
+
92
+ # Create vector db items
93
+ vec_db_items = []
94
+ for item, emb in zip(memory_items, embed_memories, strict=True):
95
+ vec_db_items.append(
96
+ VecDBItem(
97
+ id=item.id,
98
+ payload=item.model_dump(),
99
+ vector=emb,
100
+ )
101
+ )
102
+
103
+ # Add to vector db
104
+ self.vector_db.add(vec_db_items)
105
+
106
+ def update(self, memory_id: str, new_memory: TextualMemoryItem | dict[str, Any]) -> None:
107
+ """Update a memory by memory_id."""
108
+ memory_item = (
109
+ TextualMemoryItem(**new_memory) if isinstance(new_memory, dict) else new_memory
110
+ )
111
+ memory_item.id = memory_id
112
+
113
+ vec_db_item = VecDBItem(
114
+ id=memory_item.id,
115
+ payload=memory_item.model_dump(),
116
+ vector=self._embed_one_sentence(memory_item.memory),
117
+ )
118
+
119
+ self.vector_db.update(memory_id, vec_db_item)
120
+
121
+ def search(self, query: str, top_k: int, info=None, **kwargs) -> list[TextualMemoryItem]:
122
+ """Search for memories based on a query.
123
+ Args:
124
+ query (str): The query to search for.
125
+ top_k (int): The number of top results to return.
126
+ Returns:
127
+ list[TextualMemoryItem]: List of matching memories.
128
+ """
129
+ query_vector = self._embed_one_sentence(query)
130
+ search_results = self.vector_db.search(query_vector, top_k)
131
+ search_results = sorted( # make higher score first
132
+ search_results, key=lambda x: x.score, reverse=True
133
+ )
134
+ result_memories = [
135
+ TextualMemoryItem(**search_item.payload) for search_item in search_results
136
+ ]
137
+ return result_memories
138
+
139
+ def get(self, memory_id: str, user_name: str | None = None) -> TextualMemoryItem:
140
+ """Get a memory by its ID."""
141
+ result = self.vector_db.get_by_id(memory_id)
142
+ if result is None:
143
+ raise ValueError(f"Memory with ID {memory_id} not found")
144
+ return TextualMemoryItem(**result.payload)
145
+
146
+ def get_by_ids(self, memory_ids: list[str]) -> list[TextualMemoryItem]:
147
+ """Get memories by their IDs.
148
+ Args:
149
+ memory_ids (list[str]): List of memory IDs to retrieve.
150
+ Returns:
151
+ list[TextualMemoryItem]: List of memories with the specified IDs.
152
+ """
153
+ db_items = self.vector_db.get_by_ids(memory_ids)
154
+ memories = [TextualMemoryItem(**db_item.payload) for db_item in db_items]
155
+ return memories
156
+
157
+ def get_all(self) -> list[TextualMemoryItem]:
158
+ """Get all memories.
159
+ Returns:
160
+ list[TextualMemoryItem]: List of all memories.
161
+ """
162
+ all_items = self.vector_db.get_all()
163
+ all_memories = [TextualMemoryItem(**memo.payload) for memo in all_items]
164
+ return all_memories
165
+
166
+ def delete(self, memory_ids: list[str]) -> None:
167
+ """Delete a memory."""
168
+ self.vector_db.delete(memory_ids)
169
+
170
+ def delete_all(self) -> None:
171
+ """Delete all memories."""
172
+ self.vector_db.delete_collection(self.vector_db.config.collection_name)
173
+ self.vector_db.create_collection()
174
+
175
+ def load(self, dir: str) -> None:
176
+ try:
177
+ memory_file = os.path.join(dir, self.config.memory_filename)
178
+
179
+ if not os.path.exists(memory_file):
180
+ logger.warning(f"Memory file not found: {memory_file}")
181
+ return
182
+
183
+ with open(memory_file, encoding="utf-8") as f:
184
+ memories = json.load(f)
185
+
186
+ vec_db_items = [VecDBItem.from_dict(m) for m in memories]
187
+ self.vector_db.add(vec_db_items)
188
+ logger.info(f"Loaded {len(memories)} memories from {memory_file}")
189
+
190
+ except FileNotFoundError:
191
+ logger.error(f"Memory file not found in directory: {dir}")
192
+ except json.JSONDecodeError as e:
193
+ logger.error(f"Error decoding JSON from memory file: {e}")
194
+ except Exception as e:
195
+ logger.error(f"An error occurred while loading memories: {e}")
196
+
197
+ def dump(self, dir: str) -> None:
198
+ """Dump memories to os.path.join(dir, self.config.memory_filename)"""
199
+ try:
200
+ all_vec_db_items = self.vector_db.get_all()
201
+ json_memories = [memory.to_dict() for memory in all_vec_db_items]
202
+
203
+ os.makedirs(dir, exist_ok=True)
204
+ memory_file = os.path.join(dir, self.config.memory_filename)
205
+ with open(memory_file, "w", encoding="utf-8") as f:
206
+ json.dump(json_memories, f, indent=4, ensure_ascii=False)
207
+
208
+ logger.info(f"Dumped {len(all_vec_db_items)} memories to {memory_file}")
209
+
210
+ except Exception as e:
211
+ logger.error(f"An error occurred while dumping memories: {e}")
212
+ raise
213
+
214
+ def drop(
215
+ self,
216
+ ) -> None:
217
+ pass
218
+
219
+ def _embed_one_sentence(self, sentence: str) -> list[float]:
220
+ """Embed a single sentence."""
221
+ return self.embedder.embed([sentence])[0]
222
+
223
+ def parse_json_result(self, response_text):
224
+ try:
225
+ json_start = response_text.find("{")
226
+ response_text = response_text[json_start:]
227
+ response_text = response_text.replace("```", "").strip()
228
+ if response_text[-1] != "}":
229
+ response_text += "}"
230
+ response_json = json.loads(response_text)
231
+ return response_json
232
+ except json.JSONDecodeError as e:
233
+ logger.warning(
234
+ f"Failed to parse LLM response as JSON: {e}\nRaw response:\n{response_text}"
235
+ )
236
+ return {}
@@ -0,0 +1,304 @@
1
+ """Defines memory item types for textual memory."""
2
+
3
+ import json
4
+ import logging
5
+ import uuid
6
+
7
+ from datetime import datetime
8
+ from typing import Any, Literal
9
+
10
+ from pydantic import BaseModel, ConfigDict, Field, field_validator
11
+
12
+
13
+ ALLOWED_ROLES = {"user", "assistant", "system"}
14
+
15
+
16
+ class SourceMessage(BaseModel):
17
+ """
18
+ Purpose: **memory provenance / traceability**.
19
+
20
+ Capture the minimal, reproducible origin context of a memory item so it can be
21
+ audited, traced, rolled back, or de-duplicated later.
22
+
23
+ Fields & conventions:
24
+ - type: Source kind (e.g., "chat", "doc", "web", "file", "system", ...).
25
+ If not provided, upstream logic may infer it:
26
+ presence of `role` ⇒ "chat"; otherwise ⇒ "doc".
27
+ - role: Conversation role ("user" | "assistant" | "system" | "tool") when the
28
+ source is a chat turn.
29
+ - content: Minimal reproducible snippet from the source. If omitted,
30
+ upstream may fall back to `doc_path` / `url` / `message_id`.
31
+ - file_info: File information for file source.
32
+ - chat_time / message_id / doc_path: Locators for precisely pointing back
33
+ to the original record (timestamp, message id, document path).
34
+ - Extra fields: Allowed (`model_config.extra="allow"`) to carry arbitrary
35
+ provenance attributes (e.g., url, page, offset, span, local_confidence).
36
+ """
37
+
38
+ type: str | None = "chat"
39
+ role: Literal["user", "assistant", "system", "tool"] | None = None
40
+ chat_time: str | None = None
41
+ message_id: str | None = None
42
+ content: str | None = None
43
+ doc_path: str | None = None
44
+ file_info: dict | None = None
45
+ model_config = ConfigDict(extra="allow")
46
+
47
+
48
+ class TextualMemoryMetadata(BaseModel):
49
+ """Metadata for a memory item.
50
+
51
+ This includes information such as the type of memory, when it occurred,
52
+ its source, and other relevant details.
53
+ """
54
+
55
+ user_id: str | None = Field(
56
+ default=None,
57
+ description="The ID of the user associated with the memory. Useful for multi-user systems.",
58
+ )
59
+ session_id: str | None = Field(
60
+ default=None,
61
+ description="The ID of the session during which the memory was created. Useful for tracking context in conversations.",
62
+ )
63
+ status: Literal["activated", "archived", "deleted"] | None = Field(
64
+ default="activated",
65
+ description="The status of the memory, e.g., 'activated', 'archived', 'deleted'.",
66
+ )
67
+ type: str | None = Field(default=None)
68
+ key: str | None = Field(default=None, description="Memory key or title.")
69
+ confidence: float | None = Field(
70
+ default=None,
71
+ description="A numeric score (float between 0 and 100) indicating how certain you are about the accuracy or reliability of the memory.",
72
+ )
73
+ source: Literal["conversation", "retrieved", "web", "file", "system"] | None = Field(
74
+ default=None, description="The origin of the memory"
75
+ )
76
+ tags: list[str] | None = Field(
77
+ default=None,
78
+ description='A list of keywords or thematic labels associated with the memory for categorization or retrieval, e.g., `["travel", "health", "project-x"]`.',
79
+ )
80
+ visibility: Literal["private", "public", "session"] | None = Field(
81
+ default=None, description="e.g., 'private', 'public', 'session'"
82
+ )
83
+ updated_at: str | None = Field(
84
+ default_factory=lambda: datetime.now().isoformat(),
85
+ description="The timestamp of the last modification to the memory. Useful for tracking memory freshness or change history. Format: ISO 8601.",
86
+ )
87
+ info: dict | None = Field(
88
+ default=None,
89
+ description="Arbitrary key-value pairs for additional metadata.",
90
+ )
91
+
92
+ model_config = ConfigDict(extra="allow")
93
+
94
+ covered_history: Any | None = Field(
95
+ default=None,
96
+ description="Record the memory id covered by the update",
97
+ )
98
+
99
+ def __str__(self) -> str:
100
+ """Pretty string representation of the metadata."""
101
+ meta = self.model_dump(exclude_none=True)
102
+ return ", ".join(f"{k}={v}" for k, v in meta.items())
103
+
104
+
105
+ class TreeNodeTextualMemoryMetadata(TextualMemoryMetadata):
106
+ """Extended metadata for structured memory, layered retrieval, and lifecycle tracking."""
107
+
108
+ memory_type: Literal[
109
+ "WorkingMemory",
110
+ "LongTermMemory",
111
+ "UserMemory",
112
+ "OuterMemory",
113
+ "ToolSchemaMemory",
114
+ "ToolTrajectoryMemory",
115
+ ] = Field(default="WorkingMemory", description="Memory lifecycle type.")
116
+ sources: list[SourceMessage] | None = Field(
117
+ default=None, description="Multiple origins of the memory (e.g., URLs, notes)."
118
+ )
119
+ embedding: list[float] | None = Field(
120
+ default=None,
121
+ description="The vector embedding of the memory content, used for semantic search or clustering.",
122
+ )
123
+ created_at: str | None = Field(
124
+ default_factory=lambda: datetime.now().isoformat(),
125
+ description="The timestamp of the first creation to the memory. Useful "
126
+ "for tracking memory initialization. Format: ISO 8601.",
127
+ )
128
+ usage: list[str] = Field(
129
+ default_factory=list,
130
+ description="Usage history of this node",
131
+ )
132
+ background: str | None = Field(
133
+ default="",
134
+ description="background of this node",
135
+ )
136
+
137
+ file_ids: list[str] | None = Field(
138
+ default_factory=list,
139
+ description="The ids of the files associated with the memory.",
140
+ )
141
+
142
+ @field_validator("sources", mode="before")
143
+ @classmethod
144
+ def coerce_sources(cls, v):
145
+ if v is None:
146
+ return v
147
+ # Handle string representation of sources (e.g., from PostgreSQL array or malformed data)
148
+ if isinstance(v, str):
149
+ logging.info(f"[coerce_sources] v: {v} type: {type(v)}")
150
+ # If it's a string that looks like a list representation, try to parse it
151
+ # This handles cases like: "[uuid1, uuid2, uuid3]" or "[item1, item2]"
152
+ v_stripped = v.strip()
153
+ if v_stripped.startswith("[") and v_stripped.endswith("]"):
154
+ # Remove brackets and split by comma
155
+ content = v_stripped[1:-1].strip()
156
+ if content:
157
+ # Split by comma and clean up each item
158
+ items = [item.strip() for item in content.split(",")]
159
+ # Convert to list of strings
160
+ v = items
161
+ else:
162
+ v = []
163
+ else:
164
+ # Single string, wrap in list
165
+ v = [v]
166
+ if not isinstance(v, list):
167
+ raise TypeError("sources must be a list")
168
+ out = []
169
+ for item in v:
170
+ if isinstance(item, SourceMessage):
171
+ out.append(item)
172
+
173
+ elif isinstance(item, dict):
174
+ d = dict(item)
175
+ if d.get("type") is None:
176
+ d["type"] = "chat" if d.get("role") in ALLOWED_ROLES else "doc"
177
+ out.append(SourceMessage(**d))
178
+
179
+ elif isinstance(item, str):
180
+ try:
181
+ parsed = json.loads(item)
182
+ except Exception:
183
+ parsed = None
184
+
185
+ if isinstance(parsed, dict):
186
+ if parsed.get("type") is None:
187
+ parsed["type"] = "chat" if parsed.get("role") in ALLOWED_ROLES else "doc"
188
+ out.append(SourceMessage(**parsed))
189
+ else:
190
+ out.append(SourceMessage(type="doc", content=item))
191
+
192
+ else:
193
+ out.append(SourceMessage(type="doc", content=str(item)))
194
+ return out
195
+
196
+ def __str__(self) -> str:
197
+ """Pretty string representation of the metadata."""
198
+ meta = self.model_dump(exclude_none=True)
199
+ return ", ".join([f"{k}={v}" for k, v in meta.items() if k != "embedding"])
200
+
201
+
202
+ class SearchedTreeNodeTextualMemoryMetadata(TreeNodeTextualMemoryMetadata):
203
+ """Metadata for nodes returned by search, includes similarity info."""
204
+
205
+ relativity: float | None = Field(
206
+ default=None, description="Similarity score with respect to the query, 0 ~ 1."
207
+ )
208
+
209
+
210
+ class PreferenceTextualMemoryMetadata(TextualMemoryMetadata):
211
+ """Metadata for preference memory item."""
212
+
213
+ preference_type: Literal["explicit_preference", "implicit_preference"] = Field(
214
+ default="explicit_preference", description="Type of preference."
215
+ )
216
+ dialog_id: str | None = Field(default=None, description="ID of the dialog.")
217
+ original_text: str | None = Field(default=None, description="String of the dialog.")
218
+ embedding: list[float] | None = Field(default=None, description="Vector of the dialog.")
219
+ preference: str | None = Field(default=None, description="Preference.")
220
+ created_at: str | None = Field(default=None, description="Timestamp of the dialog.")
221
+ mem_cube_id: str | None = Field(default=None, description="ID of the MemCube.")
222
+ score: float | None = Field(default=None, description="Score of the retrieval result.")
223
+
224
+
225
+ class TextualMemoryItem(BaseModel):
226
+ """Represents a single memory item in the textual memory.
227
+
228
+ This serves as a standardized format for memory items across different
229
+ textual memory implementations.
230
+ """
231
+
232
+ id: str = Field(default_factory=lambda: str(uuid.uuid4()))
233
+ memory: str
234
+ metadata: (
235
+ SearchedTreeNodeTextualMemoryMetadata
236
+ | TreeNodeTextualMemoryMetadata
237
+ | TextualMemoryMetadata
238
+ | PreferenceTextualMemoryMetadata
239
+ ) = Field(default_factory=TextualMemoryMetadata)
240
+
241
+ model_config = ConfigDict(extra="forbid")
242
+
243
+ @field_validator("id")
244
+ @classmethod
245
+ def _validate_id(cls, v: str) -> str:
246
+ uuid.UUID(v)
247
+ return v
248
+
249
+ @classmethod
250
+ def from_dict(cls, data: dict) -> "TextualMemoryItem":
251
+ return cls(**data)
252
+
253
+ def to_dict(self) -> dict:
254
+ return self.model_dump(exclude_none=True)
255
+
256
+ @field_validator("metadata", mode="before")
257
+ @classmethod
258
+ def _coerce_metadata(cls, v: Any):
259
+ if isinstance(
260
+ v,
261
+ SearchedTreeNodeTextualMemoryMetadata
262
+ | TreeNodeTextualMemoryMetadata
263
+ | TextualMemoryMetadata
264
+ | PreferenceTextualMemoryMetadata,
265
+ ):
266
+ return v
267
+ if isinstance(v, dict):
268
+ if "metadata" in v and isinstance(v["metadata"], dict):
269
+ nested_metadata = v["metadata"]
270
+ nested_metadata = nested_metadata.copy()
271
+ nested_metadata.pop("id", None)
272
+ nested_metadata.pop("memory", None)
273
+ v = nested_metadata
274
+ else:
275
+ v = v.copy()
276
+ v.pop("id", None)
277
+ v.pop("memory", None)
278
+
279
+ if v.get("relativity") is not None:
280
+ return SearchedTreeNodeTextualMemoryMetadata(**v)
281
+ if v.get("preference_type") is not None:
282
+ return PreferenceTextualMemoryMetadata(**v)
283
+ if any(k in v for k in ("sources", "memory_type", "embedding", "background", "usage")):
284
+ return TreeNodeTextualMemoryMetadata(**v)
285
+ return TextualMemoryMetadata(**v)
286
+ return v
287
+
288
+ def __str__(self) -> str:
289
+ """Pretty string representation of the memory item."""
290
+ return f"<ID: {self.id} | Memory: {self.memory} | Metadata: {self.metadata!s}>"
291
+
292
+
293
+ def list_all_fields() -> list[str]:
294
+ """List all possible fields of the TextualMemoryItem model."""
295
+ top = list(TextualMemoryItem.model_fields.keys())
296
+ meta_models = [
297
+ TextualMemoryMetadata,
298
+ TreeNodeTextualMemoryMetadata,
299
+ SearchedTreeNodeTextualMemoryMetadata,
300
+ PreferenceTextualMemoryMetadata,
301
+ ]
302
+ meta_all = sorted(set().union(*[set(m.model_fields.keys()) for m in meta_models]))
303
+
304
+ return top + meta_all