Overview
The Tools API defines the interface for implementing runtime tools in AXON. Every tool implements theBaseTool abstract class, whether it’s a stub for testing or a real backend with I/O.
from axon.runtime.tools.base_tool import BaseTool, ToolResult
import httpx
class WebSearchBrave(BaseTool):
TOOL_NAME = "WebSearch"
IS_STUB = False
DEFAULT_TIMEOUT = 10.0
def validate_config(self) -> None:
if "api_key" not in self.config:
raise ValueError("Missing api_key in config")
async def execute(self, query: str, **kwargs) -> ToolResult:
max_results = kwargs.get("max_results", 5)
async with httpx.AsyncClient() as client:
response = await client.get(
"https://api.brave.com/search",
headers={"X-API-Key": self.config["api_key"]},
params={"q": query, "count": max_results}
)
data = response.json()
return ToolResult(
success=True,
data=data["results"],
metadata={"provider": "brave", "query": query}
)
Class: BaseTool
Abstract base class for all AXON runtime tools.Class Variables
Subclasses must set these:Unique identifier matching the
IRToolSpec.nameTrue for stub implementations, False for real backendsDefault timeout in seconds for tool execution
Constructor
BaseTool(config: dict[str, Any] | None = None)
Configuration dictionary (API keys, endpoints, etc.)
Abstract Methods
Subclasses must implement:validate_config() -> None
Validate that self.config has the required keys.
def validate_config(self) -> None:
required = ["api_key", "endpoint"]
for key in required:
if key not in self.config:
raise ValueError(f"Missing required config key: {key}")
if not self.config["api_key"]:
raise ValueError("api_key cannot be empty")
async execute(query: str, **kwargs) -> ToolResult
Execute the tool with the given query.
The primary input (search query, code, file path, etc.)
Tool-specific parameters (e.g.,
max_results, timeout)ToolResult with success/data/error
async def execute(self, query: str, **kwargs) -> ToolResult:
try:
result = await self._do_work(query, **kwargs)
return ToolResult(success=True, data=result)
except Exception as e:
return ToolResult(
success=False,
data=None,
error=str(e),
metadata={"exception_type": type(e).__name__}
)
Properties
get_tool_name: str
Tool name (forwards to TOOL_NAME).
tool = WebSearchBrave(config={"api_key": "..."})
print(tool.get_tool_name) # "WebSearch"
get_is_stub: bool
Whether this implementation is a stub.
print(tool.get_is_stub) # False
Class: ToolResult
Standardized result returned by every tool execution.class ToolResult:
success: bool
data: Any
error: str | None
metadata: dict[str, Any]
def to_dict(self) -> dict[str, Any]:
...
True if the tool completed without errorThe payload — its shape depends on the tool
Human-readable error message on failure
Extra info (provider, timings, warnings, etc.)
result = ToolResult(
success=True,
data={"results": [...]},
metadata={
"provider": "serper",
"query_time_ms": 234,
"result_count": 10
}
)
print(result.to_dict())
# {
# "success": true,
# "data": {"results": [...]},
# "metadata": {...}
# }
Built-in Tools
AXON includes stub implementations for common tools:WebSearch
from axon.runtime.tools.stubs.web_search_stub import WebSearchStub
tool = WebSearchStub()
result = await tool.execute("AXON programming language")
print(result.data)
# [
# {"title": "Result 1", "url": "...", "snippet": "..."},
# {"title": "Result 2", "url": "...", "snippet": "..."}
# ]
Calculator
from axon.runtime.tools.stubs.calculator_tool import CalculatorTool
tool = CalculatorTool()
result = await tool.execute("2 + 2 * 3")
print(result.data) # {"result": 8, "expression": "2 + 2 * 3"}
FileReader
from axon.runtime.tools.stubs.file_reader_stub import FileReaderStub
tool = FileReaderStub()
result = await tool.execute("/path/to/file.txt")
print(result.data) # {"content": "...", "path": "...", "size": 1234}
DateTime
from axon.runtime.tools.stubs.datetime_tool import DateTimeTool
tool = DateTimeTool()
result = await tool.execute("current_time")
print(result.data) # {"timestamp": "2024-01-15T10:30:00Z", ...}
Creating Custom Tools
Example: API Call Tool
from axon.runtime.tools.base_tool import BaseTool, ToolResult
import httpx
import json
class APICallTool(BaseTool):
TOOL_NAME = "APICall"
IS_STUB = False
DEFAULT_TIMEOUT = 15.0
def validate_config(self) -> None:
# No required config - can be used with any API
pass
async def execute(self, query: str, **kwargs) -> ToolResult:
"""
Execute an API call.
Args:
query: JSON string with {"url": "...", "method": "GET", "data": {...}}
**kwargs: Additional options
"""
try:
request_data = json.loads(query)
url = request_data["url"]
method = request_data.get("method", "GET").upper()
body = request_data.get("data")
headers = request_data.get("headers", {})
timeout = kwargs.get("timeout", self.DEFAULT_TIMEOUT)
async with httpx.AsyncClient(timeout=timeout) as client:
if method == "GET":
response = await client.get(url, headers=headers)
elif method == "POST":
response = await client.post(url, json=body, headers=headers)
elif method == "PUT":
response = await client.put(url, json=body, headers=headers)
elif method == "DELETE":
response = await client.delete(url, headers=headers)
else:
raise ValueError(f"Unsupported HTTP method: {method}")
return ToolResult(
success=response.is_success,
data={
"status_code": response.status_code,
"body": response.json() if response.headers.get("content-type", "").startswith("application/json") else response.text,
"headers": dict(response.headers)
},
error=None if response.is_success else f"HTTP {response.status_code}",
metadata={
"url": url,
"method": method,
"duration_ms": response.elapsed.total_seconds() * 1000
}
)
except json.JSONDecodeError as e:
return ToolResult(
success=False,
data=None,
error=f"Invalid JSON query: {e}"
)
except Exception as e:
return ToolResult(
success=False,
data=None,
error=str(e),
metadata={"exception_type": type(e).__name__}
)
Example: Database Query Tool
from axon.runtime.tools.base_tool import BaseTool, ToolResult
import asyncpg
class PostgresQueryTool(BaseTool):
TOOL_NAME = "PostgresQuery"
IS_STUB = False
DEFAULT_TIMEOUT = 30.0
def validate_config(self) -> None:
required = ["host", "database", "user", "password"]
for key in required:
if key not in self.config:
raise ValueError(f"Missing required config: {key}")
async def execute(self, query: str, **kwargs) -> ToolResult:
"""
Execute a Postgres query.
Args:
query: SQL query string
**kwargs: Query parameters
"""
conn = None
try:
# Connect to database
conn = await asyncpg.connect(
host=self.config["host"],
database=self.config["database"],
user=self.config["user"],
password=self.config["password"],
)
# Execute query
if query.strip().upper().startswith("SELECT"):
rows = await conn.fetch(query)
results = [dict(row) for row in rows]
return ToolResult(
success=True,
data={
"rows": results,
"count": len(results)
},
metadata={
"query_type": "SELECT",
"row_count": len(results)
}
)
else:
# For INSERT/UPDATE/DELETE
result = await conn.execute(query)
return ToolResult(
success=True,
data={"result": result},
metadata={"query_type": "MUTATION"}
)
except Exception as e:
return ToolResult(
success=False,
data=None,
error=f"Database error: {e}",
metadata={"exception_type": type(e).__name__}
)
finally:
if conn:
await conn.close()
Tool Registration
Register custom tools with theToolDispatcher:
from axon.runtime.tools.dispatcher import ToolDispatcher
from axon.runtime.tools.registry import ToolRegistry
# Create registry and dispatcher
registry = ToolRegistry()
dispatcher = ToolDispatcher(registry)
# Register custom tools
api_tool = APICallTool()
registry.register(api_tool)
db_tool = PostgresQueryTool(config={
"host": "localhost",
"database": "mydb",
"user": "user",
"password": "pass"
})
registry.register(db_tool)
# Use with executor
from axon.runtime import Executor
executor = Executor(
client=my_client,
tool_dispatcher=dispatcher
)
Testing Tools
Unit Testing
import pytest
from your_tools import WebSearchBrave
@pytest.mark.asyncio
async def test_web_search_success():
tool = WebSearchBrave(config={"api_key": "test_key"})
result = await tool.execute("AXON language")
assert result.success
assert isinstance(result.data, list)
assert len(result.data) > 0
assert "title" in result.data[0]
@pytest.mark.asyncio
async def test_web_search_error():
tool = WebSearchBrave(config={"api_key": "invalid"})
result = await tool.execute("test")
assert not result.success
assert result.error is not None
def test_config_validation():
with pytest.raises(ValueError, match="Missing api_key"):
tool = WebSearchBrave(config={})
Stub Testing
from axon.runtime.tools.base_tool import BaseTool, ToolResult
class MockWebSearch(BaseTool):
TOOL_NAME = "WebSearch"
IS_STUB = True
def validate_config(self) -> None:
pass
async def execute(self, query: str, **kwargs) -> ToolResult:
return ToolResult(
success=True,
data=[
{"title": "Mock Result 1", "url": "https://example.com"},
{"title": "Mock Result 2", "url": "https://example.org"},
],
metadata={"is_stub": True}
)
# Use mock in tests
tool = MockWebSearch()
result = await tool.execute("anything")
assert result.success
assert result.metadata["is_stub"] is True
Complete Example
import asyncio
from axon.runtime.tools.base_tool import BaseTool, ToolResult
from axon.runtime.tools.registry import ToolRegistry
from axon.runtime.tools.dispatcher import ToolDispatcher
import httpx
# 1. Define custom tool
class WeatherTool(BaseTool):
TOOL_NAME = "Weather"
IS_STUB = False
def validate_config(self) -> None:
if "api_key" not in self.config:
raise ValueError("Missing api_key")
async def execute(self, query: str, **kwargs) -> ToolResult:
location = query
async with httpx.AsyncClient() as client:
response = await client.get(
"https://api.weatherapi.com/v1/current.json",
params={"key": self.config["api_key"], "q": location}
)
data = response.json()
return ToolResult(
success=True,
data={
"location": data["location"]["name"],
"temperature_c": data["current"]["temp_c"],
"condition": data["current"]["condition"]["text"]
},
metadata={"provider": "weatherapi.com"}
)
# 2. Register and use
async def main():
# Setup
registry = ToolRegistry()
weather = WeatherTool(config={"api_key": "your_key"})
registry.register(weather)
# Execute
result = await weather.execute("San Francisco")
if result.success:
print(f"Location: {result.data['location']}")
print(f"Temperature: {result.data['temperature_c']}°C")
print(f"Condition: {result.data['condition']}")
else:
print(f"Error: {result.error}")
asyncio.run(main())
Next Steps
Executor API
Use tools in program execution
Memory API
Implement semantic memory storage
