kiln_ai.tools

1from kiln_ai.tools.base_tool import KilnTool, KilnToolInterface, UnmanagedKilnTool
2from kiln_ai.tools.tool_registry import tool_from_id
3
4__all__ = [
5    "KilnTool",
6    "KilnToolInterface",
7    "UnmanagedKilnTool",
8    "tool_from_id",
9]
class KilnTool(kiln_ai.tools.KilnToolInterface):
132class KilnTool(KilnToolInterface):
133    """
134    Base helper class that provides common functionality for tool implementations.
135    Subclasses only need to implement run() and provide tool configuration.
136    """
137
138    def __init__(
139        self,
140        tool_id: KilnBuiltInToolId,
141        name: str,
142        description: str,
143        parameters_schema: Dict[str, Any],
144    ):
145        self._id = tool_id
146        self._name = name
147        self._description = description
148        validate_schema_dict(parameters_schema)
149        self._parameters_schema = parameters_schema
150
151    async def id(self) -> KilnBuiltInToolId:
152        return self._id
153
154    async def name(self) -> str:
155        return self._name
156
157    async def description(self) -> str:
158        return self._description
159
160    async def toolcall_definition(self) -> ToolCallDefinition:
161        """Generate OpenAI-compatible tool definition."""
162        return {
163            "type": "function",
164            "function": {
165                "name": await self.name(),
166                "description": await self.description(),
167                "parameters": self._parameters_schema,
168            },
169        }
170
171    @abstractmethod
172    async def run(
173        self, context: ToolCallContext | None = None, **kwargs
174    ) -> ToolCallResult:
175        """Subclasses must implement the actual tool logic."""
176        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:
151    async def id(self) -> KilnBuiltInToolId:
152        return self._id

Return a unique identifier for this tool.

async def name(self) -> str:
154    async def name(self) -> str:
155        return self._name

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

async def description(self) -> str:
157    async def description(self) -> str:
158        return self._description

Return a description of what this tool does.

async def toolcall_definition(self) -> kiln_ai.tools.base_tool.ToolCallDefinition:
160    async def toolcall_definition(self) -> ToolCallDefinition:
161        """Generate OpenAI-compatible tool definition."""
162        return {
163            "type": "function",
164            "function": {
165                "name": await self.name(),
166                "description": await self.description(),
167                "parameters": self._parameters_schema,
168            },
169        }

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:
171    @abstractmethod
172    async def run(
173        self, context: ToolCallContext | None = None, **kwargs
174    ) -> ToolCallResult:
175        """Subclasses must implement the actual tool logic."""
176        pass

Subclasses must implement the actual tool logic.

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

Return the OpenAI-compatible tool definition for this tool.

@abstractmethod
async def id( self) -> Annotated[str, AfterValidator(func=<function <lambda> at 0x7f034561ccc0>)]:
65    @abstractmethod
66    async def id(self) -> ToolId:
67        """Return a unique identifier for this tool."""
68        pass

Return a unique identifier for this tool.

@abstractmethod
async def name(self) -> str:
70    @abstractmethod
71    async def name(self) -> str:
72        """Return the tool name (function name) of this tool."""
73        pass

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

@abstractmethod
async def description(self) -> str:
75    @abstractmethod
76    async def description(self) -> str:
77        """Return a description of what this tool does."""
78        pass

Return a description of what this tool does.

class UnmanagedKilnTool(kiln_ai.tools.KilnToolInterface):
 81class UnmanagedKilnTool(KilnToolInterface):
 82    """
 83    Helper for tools passed via ``AdapterConfig.unmanaged_tools`` (SDK-injected, not from the
 84    Kiln tool registry). Use a :class:`~kiln_ai.datamodel.tool_id.ToolId` with prefix
 85    ``kiln_unmanaged::<id>`` (see :func:`~kiln_ai.datamodel.tool_id.build_kiln_unmanaged_tool_id`).
 86    Subclass and override :meth:`run` for in-adapter execution when ``return_on_tool_call`` is
 87    False; default :meth:`run` raises (use ``return_on_tool_call`` and resume with tool results
 88    in ``prior_trace``, or provide a subclass that implements :meth:`run`).
 89    """
 90
 91    def __init__(
 92        self,
 93        tool_id: ToolId,
 94        name: str,
 95        description: str,
 96        parameters_schema: Dict[str, Any],
 97    ):
 98        validate_schema_dict(parameters_schema)
 99        self._tool_id = tool_id
100        self._name = name
101        self._description = description
102        self._parameters_schema = parameters_schema
103
104    async def id(self) -> ToolId:
105        return self._tool_id
106
107    async def name(self) -> str:
108        return self._name
109
110    async def description(self) -> str:
111        return self._description
112
113    async def toolcall_definition(self) -> ToolCallDefinition:
114        return {
115            "type": "function",
116            "function": {
117                "name": await self.name(),
118                "description": await self.description(),
119                "parameters": self._parameters_schema,
120            },
121        }
122
123    async def run(
124        self, context: ToolCallContext | None = None, **kwargs
125    ) -> ToolCallResult:
126        raise RuntimeError(
127            "This tool is supplied as an unmanaged KilnTool for API tool definitions only; "
128            "the Kiln adapter does not execute it when return_on_tool_call is True."
129        )

Helper for tools passed via AdapterConfig.unmanaged_tools (SDK-injected, not from the Kiln tool registry). Use a ~kiln_ai.datamodel.tool_id.ToolId with prefix kiln_unmanaged::<id> (see ~kiln_ai.datamodel.tool_id.build_kiln_unmanaged_tool_id()). Subclass and override run() for in-adapter execution when return_on_tool_call is False; default run() raises (use return_on_tool_call and resume with tool results in prior_trace, or provide a subclass that implements run()).

UnmanagedKilnTool( tool_id: Annotated[str, AfterValidator(func=<function <lambda>>)], name: str, description: str, parameters_schema: Dict[str, Any])
 91    def __init__(
 92        self,
 93        tool_id: ToolId,
 94        name: str,
 95        description: str,
 96        parameters_schema: Dict[str, Any],
 97    ):
 98        validate_schema_dict(parameters_schema)
 99        self._tool_id = tool_id
100        self._name = name
101        self._description = description
102        self._parameters_schema = parameters_schema
async def id( self) -> Annotated[str, AfterValidator(func=<function <lambda> at 0x7f034561ccc0>)]:
104    async def id(self) -> ToolId:
105        return self._tool_id

Return a unique identifier for this tool.

async def name(self) -> str:
107    async def name(self) -> str:
108        return self._name

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

async def description(self) -> str:
110    async def description(self) -> str:
111        return self._description

Return a description of what this tool does.

async def toolcall_definition(self) -> kiln_ai.tools.base_tool.ToolCallDefinition:
113    async def toolcall_definition(self) -> ToolCallDefinition:
114        return {
115            "type": "function",
116            "function": {
117                "name": await self.name(),
118                "description": await self.description(),
119                "parameters": self._parameters_schema,
120            },
121        }

Return the OpenAI-compatible tool definition for this tool.

async def run( self, context: kiln_ai.tools.base_tool.ToolCallContext | None = None, **kwargs) -> kiln_ai.tools.base_tool.ToolCallResult:
123    async def run(
124        self, context: ToolCallContext | None = None, **kwargs
125    ) -> ToolCallResult:
126        raise RuntimeError(
127            "This tool is supplied as an unmanaged KilnTool for API tool definitions only; "
128            "the Kiln adapter does not execute it when return_on_tool_call is True."
129        )

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

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

Get a tool from its ID.