diff --git a/astrbot/_internal/tools/base.py b/astrbot/_internal/tools/base.py index 78f389f3e..a5ad26567 100644 --- a/astrbot/_internal/tools/base.py +++ b/astrbot/_internal/tools/base.py @@ -104,6 +104,10 @@ class ToolSet: """Remove and return a tool by name.""" return self._tools.pop(name, None) + def remove_tool(self, name: str) -> None: + """Remove a tool by its name.""" + self._tools.pop(name, None) + def get(self, name: str) -> FunctionTool | None: """Get a tool by name.""" return self._tools.get(name) @@ -126,3 +130,23 @@ class ToolSet: def tools(self) -> list[FunctionTool]: """List all tools in this set.""" return list(self._tools.values()) + + def openai_schema(self, omit_empty_parameter_field: bool = False) -> list[dict[str, Any]]: + """Convert tools to OpenAI API function calling schema format.""" + result: list[dict[str, Any]] = [] + for tool in self._tools.values(): + func_def: dict[str, Any] = { + "type": "function", + "function": {"name": tool.name}, + } + if tool.description: + func_def["function"]["description"] = tool.description + + if tool.parameters is not None: + if ( + tool.parameters.get("properties") + ) or not omit_empty_parameter_field: + func_def["function"]["parameters"] = tool.parameters + + result.append(func_def) + return result diff --git a/astrbot/_internal/tools/registry.py b/astrbot/_internal/tools/registry.py index 2b27d1046..d71052552 100644 --- a/astrbot/_internal/tools/registry.py +++ b/astrbot/_internal/tools/registry.py @@ -48,24 +48,34 @@ class FunctionToolManager: """Central registry for all function tools.""" def __init__(self) -> None: - self.func_list: list[FunctionTool] = [] + self._func_list: list[FunctionTool] = [] + + @property + def func_list(self) -> list[FunctionTool]: + """Get the list of function tools.""" + return self._func_list + + @func_list.setter + def func_list(self, value: list[FunctionTool]) -> None: + """Set the list of function tools.""" + self._func_list = value def add(self, tool: FunctionTool) -> None: """Add a tool to the registry.""" - self.func_list.append(tool) + self._func_list.append(tool) def remove(self, name: str) -> bool: """Remove a tool by name. Returns True if found.""" - for i, f in enumerate(self.func_list): + for i, f in enumerate(self._func_list): if f.name == name: - self.func_list.pop(i) + self._func_list.pop(i) return True return False def get_func(self, name: str) -> FunctionTool | None: """Get a tool by name. Returns the last active tool if multiple match.""" last_match: FunctionTool | None = None - for f in reversed(self.func_list): + for f in reversed(self._func_list): if f.name == name: if getattr(f, "active", True): return f @@ -76,7 +86,7 @@ class FunctionToolManager: def get_full_tool_set(self) -> ToolSet: """Return a ToolSet with all active tools, deduplicated by name.""" seen: dict[str, FunctionTool] = {} - for tool in reversed(self.func_list): + for tool in reversed(self._func_list): if tool.name not in seen and getattr(tool, "active", True): seen[tool.name] = tool return ToolSet("default", list(seen.values())) @@ -90,6 +100,57 @@ class FunctionToolManager: if self.get_func(tool.name) is None: self.add(tool) + # MCP-related stub methods for base class compatibility + async def enable_mcp_server( + self, name: str, config: dict[str, Any], init_timeout: int = 30 + ) -> None: + """Enable an MCP server (stub).""" + pass + + async def disable_mcp_server( + self, name: str = "", timeout: int = 10, shutdown_timeout: int = 10 + ) -> None: + """Disable an MCP server (stub).""" + pass + + async def init_mcp_clients(self) -> None: + """Initialize MCP clients (stub).""" + pass + + async def test_mcp_server_connection(self, config: dict[str, Any]) -> tuple[bool, str]: + """Test MCP server connection (stub).""" + return False, "Not implemented" + + async def sync_modelscope_mcp_servers(self) -> None: + """Sync ModelScope MCP servers (stub).""" + pass + + def load_mcp_config(self) -> dict[str, Any]: + """Load MCP configuration (stub).""" + return {"mcpServers": {}} + + def save_mcp_config(self, config: dict[str, Any]) -> bool: + """Save MCP configuration (stub).""" + return True + + def activate_llm_tool(self, name: str) -> bool: + """Activate an LLM tool (stub).""" + return True + + def deactivate_llm_tool(self, name: str) -> bool: + """Deactivate an LLM tool (stub).""" + return True + + @property + def mcp_client_dict(self) -> dict[str, Any]: + """Return dict of MCP clients (stub).""" + return {} + + @property + def mcp_server_runtime_view(self) -> dict[str, Any]: + """Return runtime view of MCP servers (stub).""" + return {} + class FuncCall(FunctionToolManager): """Alias for FunctionToolManager for backward compatibility.""" @@ -97,12 +158,68 @@ class FuncCall(FunctionToolManager): def __init__(self) -> None: super().__init__() self._mcp_server_runtime_view: dict[str, Any] = {} + self._mcp_client_dict: dict[str, Any] = {} @property def mcp_server_runtime_view(self) -> dict[str, Any]: """Return runtime view of MCP servers.""" return self._mcp_server_runtime_view + @property + def mcp_client_dict(self) -> dict[str, Any]: + """Return dict of MCP clients (for backward compatibility).""" + return self._mcp_client_dict + + async def init_mcp_clients(self) -> None: + """Initialize MCP clients (stub implementation).""" + pass + + def add_func( + self, + name: str, + func_args: list[dict[str, Any]], + desc: str, + handler: Any, + ) -> None: + """Add a function tool (deprecated, use add() instead).""" + params: dict[str, Any] = { + "type": "object", + "properties": {}, + } + for param in func_args: + params["properties"][param["name"]] = { + "type": param.get("type", "string"), + "description": param.get("description", ""), + } + func = FunctionTool( + name=name, + parameters=params, + description=desc, + handler=handler, + ) + self.add(func) + + def remove_func(self, name: str) -> None: + """Remove a function tool by name (deprecated, use remove() instead).""" + self.remove(name) + + def get_func(self, name: str) -> FunctionTool | None: + """Get a function tool by name.""" + return super().get_func(name) + + def names(self) -> list[str]: + """Get all tool names.""" + return [f.name for f in self.func_list] + + def remove_tool(self, name: str) -> None: + """Remove a tool by its name (alias for remove).""" + self.remove(name) + + def get_func_desc_openai_style(self, omit_empty_parameter_field: bool = False) -> list[dict[str, Any]]: + """Get tools in OpenAI style (deprecated, use get_full_tool_set().openai_schema()).""" + tool_set = self.get_full_tool_set() + return tool_set.openai_schema(omit_empty_parameter_field) + async def enable_mcp_server( self, name: str, config: dict[str, Any], init_timeout: int = 30 ) -> None: