Creating Custom Tools¶
Extend Amplifier with your own tools.
Overview¶
Custom tools let you:
- Add new capabilities - Integrate with any system
- Wrap existing CLIs - Make tools AI-friendly
- Connect to APIs - Custom integrations
- Automate workflows - Complex operations as single tools
Tool Structure¶
A tool is a Python module that implements the Tool protocol:
# my_tool.py
from amplifier_core import Tool
class MyTool(Tool):
name = "my-tool"
description = "What this tool does (shown to AI)"
input_schema = {
"type": "object",
"properties": {
"param1": {
"type": "string",
"description": "First parameter"
}
},
"required": ["param1"]
}
async def execute(self, input: dict) -> str:
param1 = input["param1"]
# Do something
return f"Result: {param1}"
Basic Example: Timestamp Tool¶
# modules/timestamp_tool/tool.py
from datetime import datetime
from amplifier_core import Tool
class TimestampTool(Tool):
name = "timestamp"
description = "Get the current timestamp in various formats"
input_schema = {
"type": "object",
"properties": {
"format": {
"type": "string",
"description": "Output format: iso, unix, or human",
"enum": ["iso", "unix", "human"],
"default": "iso"
}
}
}
async def execute(self, input: dict) -> str:
fmt = input.get("format", "iso")
now = datetime.now()
if fmt == "iso":
return now.isoformat()
elif fmt == "unix":
return str(int(now.timestamp()))
else:
return now.strftime("%B %d, %Y at %I:%M %p")
Register in Bundle¶
# bundle.yaml
tools:
- module: timestamp-tool
source: ./modules/timestamp_tool
API Integration Example¶
# modules/weather_tool/tool.py
import httpx
from amplifier_core import Tool
class WeatherTool(Tool):
name = "weather"
description = "Get current weather for a location"
input_schema = {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "City name"
}
},
"required": ["city"]
}
def __init__(self, config: dict = None):
self.api_key = config.get("api_key") if config else None
async def execute(self, input: dict) -> str:
city = input["city"]
async with httpx.AsyncClient() as client:
response = await client.get(
f"https://api.weather.example.com/current",
params={"city": city, "key": self.api_key}
)
data = response.json()
return f"Weather in {city}: {data['temp']}°F, {data['condition']}"
Configure with API key:
tools:
- module: weather-tool
source: ./modules/weather_tool
config:
api_key: ${WEATHER_API_KEY}
CLI Wrapper Example¶
Wrap an existing CLI tool:
# modules/docker_tool/tool.py
import subprocess
import json
from amplifier_core import Tool
class DockerTool(Tool):
name = "docker"
description = "Manage Docker containers"
input_schema = {
"type": "object",
"properties": {
"action": {
"type": "string",
"enum": ["list", "start", "stop", "logs"],
"description": "Action to perform"
},
"container": {
"type": "string",
"description": "Container name/ID (for start/stop/logs)"
}
},
"required": ["action"]
}
async def execute(self, input: dict) -> str:
action = input["action"]
container = input.get("container")
if action == "list":
result = subprocess.run(
["docker", "ps", "--format", "json"],
capture_output=True, text=True
)
return result.stdout
elif action == "start":
result = subprocess.run(
["docker", "start", container],
capture_output=True, text=True
)
return f"Started {container}" if result.returncode == 0 else result.stderr
elif action == "stop":
result = subprocess.run(
["docker", "stop", container],
capture_output=True, text=True
)
return f"Stopped {container}" if result.returncode == 0 else result.stderr
elif action == "logs":
result = subprocess.run(
["docker", "logs", "--tail", "50", container],
capture_output=True, text=True
)
return result.stdout or result.stderr
Input Schema¶
Define what the AI can pass to your tool:
input_schema = {
"type": "object",
"properties": {
# Required string
"name": {
"type": "string",
"description": "User's name"
},
# Optional with default
"count": {
"type": "integer",
"description": "Number of items",
"default": 10
},
# Enum (limited choices)
"format": {
"type": "string",
"enum": ["json", "csv", "xml"],
"description": "Output format"
},
# Boolean
"verbose": {
"type": "boolean",
"description": "Show detailed output",
"default": False
},
# Array
"tags": {
"type": "array",
"items": {"type": "string"},
"description": "List of tags"
}
},
"required": ["name"] # Which params are required
}
Error Handling¶
Return clear error messages:
async def execute(self, input: dict) -> str:
try:
result = self.do_something(input)
return result
except FileNotFoundError as e:
return f"Error: File not found - {e}"
except PermissionError as e:
return f"Error: Permission denied - {e}"
except Exception as e:
return f"Error: {type(e).__name__} - {e}"
Async Operations¶
For I/O-bound operations, use async:
import httpx
async def execute(self, input: dict) -> str:
async with httpx.AsyncClient() as client:
response = await client.get(input["url"])
return response.text
Configuration¶
Tools can accept configuration:
class MyTool(Tool):
def __init__(self, config: dict = None):
config = config or {}
self.api_key = config.get("api_key")
self.base_url = config.get("base_url", "https://api.example.com")
tools:
- module: my-tool
source: ./modules/my_tool
config:
api_key: ${API_KEY}
base_url: https://api.custom.com
Module Structure¶
modules/my_tool/
├── __init__.py # Export the tool class
├── tool.py # Tool implementation
└── pyproject.toml # Dependencies (optional)
__init__.py:
from .tool import MyTool
__all__ = ["MyTool"]
Try It Yourself¶
Exercise 1: Simple Tool¶
Create modules/greet/tool.py:
from amplifier_core import Tool
class GreetTool(Tool):
name = "greet"
description = "Generate a greeting"
input_schema = {
"type": "object",
"properties": {
"name": {"type": "string", "description": "Name to greet"}
},
"required": ["name"]
}
async def execute(self, input: dict) -> str:
return f"Hello, {input['name']}! Welcome to Amplifier."
Add to bundle and test:
> Use the greet tool to greet Alice
Exercise 2: CLI Wrapper¶
Wrap a CLI tool you use frequently (git, kubectl, etc.).
Exercise 3: API Integration¶
Create a tool that calls an API you work with.
Best Practices¶
- Clear descriptions - Help the AI know when to use your tool
- Specific schemas - Use enums and defaults where appropriate
- Good error messages - Return actionable error info
- Async when possible - Don't block on I/O
- Minimal dependencies - Keep tools lightweight