Tools-rs - Tool Collection and Execution Framework
It's pronounced tools-r-us!!
Tools-rs is a framework for building, registering, and executing tools with automatic JSON schema generation for Large Language Model (LLM) integration.
Features
- Automatic Registration - Use
#[tool]to automatically register functions with compile-time discovery - JSON Schema Generation - Automatic schema generation for LLM integration with full type information
- Type Safety - Full type safety with JSON serialization at boundaries, compile-time parameter validation
- Async Support - Built for async/await from the ground up with
tokiointegration - Error Handling - Comprehensive error types with context and proper error chaining
- LLM Integration - Export function declarations for LLM function calling APIs (OpenAI, Anthropic, etc.)
- Manual Registration - Programmatic tool registration for dynamic scenarios
- Inventory System - Link-time tool collection using the
inventorycrate for zero-runtime-cost discovery - Typed Metadata - Attach
#[tool(key = value)]attributes to tools and read them through a user-definedMtype onToolCollection<M>(see Tool Metadata)
Quick Start
use json;
use ;
/// Adds two numbers.
async
/// Greets a person.
async
async
Installation
Add the following to your Cargo.toml:
[]
= "0.2.0"
= { = "1.45", = ["macros", "rt-multi-thread"] }
= "1.0"
Crate Structure
The tools-rs system is organized as a Rust workspace with three main crates:
- tools-rs: Main entry point, re-exports the most commonly used items
- tools_core: Core runtime implementation including:
- Tool collection and execution (
ToolCollection) - JSON schema generation (
ToolSchematrait) - Error handling (
ToolError,DeserializationError) - Core data structures (
FunctionCall,ToolRegistration, etc.)
- Tool collection and execution (
- tools_macros: Procedural macros for tool registration:
#[tool]attribute macro for automatic registration#[derive(ToolSchema)]for automatic schema generation
- examples: Comprehensive examples demonstrating different use cases
For more details about the codebase organization, see CODE_ORGANIZATION.md.
Compatibility
Rust Version Support
Tools-rs requires Rust 1.85 or later and supports:
- Automatically generate JSON schemas for LLM consumption
- Execute tools safely with full type checking
- Handle errors gracefully with detailed context
Function Declarations for LLMs
Tools-rs can automatically generate function declarations suitable for LLM APIs:
use ;
/// Return the current date in ISO-8601 format.
async
async
The generated declarations follow proper JSON Schema format:
Manual Registration
While the #[tool] macro provides the most convenient way to register tools, you can also register tools manually for more dynamic scenarios:
use ToolCollection;
use json;
async
Advanced Manual Registration
For complex scenarios with custom types:
use ;
use ;
async
Tool Metadata
#[tool(...)] accepts flat key = value attributes that get stored on each
tool and read back through a user-defined metadata type. This lets a single
tool declaration feed multiple collections, each typed to the schema that
collection cares about — useful for HITL approval gates, cost tiering, and
similar policy concerns that don't belong in the function body.
use Deserialize;
use ;
/// Deletes a file.
async
/// Reads a file (no metadata declared — fields default).
async
Default behavior
ToolCollection defaults to ToolCollection<NoMeta>. NoMeta is an empty
struct that swallows any attributes a tool declared, so existing
collect_tools() callers see no behavioral change. Opt into typed metadata
by writing ToolCollection::<MyMeta>::collect_tools() instead.
Validation
ToolCollection::<M>::collect_tools() fails fast on the first tool whose
attributes don't match M. For CI, two helpers accumulate every failure
across the inventory before returning:
use ;
# ;
Attribute syntax
#[tool(key = "value")]— string#[tool(key = 42)]— integer (negative literals are accepted:-3)#[tool(key = 1.5)]— float#[tool(key = true)]— boolean#[tool(flag)]— bare flag, equivalent toflag = true- Multiple attributes are comma-separated:
#[tool(a = 1, b = "x", flag)]
Attributes are flat-only — nested structures (#[tool(policy = { ... })])
are not supported. Use richer types in runtime metadata, not at the
attribute site. The keys name and description are reserved (the
function name and doc comment supply them).
Programmatic registration with metadata
ToolCollection::register takes a metadata argument. For untyped
collections, pass (); passing () to a typed collection is a compile
error.
# use ToolCollection;
#
let mut tools: = new;
tools.register?;
# Ok::
Examples
Check out the examples directory for comprehensive sample code:
# Run the basic example - simple tool registration and calling
# Run the function declarations example - LLM integration demo
# Run the schema example - complex type schemas and validation
# Run the newtype demo - custom type wrapping examples
Each example demonstrates different aspects of the framework:
- basic: Simple tool registration with
#[tool]and basic function calls - function_declarations: Complete LLM integration workflow with JSON schema generation
- schema: Advanced schema generation for complex nested types and collections
- newtype_demo: Working with custom wrapper types and serialization patterns
API Reference
Core Functions
collect_tools()- Discover all tools registered via#[tool]macrofunction_declarations()- Generate JSON schema declarations for LLMscall_tool(name, args)- Execute a tool by name with JSON argumentscall_tool_with(name, typed_args)- Execute a tool with typed argumentscall_tool_by_name(collection, name, args)- Execute tool on specific collectionlist_tool_names(collection)- List all available tool names
Core Types
ToolCollection- Container for registered tools with execution capabilitiesFunctionCall- Represents a tool invocation with id, name, and argumentsFunctionResponse- Represents the response of a tool invocation with matching id to call, name, and resultToolError- Comprehensive error type for tool operationsToolSchema- Trait for automatic JSON schema generationToolRegistration- Internal representation of registered toolsFunctionDecl- LLM-compatible function declaration structure
Macros
#[tool]- Attribute macro for automatic tool registration#[derive(ToolSchema)]- Derive macro for automatic schema generation
Error Handling
Tools-rs provides comprehensive error handling with detailed context:
use ;
use json;
async
Performance Considerations
Schema Caching
- JSON schemas are generated once and cached.
- Schema generation has minimal runtime overhead after first access
- Primitive types use pre-computed static schemas for optimal performance
Tool Discovery
- Tool registration happens at compile-time via the
inventorycrate - Runtime tool collection (
collect_tools()) is a zero-cost operation - Tools are stored in efficient hash maps for O(1) lookup by name
Execution Performance
- Tool calls have minimal overhead beyond JSON serialization/deserialization
- Async execution allows for concurrent tool invocation
- Error handling uses
Resulttypes to avoid exceptions and maintain performance
Memory Usage
- Tool metadata is stored statically with minimal heap allocation
- JSON schemas are shared across all instances of the same type
- Function declarations are generated on-demand and can be cached by the application
Optimization Tips
// Reuse ToolCollection instances to avoid repeated discovery
let tools = collect_tools; // Call once, reuse multiple times
// Generate function declarations once for LLM integration
let declarations = function_declarations?;
// Cache and reuse declarations across multiple LLM requests
// Use typed parameters to avoid repeated JSON parsing
use call_tool_with;
let result = call_tool_with.await?.result;
Troubleshooting
Common Issues
Tool not found at runtime
- Ensure the
#[tool]macro is applied to your function - Verify the function is in a module that gets compiled (not behind unused feature flags)
- Check that
inventoryis properly collecting tools withcollect_tools()
Schema generation errors
- Ensure all parameter and return types implement
ToolSchema - For custom types, add
#[derive(ToolSchema)]to struct definitions - Complex generic types may need manual
ToolSchemaimplementations
Deserialization failures
- Verify JSON arguments match the expected parameter structure
- Check that argument names match function parameter names exactly
- Use
serdeattributes like#[serde(rename = "...")]for custom field names
Async execution issues
- All tool functions must be
async fnwhen using#[tool] - Ensure you're using
tokioruntime for async execution - Tool execution is inherently async - use
.awaitwhen calling tools
Debugging Tips
// Enable debug logging to see tool registration and execution
use ;
let tools = collect_tools;
println!;
// Inspect generated schemas
let declarations = tools.json?;
println!;
Contributing
We welcome contributions!
Development Setup
# Clone the repository
# Run tests
# Run examples
License
This project is licensed under the MIT License - see the LICENSE file for details.