ForcePilot/backend/package/yuxi/agents/middlewares/dynamic_tool.py
2026-06-02 19:20:15 +08:00

70 lines
3.0 KiB
Python

from collections.abc import Callable
from typing import Any
from langchain.agents.middleware import AgentMiddleware, ModelRequest, ModelResponse
from yuxi.agents.mcp.service import get_mcp_tools
from yuxi.utils import logger
class DynamicToolMiddleware(AgentMiddleware):
"""动态工具选择中间件 - 支持 MCP 工具的动态加载和注册
注意:所有可能用到的 MCP 工具必须在初始化时预加载并注册到 self.tools
运行时只是根据配置筛选工具,不能动态添加新工具
"""
def __init__(self, base_tools: list[Any], mcp_servers: list[str] | None = None):
"""初始化中间件
Args:
base_tools: 基础工具列表
mcp_servers: 需要预加载的 MCP 服务器列表(可选)
"""
super().__init__()
self.tools: list[Any] = base_tools
self._all_mcp_tools: dict[str, list[Any]] = {} # 所有已加载的 MCP 工具
self._mcp_servers = mcp_servers or []
async def initialize_mcp_tools(self) -> None:
"""异步初始化:预加载所有可能用到的 MCP 工具"""
for mcp_name in self._mcp_servers:
if mcp_name not in self._all_mcp_tools:
logger.info(f"Pre-loading MCP tools from: {mcp_name}")
mcp_tools = await get_mcp_tools(mcp_name)
self._all_mcp_tools[mcp_name] = mcp_tools
# 将 MCP 工具注册到 middleware.tools
self.tools.extend(mcp_tools)
logger.info(f"Registered {len(mcp_tools)} tools from {mcp_name}")
async def awrap_model_call(
self, request: ModelRequest, handler: Callable[[ModelRequest], ModelResponse]
) -> ModelResponse:
"""根据配置动态选择工具(从已注册的工具中筛选)"""
# 从 runtime context 获取配置
selected_tools = request.runtime.context.tools
selected_mcps = request.runtime.context.mcps
enabled_tools = []
# 根据配置筛选基础工具
if selected_tools and isinstance(selected_tools, list) and len(selected_tools) > 0:
enabled_tools = [tool for tool in self.tools if tool.name in selected_tools]
# 根据配置筛选 MCP 工具(从已注册的工具中选择)
if selected_mcps and isinstance(selected_mcps, list) and len(selected_mcps) > 0:
for mcp in selected_mcps:
if mcp in self._all_mcp_tools:
enabled_tools.extend(self._all_mcp_tools[mcp])
else:
logger.warning(f"MCP server '{mcp}' not pre-loaded. Please add it to mcp_servers list.")
logger.info(
f"Dynamic tool selection: {len(enabled_tools)} tools enabled: {[tool.name for tool in enabled_tools]}, "
f"selected_tools: {selected_tools}, selected_mcps: {selected_mcps}"
)
# 更新 request 中的工具列表
request = request.override(tools=enabled_tools)
return await handler(request)