Skip to content

Latest commit

 

History

History
executable file
·
959 lines (747 loc) · 25.6 KB

File metadata and controls

executable file
·
959 lines (747 loc) · 25.6 KB

Tools Guide

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.

Overview

Tools in Helios Engine follow a simple pattern:

  1. Definition: Each tool defines its name, description, and parameters
  2. Execution: Tools receive JSON parameters and return structured results
  3. Registration: Tools are registered with agents during creation

Built-in Tools

Core Tools

CalculatorTool

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?;

EchoTool

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

File Management Tools

FileSearchTool

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?;

FileReadTool

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 read
  • start_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?;

Adding Multiple Tools (New Improved Syntax!)

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

FileWriteTool

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 to
  • content (string, required): Content to write

Example:

agent.chat("Create a new file called notes.txt with content 'Hello World'").await?;

FileIOTool

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

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?;

FileEditTool

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 edit
  • find (string, required): Text pattern to find
  • replace (string, required): Replacement text
  • regex (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?;

FileListTool

List directory contents with detailed metadata.

use helios_engine::FileListTool;

agent.tool(Box::new(FileListTool));

Parameters:

  • path (string, optional): Directory path to list
  • show_hidden (boolean, optional): Show hidden files
  • recursive (boolean, optional): List recursively
  • max_depth (number, optional): Maximum recursion depth

Web & API Tools

WebScraperTool

Fetch and extract content from web URLs.

use helios_engine::WebScraperTool;

agent.tool(Box::new(WebScraperTool));

Parameters:

  • url (string, required): URL to scrape
  • extract_text (boolean, optional): Extract readable text from HTML
  • timeout_seconds (number, optional): Request timeout

HttpRequestTool

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 URL
  • headers (object, optional): Request headers
  • body (string, optional): Request body
  • timeout_seconds (number, optional): Request timeout

JsonParserTool

Parse, validate, format, and manipulate JSON data.

use helios_engine::JsonParserTool;

agent.tool(Box::new(JsonParserTool));

Operations:

  • parse - Parse and validate JSON
  • stringify - Format JSON with optional indentation
  • get_value - Extract values by JSON path
  • set_value - Modify JSON values
  • validate - Check JSON validity

System & Utility Tools

ShellCommandTool

Execute shell commands safely with security restrictions.

use helios_engine::ShellCommandTool;

agent.tool(Box::new(ShellCommandTool));

Parameters:

  • command (string, required): Shell command to execute
  • timeout_seconds (number, optional): Command timeout

SystemInfoTool

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)

TimestampTool

Work with timestamps and date/time operations.

use helios_engine::TimestampTool;

agent.tool(Box::new(TimestampTool));

Operations:

  • now - Current time
  • format - Format timestamps
  • parse - Parse timestamp strings
  • add/subtract - Time arithmetic
  • diff - Time difference calculation

TextProcessorTool

Process and manipulate text with various operations.

use helios_engine::TextProcessorTool;

agent.tool(Box::new(TextProcessorTool));

Operations:

  • search - Regex-based text search
  • replace - Find and replace with regex
  • split/join - Text splitting and joining
  • count - Character, word, and line counts
  • uppercase/lowercase - Case conversion
  • trim - Whitespace removal
  • lines/words - Text formatting

Data Storage Tools

MemoryDBTool

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 pairs
  • get - Retrieve values
  • delete - Remove entries
  • list - Show all stored data
  • clear - Remove all data
  • exists - Check key existence

QdrantRAGTool

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 documents
  • search - Semantic search
  • delete - Remove documents
  • clear - Clear collection

Creating Custom Tools

Easy Way: Using ToolBuilder (Recommended)

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 the quick_tool! macro that makes tool creation incredibly simple with ZERO boilerplate.
See the Quick Start below or the full Simplified Tool Builder Guide.

Why Use ToolBuilder?

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();

Quick Start

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(())
}

ToolBuilder API Reference

Builder Methods:

  • new(name) - Create a new ToolBuilder with the given name
  • description(desc) - Set the tool description
  • parameter(name, type, desc, required) - Add a parameter
  • required_parameter(name, type, desc) - Add a required parameter
  • optional_parameter(name, type, desc) - Add an optional parameter
  • function(async_fn) - Set an async function to execute
  • sync_function(sync_fn) - Set a synchronous function to execute
  • build() - 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

Quick Start: quick_tool! Macro

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.

ToolBuilder Patterns

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();

ToolBuilder Best Practices

  1. Clear Descriptions: Write clear descriptions for tools and parameters to help the LLM choose the right tool
  2. Parameter Validation: Always validate required parameters and provide helpful error messages
  3. Type Safety: Use appropriate parameter types (string, number, boolean, etc.)
  4. Error Handling: Handle errors gracefully using Result and ToolResult
  5. Async When Needed: Use function() for async operations (API calls, I/O), sync_function() for simple computations

Complete Example

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

Advanced Way: Implementing the Tool Trait

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.

Basic Tool Structure

Advanced Way: Implementing the Tool Trait

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.

Basic Tool Structure

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
        )))
    }
}

Complete Example

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(())
}

Tool Best Practices

Error Handling

  • Always handle errors gracefully in your execute method
  • Return appropriate ToolResult types for different outcomes
  • Provide meaningful error messages

Parameter Validation

  • Validate required parameters early
  • Provide sensible defaults for optional parameters
  • Use clear parameter names and descriptions

Performance

  • Keep tool execution reasonably fast
  • Avoid blocking operations when possible
  • Consider implementing timeouts for external API calls

Security

  • Validate file paths to prevent directory traversal
  • Sanitize command inputs for shell tools
  • Be cautious with network requests and API keys

Naming Conventions

  • Use lowercase with underscores for tool names: file_search, web_scraper
  • Make descriptions clear and actionable
  • Parameter names should be descriptive but concise

Advanced Tool Patterns

Stateful Tools

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
}

Async Tools

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)))
}

Tool Composition

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
}

Tool Registry

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?;

Next Steps