Tool Interface Analysis
Analyzes how agent frameworks model, register, and execute tools. This skill examines the tool abstraction layer, schema generation, built-in inventory, and error feedback mechanisms.
Distinction from harness-model-protocol
| tool-interface-analysis | harness-model-protocol | |------------------------|------------------------| | How a "tool" is represented (types, base classes) | How tool calls are encoded on the wire | | Schema generation (Pydantic -> JSON Schema) | Schema transmission to LLM API | | Built-in tool inventory | Provider-specific tool formats | | Registration and discovery patterns | Message format translation | | Error feedback to LLM for retry | Response parsing and streaming | | Tool execution orchestration | Partial tool call handling |
Process
- Map tool modeling - Identify how tools are represented (types, protocols, base classes)
- Analyze schema generation - How tool definitions become JSON Schema
- Catalog built-in inventory - What tools ship with the framework
- Trace registration flow - How tools are discovered and made available
- Document execution patterns - Invocation, validation, error handling
- Evaluate retry mechanisms - Self-correction and feedback loops
Tool Modeling Patterns
Abstract Base Class Pattern
from abc import ABC, abstractmethod
from typing import Any
class BaseTool(ABC):
"""Framework's tool abstraction."""
name: str
description: str
@abstractmethod
def execute(self, **kwargs) -> Any:
"""Execute the tool with validated arguments."""
...
@property
@abstractmethod
def parameters_schema(self) -> dict:
"""Return JSON Schema for parameters."""
...
Characteristics: Explicit contract, inheritance-based, type-safe Used by: LangChain, CrewAI, AutoGen
Protocol/Interface Pattern
from typing import Protocol, runtime_checkable
@runtime_checkable
class Tool(Protocol):
"""Structural typing for tools."""
name: str
description: str
def __call__(self, **kwargs) -> Any: ...
def get_schema(self) -> dict: ...
Characteristics: Duck typing, flexible, composition-friendly Used by: Pydantic-AI, OpenAI Agents SDK
Decorated Function Pattern
from functools import wraps
def tool(name: str = None, description: str = None):
def decorator(func):
@wraps(func)
def wrapper(*args, **kwargs):
return func(*args, **kwargs)
wrapper._tool_name = name or func.__name__
wrapper._tool_description = description or func.__doc__
wrapper._is_tool = True
return wrapper
return decorator
@tool(description="Search the web for information")
def search(query: str, max_results: int = 10) -> list[str]:
...
Characteristics: Lightweight, DRY, preserves function identity Used by: Google ADK, Swarm, Function calling patterns
Pydantic Model Pattern
from pydantic import BaseModel, Field
class SearchInput(BaseModel):
"""Input schema for search tool."""
query: str = Field(description="The search query")
max_results: int = Field(default=10, ge=1, le=100)
class SearchTool(BaseTool):
name = "search"
description = "Search the web"
args_schema = SearchInput
def execute(self, **kwargs) -> list[str]:
validated = SearchInput(**kwargs)
return perform_search(validated.query, validated.max_results)
Characteristics: Rich validation, auto-schema, clear separation Used by: LangChain, CrewAI
Schema Generation Methods
Introspection-Based (Automatic)
import inspect
from typing import get_type_hints
def generate_schema_from_function(func) -> dict:
hints = get_type_hints(func)
sig = inspect.signature(func)
doc = inspect.getdoc(func) or ""
schema = {
"type": "function",
"function": {
"name": func.__name__,
"description": doc.split("\n")[0],
"parameters": {
"type": "object",
"properties": {},
"required": []
}
}
}
for name, param in sig.parameters.items():
if name in ("self", "cls"):
continue
prop = {"type": python_type_to_json(hints.get(name, str))}
# Extract description from docstring if available
if f":param {name}:" in doc:
prop["description"] = extract_param_doc(doc, name)
if param.default is inspect.Parameter.empty:
schema["function"]["parameters"]["required"].append(name)
else:
prop["default"] = param.default
schema["function"]["parameters"]["properties"][name] = prop
return schema
Pros: DRY, always in sync with code, minimal boilerplate Cons: Limited expressiveness, relies on annotations, docstring parsing fragile
Pydantic-Based (Semi-Automatic)
from pydantic import BaseModel, Field
from pydantic.json_schema import GenerateJsonSchema
class SearchInput(BaseModel):
"""Search the web for information."""
query: str = Field(description="The search query")
max_results: int = Field(default=10, ge=1, le=100, description="Max results to return")
def generate_schema_from_pydantic(model: type[BaseModel]) -> dict:
return {
"type": "function",
"function": {
"name": model.__name__.replace("Input", "").lower(),
"description": model.__doc__ or "",
"parameters": model.model_json_schema()
}
}
Pros: Rich validation, excellent descriptions, composable Cons: Class per tool, more boilerplate, Pydantic dependency
Decorator-Based (Explicit)
@tool(
name="search",
description="Search the web for information",
parameters={
"query": {"type": "string", "description": "Search query"},
"max_results": {"type": "integer", "default": 10}
}
)
def search(query: str, max_results: int = 10) -> list[str]:
...
Pros: Explicit, flexible, no dependencies Cons: Can drift from implementation, manual maintenance
Manual Definition
TOOLS = [
{
"type": "function",
"function": {
"name": "search",
"description": "Search the web for information",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The search query"
}
},
"required": ["query"]
}
}
}
]
Pros: Full control, no magic, portable Cons: Maintenance burden, drift risk, duplication
Schema Generation Comparison
| Method | Sync with Code | Expressiveness | Boilerplate | Validation | |--------|---------------|----------------|-------------|------------| | Introspection | Automatic | Low | None | None | | Pydantic | Automatic | High | Medium | Built-in | | Decorator | Manual | Medium | Low | Optional | | Manual | Manual | Full | High | None |
Registration Patterns
Declarative List
agent = Agent(
tools=[search_tool, calculator_tool, weather_tool]
)
Characteristics: Explicit, static, easy to understand, testable
Registry Pattern
TOOL_REGISTRY = {}
def register_tool(name: str = None):
def decorator(func):
tool_name = name or func.__name__
TOOL_REGISTRY[tool_name] = func
return func
return decorator
@register_tool("search")
def search(query: str) -> list[str]: ...
# Agent uses registry
agent = Agent(tools=list(TOOL_REGISTRY.values()))
Characteristics: Dynamic, plugin-friendly, implicit coupling
Discovery-Based (Auto-Import)
import importlib
import pkgutil
def discover_tools(package):
tools = []
for m