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
memos/llms/utils.py ADDED
@@ -0,0 +1,14 @@
1
+ import re
2
+
3
+
4
+ def remove_thinking_tags(text: str) -> str:
5
+ """
6
+ Remove thinking tags from the generated text.
7
+
8
+ Args:
9
+ text: The generated text.
10
+
11
+ Returns:
12
+ str: The cleaned text.
13
+ """
14
+ return re.sub(r"^<think>.*?</think>\s*", "", text, flags=re.DOTALL).strip()
memos/llms/vllm.py ADDED
@@ -0,0 +1,218 @@
1
+ import json
2
+
3
+ from typing import Any, cast
4
+
5
+ import openai
6
+
7
+ from openai.types.chat.chat_completion_message_tool_call import ChatCompletionMessageToolCall
8
+
9
+ from memos.configs.llm import VLLMLLMConfig
10
+ from memos.llms.base import BaseLLM
11
+ from memos.llms.utils import remove_thinking_tags
12
+ from memos.log import get_logger
13
+ from memos.types import MessageDict
14
+
15
+
16
+ logger = get_logger(__name__)
17
+
18
+
19
+ class VLLMLLM(BaseLLM):
20
+ """
21
+ VLLM LLM class for connecting to existing vLLM servers.
22
+ """
23
+
24
+ def __init__(self, config: VLLMLLMConfig):
25
+ """
26
+ Initialize the VLLM LLM to connect to an existing vLLM server.
27
+ """
28
+ self.config = config
29
+
30
+ # Initialize OpenAI client for API calls
31
+ self.client = None
32
+ api_key = getattr(self.config, "api_key", "dummy")
33
+ if not api_key:
34
+ api_key = "dummy"
35
+
36
+ self.client = openai.Client(
37
+ api_key=api_key,
38
+ base_url=getattr(self.config, "api_base", "http://localhost:8088/v1"),
39
+ default_headers=self.config.default_headers,
40
+ )
41
+
42
+ def build_vllm_kv_cache(self, messages: Any) -> str:
43
+ """
44
+ Build a KV cache from chat messages via one vLLM request.
45
+ Handles str, list[str], and MessageList formats.
46
+ """
47
+ # 1. Normalize input to a MessageList
48
+ processed_messages: list[MessageDict] = []
49
+ if isinstance(messages, str):
50
+ processed_messages = [
51
+ {
52
+ "role": "system",
53
+ "content": f"Below is some information about the user.\n{messages}",
54
+ }
55
+ ]
56
+ elif isinstance(messages, list):
57
+ if not messages:
58
+ pass # Empty list
59
+ elif isinstance(messages[0], str):
60
+ str_content = " ".join(str(msg) for msg in messages)
61
+ processed_messages = [
62
+ {
63
+ "role": "system",
64
+ "content": f"Below is some information about the user.\n{str_content}",
65
+ }
66
+ ]
67
+ elif isinstance(messages[0], dict):
68
+ processed_messages = cast("list[MessageDict]", messages)
69
+
70
+ # 2. Convert to prompt for logging/return value.
71
+ prompt = self._messages_to_prompt(processed_messages)
72
+
73
+ if not prompt.strip():
74
+ raise ValueError("Prompt is empty, cannot build KV cache.")
75
+
76
+ # 3. Send request to vLLM server to preload the KV cache
77
+ if self.client:
78
+ try:
79
+ # Use the processed messages for the API call
80
+ prefill_kwargs = {
81
+ "model": self.config.model_name_or_path,
82
+ "messages": processed_messages,
83
+ "max_tokens": 2,
84
+ "temperature": 0.0,
85
+ "top_p": 1.0,
86
+ }
87
+ self.client.chat.completions.create(**prefill_kwargs)
88
+ logger.info(f"vLLM KV cache prefill completed for prompt: '{prompt[:100]}...'")
89
+ except Exception as e:
90
+ logger.warning(f"Failed to prefill vLLM KV cache: {e}")
91
+
92
+ return prompt
93
+
94
+ def generate(self, messages: list[MessageDict], **kwargs) -> str:
95
+ """
96
+ Generate a response from the model.
97
+ """
98
+ if self.client:
99
+ return self._generate_with_api_client(messages, **kwargs)
100
+ else:
101
+ raise RuntimeError("API client is not available")
102
+
103
+ def _generate_with_api_client(self, messages: list[MessageDict], **kwargs) -> str:
104
+ """
105
+ Generate response using vLLM API client. detail view https://docs.vllm.ai/en/latest/features/reasoning_outputs/
106
+ """
107
+ if self.client:
108
+ completion_kwargs = {
109
+ "model": kwargs.get("model_name_or_path", self.config.model_name_or_path),
110
+ "messages": messages,
111
+ "temperature": kwargs.get("temperature", self.config.temperature),
112
+ "max_tokens": kwargs.get("max_tokens", self.config.max_tokens),
113
+ "top_p": kwargs.get("top_p", self.config.top_p),
114
+ "extra_body": {
115
+ "chat_template_kwargs": {
116
+ "enable_thinking": kwargs.get(
117
+ "enable_thinking", self.config.enable_thinking
118
+ )
119
+ }
120
+ },
121
+ }
122
+ if kwargs.get("tools"):
123
+ completion_kwargs["tools"] = kwargs.get("tools")
124
+ completion_kwargs["tool_choice"] = kwargs.get("tool_choice", "auto")
125
+
126
+ response = self.client.chat.completions.create(**completion_kwargs)
127
+
128
+ if not response.choices:
129
+ logger.warning("VLLM response has no choices")
130
+ return ""
131
+
132
+ if response.choices[0].message.tool_calls:
133
+ return self.tool_call_parser(response.choices[0].message.tool_calls)
134
+
135
+ reasoning_content = (
136
+ f"<think>{response.choices[0].message.reasoning}</think>"
137
+ if hasattr(response.choices[0].message, "reasoning")
138
+ else ""
139
+ )
140
+ response_text = response.choices[0].message.content or ""
141
+ logger.info(f"VLLM API response: {response_text}")
142
+ return (
143
+ remove_thinking_tags(response_text)
144
+ if getattr(self.config, "remove_think_prefix", False)
145
+ else reasoning_content + response_text
146
+ )
147
+ else:
148
+ raise RuntimeError("API client is not available")
149
+
150
+ def _messages_to_prompt(self, messages: list[MessageDict]) -> str:
151
+ """
152
+ Convert messages to prompt string.
153
+ """
154
+ prompt_parts = []
155
+ for msg in messages:
156
+ role = msg["role"]
157
+ content = msg["content"]
158
+ prompt_parts.append(f"{role.capitalize()}: {content}")
159
+ return "\n".join(prompt_parts)
160
+
161
+ def generate_stream(self, messages: list[MessageDict], **kwargs):
162
+ """
163
+ Generate a response from the model using streaming.
164
+ Yields content chunks as they are received.
165
+ """
166
+ if kwargs.get("tools"):
167
+ logger.info("stream api not support tools")
168
+ return
169
+
170
+ if self.client:
171
+ completion_kwargs = {
172
+ "model": self.config.model_name_or_path,
173
+ "messages": messages,
174
+ "temperature": kwargs.get("temperature", self.config.temperature),
175
+ "max_tokens": kwargs.get("max_tokens", self.config.max_tokens),
176
+ "top_p": kwargs.get("top_p", self.config.top_p),
177
+ "stream": True,
178
+ "extra_body": {
179
+ "chat_template_kwargs": {
180
+ "enable_thinking": kwargs.get(
181
+ "enable_thinking", self.config.enable_thinking
182
+ )
183
+ }
184
+ },
185
+ }
186
+
187
+ stream = self.client.chat.completions.create(**completion_kwargs)
188
+
189
+ reasoning_started = False
190
+ for chunk in stream:
191
+ if not chunk.choices:
192
+ continue
193
+ delta = chunk.choices[0].delta
194
+ if hasattr(delta, "reasoning") and delta.reasoning:
195
+ if not reasoning_started and not self.config.remove_think_prefix:
196
+ yield "<think>"
197
+ reasoning_started = True
198
+ yield delta.reasoning
199
+
200
+ if hasattr(delta, "content") and delta.content:
201
+ if reasoning_started and not self.config.remove_think_prefix:
202
+ yield "</think>"
203
+ reasoning_started = False
204
+ yield delta.content
205
+
206
+ else:
207
+ raise RuntimeError("API client is not available")
208
+
209
+ def tool_call_parser(self, tool_calls: list[ChatCompletionMessageToolCall]) -> list[dict]:
210
+ """Parse tool calls from OpenAI response."""
211
+ return [
212
+ {
213
+ "tool_call_id": tool_call.id,
214
+ "function_name": tool_call.function.name,
215
+ "arguments": json.loads(tool_call.function.arguments),
216
+ }
217
+ for tool_call in tool_calls
218
+ ]
memos/log.py ADDED
@@ -0,0 +1,237 @@
1
+ import atexit
2
+ import logging
3
+ import os
4
+ import threading
5
+ import time
6
+
7
+ from concurrent.futures import ThreadPoolExecutor
8
+ from logging.config import dictConfig
9
+ from pathlib import Path
10
+ from sys import stdout
11
+
12
+ import requests
13
+
14
+ from dotenv import load_dotenv
15
+
16
+ from memos import settings
17
+ from memos.context.context import (
18
+ get_current_api_path,
19
+ get_current_env,
20
+ get_current_trace_id,
21
+ get_current_user_name,
22
+ get_current_user_type,
23
+ )
24
+
25
+
26
+ # Load environment variables
27
+ load_dotenv()
28
+
29
+ selected_log_level = logging.DEBUG if settings.DEBUG else logging.WARNING
30
+
31
+
32
+ def _setup_logfile() -> Path:
33
+ """ensure the logger filepath is in place
34
+
35
+ Returns: the logfile Path
36
+ """
37
+ logfile = Path(settings.MEMOS_DIR / "logs" / "memos.log")
38
+ logfile.parent.mkdir(parents=True, exist_ok=True)
39
+ logfile.touch(exist_ok=True)
40
+
41
+ return logfile
42
+
43
+
44
+ class ContextFilter(logging.Filter):
45
+ """add context to the log record"""
46
+
47
+ def filter(self, record):
48
+ try:
49
+ trace_id = get_current_trace_id()
50
+ record.trace_id = trace_id if trace_id else "trace-id"
51
+ record.env = get_current_env()
52
+ record.user_type = get_current_user_type()
53
+ record.user_name = get_current_user_name()
54
+ record.api_path = get_current_api_path()
55
+ except Exception:
56
+ record.api_path = "unknown"
57
+ record.trace_id = "trace-id"
58
+ record.env = "prod"
59
+ record.user_type = "normal"
60
+ record.user_name = "unknown"
61
+ return True
62
+
63
+
64
+ class CustomLoggerRequestHandler(logging.Handler):
65
+ _instance = None
66
+ _lock = threading.Lock()
67
+
68
+ def __new__(cls):
69
+ if cls._instance is None:
70
+ with cls._lock:
71
+ if cls._instance is None:
72
+ cls._instance = super().__new__(cls)
73
+ cls._instance._initialized = False
74
+ cls._instance._executor = None
75
+ cls._instance._session = None
76
+ cls._instance._is_shutting_down = None
77
+ return cls._instance
78
+
79
+ def __init__(self):
80
+ """Initialize handler with minimal setup"""
81
+ if not self._initialized:
82
+ super().__init__()
83
+ workers = int(os.getenv("CUSTOM_LOGGER_WORKERS", "2"))
84
+ self._executor = ThreadPoolExecutor(
85
+ max_workers=workers, thread_name_prefix="log_sender"
86
+ )
87
+ self._is_shutting_down = threading.Event()
88
+ self._session = requests.Session()
89
+ self._initialized = True
90
+ atexit.register(self._cleanup)
91
+
92
+ def emit(self, record):
93
+ """Process log records of INFO or ERROR level (non-blocking)"""
94
+ if os.getenv("CUSTOM_LOGGER_URL") is None or self._is_shutting_down.is_set():
95
+ return
96
+
97
+ # Only process INFO and ERROR level logs
98
+ if record.levelno < logging.INFO: # Skip DEBUG and lower
99
+ return
100
+
101
+ try:
102
+ trace_id = get_current_trace_id() or "trace-id"
103
+ api_path = get_current_api_path()
104
+ env = get_current_env()
105
+ user_type = get_current_user_type()
106
+ user_name = get_current_user_name()
107
+ if api_path is not None:
108
+ self._executor.submit(
109
+ self._send_log_sync,
110
+ record.getMessage(),
111
+ trace_id,
112
+ api_path,
113
+ env,
114
+ user_type,
115
+ user_name,
116
+ )
117
+ except Exception as e:
118
+ if not self._is_shutting_down.is_set():
119
+ print(f"Error sending log: {e}")
120
+
121
+ def _send_log_sync(self, message, trace_id, api_path, env, user_type, user_name):
122
+ """Send log message synchronously in a separate thread"""
123
+ try:
124
+ logger_url = os.getenv("CUSTOM_LOGGER_URL")
125
+ token = os.getenv("CUSTOM_LOGGER_TOKEN")
126
+
127
+ headers = {"Content-Type": "application/json"}
128
+ post_content = {
129
+ "message": message,
130
+ "trace_id": trace_id,
131
+ "action": api_path,
132
+ "current_time": round(time.time(), 3),
133
+ "env": env,
134
+ "user_type": user_type,
135
+ "user_name": user_name,
136
+ }
137
+
138
+ # Add auth token if exists
139
+ if token:
140
+ headers["Authorization"] = f"Bearer {token}"
141
+
142
+ # Add traceId to headers for consistency
143
+ headers["traceId"] = trace_id
144
+
145
+ # Add custom attributes from env
146
+ for key, value in os.environ.items():
147
+ if key.startswith("CUSTOM_LOGGER_ATTRIBUTE_"):
148
+ attribute_key = key[len("CUSTOM_LOGGER_ATTRIBUTE_") :].lower()
149
+ post_content[attribute_key] = value
150
+
151
+ self._session.post(logger_url, headers=headers, json=post_content, timeout=5)
152
+ except Exception:
153
+ # Silently ignore errors to avoid affecting main application
154
+ pass
155
+
156
+ def _cleanup(self):
157
+ """Clean up resources during program exit"""
158
+ if not self._initialized:
159
+ return
160
+
161
+ self._is_shutting_down.set()
162
+ try:
163
+ self._executor.shutdown(wait=False)
164
+ self._session.close()
165
+ except Exception as e:
166
+ print(f"Error during cleanup: {e}")
167
+
168
+ def close(self):
169
+ """Override close to prevent premature shutdown"""
170
+
171
+
172
+ LOGGING_CONFIG = {
173
+ "version": 1,
174
+ "disable_existing_loggers": False,
175
+ "formatters": {
176
+ "standard": {
177
+ "format": "%(asctime)s | %(trace_id)s | path=%(api_path)s | env=%(env)s | user_type=%(user_type)s | user_name=%(user_name)s | %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(funcName)s - %(message)s"
178
+ },
179
+ "no_datetime": {
180
+ "format": "%(trace_id)s | path=%(api_path)s | %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(funcName)s - %(message)s"
181
+ },
182
+ "simplified": {
183
+ "format": "%(asctime)s | %(trace_id)s | path=%(api_path)s | % %(levelname)s | %(filename)s:%(lineno)d: %(funcName)s | %(message)s"
184
+ },
185
+ },
186
+ "filters": {
187
+ "package_tree_filter": {"()": "logging.Filter", "name": settings.LOG_FILTER_TREE_PREFIX},
188
+ "context_filter": {"()": "memos.log.ContextFilter"},
189
+ },
190
+ "handlers": {
191
+ "console": {
192
+ "level": selected_log_level,
193
+ "class": "logging.StreamHandler",
194
+ "stream": stdout,
195
+ "formatter": "no_datetime",
196
+ "filters": ["package_tree_filter", "context_filter"],
197
+ },
198
+ "file": {
199
+ "level": "INFO",
200
+ "class": "concurrent_log_handler.ConcurrentTimedRotatingFileHandler",
201
+ "when": "midnight",
202
+ "interval": 1,
203
+ "backupCount": 3,
204
+ "filename": _setup_logfile(),
205
+ "formatter": "standard",
206
+ "filters": ["context_filter"],
207
+ },
208
+ "custom_logger": {
209
+ "level": "INFO",
210
+ "class": "memos.log.CustomLoggerRequestHandler",
211
+ "formatter": "simplified",
212
+ },
213
+ },
214
+ "root": { # Root logger handles all logs
215
+ "level": logging.DEBUG if settings.DEBUG else logging.INFO,
216
+ "handlers": ["console", "file"],
217
+ },
218
+ "loggers": {
219
+ "memos": {
220
+ "level": logging.DEBUG if settings.DEBUG else logging.INFO,
221
+ "propagate": True, # Let logs bubble up to root
222
+ },
223
+ },
224
+ }
225
+
226
+
227
+ def get_logger(name: str | None = None) -> logging.Logger:
228
+ """returns the project logger, scoped to a child name if provided
229
+ Args:
230
+ name: will define a child logger
231
+ """
232
+ dictConfig(LOGGING_CONFIG)
233
+
234
+ parent_logger = logging.getLogger("")
235
+ if name:
236
+ return parent_logger.getChild(name)
237
+ return parent_logger
@@ -0,0 +1,19 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ from memos.configs.mem_agent import BaseAgentConfig
4
+
5
+
6
+ class BaseMemAgent(ABC):
7
+ """
8
+ Base class for all agents.
9
+ """
10
+
11
+ def __init__(self, config: BaseAgentConfig):
12
+ """Initialize the BaseMemAgent with the given configuration."""
13
+ self.config = config
14
+
15
+ @abstractmethod
16
+ def run(self, input: str) -> str:
17
+ """
18
+ Run the agent.
19
+ """