Building Tools
This guide will walk you through the process of creating your own tools for the agentic framework. While you can always use basic functions as tools, creating a proper tool class with the right structure provides better integration with the framework and enables more advanced features.
Getting Started
To create a new tool, you'll need to:
- Install the agentic framework
- Set up a project with
agentic init
- Create a new tool file in the tools directory
- Register your tool with the framework
- Implement the required functions
Let's walk through these steps in detail.
1. Installation
If you haven't already installed the agentic framework, do so with pip. It is best practice to create a new virtual environment for your project:
pip install uv
uv venv --python 3.12
source .venv/bin/activate
uv pip install "agentic-framework[all]"
2. Project Setup
Create a new project or navigate to your existing project directory and initialize it with agentic::
agentic init .
This will create the necessary directory structure, including a tools
directory where your custom tools will live.
3. Creating a Tool File
Navigate to the tools
directory and create a new Python file for your tool. For example, if you're creating a weather tool:
cd tools
touch weather_tool.py
4. Basic Tool Structure
Here's the basic structure for a tool:
from typing import Callable, Dict, Optional
from agentic.tools.base import BaseAgenticTool
from agentic.tools.utils.registry import tool_registry, Dependency, ConfigRequirement
from agentic.common import RunContext
@tool_registry.register(
name="YourToolName",
description="A clear description of what your tool does",
dependencies=[], # Any pip packages your tool depends on
config_requirements=[], # Any required configuration settings
)
class YourTool(BaseAgenticTool):
"""Detailed description of your tool class."""
def __init__(self, param1: str = None, param2: str = "default"):
"""Initialize your tool with any necessary parameters."""
self.param1 = param1
self.param2 = param2
def get_tools(self) -> list[Callable]:
"""Return a list of functions that will be exposed to the agent."""
return [
self.your_function,
# Add more functions here
]
def your_function(self, run_context: RunContext, param: str) -> str:
"""
A function that the agent can call.
Args:
run_context: Execution context for accessing secrets and settings
param: Description of parameter
Returns:
Result of the operation
"""
# Implement your functionality here
return f"Processed {param} with {self.param1}"
Tool Registry Decorator
The @tool_registry.register
decorator registers your tool with the framework and provides important metadata:
@tool_registry.register(
name="YourToolName", # Name of your tool
description="Description", # Description of what your tool does
dependencies=[ # External packages required by your tool
Dependency(
name="package-name",
version="1.0.0",
type="pip",
),
],
config_requirements=[ # Configuration settings required by your tool
ConfigRequirement(
key="API_KEY_NAME",
description="Description of the setting",
required=True, # Whether this setting is required
),
],
)
Required Methods
Every tool must implement:
__init__
: Initialize the tool with any necessary parametersget_tools
: Return a list of callable functions that will be exposed to the agent
Optional Methods
Common optional functions include:
def required_secrets(self) -> Dict[str, str]:
"""Define secrets that this tool requires."""
return {
"API_KEY_NAME": "Description of the API key",
"OTHER_SECRET": "Description of other secret",
}
def test_credential(self, cred: str, secrets: Dict[str, str]) -> Optional[str]:
"""
Test that the given credential secrets are valid.
Return None if valid, otherwise return an error message.
"""
# Validate credentials
api_key = secrets.get("API_KEY_NAME")
if not api_key:
return "API key is missing"
# Test the API key
return None # Return None if valid
Advanced Features
Asynchronous Methods
You can create asynchronous functions for operations that involve I/O or network requests:
async def fetch_data(self, run_context: RunContext, query: str) -> Dict[str, any]:
"""
Asynchronously fetch data based on the query.
Args:
run_context: Execution context
query: Search query
Returns:
Dictionary containing the results
"""
# Async implementation
# You can use httpx, aiohttp, etc. for async HTTP requests
return {"results": ["data1", "data2"]}
Handling Authentication
Tools often need API keys or other credentials. You can get these from the RunContext:
def authenticated_function(self, run_context: RunContext, param: str) -> str:
"""Method that requires authentication."""
# Get API key from secrets or instance variable
api_key = run_context.get_secret("API_KEY_NAME", self.api_key)
# If no API key is available, request it from the user
if not api_key:
from agentic.events import PauseForInputResult
return PauseForInputResult(
{"API_KEY_NAME": "Please provide your API key"}
)
# Use the API key for authentication
return "Authenticated operation completed"
Progress Reporting
For long-running operations, you can report progress using generators:
def long_operation(self, run_context: RunContext) -> str:
"""Perform a long-running operation with progress updates."""
total_steps = 10
for step in range(total_steps):
# Perform some work...
# Report progress
yield run_context.log(f"Step {step+1}/{total_steps} completed")
return "Operation completed successfully"
Registering Your Tool in the Framework
After creating your tool, you need to register it in the framework's tool registry. This happens automatically when your tool is loaded, thanks to the @tool_registry.register
decorator.
Example: Building a Simple Weather Tool
Here's a complete example of a simple weather tool:
from typing import Callable, Dict, Optional
import requests
from agentic.tools.base import BaseAgenticTool
from agentic.tools.utils.registry import tool_registry, Dependency
from agentic.common import RunContext
@tool_registry.register(
name="SimpleWeatherTool",
description="A tool for getting basic weather information",
dependencies=[
Dependency(
name="requests",
version="2.28.1",
type="pip",
),
],
)
class SimpleWeatherTool(BaseAgenticTool):
"""A simple tool for fetching weather data."""
def __init__(self, api_key: str = None):
self.api_key = api_key
def required_secrets(self) -> Dict[str, str]:
return {
"WEATHER_API_KEY": "API key for accessing weather data"
}
def get_tools(self) -> list[Callable]:
return [
self.get_current_weather,
]
def get_current_weather(
self,
run_context: RunContext,
city: str,
units: str = "metric"
) -> Dict[str, any]:
"""
Get the current weather for a city.
Args:
run_context: Execution context
city: The name of the city
units: Units of measurement ('metric' or 'imperial')
Returns:
Weather data for the city
"""
# Get API key from secrets or instance variable
api_key = run_context.get_secret("WEATHER_API_KEY", self.api_key)
# If no API key is available, request it from the user
if not api_key:
from agentic.events import PauseForInputResult
return PauseForInputResult(
{"WEATHER_API_KEY": "Please provide your weather API key"}
)
# Make API request
url = f"https://api.example.com/weather"
params = {
"q": city,
"units": units,
"appid": api_key
}
run_context.info(f"Fetching weather data for {city}")
try:
response = requests.get(url, params=params)
response.raise_for_status()
data = response.json()
return {
"city": city,
"temperature": data["main"]["temp"],
"conditions": data["weather"][0]["description"],
"humidity": data["main"]["humidity"],
"wind_speed": data["wind"]["speed"]
}
except Exception as e:
run_context.error(f"Error fetching weather data: {str(e)}")
return {"error": f"Failed to fetch weather data: {str(e)}"}
Testing Your Tool
It's important to test your tool to ensure it works correctly. You can create a simple script to test your tool:
from agentic.common import Agent
from your_tools import YourTool
# Create an instance of your tool
your_tool = YourTool()
# Create an agent with your tool
agent = Agent(
name="TestAgent",
tools=[your_tool]
)
# Test your agent with a prompt that will use your tool
result = agent.run("Use YourTool to perform a test operation")
print(result)
Best Practices
- Clear Documentation: Write comprehensive docstrings for your tool class and all its functions
- Descriptive Names: Use clear, descriptive names for your tool and its functions
- Type Hints: Always use proper type hints for parameters and return values
- Error Handling: Handle errors gracefully and return informative error messages
- Async for I/O: Use async functions for I/O operations to avoid blocking
- Progress Reporting: For long-running operations, report progress using generators
- Secrets Management: Use the required_secrets function to declare required secrets
- Testing: Write tests for your tool to ensure it works correctly
By following these guidelines, you can create powerful, reliable tools that will enhance your agents' capabilities and provide a seamless experience for users.