feat(plugin): 集成 MCP 协议支持并优化代码风格

- 新增 fastmcp 依赖,支持通过 Streamable HTTP 连接外部工具服务器
- 在 component_registry 与 tool_api 中实现 MCP 工具加载、注册及调用链路
- 补充 README 中的 MCP 特性说明
- 统一修复多处 import 顺序、空行、引号及类型注解,提升代码整洁度
- 在 pyproject.toml 中忽略 PERF203 规则,允许循环内异常处理
- 优化语音缓存与本地 ASR 调用逻辑,减少冗余代码
This commit is contained in:
明天好像没什么
2025-10-26 13:10:31 +08:00
parent 5e6857c8f7
commit 7b80d7c0b3
31 changed files with 1034 additions and 43 deletions

View File

@@ -878,7 +878,7 @@ class MemorySystem:
except Exception as e:
logger.warning(f"检索瞬时记忆失败: {e}", exc_info=True)
# 最终截断
final_memories = final_memories[:effective_limit]

View File

@@ -72,4 +72,4 @@ class MessageCollectionProcessor:
"active_buffers": len(self.message_buffers),
"total_buffered_messages": total_buffered_messages,
"buffer_capacity_per_chat": self.buffer_size,
}
}

View File

@@ -3,7 +3,6 @@
专用于存储和检索消息集合,以提供即时上下文。
"""
import asyncio
import time
from typing import Any
@@ -125,7 +124,7 @@ class MessageCollectionStorage:
if results and results.get("ids") and results["ids"][0]:
for metadata in results["metadatas"][0]:
collections.append(MessageCollection.from_dict(metadata))
return collections
except Exception as e:
logger.error(f"检索相关消息集合失败: {e}", exc_info=True)
@@ -163,7 +162,7 @@ class MessageCollectionStorage:
# 格式化消息集合为 prompt 上下文
final_context = "\n\n---\n\n".join(context_parts) + "\n\n---"
logger.info(f"🔗 为查询 '{query_text[:50]}...' 在聊天 '{chat_id}' 中找到 {len(collections)} 个相关消息集合上下文")
return f"\n{final_context}\n"
@@ -192,4 +191,4 @@ class MessageCollectionStorage:
}
except Exception as e:
logger.error(f"获取消息集合存储统计失败: {e}")
return {}
return {}

View File

@@ -9,10 +9,10 @@ from maim_message import BaseMessageInfo, MessageBase, Seg, UserInfo
from rich.traceback import install
from src.chat.message_receive.chat_stream import ChatStream
from src.chat.utils.self_voice_cache import consume_self_voice_text
from src.chat.utils.utils_image import get_image_manager
from src.chat.utils.utils_video import get_video_analyzer, is_video_analysis_available
from src.chat.utils.utils_voice import get_voice_text
from src.chat.utils.self_voice_cache import consume_self_voice_text
from src.common.logger import get_logger
from src.config.config import global_config
@@ -212,7 +212,7 @@ class MessageRecv(Message):
return f"[语音:{cached_text}]"
else:
logger.warning("机器人自身语音消息缓存未命中,将回退到标准语音识别。")
# 标准语音识别流程 (也作为缓存未命中的后备方案)
if isinstance(segment.data, str):
return await get_voice_text(segment.data)
@@ -364,7 +364,7 @@ class MessageRecvS4U(MessageRecv):
self.is_picid = False
self.is_emoji = False
self.is_voice = True
# 检查消息是否由机器人自己发送
# 检查消息是否由机器人自己发送
if self.message_info and self.message_info.user_info and str(self.message_info.user_info.user_id) == str(global_config.bot.qq_account):

View File

@@ -303,7 +303,7 @@ class Prompt:
@staticmethod
def _process_escaped_braces(template) -> str:
"""预处理模板,将 `\{` 和 `\}` 替换为临时标记."""
r"""预处理模板,将 `\{` 和 `\}` 替换为临时标记."""
if isinstance(template, list):
template = "\n".join(str(item) for item in template)
elif not isinstance(template, str):

View File

@@ -1,6 +1,5 @@
import asyncio
import re
from typing import Type
from src.chat.utils.prompt_params import PromptParameters
from src.common.logger import get_logger
@@ -21,7 +20,7 @@ class PromptComponentManager:
3. 提供一个接口以便在构建核心Prompt时能够获取并执行所有相关的组件。
"""
def _get_rules_for(self, target_prompt_name: str) -> list[tuple[InjectionRule, Type[BasePrompt]]]:
def _get_rules_for(self, target_prompt_name: str) -> list[tuple[InjectionRule, type[BasePrompt]]]:
"""
获取指定目标Prompt的所有注入规则及其关联的组件类。

View File

@@ -6,15 +6,14 @@
避免不必要的自我语音识别。
"""
import hashlib
from typing import Dict
# 一个简单的内存缓存,用于将机器人自己发送的语音消息映射到其原始文本。
# 键是语音base64内容的SHA256哈希值。
_self_voice_cache: Dict[str, str] = {}
_self_voice_cache: dict[str, str] = {}
def get_voice_key(base64_content: str) -> str:
"""为语音内容生成一个一致的键。"""
return hashlib.sha256(base64_content.encode('utf-8')).hexdigest()
return hashlib.sha256(base64_content.encode("utf-8")).hexdigest()
def register_self_voice(base64_content: str, text: str):
"""
@@ -39,4 +38,4 @@ def consume_self_voice_text(base64_content: str) -> str | None:
str | None: 如果找到则返回原始文本否则返回None。
"""
key = get_voice_key(base64_content)
return _self_voice_cache.pop(key, None)
return _self_voice_cache.pop(key, None)

View File

@@ -19,10 +19,11 @@ async def get_voice_text(voice_base64: str) -> str:
# 如果选择本地识别
if asr_provider == "local":
from src.plugin_system.apis import tool_api
import tempfile
import base64
import os
import tempfile
from src.plugin_system.apis import tool_api
local_asr_tool = tool_api.get_tool_instance("local_asr")
if not local_asr_tool:
@@ -39,8 +40,8 @@ async def get_voice_text(voice_base64: str) -> str:
text = await local_asr_tool.execute(function_args={"audio_path": audio_path})
if "失败" in text or "出错" in text or "错误" in text:
logger.warning(f"本地语音识别失败: {text}")
return f"[语音(本地识别失败)]"
return "[语音(本地识别失败)]"
logger.info(f"本地语音识别成功: {text}")
return f"[语音] {text}"