"""Tool registry — ABC and dynamic registration.""" from __future__ import annotations from abc import ABC, abstractmethod from typing import Any from loguru import logger class Tool(ABC): """Abstract base for all tools.""" @property @abstractmethod def name(self) -> str: ... @property @abstractmethod def description(self) -> str: ... @property @abstractmethod def parameters(self) -> dict[str, Any]: """JSON Schema for tool parameters.""" @abstractmethod async def execute(self, **kwargs: Any) -> str: """Execute the tool and return a string result.""" def to_openai_schema(self) -> dict[str, Any]: """Convert to OpenAI function-calling format.""" return { "type": "function", "function": { "name": self.name, "description": self.description, "parameters": self.parameters, }, } class ToolRegistry: """Manages registered tools and dispatches execution.""" def __init__(self) -> None: self._tools: dict[str, Tool] = {} def register(self, tool: Tool) -> None: self._tools[tool.name] = tool def get(self, name: str) -> Tool | None: return self._tools.get(name) def names(self) -> list[str]: return list(self._tools.keys()) def get_definitions(self) -> list[dict[str, Any]]: """Get all tool schemas for the LLM.""" return [t.to_openai_schema() for t in self._tools.values()] def filtered(self, allowed: list[str]) -> ToolRegistry: """Return a new registry containing only the specified tools.""" filtered_reg = ToolRegistry() for name in allowed: tool = self._tools.get(name) if tool: filtered_reg.register(tool) return filtered_reg async def execute(self, name: str, arguments: dict[str, Any]) -> str: """Execute a tool by name.""" tool = self._tools.get(name) if not tool: return f"Error: Unknown tool '{name}'" try: return await tool.execute(**arguments) except Exception as e: logger.error(f"Tool '{name}' failed: {e}") return f"Error executing '{name}': {e}"