kiln_ai.tools

1from kiln_ai.tools.base_tool import KilnTool, KilnToolInterface
2from kiln_ai.tools.tool_registry import tool_from_id
3
4__all__ = [
5    "KilnTool",
6    "KilnToolInterface",
7    "tool_from_id",
8]
class KilnTool(kiln_ai.tools.KilnToolInterface):
 73class KilnTool(KilnToolInterface):
 74    """
 75    Base helper class that provides common functionality for tool implementations.
 76    Subclasses only need to implement run() and provide tool configuration.
 77    """
 78
 79    def __init__(
 80        self,
 81        tool_id: KilnBuiltInToolId,
 82        name: str,
 83        description: str,
 84        parameters_schema: Dict[str, Any],
 85    ):
 86        self._id = tool_id
 87        self._name = name
 88        self._description = description
 89        validate_schema_dict(parameters_schema)
 90        self._parameters_schema = parameters_schema
 91
 92    async def id(self) -> KilnBuiltInToolId:
 93        return self._id
 94
 95    async def name(self) -> str:
 96        return self._name
 97
 98    async def description(self) -> str:
 99        return self._description
100
101    async def toolcall_definition(self) -> ToolCallDefinition:
102        """Generate OpenAI-compatible tool definition."""
103        return {
104            "type": "function",
105            "function": {
106                "name": await self.name(),
107                "description": await self.description(),
108                "parameters": self._parameters_schema,
109            },
110        }
111
112    @abstractmethod
113    async def run(
114        self, context: ToolCallContext | None = None, **kwargs
115    ) -> ToolCallResult:
116        """Subclasses must implement the actual tool logic."""
117        pass

Base helper class that provides common functionality for tool implementations. Subclasses only need to implement run() and provide tool configuration.

async def id(self) -> kiln_ai.datamodel.tool_id.KilnBuiltInToolId:
92    async def id(self) -> KilnBuiltInToolId:
93        return self._id

Return a unique identifier for this tool.

async def name(self) -> str:
95    async def name(self) -> str:
96        return self._name

Return the tool name (function name) of this tool.

async def description(self) -> str:
98    async def description(self) -> str:
99        return self._description

Return a description of what this tool does.

async def toolcall_definition(self) -> kiln_ai.tools.base_tool.ToolCallDefinition:
101    async def toolcall_definition(self) -> ToolCallDefinition:
102        """Generate OpenAI-compatible tool definition."""
103        return {
104            "type": "function",
105            "function": {
106                "name": await self.name(),
107                "description": await self.description(),
108                "parameters": self._parameters_schema,
109            },
110        }

Generate OpenAI-compatible tool definition.

@abstractmethod
async def run( self, context: kiln_ai.tools.base_tool.ToolCallContext | None = None, **kwargs) -> kiln_ai.tools.base_tool.ToolCallResult:
112    @abstractmethod
113    async def run(
114        self, context: ToolCallContext | None = None, **kwargs
115    ) -> ToolCallResult:
116        """Subclasses must implement the actual tool logic."""
117        pass

Subclasses must implement the actual tool logic.

class KilnToolInterface(abc.ABC):
39class KilnToolInterface(ABC):
40    """
41    Abstract interface defining the core API that all Kiln tools must implement.
42    This ensures consistency across all tool implementations.
43    """
44
45    @abstractmethod
46    async def run(
47        self, context: ToolCallContext | None = None, **kwargs
48    ) -> ToolCallResult:
49        """Execute the tool with the given parameters and calling context if provided."""
50        pass
51
52    @abstractmethod
53    async def toolcall_definition(self) -> ToolCallDefinition:
54        """Return the OpenAI-compatible tool definition for this tool."""
55        pass
56
57    @abstractmethod
58    async def id(self) -> ToolId:
59        """Return a unique identifier for this tool."""
60        pass
61
62    @abstractmethod
63    async def name(self) -> str:
64        """Return the tool name (function name) of this tool."""
65        pass
66
67    @abstractmethod
68    async def description(self) -> str:
69        """Return a description of what this tool does."""
70        pass

Abstract interface defining the core API that all Kiln tools must implement. This ensures consistency across all tool implementations.

@abstractmethod
async def run( self, context: kiln_ai.tools.base_tool.ToolCallContext | None = None, **kwargs) -> kiln_ai.tools.base_tool.ToolCallResult:
45    @abstractmethod
46    async def run(
47        self, context: ToolCallContext | None = None, **kwargs
48    ) -> ToolCallResult:
49        """Execute the tool with the given parameters and calling context if provided."""
50        pass

Execute the tool with the given parameters and calling context if provided.

