diff --git a/main/xiaozhi-server/core/connection.py b/main/xiaozhi-server/core/connection.py index eaae4e19..7826a1e9 100644 --- a/main/xiaozhi-server/core/connection.py +++ b/main/xiaozhi-server/core/connection.py @@ -17,7 +17,6 @@ from core.utils.util import ( filter_sensitive_info, ) from typing import Dict, Any -from core.providers.tools.base import ToolAction from core.utils.modules_initialize import ( initialize_modules, initialize_tts, @@ -725,15 +724,12 @@ class ConnectionHandler: } # 使用统一工具处理器处理所有工具调用 - tool_result = asyncio.run_coroutine_threadsafe( + result = asyncio.run_coroutine_threadsafe( self.func_handler.handle_llm_function_call( self, function_call_data ), self.loop, ).result() - - # 转换ToolResult为ActionResponse - result = self._convert_tool_result_to_action_response(tool_result) self._handle_function_result(result, function_call_data) # 存储对话内容 @@ -756,39 +752,6 @@ class ConnectionHandler: return True - def _convert_tool_result_to_action_response(self, tool_result): - """转换ToolResult为ActionResponse""" - if tool_result.action == ToolAction.ERROR: - return ActionResponse( - action=Action.ERROR, - result=tool_result.error or tool_result.content, - response=tool_result.response or tool_result.content, - ) - elif tool_result.action == ToolAction.NOT_FOUND: - return ActionResponse( - action=Action.NOTFOUND, - result=tool_result.content, - response=tool_result.response or tool_result.content, - ) - elif tool_result.action == ToolAction.RESPONSE: - return ActionResponse( - action=Action.RESPONSE, - result=tool_result.content, - response=tool_result.response or tool_result.content, - ) - elif tool_result.action == ToolAction.REQUEST_LLM: - return ActionResponse( - action=Action.REQLLM, - result=tool_result.content, - response=tool_result.response or "", - ) - else: - return ActionResponse( - action=Action.NONE, - result=tool_result.content, - response=tool_result.response or "", - ) - def _handle_function_result(self, result, function_call_data): if result.action == Action.RESPONSE: # 直接回复前端 text = result.response @@ -828,7 +791,7 @@ class ConnectionHandler: ) self.chat(text, tool_call=True) elif result.action == Action.NOTFOUND or result.action == Action.ERROR: - text = result.result + text = result.response if result.response else result.result self.tts.tts_one_sentence(self, ContentType.TEXT, content_detail=text) self.dialogue.put(Message(role="assistant", content=text)) else: diff --git a/main/xiaozhi-server/core/handle/intentHandler.py b/main/xiaozhi-server/core/handle/intentHandler.py index b80c43fe..2d0baca4 100644 --- a/main/xiaozhi-server/core/handle/intentHandler.py +++ b/main/xiaozhi-server/core/handle/intentHandler.py @@ -108,15 +108,12 @@ async def process_intent_result(conn, intent_result, original_text): # 使用统一工具处理器处理所有工具调用 try: - tool_result = asyncio.run_coroutine_threadsafe( + result = asyncio.run_coroutine_threadsafe( conn.func_handler.handle_llm_function_call( conn, function_call_data ), conn.loop, ).result() - - # 转换ToolResult为ActionResponse - result = conn._convert_tool_result_to_action_response(tool_result) except Exception as e: conn.logger.bind(tag=TAG).error(f"工具调用失败: {e}") result = ActionResponse( diff --git a/main/xiaozhi-server/core/providers/tools/base/__init__.py b/main/xiaozhi-server/core/providers/tools/base/__init__.py index 1bc6dc32..7476ddd5 100644 --- a/main/xiaozhi-server/core/providers/tools/base/__init__.py +++ b/main/xiaozhi-server/core/providers/tools/base/__init__.py @@ -1,6 +1,6 @@ """基础工具定义模块""" -from .tool_types import ToolType, ToolAction, ToolResult, ToolDefinition +from .tool_types import ToolType, ToolDefinition from .tool_executor import ToolExecutor -__all__ = ["ToolType", "ToolAction", "ToolResult", "ToolDefinition", "ToolExecutor"] +__all__ = ["ToolType", "ToolDefinition", "ToolExecutor"] diff --git a/main/xiaozhi-server/core/providers/tools/base/tool_executor.py b/main/xiaozhi-server/core/providers/tools/base/tool_executor.py index 47cdf718..7685e7c2 100644 --- a/main/xiaozhi-server/core/providers/tools/base/tool_executor.py +++ b/main/xiaozhi-server/core/providers/tools/base/tool_executor.py @@ -2,7 +2,8 @@ from abc import ABC, abstractmethod from typing import Dict, Any -from .tool_types import ToolDefinition, ToolResult +from .tool_types import ToolDefinition +from plugins_func.register import ActionResponse class ToolExecutor(ABC): @@ -11,7 +12,7 @@ class ToolExecutor(ABC): @abstractmethod async def execute( self, conn, tool_name: str, arguments: Dict[str, Any] - ) -> ToolResult: + ) -> ActionResponse: """执行工具调用""" pass diff --git a/main/xiaozhi-server/core/providers/tools/base/tool_types.py b/main/xiaozhi-server/core/providers/tools/base/tool_types.py index ba93c3a7..0f86d99e 100644 --- a/main/xiaozhi-server/core/providers/tools/base/tool_types.py +++ b/main/xiaozhi-server/core/providers/tools/base/tool_types.py @@ -1,9 +1,10 @@ """工具系统的类型定义""" from enum import Enum + from dataclasses import dataclass -from typing import Any, Dict, Optional, Callable, Awaitable -from abc import ABC, abstractmethod +from typing import Any, Dict, Optional +from plugins_func.register import Action class ToolType(Enum): @@ -15,26 +16,6 @@ class ToolType(Enum): DEVICE_MCP = "device_mcp" # 设备端MCP -class ToolAction(Enum): - """工具执行后的动作类型""" - - ERROR = "error" # 错误 - NOT_FOUND = "not_found" # 工具未找到 - RESPONSE = "response" # 直接回复 - REQUEST_LLM = "request_llm" # 需要LLM处理 - NONE = "none" # 无需特殊处理 - - -@dataclass -class ToolResult: - """工具执行结果""" - - action: ToolAction - content: str # 结果内容 - response: Optional[str] = None # 直接回复内容 - error: Optional[str] = None # 错误信息 - - @dataclass class ToolDefinition: """工具定义""" diff --git a/main/xiaozhi-server/core/providers/tools/device_iot/iot_executor.py b/main/xiaozhi-server/core/providers/tools/device_iot/iot_executor.py index cf9ced88..7be9e112 100644 --- a/main/xiaozhi-server/core/providers/tools/device_iot/iot_executor.py +++ b/main/xiaozhi-server/core/providers/tools/device_iot/iot_executor.py @@ -3,7 +3,8 @@ import json import asyncio from typing import Dict, Any -from ..base import ToolType, ToolDefinition, ToolResult, ToolExecutor, ToolAction +from ..base import ToolType, ToolDefinition, ToolExecutor +from plugins_func.register import Action, ActionResponse class DeviceIoTExecutor(ToolExecutor): @@ -15,11 +16,11 @@ class DeviceIoTExecutor(ToolExecutor): async def execute( self, conn, tool_name: str, arguments: Dict[str, Any] - ) -> ToolResult: + ) -> ActionResponse: """执行设备端IoT工具""" if not self.has_tool(tool_name): - return ToolResult( - action=ToolAction.NOT_FOUND, content=f"IoT工具 {tool_name} 不存在" + return ActionResponse( + action=Action.NOTFOUND, response=f"IoT工具 {tool_name} 不存在" ) try: @@ -39,19 +40,16 @@ class DeviceIoTExecutor(ToolExecutor): ) response = response_success.replace("{value}", str(value)) - return ToolResult( - action=ToolAction.RESPONSE, - content=str(value), + return ActionResponse( + action=Action.RESPONSE, response=response, ) else: response_failure = arguments.get( "response_failure", f"无法获取{device_name}的状态" ) - return ToolResult( - action=ToolAction.ERROR, - content=f"属性{property_name}不存在", - error=response_failure, + return ActionResponse( + action=Action.ERROR, response=response_failure ) else: # 控制操作:devicename_method @@ -90,19 +88,16 @@ class DeviceIoTExecutor(ToolExecutor): ) break - return ToolResult( - action=ToolAction.REQUEST_LLM, - content=f"{device_name}操作执行成功,请继续处理剩余指令", - response=response_success, + return ActionResponse( + action=Action.REQLLM, + result=response_success, ) - return ToolResult(action=ToolAction.ERROR, content="无法解析IoT工具名称") + return ActionResponse(action=Action.ERROR, response="无法解析IoT工具名称") except Exception as e: response_failure = arguments.get("response_failure", "操作失败") - return ToolResult( - action=ToolAction.ERROR, content=str(e), error=response_failure - ) + return ActionResponse(action=Action.ERROR, response=response_failure) async def _get_iot_status(self, device_name: str, property_name: str): """获取IoT设备状态""" diff --git a/main/xiaozhi-server/core/providers/tools/device_mcp/mcp_executor.py b/main/xiaozhi-server/core/providers/tools/device_mcp/mcp_executor.py index 97d37f9b..4de1eecb 100644 --- a/main/xiaozhi-server/core/providers/tools/device_mcp/mcp_executor.py +++ b/main/xiaozhi-server/core/providers/tools/device_mcp/mcp_executor.py @@ -1,7 +1,8 @@ """设备端MCP工具执行器""" from typing import Dict, Any -from ..base import ToolType, ToolDefinition, ToolResult, ToolExecutor, ToolAction +from ..base import ToolType, ToolDefinition, ToolExecutor +from plugins_func.register import Action, ActionResponse from .mcp_handler import call_mcp_tool @@ -13,20 +14,18 @@ class DeviceMCPExecutor(ToolExecutor): async def execute( self, conn, tool_name: str, arguments: Dict[str, Any] - ) -> ToolResult: + ) -> ActionResponse: """执行设备端MCP工具""" if not hasattr(conn, "mcp_client") or not conn.mcp_client: - return ToolResult( - action=ToolAction.ERROR, - content="设备端MCP客户端未初始化", - error="设备端MCP客户端未初始化", + return ActionResponse( + action=Action.ERROR, + response="设备端MCP客户端未初始化", ) if not await conn.mcp_client.is_ready(): - return ToolResult( - action=ToolAction.ERROR, - content="设备端MCP客户端未准备就绪", - error="设备端MCP客户端未准备就绪", + return ActionResponse( + action=Action.ERROR, + response="设备端MCP客户端未准备就绪", ) try: @@ -38,12 +37,12 @@ class DeviceMCPExecutor(ToolExecutor): # 调用设备端MCP工具 result = await call_mcp_tool(conn, conn.mcp_client, tool_name, args_str) - return ToolResult(action=ToolAction.RESPONSE, content=str(result)) + return ActionResponse(action=Action.REQLLM, result=str(result)) except ValueError as e: - return ToolResult(action=ToolAction.NOT_FOUND, content=str(e), error=str(e)) + return ActionResponse(action=Action.NOTFOUND, response=str(e)) except Exception as e: - return ToolResult(action=ToolAction.ERROR, content=str(e), error=str(e)) + return ActionResponse(action=Action.ERROR, response=str(e)) def get_tools(self) -> Dict[str, ToolDefinition]: """获取所有设备端MCP工具""" diff --git a/main/xiaozhi-server/core/providers/tools/server_mcp/mcp_executor.py b/main/xiaozhi-server/core/providers/tools/server_mcp/mcp_executor.py index 3902b6b4..a41b29dc 100644 --- a/main/xiaozhi-server/core/providers/tools/server_mcp/mcp_executor.py +++ b/main/xiaozhi-server/core/providers/tools/server_mcp/mcp_executor.py @@ -1,7 +1,8 @@ """服务端MCP工具执行器""" from typing import Dict, Any, Optional -from ..base import ToolType, ToolDefinition, ToolResult, ToolExecutor, ToolAction +from ..base import ToolType, ToolDefinition, ToolExecutor +from plugins_func.register import Action, ActionResponse from .mcp_manager import ServerMCPManager @@ -22,13 +23,12 @@ class ServerMCPExecutor(ToolExecutor): async def execute( self, conn, tool_name: str, arguments: Dict[str, Any] - ) -> ToolResult: + ) -> ActionResponse: """执行服务端MCP工具""" if not self._initialized or not self.mcp_manager: - return ToolResult( - action=ToolAction.ERROR, - content="MCP管理器未初始化", - error="MCP管理器未初始化", + return ActionResponse( + action=Action.ERROR, + response="MCP管理器未初始化", ) try: @@ -39,12 +39,18 @@ class ServerMCPExecutor(ToolExecutor): result = await self.mcp_manager.execute_tool(actual_tool_name, arguments) - return ToolResult(action=ToolAction.RESPONSE, content=str(result)) + return ActionResponse(action=Action.REQLLM, result=str(result)) except ValueError as e: - return ToolResult(action=ToolAction.NOT_FOUND, content=str(e), error=str(e)) + return ActionResponse( + action=Action.NOTFOUND, + response=str(e), + ) except Exception as e: - return ToolResult(action=ToolAction.ERROR, content=str(e), error=str(e)) + return ActionResponse( + action=Action.ERROR, + response=str(e), + ) def get_tools(self) -> Dict[str, ToolDefinition]: """获取所有服务端MCP工具""" diff --git a/main/xiaozhi-server/core/providers/tools/server_plugins/plugin_executor.py b/main/xiaozhi-server/core/providers/tools/server_plugins/plugin_executor.py index 0d74aa8e..e9194e5a 100644 --- a/main/xiaozhi-server/core/providers/tools/server_plugins/plugin_executor.py +++ b/main/xiaozhi-server/core/providers/tools/server_plugins/plugin_executor.py @@ -1,7 +1,7 @@ """服务端插件工具执行器""" from typing import Dict, Any -from ..base import ToolType, ToolDefinition, ToolResult, ToolExecutor, ToolAction +from ..base import ToolType, ToolDefinition, ToolExecutor from plugins_func.register import all_function_registry, Action, ActionResponse @@ -14,12 +14,12 @@ class ServerPluginExecutor(ToolExecutor): async def execute( self, conn, tool_name: str, arguments: Dict[str, Any] - ) -> ToolResult: + ) -> ActionResponse: """执行服务端插件工具""" func_item = all_function_registry.get(tool_name) if not func_item: - return ToolResult( - action=ToolAction.NOT_FOUND, content=f"插件函数 {tool_name} 不存在" + return ActionResponse( + action=Action.NOTFOUND, response=f"插件函数 {tool_name} 不存在" ) try: @@ -38,38 +38,13 @@ class ServerPluginExecutor(ToolExecutor): # 默认不传conn参数 result = func_item.func(**arguments) - # 转换ActionResponse到ToolResult - if isinstance(result, ActionResponse): - if result.action == Action.ERROR: - return ToolResult( - action=ToolAction.ERROR, - content=result.result, - error=result.response, - ) - elif result.action == Action.RESPONSE: - return ToolResult( - action=ToolAction.RESPONSE, - content=result.result, - response=result.response, - ) - elif result.action == Action.REQLLM: - return ToolResult( - action=ToolAction.REQUEST_LLM, - content=result.result, - response=result.response, - ) - else: - return ToolResult( - action=ToolAction.NONE, - content=result.result, - response=result.response, - ) - else: - # 直接返回结果 - return ToolResult(action=ToolAction.RESPONSE, content=str(result)) + return result except Exception as e: - return ToolResult(action=ToolAction.ERROR, content=str(e), error=str(e)) + return ActionResponse( + action=Action.ERROR, + response=str(e), + ) def get_tools(self) -> Dict[str, ToolDefinition]: """获取所有注册的服务端插件工具""" diff --git a/main/xiaozhi-server/core/providers/tools/unified_tool_handler.py b/main/xiaozhi-server/core/providers/tools/unified_tool_handler.py index 91608b48..8e3b20b5 100644 --- a/main/xiaozhi-server/core/providers/tools/unified_tool_handler.py +++ b/main/xiaozhi-server/core/providers/tools/unified_tool_handler.py @@ -5,7 +5,8 @@ from typing import Dict, List, Any, Optional from config.logger import setup_logging from plugins_func.loadplugins import auto_import_modules -from .base import ToolType, ToolResult, ToolAction +from .base import ToolType +from plugins_func.register import Action, ActionResponse from .unified_tool_manager import ToolManager from .server_plugins import ServerPluginExecutor from .server_mcp import ServerMCPExecutor @@ -97,7 +98,7 @@ class UnifiedToolHandler: async def handle_llm_function_call( self, conn, function_call_data: Dict[str, Any] - ) -> Optional[ToolResult]: + ) -> Optional[ActionResponse]: """处理LLM函数调用""" try: # 处理多函数调用 @@ -120,10 +121,9 @@ class UnifiedToolHandler: arguments = json.loads(arguments) if arguments else {} except json.JSONDecodeError: self.logger.error(f"无法解析函数参数: {arguments}") - return ToolResult( - action=ToolAction.ERROR, - content="无法解析函数参数", - error="无法解析函数参数", + return ActionResponse( + action=Action.ERROR, + response="无法解析函数参数", ) self.logger.debug(f"调用函数: {function_name}, 参数: {arguments}") @@ -134,16 +134,16 @@ class UnifiedToolHandler: except Exception as e: self.logger.error(f"处理function call错误: {e}") - return ToolResult(action=ToolAction.ERROR, content=str(e), error=str(e)) + return ActionResponse(action=Action.ERROR, response=str(e)) - def _combine_responses(self, responses: List[ToolResult]) -> ToolResult: + def _combine_responses(self, responses: List[ActionResponse]) -> ActionResponse: """合并多个函数调用的响应""" if not responses: - return ToolResult(action=ToolAction.NONE, content="无响应") + return ActionResponse(action=Action.NONE, response="无响应") # 如果有任何错误,返回第一个错误 for response in responses: - if response.action == ToolAction.ERROR: + if response.action == Action.ERROR: return response # 合并所有成功的响应 @@ -157,15 +157,15 @@ class UnifiedToolHandler: responses_text.append(response.response) # 确定最终的动作类型 - final_action = ToolAction.RESPONSE + final_action = Action.RESPONSE for response in responses: - if response.action == ToolAction.REQUEST_LLM: - final_action = ToolAction.REQUEST_LLM + if response.action == Action.REQLLM: + final_action = Action.REQLLM break - return ToolResult( + return ActionResponse( action=final_action, - content="; ".join(contents), + result="; ".join(contents) if contents else None, response="; ".join(responses_text) if responses_text else None, ) diff --git a/main/xiaozhi-server/core/providers/tools/unified_tool_manager.py b/main/xiaozhi-server/core/providers/tools/unified_tool_manager.py index c4187b7c..e2a91869 100644 --- a/main/xiaozhi-server/core/providers/tools/unified_tool_manager.py +++ b/main/xiaozhi-server/core/providers/tools/unified_tool_manager.py @@ -1,37 +1,37 @@ """统一工具管理器""" -import asyncio from typing import Dict, List, Optional, Any from config.logger import setup_logging -from .base import ToolType, ToolDefinition, ToolResult, ToolExecutor, ToolAction +from plugins_func.register import Action, ActionResponse +from .base import ToolType, ToolDefinition, ToolExecutor class ToolManager: """统一工具管理器,管理所有类型的工具""" - + def __init__(self, conn): self.conn = conn self.logger = setup_logging() self.executors: Dict[ToolType, ToolExecutor] = {} self._cached_tools: Optional[Dict[str, ToolDefinition]] = None self._cached_function_descriptions: Optional[List[Dict[str, Any]]] = None - + def register_executor(self, tool_type: ToolType, executor: ToolExecutor): """注册工具执行器""" self.executors[tool_type] = executor self._invalidate_cache() self.logger.info(f"注册工具执行器: {tool_type.value}") - + def _invalidate_cache(self): """使缓存失效""" self._cached_tools = None self._cached_function_descriptions = None - + def get_all_tools(self) -> Dict[str, ToolDefinition]: """获取所有工具定义""" if self._cached_tools is not None: return self._cached_tools - + all_tools = {} for tool_type, executor in self.executors.items(): try: @@ -42,79 +42,75 @@ class ToolManager: all_tools[name] = definition except Exception as e: self.logger.error(f"获取{tool_type.value}工具时出错: {e}") - + self._cached_tools = all_tools return all_tools - + def get_function_descriptions(self) -> List[Dict[str, Any]]: """获取所有工具的函数描述(OpenAI格式)""" if self._cached_function_descriptions is not None: return self._cached_function_descriptions - + descriptions = [] tools = self.get_all_tools() for tool_definition in tools.values(): descriptions.append(tool_definition.description) - + self._cached_function_descriptions = descriptions return descriptions - + def has_tool(self, tool_name: str) -> bool: """检查是否存在指定工具""" tools = self.get_all_tools() return tool_name in tools - + def get_tool_type(self, tool_name: str) -> Optional[ToolType]: """获取工具类型""" tools = self.get_all_tools() tool_def = tools.get(tool_name) return tool_def.tool_type if tool_def else None - - async def execute_tool(self, tool_name: str, arguments: Dict[str, Any]) -> ToolResult: + + async def execute_tool( + self, tool_name: str, arguments: Dict[str, Any] + ) -> ActionResponse: """执行工具调用""" try: # 查找工具类型 tool_type = self.get_tool_type(tool_name) if not tool_type: - return ToolResult( - action=ToolAction.NOT_FOUND, - content=f"工具 {tool_name} 不存在", - error=f"工具 {tool_name} 不存在" + return ActionResponse( + action=Action.NOTFOUND, + response=f"工具 {tool_name} 不存在", ) - + # 获取对应的执行器 executor = self.executors.get(tool_type) if not executor: - return ToolResult( - action=ToolAction.ERROR, - content=f"工具类型 {tool_type.value} 的执行器未注册", - error=f"工具类型 {tool_type.value} 的执行器未注册" + return ActionResponse( + action=Action.ERROR, + response=f"工具类型 {tool_type.value} 的执行器未注册", ) - + # 执行工具 self.logger.info(f"执行工具: {tool_name},参数: {arguments}") result = await executor.execute(self.conn, tool_name, arguments) self.logger.debug(f"工具执行结果: {result}") return result - + except Exception as e: self.logger.error(f"执行工具 {tool_name} 时出错: {e}") - return ToolResult( - action=ToolAction.ERROR, - content=str(e), - error=str(e) - ) - + return ActionResponse(action=Action.ERROR, response=str(e)) + def get_supported_tool_names(self) -> List[str]: """获取所有支持的工具名称""" tools = self.get_all_tools() return list(tools.keys()) - + def refresh_tools(self): """刷新工具缓存""" self._invalidate_cache() self.logger.info("工具缓存已刷新") - + def get_tool_statistics(self) -> Dict[str, int]: """获取工具统计信息""" stats = {} @@ -125,4 +121,4 @@ class ToolManager: except Exception as e: self.logger.error(f"获取{tool_type.value}工具统计时出错: {e}") stats[tool_type.value] = 0 - return stats \ No newline at end of file + return stats diff --git a/main/xiaozhi-server/plugins_func/register.py b/main/xiaozhi-server/plugins_func/register.py index 873c61e1..5c2b0781 100644 --- a/main/xiaozhi-server/plugins_func/register.py +++ b/main/xiaozhi-server/plugins_func/register.py @@ -35,7 +35,7 @@ class Action(Enum): class ActionResponse: - def __init__(self, action: Action, result, response): + def __init__(self, action: Action, result=None, response=None): self.action = action # 动作类型 self.result = result # 动作产生的结果 self.response = response # 直接回复的内容