MemoryOS 2.0.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- memoryos-2.0.3.dist-info/METADATA +418 -0
- memoryos-2.0.3.dist-info/RECORD +315 -0
- memoryos-2.0.3.dist-info/WHEEL +4 -0
- memoryos-2.0.3.dist-info/entry_points.txt +3 -0
- memoryos-2.0.3.dist-info/licenses/LICENSE +201 -0
- memos/__init__.py +20 -0
- memos/api/client.py +571 -0
- memos/api/config.py +1018 -0
- memos/api/context/dependencies.py +50 -0
- memos/api/exceptions.py +53 -0
- memos/api/handlers/__init__.py +62 -0
- memos/api/handlers/add_handler.py +158 -0
- memos/api/handlers/base_handler.py +194 -0
- memos/api/handlers/chat_handler.py +1401 -0
- memos/api/handlers/component_init.py +388 -0
- memos/api/handlers/config_builders.py +190 -0
- memos/api/handlers/feedback_handler.py +93 -0
- memos/api/handlers/formatters_handler.py +237 -0
- memos/api/handlers/memory_handler.py +316 -0
- memos/api/handlers/scheduler_handler.py +497 -0
- memos/api/handlers/search_handler.py +222 -0
- memos/api/handlers/suggestion_handler.py +117 -0
- memos/api/mcp_serve.py +614 -0
- memos/api/middleware/request_context.py +101 -0
- memos/api/product_api.py +38 -0
- memos/api/product_models.py +1206 -0
- memos/api/routers/__init__.py +1 -0
- memos/api/routers/product_router.py +477 -0
- memos/api/routers/server_router.py +394 -0
- memos/api/server_api.py +44 -0
- memos/api/start_api.py +433 -0
- memos/chunkers/__init__.py +4 -0
- memos/chunkers/base.py +24 -0
- memos/chunkers/charactertext_chunker.py +41 -0
- memos/chunkers/factory.py +24 -0
- memos/chunkers/markdown_chunker.py +62 -0
- memos/chunkers/sentence_chunker.py +54 -0
- memos/chunkers/simple_chunker.py +50 -0
- memos/cli.py +113 -0
- memos/configs/__init__.py +0 -0
- memos/configs/base.py +82 -0
- memos/configs/chunker.py +59 -0
- memos/configs/embedder.py +88 -0
- memos/configs/graph_db.py +236 -0
- memos/configs/internet_retriever.py +100 -0
- memos/configs/llm.py +151 -0
- memos/configs/mem_agent.py +54 -0
- memos/configs/mem_chat.py +81 -0
- memos/configs/mem_cube.py +105 -0
- memos/configs/mem_os.py +83 -0
- memos/configs/mem_reader.py +91 -0
- memos/configs/mem_scheduler.py +385 -0
- memos/configs/mem_user.py +70 -0
- memos/configs/memory.py +324 -0
- memos/configs/parser.py +38 -0
- memos/configs/reranker.py +18 -0
- memos/configs/utils.py +8 -0
- memos/configs/vec_db.py +80 -0
- memos/context/context.py +355 -0
- memos/dependency.py +52 -0
- memos/deprecation.py +262 -0
- memos/embedders/__init__.py +0 -0
- memos/embedders/ark.py +95 -0
- memos/embedders/base.py +106 -0
- memos/embedders/factory.py +29 -0
- memos/embedders/ollama.py +77 -0
- memos/embedders/sentence_transformer.py +49 -0
- memos/embedders/universal_api.py +51 -0
- memos/exceptions.py +30 -0
- memos/graph_dbs/__init__.py +0 -0
- memos/graph_dbs/base.py +274 -0
- memos/graph_dbs/factory.py +27 -0
- memos/graph_dbs/item.py +46 -0
- memos/graph_dbs/nebular.py +1794 -0
- memos/graph_dbs/neo4j.py +1942 -0
- memos/graph_dbs/neo4j_community.py +1058 -0
- memos/graph_dbs/polardb.py +5446 -0
- memos/hello_world.py +97 -0
- memos/llms/__init__.py +0 -0
- memos/llms/base.py +25 -0
- memos/llms/deepseek.py +13 -0
- memos/llms/factory.py +38 -0
- memos/llms/hf.py +443 -0
- memos/llms/hf_singleton.py +114 -0
- memos/llms/ollama.py +135 -0
- memos/llms/openai.py +222 -0
- memos/llms/openai_new.py +198 -0
- memos/llms/qwen.py +13 -0
- memos/llms/utils.py +14 -0
- memos/llms/vllm.py +218 -0
- memos/log.py +237 -0
- memos/mem_agent/base.py +19 -0
- memos/mem_agent/deepsearch_agent.py +391 -0
- memos/mem_agent/factory.py +36 -0
- memos/mem_chat/__init__.py +0 -0
- memos/mem_chat/base.py +30 -0
- memos/mem_chat/factory.py +21 -0
- memos/mem_chat/simple.py +200 -0
- memos/mem_cube/__init__.py +0 -0
- memos/mem_cube/base.py +30 -0
- memos/mem_cube/general.py +240 -0
- memos/mem_cube/navie.py +172 -0
- memos/mem_cube/utils.py +169 -0
- memos/mem_feedback/base.py +15 -0
- memos/mem_feedback/feedback.py +1192 -0
- memos/mem_feedback/simple_feedback.py +40 -0
- memos/mem_feedback/utils.py +230 -0
- memos/mem_os/client.py +5 -0
- memos/mem_os/core.py +1203 -0
- memos/mem_os/main.py +582 -0
- memos/mem_os/product.py +1608 -0
- memos/mem_os/product_server.py +455 -0
- memos/mem_os/utils/default_config.py +359 -0
- memos/mem_os/utils/format_utils.py +1403 -0
- memos/mem_os/utils/reference_utils.py +162 -0
- memos/mem_reader/__init__.py +0 -0
- memos/mem_reader/base.py +47 -0
- memos/mem_reader/factory.py +53 -0
- memos/mem_reader/memory.py +298 -0
- memos/mem_reader/multi_modal_struct.py +965 -0
- memos/mem_reader/read_multi_modal/__init__.py +43 -0
- memos/mem_reader/read_multi_modal/assistant_parser.py +311 -0
- memos/mem_reader/read_multi_modal/base.py +273 -0
- memos/mem_reader/read_multi_modal/file_content_parser.py +826 -0
- memos/mem_reader/read_multi_modal/image_parser.py +359 -0
- memos/mem_reader/read_multi_modal/multi_modal_parser.py +252 -0
- memos/mem_reader/read_multi_modal/string_parser.py +139 -0
- memos/mem_reader/read_multi_modal/system_parser.py +327 -0
- memos/mem_reader/read_multi_modal/text_content_parser.py +131 -0
- memos/mem_reader/read_multi_modal/tool_parser.py +210 -0
- memos/mem_reader/read_multi_modal/user_parser.py +218 -0
- memos/mem_reader/read_multi_modal/utils.py +358 -0
- memos/mem_reader/simple_struct.py +912 -0
- memos/mem_reader/strategy_struct.py +163 -0
- memos/mem_reader/utils.py +157 -0
- memos/mem_scheduler/__init__.py +0 -0
- memos/mem_scheduler/analyzer/__init__.py +0 -0
- memos/mem_scheduler/analyzer/api_analyzer.py +714 -0
- memos/mem_scheduler/analyzer/eval_analyzer.py +219 -0
- memos/mem_scheduler/analyzer/mos_for_test_scheduler.py +571 -0
- memos/mem_scheduler/analyzer/scheduler_for_eval.py +280 -0
- memos/mem_scheduler/base_scheduler.py +1319 -0
- memos/mem_scheduler/general_modules/__init__.py +0 -0
- memos/mem_scheduler/general_modules/api_misc.py +137 -0
- memos/mem_scheduler/general_modules/base.py +80 -0
- memos/mem_scheduler/general_modules/init_components_for_scheduler.py +425 -0
- memos/mem_scheduler/general_modules/misc.py +313 -0
- memos/mem_scheduler/general_modules/scheduler_logger.py +389 -0
- memos/mem_scheduler/general_modules/task_threads.py +315 -0
- memos/mem_scheduler/general_scheduler.py +1495 -0
- memos/mem_scheduler/memory_manage_modules/__init__.py +5 -0
- memos/mem_scheduler/memory_manage_modules/memory_filter.py +306 -0
- memos/mem_scheduler/memory_manage_modules/retriever.py +547 -0
- memos/mem_scheduler/monitors/__init__.py +0 -0
- memos/mem_scheduler/monitors/dispatcher_monitor.py +366 -0
- memos/mem_scheduler/monitors/general_monitor.py +394 -0
- memos/mem_scheduler/monitors/task_schedule_monitor.py +254 -0
- memos/mem_scheduler/optimized_scheduler.py +410 -0
- memos/mem_scheduler/orm_modules/__init__.py +0 -0
- memos/mem_scheduler/orm_modules/api_redis_model.py +518 -0
- memos/mem_scheduler/orm_modules/base_model.py +729 -0
- memos/mem_scheduler/orm_modules/monitor_models.py +261 -0
- memos/mem_scheduler/orm_modules/redis_model.py +699 -0
- memos/mem_scheduler/scheduler_factory.py +23 -0
- memos/mem_scheduler/schemas/__init__.py +0 -0
- memos/mem_scheduler/schemas/analyzer_schemas.py +52 -0
- memos/mem_scheduler/schemas/api_schemas.py +233 -0
- memos/mem_scheduler/schemas/general_schemas.py +55 -0
- memos/mem_scheduler/schemas/message_schemas.py +173 -0
- memos/mem_scheduler/schemas/monitor_schemas.py +406 -0
- memos/mem_scheduler/schemas/task_schemas.py +132 -0
- memos/mem_scheduler/task_schedule_modules/__init__.py +0 -0
- memos/mem_scheduler/task_schedule_modules/dispatcher.py +740 -0
- memos/mem_scheduler/task_schedule_modules/local_queue.py +247 -0
- memos/mem_scheduler/task_schedule_modules/orchestrator.py +74 -0
- memos/mem_scheduler/task_schedule_modules/redis_queue.py +1385 -0
- memos/mem_scheduler/task_schedule_modules/task_queue.py +162 -0
- memos/mem_scheduler/utils/__init__.py +0 -0
- memos/mem_scheduler/utils/api_utils.py +77 -0
- memos/mem_scheduler/utils/config_utils.py +100 -0
- memos/mem_scheduler/utils/db_utils.py +50 -0
- memos/mem_scheduler/utils/filter_utils.py +176 -0
- memos/mem_scheduler/utils/metrics.py +125 -0
- memos/mem_scheduler/utils/misc_utils.py +290 -0
- memos/mem_scheduler/utils/monitor_event_utils.py +67 -0
- memos/mem_scheduler/utils/status_tracker.py +229 -0
- memos/mem_scheduler/webservice_modules/__init__.py +0 -0
- memos/mem_scheduler/webservice_modules/rabbitmq_service.py +485 -0
- memos/mem_scheduler/webservice_modules/redis_service.py +380 -0
- memos/mem_user/factory.py +94 -0
- memos/mem_user/mysql_persistent_user_manager.py +271 -0
- memos/mem_user/mysql_user_manager.py +502 -0
- memos/mem_user/persistent_factory.py +98 -0
- memos/mem_user/persistent_user_manager.py +260 -0
- memos/mem_user/redis_persistent_user_manager.py +225 -0
- memos/mem_user/user_manager.py +488 -0
- memos/memories/__init__.py +0 -0
- memos/memories/activation/__init__.py +0 -0
- memos/memories/activation/base.py +42 -0
- memos/memories/activation/item.py +56 -0
- memos/memories/activation/kv.py +292 -0
- memos/memories/activation/vllmkv.py +219 -0
- memos/memories/base.py +19 -0
- memos/memories/factory.py +42 -0
- memos/memories/parametric/__init__.py +0 -0
- memos/memories/parametric/base.py +19 -0
- memos/memories/parametric/item.py +11 -0
- memos/memories/parametric/lora.py +41 -0
- memos/memories/textual/__init__.py +0 -0
- memos/memories/textual/base.py +92 -0
- memos/memories/textual/general.py +236 -0
- memos/memories/textual/item.py +304 -0
- memos/memories/textual/naive.py +187 -0
- memos/memories/textual/prefer_text_memory/__init__.py +0 -0
- memos/memories/textual/prefer_text_memory/adder.py +504 -0
- memos/memories/textual/prefer_text_memory/config.py +106 -0
- memos/memories/textual/prefer_text_memory/extractor.py +221 -0
- memos/memories/textual/prefer_text_memory/factory.py +85 -0
- memos/memories/textual/prefer_text_memory/retrievers.py +177 -0
- memos/memories/textual/prefer_text_memory/spliter.py +132 -0
- memos/memories/textual/prefer_text_memory/utils.py +93 -0
- memos/memories/textual/preference.py +344 -0
- memos/memories/textual/simple_preference.py +161 -0
- memos/memories/textual/simple_tree.py +69 -0
- memos/memories/textual/tree.py +459 -0
- memos/memories/textual/tree_text_memory/__init__.py +0 -0
- memos/memories/textual/tree_text_memory/organize/__init__.py +0 -0
- memos/memories/textual/tree_text_memory/organize/handler.py +184 -0
- memos/memories/textual/tree_text_memory/organize/manager.py +518 -0
- memos/memories/textual/tree_text_memory/organize/relation_reason_detector.py +238 -0
- memos/memories/textual/tree_text_memory/organize/reorganizer.py +622 -0
- memos/memories/textual/tree_text_memory/retrieve/__init__.py +0 -0
- memos/memories/textual/tree_text_memory/retrieve/advanced_searcher.py +364 -0
- memos/memories/textual/tree_text_memory/retrieve/bm25_util.py +186 -0
- memos/memories/textual/tree_text_memory/retrieve/bochasearch.py +419 -0
- memos/memories/textual/tree_text_memory/retrieve/internet_retriever.py +270 -0
- memos/memories/textual/tree_text_memory/retrieve/internet_retriever_factory.py +102 -0
- memos/memories/textual/tree_text_memory/retrieve/reasoner.py +61 -0
- memos/memories/textual/tree_text_memory/retrieve/recall.py +497 -0
- memos/memories/textual/tree_text_memory/retrieve/reranker.py +111 -0
- memos/memories/textual/tree_text_memory/retrieve/retrieval_mid_structs.py +16 -0
- memos/memories/textual/tree_text_memory/retrieve/retrieve_utils.py +472 -0
- memos/memories/textual/tree_text_memory/retrieve/searcher.py +848 -0
- memos/memories/textual/tree_text_memory/retrieve/task_goal_parser.py +135 -0
- memos/memories/textual/tree_text_memory/retrieve/utils.py +54 -0
- memos/memories/textual/tree_text_memory/retrieve/xinyusearch.py +387 -0
- memos/memos_tools/dinding_report_bot.py +453 -0
- memos/memos_tools/lockfree_dict.py +120 -0
- memos/memos_tools/notification_service.py +44 -0
- memos/memos_tools/notification_utils.py +142 -0
- memos/memos_tools/singleton.py +174 -0
- memos/memos_tools/thread_safe_dict.py +310 -0
- memos/memos_tools/thread_safe_dict_segment.py +382 -0
- memos/multi_mem_cube/__init__.py +0 -0
- memos/multi_mem_cube/composite_cube.py +86 -0
- memos/multi_mem_cube/single_cube.py +874 -0
- memos/multi_mem_cube/views.py +54 -0
- memos/parsers/__init__.py +0 -0
- memos/parsers/base.py +15 -0
- memos/parsers/factory.py +21 -0
- memos/parsers/markitdown.py +28 -0
- memos/reranker/__init__.py +4 -0
- memos/reranker/base.py +25 -0
- memos/reranker/concat.py +103 -0
- memos/reranker/cosine_local.py +102 -0
- memos/reranker/factory.py +72 -0
- memos/reranker/http_bge.py +324 -0
- memos/reranker/http_bge_strategy.py +327 -0
- memos/reranker/noop.py +19 -0
- memos/reranker/strategies/__init__.py +4 -0
- memos/reranker/strategies/base.py +61 -0
- memos/reranker/strategies/concat_background.py +94 -0
- memos/reranker/strategies/concat_docsource.py +110 -0
- memos/reranker/strategies/dialogue_common.py +109 -0
- memos/reranker/strategies/factory.py +31 -0
- memos/reranker/strategies/single_turn.py +107 -0
- memos/reranker/strategies/singleturn_outmem.py +98 -0
- memos/settings.py +10 -0
- memos/templates/__init__.py +0 -0
- memos/templates/advanced_search_prompts.py +211 -0
- memos/templates/cloud_service_prompt.py +107 -0
- memos/templates/instruction_completion.py +66 -0
- memos/templates/mem_agent_prompts.py +85 -0
- memos/templates/mem_feedback_prompts.py +822 -0
- memos/templates/mem_reader_prompts.py +1096 -0
- memos/templates/mem_reader_strategy_prompts.py +238 -0
- memos/templates/mem_scheduler_prompts.py +626 -0
- memos/templates/mem_search_prompts.py +93 -0
- memos/templates/mos_prompts.py +403 -0
- memos/templates/prefer_complete_prompt.py +735 -0
- memos/templates/tool_mem_prompts.py +139 -0
- memos/templates/tree_reorganize_prompts.py +230 -0
- memos/types/__init__.py +34 -0
- memos/types/general_types.py +151 -0
- memos/types/openai_chat_completion_types/__init__.py +15 -0
- memos/types/openai_chat_completion_types/chat_completion_assistant_message_param.py +56 -0
- memos/types/openai_chat_completion_types/chat_completion_content_part_image_param.py +27 -0
- memos/types/openai_chat_completion_types/chat_completion_content_part_input_audio_param.py +23 -0
- memos/types/openai_chat_completion_types/chat_completion_content_part_param.py +43 -0
- memos/types/openai_chat_completion_types/chat_completion_content_part_refusal_param.py +16 -0
- memos/types/openai_chat_completion_types/chat_completion_content_part_text_param.py +16 -0
- memos/types/openai_chat_completion_types/chat_completion_message_custom_tool_call_param.py +27 -0
- memos/types/openai_chat_completion_types/chat_completion_message_function_tool_call_param.py +32 -0
- memos/types/openai_chat_completion_types/chat_completion_message_param.py +18 -0
- memos/types/openai_chat_completion_types/chat_completion_message_tool_call_union_param.py +15 -0
- memos/types/openai_chat_completion_types/chat_completion_system_message_param.py +36 -0
- memos/types/openai_chat_completion_types/chat_completion_tool_message_param.py +30 -0
- memos/types/openai_chat_completion_types/chat_completion_user_message_param.py +34 -0
- memos/utils.py +123 -0
- memos/vec_dbs/__init__.py +0 -0
- memos/vec_dbs/base.py +117 -0
- memos/vec_dbs/factory.py +23 -0
- memos/vec_dbs/item.py +50 -0
- memos/vec_dbs/milvus.py +654 -0
- memos/vec_dbs/qdrant.py +355 -0
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
|
memos/mem_agent/base.py
ADDED
|
@@ -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
|
+
"""
|