Source code for yoker.tools.base
"""Base types and abstract class for Yoker tools.
Provides the Tool abstract base class, result types, and validation types
that all concrete tools must implement.
"""
from abc import ABC, abstractmethod
from dataclasses import dataclass
from pathlib import Path
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
from yoker.tools.guardrails import Guardrail
[docs]
@dataclass(frozen=True)
class ToolResult:
"""Result of a tool execution.
Attributes:
success: Whether the tool executed successfully.
result: The result data (string content or dict for structured results).
error: Error message if success is False.
content_metadata: Optional metadata for content display events.
When provided, the agent emits a ToolContentEvent after ToolResultEvent.
Contains operation, path, content_type, content, and metadata dict.
"""
success: bool
result: str | dict[str, Any]
error: str | None = None
content_metadata: dict[str, Any] | None = None
[docs]
@dataclass(frozen=True)
class ValidationResult:
"""Result of a guardrail validation check.
Attributes:
valid: Whether the parameters passed validation.
reason: Explanation if validation failed.
"""
valid: bool
reason: str | None = None
[docs]
class Tool(ABC):
"""Abstract base class for all Yoker tools.
Each tool must define its name, description, JSON schema for the LLM,
and an execute method that returns a ToolResult.
Tools may optionally accept a Guardrail instance for defense-in-depth
validation. When a guardrail is provided, the tool validates parameters
in execute() before performing any I/O.
Example:
class MyTool(Tool):
@property
def name(self) -> str:
return "my_tool"
@property
def description(self) -> str:
return "Does something useful"
def get_schema(self) -> dict[str, Any]:
return {
"type": "function",
"function": {
"name": self.name,
"description": self.description,
"parameters": {
"type": "object",
"properties": {
"arg": {"type": "string"}
},
"required": ["arg"]
}
}
}
async def execute(self, arg: str) -> ToolResult:
return ToolResult(success=True, result=f"Got: {arg}")
"""
def __init__(self, guardrail: "Guardrail | None" = None) -> None:
"""Initialize the tool with an optional guardrail.
Args:
guardrail: Optional guardrail for defense-in-depth validation.
"""
self._guardrail = guardrail
[docs]
def exists(self, path: str) -> bool:
"""Check if a file exists, validating through the guardrail if available.
This is a utility method for tools that need to check file existence
as part of their operations (e.g., ReadTool before reading).
Args:
path: The file path to check.
Returns:
True if the path exists and passes guardrail validation,
False otherwise (including if guardrail rejects the path).
"""
# Validate through guardrail if available
if self._guardrail is not None:
validation = self._guardrail.validate(self.name, {"path": path})
if not validation.valid:
return False
# Check file existence
return Path(path).exists()
@property
@abstractmethod
def name(self) -> str:
"""Tool name used for registration and LLM tool calling."""
@property
@abstractmethod
def description(self) -> str:
"""Tool description shown to the LLM."""
[docs]
@abstractmethod
def get_schema(self) -> dict[str, Any]:
"""Return Ollama-compatible function schema.
Returns:
Dict in OpenAI function-calling format:
{
"type": "function",
"function": {
"name": "...",
"description": "...",
"parameters": {...}
}
}
"""
[docs]
@abstractmethod
async def execute(self, **kwargs: Any) -> ToolResult:
"""Execute the tool with the given parameters.
All tools must implement async execution for proper async/await
integration with the agent's event loop.
Args:
**kwargs: Parameters from the LLM tool call.
Returns:
ToolResult with success status and output.
"""