"""服务端插件工具执行器"""
|
|
import asyncio
|
from typing import Dict, Any, TYPE_CHECKING
|
|
if TYPE_CHECKING:
|
from core.connection import ConnectionHandler
|
from ..base import ToolType, ToolDefinition, ToolExecutor
|
from plugins_func.register import all_function_registry, module_func_map, Action, ActionResponse
|
|
|
class ServerPluginExecutor(ToolExecutor):
|
"""服务端插件工具执行器"""
|
|
def __init__(self, conn: "ConnectionHandler"):
|
self.conn = conn
|
self.config = conn.config
|
|
async def execute(
|
self, conn: "ConnectionHandler", tool_name: str, arguments: Dict[str, Any]
|
) -> ActionResponse:
|
"""执行服务端插件工具"""
|
func_item = all_function_registry.get(tool_name)
|
if not func_item:
|
return ActionResponse(
|
action=Action.NOTFOUND, response=f"插件函数 {tool_name} 不存在"
|
)
|
|
try:
|
# 根据工具类型决定如何调用
|
if hasattr(func_item, "type"):
|
func_type = func_item.type
|
if func_type.code in [4, 5]: # SYSTEM_CTL, IOT_CTL (需要conn参数)
|
result = func_item.func(conn, **arguments)
|
elif func_type.code == 2: # WAIT
|
result = func_item.func(**arguments)
|
elif func_type.code == 3: # CHANGE_SYS_PROMPT
|
result = func_item.func(conn, **arguments)
|
else:
|
result = func_item.func(**arguments)
|
else:
|
# 默认不传conn参数
|
result = func_item.func(**arguments)
|
|
# 兼容 async def 工具函数
|
if asyncio.iscoroutine(result):
|
result = await result
|
|
return result
|
|
except Exception as e:
|
return ActionResponse(
|
action=Action.ERROR,
|
response=str(e),
|
)
|
|
def _expand_plugin_names(self, config_functions):
|
"""将模块级别的插件名展开为具体函数名。
|
|
在一个 function 文件中可能注册多个 @register_function,
|
配置中如果使用的是模块名(文件名),需要展开为具体的函数名列表。
|
"""
|
if not isinstance(config_functions, list):
|
try:
|
config_functions = list(config_functions)
|
except TypeError:
|
return []
|
|
expanded = []
|
for name in config_functions:
|
if name in module_func_map:
|
# 模块名,展开为该模块下所有注册函数名
|
expanded.extend(module_func_map[name])
|
elif name in all_function_registry:
|
# 精确匹配函数名,直接保留
|
expanded.append(name)
|
else:
|
# 未知名称,保留原值(可能是 MCP 或其他工具)
|
expanded.append(name)
|
return expanded
|
|
def _get_plugin_description(self, func_name):
|
"""获取插件函数的描述,优先精确匹配函数名,其次匹配模块名。"""
|
plugins = self.config.get("plugins", {})
|
# 精确匹配函数名
|
if func_name in plugins:
|
return plugins[func_name].get("description", "")
|
# 通过 module_func_map 反向查找模块名
|
for module_name, func_names in module_func_map.items():
|
if func_name in func_names and module_name in plugins:
|
return plugins[module_name].get("description", "")
|
return ""
|
|
def get_tools(self) -> Dict[str, ToolDefinition]:
|
"""获取所有注册的服务端插件工具"""
|
tools = {}
|
|
# 获取必要的函数
|
necessary_functions = ["handle_exit_intent", "get_lunar"]
|
|
# 获取配置中的函数
|
config_functions = self.config["Intent"][
|
self.config["selected_module"]["Intent"]
|
].get("functions", [])
|
|
# 将模块级别的插件名展开为具体函数名(兜底机制)
|
config_functions = self._expand_plugin_names(config_functions)
|
|
# 合并所有需要的函数
|
all_required_functions = list(set(necessary_functions + config_functions))
|
|
for func_name in all_required_functions:
|
func_item = all_function_registry.get(func_name)
|
if func_item:
|
# 从函数注册中获取描述(支持模块名和函数名的双层查找)
|
fun_description = self._get_plugin_description(func_name)
|
if fun_description is not None and len(fun_description) > 0:
|
if "function" in func_item.description and isinstance(
|
func_item.description["function"], dict
|
):
|
func_item.description["function"][
|
"description"
|
] = fun_description
|
|
# 新闻插件:根据配置更新新闻源参数描述
|
if func_name == "get_news_from_newsnow":
|
self._init_news_source_description(func_item, func_name)
|
|
tools[func_name] = ToolDefinition(
|
name=func_name,
|
description=func_item.description,
|
tool_type=ToolType.SERVER_PLUGIN,
|
)
|
|
return tools
|
|
def has_tool(self, tool_name: str) -> bool:
|
"""检查是否有指定的服务端插件工具"""
|
return tool_name in all_function_registry
|
|
def _init_news_source_description(self, func_item, func_name):
|
"""根据连接配置初始化新闻工具的参数描述"""
|
news_sources = (
|
self.config.get("plugins", {})
|
.get(func_name, {})
|
.get("news_sources", "")
|
)
|
if not news_sources:
|
news_sources = "澎湃新闻;百度热搜;财联社"
|
sources_str = news_sources.replace(";", "、")
|
try:
|
func_item.description["function"]["parameters"]["properties"]["source"][
|
"description"
|
] = f"新闻源的标准中文名称,例如{sources_str}等。可选参数,如果不提供则使用默认新闻源"
|
except (KeyError, TypeError):
|
pass
|