Helios Engine includes 16+ built-in tools for common tasks, and provides a flexible system for creating custom tools. Tools allow agents to perform actions beyond just text generation, enabling them to interact with files, execute commands, access web resources, and manipulate data.
Tools in Helios Engine follow a simple pattern:
- Definition: Each tool defines its name, description, and parameters
- Execution: Tools receive JSON parameters and return structured results
- Registration: Tools are registered with agents during creation
Performs mathematical calculations and evaluations.
use helios_engine::CalculatorTool;
let mut agent = Agent::builder("MathAgent")
.config(config)
.tool(Box::new(CalculatorTool))
.build()
.await?;Parameters:
expression(string, required): Mathematical expression to evaluate
Example Usage:
let result = agent.chat("Calculate 15 * 7 + 3").await?;Simply echoes back the input message (useful for testing).
use helios_engine::EchoTool;
agent.tool(Box::new(EchoTool));Parameters:
message(string, required): Message to echo back
Search for files by name pattern or content within files.
use helios_engine::FileSearchTool;
agent.tool(Box::new(FileSearchTool));Parameters:
pattern(string, required): Search pattern (supports glob patterns like "*.rs")search_content(boolean, optional): Whether to search within file contents (default: false)path(string, optional): Directory path to search in (default: current directory)
Examples:
// Find all Rust files
agent.chat("Find all .rs files").await?;
// Search for specific content
agent.chat("Find files containing 'TODO'").await?;Read the contents of a file with optional line range selection.
use helios_engine::FileReadTool;
agent.tool(Box::new(FileReadTool));Parameters:
path(string, required): File path to readstart_line(number, optional): Starting line number (1-indexed)end_line(number, optional): Ending line number (1-indexed)
Examples:
// Read entire file
agent.chat("Read the file config.toml").await?;
// Read specific lines
agent.chat("Read lines 10-20 of main.rs").await?;Old way (still supported):
let mut agent = Agent::builder("MyAgent")
.config(config)
.tool(Box::new(CalculatorTool))
.tool(Box::new(EchoTool))
.tool(Box::new(FileSearchTool))
.tool(Box::new(FileReadTool))
.tool(Box::new(FileWriteTool))
.build()
.await?;New improved way (recommended):
let mut agent = Agent::builder("MyAgent")
.config(config)
.tools(vec![
Box::new(CalculatorTool),
Box::new(EchoTool),
Box::new(FileSearchTool),
Box::new(FileReadTool),
Box::new(FileWriteTool),
])
.build()
.await?;Benefits of the new syntax:
- Cleaner and more readable
- Easier to organize tools into groups
- Less repetitive code
- Can combine with individual
.tool()calls
Write content to a file (creates new or overwrites existing).
use helios_engine::FileWriteTool;
agent.tool(Box::new(FileWriteTool));Parameters:
path(string, required): File path to write tocontent(string, required): Content to write
Example:
agent.chat("Create a new file called notes.txt with content 'Hello World'").await?;Unified file operations: read, write, append, delete, copy, move, exists, size.
use helios_engine::FileIOTool;
agent.tool(Box::new(FileIOTool));Parameters:
operation(string, required): Operation type (read, write, append, delete, copy, move, exists, size)path(string, required): File path- Additional parameters depending on operation:
- For write/append:
content(string, required) - For copy/move:
destination(string, required) - For exists: none additional
- For write/append:
Examples:
// Check if file exists
agent.chat("Check if config.json exists").await?;
// Copy a file
agent.chat("Copy file1.txt to backup/file1.txt").await?;
// Get file size
agent.chat("Get the size of data.log").await?;Edit file contents by finding and replacing text patterns.
use helios_engine::FileEditTool;
agent.tool(Box::new(FileEditTool));Parameters:
path(string, required): File path to editfind(string, required): Text pattern to findreplace(string, required): Replacement textregex(boolean, optional): Whether to treat 'find' as a regex pattern (default: false)
Example:
agent.chat("In main.rs, replace 'old_function' with 'new_function'").await?;List directory contents with detailed metadata.
use helios_engine::FileListTool;
agent.tool(Box::new(FileListTool));Parameters:
path(string, optional): Directory path to listshow_hidden(boolean, optional): Show hidden filesrecursive(boolean, optional): List recursivelymax_depth(number, optional): Maximum recursion depth
Fetch and extract content from web URLs.
use helios_engine::WebScraperTool;
agent.tool(Box::new(WebScraperTool));Parameters:
url(string, required): URL to scrapeextract_text(boolean, optional): Extract readable text from HTMLtimeout_seconds(number, optional): Request timeout
Make HTTP requests with various methods.
use helios_engine::HttpRequestTool;
agent.tool(Box::new(HttpRequestTool));Parameters:
method(string, required): HTTP method (GET, POST, PUT, DELETE, etc.)url(string, required): Request URLheaders(object, optional): Request headersbody(string, optional): Request bodytimeout_seconds(number, optional): Request timeout
Parse, validate, format, and manipulate JSON data.
use helios_engine::JsonParserTool;
agent.tool(Box::new(JsonParserTool));Operations:
parse- Parse and validate JSONstringify- Format JSON with optional indentationget_value- Extract values by JSON pathset_value- Modify JSON valuesvalidate- Check JSON validity
Execute shell commands safely with security restrictions.
use helios_engine::ShellCommandTool;
agent.tool(Box::new(ShellCommandTool));Parameters:
command(string, required): Shell command to executetimeout_seconds(number, optional): Command timeout
Retrieve system information (OS, CPU, memory, disk, network).
use helios_engine::SystemInfoTool;
agent.tool(Box::new(SystemInfoTool));Parameters:
category(string, optional): Info category (all, os, cpu, memory, disk, network)
Work with timestamps and date/time operations.
use helios_engine::TimestampTool;
agent.tool(Box::new(TimestampTool));Operations:
now- Current timeformat- Format timestampsparse- Parse timestamp stringsadd/subtract- Time arithmeticdiff- Time difference calculation
Process and manipulate text with various operations.
use helios_engine::TextProcessorTool;
agent.tool(Box::new(TextProcessorTool));Operations:
search- Regex-based text searchreplace- Find and replace with regexsplit/join- Text splitting and joiningcount- Character, word, and line countsuppercase/lowercase- Case conversiontrim- Whitespace removallines/words- Text formatting
In-memory key-value database for caching data during conversations.
use helios_engine::MemoryDBTool;
agent.tool(Box::new(MemoryDBTool::new()));Operations:
set- Store key-value pairsget- Retrieve valuesdelete- Remove entrieslist- Show all stored dataclear- Remove all dataexists- Check key existence
RAG (Retrieval-Augmented Generation) tool with Qdrant vector database.
use helios_engine::QdrantRAGTool;
let rag_tool = QdrantRAGTool::new(
"http://localhost:6333", // Qdrant URL
"my_collection", // Collection name
"https://api.openai.com/v1/embeddings", // Embedding API
std::env::var("OPENAI_API_KEY").unwrap(), // API key
);
agent.tool(Box::new(rag_tool));Operations:
add_document- Store and embed documentssearch- Semantic searchdelete- Remove documentsclear- Clear collection
The ToolBuilder provides a simplified way to create custom tools without implementing the Tool trait manually. This is the recommended approach for most use cases.
NEW:
quick_tool!Macro - The EASIEST Way!
We've added thequick_tool!macro that makes tool creation incredibly simple with ZERO boilerplate.
See the Quick Start below or the full Simplified Tool Builder Guide.
Before (Manual Implementation):
use async_trait::async_trait;
use helios_engine::{Tool, ToolParameter, ToolResult};
use serde_json::Value;
use std::collections::HashMap;
struct MyTool;
#[async_trait]
impl Tool for MyTool {
fn name(&self) -> &str {
"my_tool"
}
fn description(&self) -> &str {
"Does something useful"
}
fn parameters(&self) -> HashMap<String, ToolParameter> {
let mut params = HashMap::new();
params.insert(
"input".to_string(),
ToolParameter {
param_type: "string".to_string(),
description: "The input value".to_string(),
required: Some(true),
},
);
params
}
async fn execute(&self, args: Value) -> helios_engine::Result<ToolResult> {
let input = args
.get("input")
.and_then(|v| v.as_str())
.ok_or_else(|| helios_engine::HeliosError::ToolError(
"Missing input parameter".to_string()
))?;
Ok(ToolResult::success(format!("Processed: {}", input)))
}
}After (Using ToolBuilder):
use helios_engine::{ToolBuilder, ToolResult};
use serde_json::Value;
let tool = ToolBuilder::new("my_tool")
.description("Does something useful")
.required_parameter("input", "string", "The input value")
.sync_function(|args: Value| {
let input = args.get("input").and_then(|v| v.as_str())
.ok_or_else(|| helios_engine::HeliosError::ToolError(
"Missing input parameter".to_string()
))?;
Ok(ToolResult::success(format!("Processed: {}", input)))
})
.build();use helios_engine::{Agent, Config, ToolBuilder, ToolResult};
use serde_json::Value;
#[tokio::main]
async fn main() -> helios_engine::Result<()> {
let config = Config::from_file("config.toml")?;
// Create a tool in just a few lines!
let calculator = ToolBuilder::new("multiply")
.description("Multiply two numbers")
.required_parameter("x", "number", "First number")
.required_parameter("y", "number", "Second number")
.sync_function(|args: Value| {
let x = args.get("x").and_then(|v| v.as_f64()).unwrap_or(0.0);
let y = args.get("y").and_then(|v| v.as_f64()).unwrap_or(0.0);
Ok(ToolResult::success((x * y).to_string()))
})
.build();
// Use it with an agent
let mut agent = Agent::builder("MathAgent")
.config(config)
.tool(calculator)
.build()
.await?;
let response = agent.chat("What is 7 times 8?").await?;
println!("Agent: {}", response);
Ok(())
}Builder Methods:
new(name)- Create a new ToolBuilder with the given namedescription(desc)- Set the tool descriptionparameter(name, type, desc, required)- Add a parameterrequired_parameter(name, type, desc)- Add a required parameteroptional_parameter(name, type, desc)- Add an optional parameterfunction(async_fn)- Set an async function to executesync_function(sync_fn)- Set a synchronous function to executebuild()- Build the tool (panics if function not set)try_build()- Build the tool (returns Result)
Parameter Types:
"string"- Text values"number"- Numeric values (integers or floats)"boolean"- True/false values"object"- JSON objects"array"- JSON arrays
This is the EASIEST way to create tools! Zero boilerplate, automatic parameter extraction:
use helios_engine::quick_tool;
// Create a tool in ONE expression!
let volume_tool = quick_tool! {
name: calculate_volume,
description: "Calculate the volume of a box",
params: (width: f64, height: f64, depth: f64),
execute: |width, height, depth| {
format!("Volume: {:.2} cubic meters", width * height * depth)
}
};
// Another example - BMI calculator
let bmi_tool = quick_tool! {
name: calculate_bmi,
description: "Calculate Body Mass Index",
params: (weight_kg: f64, height_m: f64),
execute: |weight, height| {
let bmi = weight / (height * height);
format!("BMI: {:.1}", bmi)
}
};
// Works with different types too!
let greet_tool = quick_tool! {
name: greet_user,
description: "Greet a user",
params: (name: String, formal: bool),
execute: |name, formal| {
if formal {
format!("Good day, {}.", name)
} else {
format!("Hey {}!", name)
}
}
};Supported types: i32, i64, u32, u64, f32, f64, bool, String
What it does automatically:
- Extracts parameters from JSON
- Handles type conversion
- Provides sensible defaults
- Zero manual parameter handling!
For complete documentation and alternative methods, see TOOL_BUILDER_SIMPLIFIED.md.
Wrapping Existing Functions:
// Your existing function
fn calculate_discount(price: f64, discount_percent: f64) -> f64 {
price * (1.0 - discount_percent / 100.0)
}
// Wrap it as a tool
let discount_tool = ToolBuilder::new("calculate_discount")
.description("Calculate discounted price")
.required_parameter("price", "number", "Original price")
.required_parameter("discount_percent", "number", "Discount percentage")
.sync_function(|args: Value| {
let price = args.get("price").and_then(|v| v.as_f64()).unwrap_or(0.0);
let discount = args.get("discount_percent").and_then(|v| v.as_f64()).unwrap_or(0.0);
let result = calculate_discount(price, discount);
Ok(ToolResult::success(format!("${:.2}", result)))
})
.build();Async Operations:
async fn fetch_data(id: &str) -> Result<String, String> {
// Async operation
Ok(format!("Data for {}", id))
}
let tool = ToolBuilder::new("fetch")
.description("Fetch data by ID")
.required_parameter("id", "string", "Resource ID")
.function(|args: Value| async move {
let id = args.get("id").and_then(|v| v.as_str()).unwrap_or("");
match fetch_data(id).await {
Ok(data) => Ok(ToolResult::success(data)),
Err(e) => Ok(ToolResult::error(e)),
}
})
.build();Optional Parameters:
let tool = ToolBuilder::new("greet")
.description("Greet someone")
.required_parameter("name", "string", "Name of person")
.optional_parameter("title", "string", "Optional title")
.sync_function(|args: Value| {
let name = args.get("name").and_then(|v| v.as_str()).unwrap_or("stranger");
let title = args.get("title").and_then(|v| v.as_str());
let greeting = if let Some(t) = title {
format!("Hello, {} {}!", t, name)
} else {
format!("Hello, {}!", name)
};
Ok(ToolResult::success(greeting))
})
.build();Capturing External State:
let api_key = "secret_key".to_string();
let multiplier = 10;
let tool = ToolBuilder::new("api_multiply")
.description("Multiply a number and use captured state")
.required_parameter("value", "number", "Value to multiply")
.function(move |args: Value| {
let key = api_key.clone();
let mult = multiplier;
async move {
let value = args.get("value").and_then(|v| v.as_f64()).unwrap_or(0.0);
let result = value * mult as f64;
// Use key in API call...
Ok(ToolResult::success(result.to_string()))
}
})
.build();Error Handling:
let validator_tool = ToolBuilder::new("validate_email")
.description("Validate an email address")
.required_parameter("email", "string", "Email to validate")
.sync_function(|args: Value| {
let email = args.get("email")
.and_then(|v| v.as_str())
.ok_or_else(|| helios_engine::HeliosError::ToolError(
"Missing email parameter".to_string()
))?;
if email.contains('@') && email.contains('.') {
Ok(ToolResult::success(format!("{} is valid", email)))
} else {
Ok(ToolResult::error(format!("{} is not a valid email", email)))
}
})
.build();Complex JSON Parameters:
let tool = ToolBuilder::new("process_order")
.description("Process a customer order")
.required_parameter("order", "object", "Order details")
.sync_function(|args: Value| {
let order = args.get("order").ok_or_else(|| {
helios_engine::HeliosError::ToolError("Missing order".to_string())
})?;
let customer = order.get("customer").and_then(|v| v.as_str());
let total = order.get("total").and_then(|v| v.as_f64());
Ok(ToolResult::success(format!(
"Order for {} - ${:.2}",
customer.unwrap_or("unknown"),
total.unwrap_or(0.0)
)))
})
.build();- Clear Descriptions: Write clear descriptions for tools and parameters to help the LLM choose the right tool
- Parameter Validation: Always validate required parameters and provide helpful error messages
- Type Safety: Use appropriate parameter types (
string,number,boolean, etc.) - Error Handling: Handle errors gracefully using
ResultandToolResult - Async When Needed: Use
function()for async operations (API calls, I/O),sync_function()for simple computations
See examples/tool_builder_demo.rs for a comprehensive example demonstrating:
- Wrapping existing functions
- Async operations
- Optional parameters
- Closure capture
- Multiple tools in one agent
For advanced use cases or when you need more control, you can implement the Tool trait directly. Tools must be thread-safe and handle errors gracefully.
For advanced use cases or when you need more control, you can implement the Tool trait directly. Tools must be thread-safe and handle errors gracefully.
use async_trait::async_trait;
use helios_engine::{Tool, ToolParameter, ToolResult};
use serde_json::Value;
use std::collections::HashMap;
struct WeatherTool;
#[async_trait]
impl Tool for WeatherTool {
fn name(&self) -> &str {
"get_weather"
}
fn description(&self) -> &str {
"Get the current weather for a location"
}
fn parameters(&self) -> HashMap<String, ToolParameter> {
let mut params = HashMap::new();
params.insert(
"location".to_string(),
ToolParameter {
param_type: "string".to_string(),
description: "City name or location".to_string(),
required: Some(true),
},
);
params
}
async fn execute(&self, args: Value) -> helios_engine::Result<ToolResult> {
let location = args["location"]
.as_str()
.ok_or_else(|| helios_engine::Error::InvalidParameter("location".to_string()))?;
// Your weather API logic here
let weather_data = fetch_weather_data(location).await?;
Ok(ToolResult::success(format!(
"Weather in {}: {}°, {}",
location, weather_data.temperature, weather_data.condition
)))
}
}use async_trait::async_trait;
use helios_engine::{Tool, ToolParameter, ToolResult, Agent, Config};
use serde_json::Value;
use std::collections::HashMap;
struct WeatherTool;
#[async_trait]
impl Tool for WeatherTool {
fn name(&self) -> &str {
"get_weather"
}
fn description(&self) -> &str {
"Get current weather information for a location"
}
fn parameters(&self) -> HashMap<String, ToolParameter> {
let mut params = HashMap::new();
params.insert(
"location".to_string(),
ToolParameter {
param_type: "string".to_string(),
description: "City name (e.g., 'New York', 'London, UK')".to_string(),
required: Some(true),
},
);
params.insert(
"unit".to_string(),
ToolParameter {
param_type: "string".to_string(),
description: "Temperature unit: 'celsius' or 'fahrenheit'".to_string(),
required: Some(false),
},
);
params
}
async fn execute(&self, args: Value) -> helios_engine::Result<ToolResult> {
let location = args["location"]
.as_str()
.ok_or_else(|| helios_engine::Error::InvalidParameter("location is required".to_string()))?;
let unit = args["unit"]
.as_str()
.unwrap_or("celsius");
// Simulate weather API call
let temperature = 22;
let condition = "Sunny";
let temp_display = match unit {
"fahrenheit" => format!("{}°F", temperature * 9/5 + 32),
_ => format!("{}°C", temperature),
};
Ok(ToolResult::success(format!(
"Weather in {}: {}, {}",
location, temp_display, condition
)))
}
}
// Use your custom tool
#[tokio::main]
async fn main() -> helios_engine::Result<()> {
let config = Config::from_file("config.toml")?;
let mut agent = Agent::builder("WeatherAgent")
.config(config)
.system_prompt("You are a helpful assistant with access to weather information.")
.tool(Box::new(WeatherTool))
.build()
.await?;
let response = agent.chat("What's the weather like in Tokyo?").await?;
println!("{}", response);
Ok(())
}- Always handle errors gracefully in your
executemethod - Return appropriate
ToolResulttypes for different outcomes - Provide meaningful error messages
- Validate required parameters early
- Provide sensible defaults for optional parameters
- Use clear parameter names and descriptions
- Keep tool execution reasonably fast
- Avoid blocking operations when possible
- Consider implementing timeouts for external API calls
- Validate file paths to prevent directory traversal
- Sanitize command inputs for shell tools
- Be cautious with network requests and API keys
- Use lowercase with underscores for tool names:
file_search,web_scraper - Make descriptions clear and actionable
- Parameter names should be descriptive but concise
Tools can maintain state between executions:
use std::sync::Mutex;
struct CounterTool {
count: Mutex<i32>,
}
#[async_trait]
impl Tool for CounterTool {
fn name(&self) -> &str {
"counter"
}
fn description(&self) -> &str {
"A simple counter that maintains state"
}
// ... parameters and execute methods
}Tools can perform async operations naturally since execute is async:
async fn execute(&self, args: Value) -> helios_engine::Result<ToolResult> {
// Perform async HTTP request
let response = reqwest::get("https://api.example.com/data").await?;
let data: serde_json::Value = response.json().await?;
Ok(ToolResult::success(format!("Fetched: {}", data)))
}Create complex tools by combining simpler ones:
struct DataProcessorTool {
file_tool: FileReadTool,
json_tool: JsonParserTool,
}
#[async_trait]
impl Tool for DataProcessorTool {
// Implementation that uses both tools internally
}The ToolRegistry manages all available tools:
use helios_engine::ToolRegistry;
// Create registry
let mut registry = ToolRegistry::new();
// Register tools
registry.register(Box::new(CalculatorTool));
registry.register(Box::new(FileReadTool));
// List available tools
let tool_names = registry.list_tools();
println!("Available tools: {:?}", tool_names);
// Execute tools directly
let result = registry.execute("calculator", serde_json::json!({
"expression": "2 + 2"
})).await?;- Examples - See tools in action
- API Reference - Complete Tool trait documentation
- Usage Guide - More usage patterns