Advanced BaseTool Features¶
This guide explains how to leverage advanced BaseTool capabilities for local tools. These features help you build more robust, efficient, and feature-rich tools.
Note
New to local tools? Start with Local Tools Tutorial for basic tool creation, then return here for advanced features.
Overview¶
The BaseTool class now provides several standard capabilities that can be overridden by custom tool implementations:
Parameter Validation: validate_parameters() - Validate input parameters against schemas
Error Handling: handle_error() - Classify exceptions into structured errors
Caching: get_cache_key() - Generate cache keys for tool calls
Capability Queries: supports_streaming(), supports_caching() - Declare tool capabilities
Tool Information: get_tool_info() - Provide comprehensive tool metadata
Custom Parameter Validation¶
Override validate_parameters() to implement custom validation logic:
class GraphQLTool(BaseTool):
def validate_parameters(self, arguments):
# First, run base validation
base_error = super().validate_parameters(arguments)
if base_error:
return base_error
# Add custom validation
query = arguments.get("query", "")
if "mutation" in query.lower() and not self.allow_mutations:
return ToolValidationError(
"Mutations not allowed",
details={"custom_rule": "no_mutations"}
)
return None # Validation passed
Custom Error Handling¶
Override handle_error() to provide tool-specific error classification:
class APITool(BaseTool):
def handle_error(self, exception):
error_str = str(exception).lower()
# Tool-specific error patterns
if "api_key" in error_str:
return ToolAuthError(
"API key error",
next_steps=["Check API key", "Regenerate key if needed"]
)
# Fall back to base error handling
return super().handle_error(exception)
Custom Caching¶
Override get_cache_key() for custom cache key generation:
class DatabaseTool(BaseTool):
def get_cache_key(self, arguments):
# Include database-specific information
cache_data = {
"tool_name": self.tool_config.get("name"),
"query": arguments.get("query", ""),
"database": self.tool_config.get("database", "default")
}
return f"db_{hash(json.dumps(cache_data, sort_keys=True))}"
Capability Declaration¶
Override capability methods to declare tool features:
class StreamingTool(BaseTool):
def supports_streaming(self):
return True # This tool supports streaming
def supports_caching(self):
# Don't cache streaming tools
return False
Complete Example¶
Here’s a complete example of a custom tool with all capabilities:
import json
from tooluniverse.base_tool import BaseTool
from tooluniverse.exceptions import ToolValidationError, ToolAuthError
class WeatherTool(BaseTool):
def __init__(self, tool_config):
super().__init__(tool_config)
self.api_key = tool_config.get("api_key")
self.max_forecast_days = tool_config.get("max_forecast_days", 7)
def validate_parameters(self, arguments):
# Base validation
base_error = super().validate_parameters(arguments)
if base_error:
return base_error
# Weather-specific validation
location = arguments.get("location", "")
if not location:
return ToolValidationError(
"Location is required",
details={"weather_rule": "location_required"}
)
days = arguments.get("days", 1)
if days > self.max_forecast_days:
return ToolValidationError(
f"Forecast days ({days}) exceeds maximum ({self.max_forecast_days})",
details={
"weather_rule": "max_days_exceeded",
"max_days": self.max_forecast_days
}
)
return None
def handle_error(self, exception):
error_str = str(exception).lower()
if "api key" in error_str or "unauthorized" in error_str:
return ToolAuthError(
"Weather API authentication failed",
next_steps=[
"Check API key configuration",
"Verify API key permissions",
"Contact API provider if issues persist"
]
)
return super().handle_error(exception)
def get_cache_key(self, arguments):
# Include location and days in cache key
cache_data = {
"tool_name": "weather_tool",
"location": arguments.get("location", ""),
"days": arguments.get("days", 1)
}
return f"weather_{hash(json.dumps(cache_data, sort_keys=True))}"
def supports_streaming(self):
return False # Weather data doesn't need streaming
def supports_caching(self):
return True # Weather data can be cached
def run(self, arguments=None):
if arguments is None:
arguments = {}
location = arguments.get("location", "")
days = arguments.get("days", 1)
# Mock weather API call
return {
"location": location,
"forecast": [
{"day": i+1, "temperature": 20+i, "condition": "sunny"}
for i in range(days)
],
"source": "weather_api"
}
Migration Guide¶
If you have existing tools, here’s how to migrate them to use the new capabilities:
No Changes Required: Existing tools will continue to work without modification. The new methods have sensible defaults.
Gradual Enhancement: You can gradually add custom validation and error handling:
# Before: Basic tool class MyTool(BaseTool): def run(self, arguments=None): return "result" # After: Enhanced tool class MyTool(BaseTool): def validate_parameters(self, arguments): # Add custom validation return super().validate_parameters(arguments) def handle_error(self, exception): # Add custom error handling return super().handle_error(exception) def run(self, arguments=None): return "result"
Exception Handling: If you’re catching old exception classes, consider migrating to new ones:
# Old way (still works) try: # tool operation except ValidationError as e: # handle error # New way (recommended) try: # tool operation except ToolValidationError as e: # handle error with structured details print(e.next_steps)
Best Practices¶
Always Call Super: When overriding methods, call the parent implementation first:
def validate_parameters(self, arguments): # Run base validation first base_error = super().validate_parameters(arguments) if base_error: return base_error # Add custom validation # ...
Provide Recovery Steps: Include helpful next steps in error messages:
return ToolValidationError( "Invalid input", next_steps=[ "Check parameter format", "Review documentation", "Try with different values" ] )
Use Structured Details: Include structured information in error details:
return ToolValidationError( "Validation failed", details={ "field": "email", "expected_format": "user@domain.com", "provided_value": "invalid-email" } )
Cache Appropriately: Only cache results that are safe to cache:
def supports_caching(self): # Don't cache if results change frequently return not self.tool_config.get("real_time", False)
Testing Custom Tools¶
Test your custom tools using the standard testing patterns:
import pytest
from tooluniverse.exceptions import ToolValidationError
def test_custom_validation():
tool = MyCustomTool(tool_config)
# Test valid input
error = tool.validate_parameters({"param": "value"})
assert error is None
# Test invalid input
error = tool.validate_parameters({"param": "invalid"})
assert isinstance(error, ToolValidationError)
assert "param" in str(error)
Backward Compatibility¶
All existing tools and code will continue to work without modification:
Old exception classes still work (with deprecation warnings)
Tools without new methods use default implementations
ToolUniverse provides fallback logic for missing methods
All existing APIs remain unchanged
The refactoring is designed to be completely backward compatible while providing enhanced capabilities for new development.