@abstractmethod
async def toolcall_definition(self) -> kiln_ai.tools.base_tool.ToolCallDefinition:
52    @abstractmethod
53    async def toolcall_definition(self) -> ToolCallDefinition:
54        """Return the OpenAI-compatible tool definition for this tool."""
55        pass

Return the OpenAI-compatible tool definition for this tool.

@abstractmethod
async def id( self) -> Annotated[str, AfterValidator(func=<function <lambda> at 0x7f1944608860>)]:
57    @abstractmethod
58    async def id(self) -> ToolId:
59        """Return a unique identifier for this tool."""
60        pass

Return a unique identifier for this tool.

@abstractmethod
async def name(self) -> str:
62    @abstractmethod
63    async def name(self) -> str:
64        """Return the tool name (function name) of this tool."""
65        pass

Return the tool name (function name) of this tool.

@abstractmethod
async def description(self) -> str:
67    @abstractmethod
68    async def description(self) -> str:
69        """Return a description of what this tool does."""
70        pass

Return a description of what this tool does.

def tool_from_id( tool_id: str, task: kiln_ai.datamodel.Task | None = None) -> KilnToolInterface:
 26def tool_from_id(tool_id: str, task: Task | None = None) -> KilnToolInterface:
 27    """
 28    Get a tool from its ID.
 29    """
 30    # Check built-in tools
 31    if tool_id in [member.value for member in KilnBuiltInToolId]:
 32        typed_tool_id = KilnBuiltInToolId(tool_id)
 33        match typed_tool_id:
 34            case KilnBuiltInToolId.ADD_NUMBERS:
 35                return AddTool()
 36            case KilnBuiltInToolId.SUBTRACT_NUMBERS:
 37                return SubtractTool()
 38            case KilnBuiltInToolId.MULTIPLY_NUMBERS:
 39                return MultiplyTool()
 40            case KilnBuiltInToolId.DIVIDE_NUMBERS:
 41                return DivideTool()
 42            case _:
 43                raise_exhaustive_enum_error(typed_tool_id)
 44
 45    # Check if this looks like an MCP or Kiln Task tool ID that requires a project
 46    is_mcp_tool = tool_id.startswith(
 47        (MCP_REMOTE_TOOL_ID_PREFIX, MCP_LOCAL_TOOL_ID_PREFIX)
 48    )
 49    is_kiln_task_tool = tool_id.startswith(KILN_TASK_TOOL_ID_PREFIX)
 50
 51    if is_mcp_tool or is_kiln_task_tool:
 52        project = task.parent_project() if task is not None else None
 53        if project is None or project.id is None:
 54            raise ValueError(
 55                f"Unable to resolve tool from id: {tool_id}. Requires a parent project/task."
 56            )
 57
 58        # Check MCP Server Tools
 59        if is_mcp_tool:
 60            # Get the tool server ID and tool name from the ID
 61            tool_server_id, tool_name = mcp_server_and_tool_name_from_id(
 62                tool_id
 63            )  # Fixed function name
 64
 65            server = next(
 66                (
 67                    server
 68                    for server in project.external_tool_servers()
 69                    if server.id == tool_server_id
 70                ),
 71                None,
 72            )
 73            if server is None:
 74                raise ValueError(
 75                    f"External tool server not found: {tool_server_id} in project ID {project.id}"
 76                )
 77
 78            return MCPServerTool(server, tool_name)
 79
 80        # Check Kiln Task Tools
 81        if is_kiln_task_tool:
 82            server_id = kiln_task_server_id_from_tool_id(tool_id)
 83
 84            server = next(
 85                (
 86                    server
 87                    for server in project.external_tool_servers()
 88                    if server.id == server_id
 89                ),
 90                None,
 91            )
 92            if server is None:
 93                raise ValueError(
 94                    f"Kiln Task External tool server not found: {server_id} in project ID {project.id}"
 95                )
 96
 97            return KilnTaskTool(project.id, tool_id, server)
 98
 99    elif tool_id.startswith(RAG_TOOL_ID_PREFIX):
100        project = task.parent_project() if task is not None else None
101        if project is None:
102            raise ValueError(
103                f"Unable to resolve tool from id: {tool_id}. Requires a parent project/task."
104            )
105
106        rag_config_id = rag_config_id_from_id(tool_id)
107        rag_config = RagConfig.from_id_and_parent_path(rag_config_id, project.path)
108        if rag_config is None:
109            raise ValueError(
110                f"RAG config not found: {rag_config_id} in project {project.id} for tool {tool_id}"
111            )
112
113        # Lazy import to avoid circular dependency
114        from kiln_ai.tools.rag_tools import RagTool
115
116        return RagTool(tool_id, rag_config)
117
118    raise ValueError(f"Tool ID {tool_id} not found in tool registry")

Get a tool from its ID.