This document describes the organization of the tools-rs codebase following Rust best practices.
The project is organized as a Rust workspace with two main crates:
tools-rs/
├── Cargo.toml # Workspace definition and main crate
├── src/ # Main crate source (tools-rs)
│ └── lib.rs # Re-exports and high-level API
├── tools_core/ # Core implementation crate
│ ├── Cargo.toml
│ └── src/
│ └── lib.rs # Runtime functionality and schema trait
├── tools_macros/ # Procedural macros crate
│ ├── Cargo.toml
│ └── src/
│ └── lib.rs # All procedural macros
├── examples/ # Example code (separate crate)
│ ├── Cargo.toml
│ ├── README.md
│ ├── basic/ # Basic usage examples
│ │ └── main.rs # Simple tool registration and usage
│ ├── function_declarations/ # Function declaration examples
│ │ └── main.rs # LLM integration examples
│ └── schema/ # Schema generation examples
│ └── main.rs # Advanced schema usage
└── tests/ # Integration tests
└── no_features.rs # Test schema generation without features
This is the main crate that users interact with. It:
- Re-exports the most commonly used types and functions from
tools_core - Re-exports both macros from
tools_macros(toolandToolSchema) - Provides a simple API via
collect_tools()andfunction_declarations() - Acts as a convenience layer over the core
tools_corecrate
This is the core implementation crate that contains:
- Tool Runtime:
ToolCollectionfor managing and executing registered tools - Schema Generation:
ToolSchematrait and implementations for all primitive types - Error Handling: Comprehensive error types (
ToolError,DeserializationError) - Core Models:
FunctionCall,ToolRegistration,FunctionDecl, etc. - Async Execution: Type-safe tool invocation with JSON serialization
- Inventory Integration: Runtime collection of tools registered via macros
This is the procedural macro crate that provides:
#[derive(ToolSchema)]: Automatic JSON Schema generation for structs- Supports named structs, tuple structs, and unit structs
- Handles nested types and collections
- Detects optional fields (
Option<T>) for schema generation
#[tool]: Attribute macro for automatic tool registration- Generates wrapper structs for function parameters
- Integrates with the
inventorycrate for compile-time tool collection - Supports async functions with automatic JSON conversion
The 2-crate structure follows Rust best practices:
- Runtime vs. Compile-time:
tools_corehandles runtime functionality whiletools_macrosprovides compile-time code generation - Proc-macro Isolation: Procedural macros require
proc-macro = trueand have different compilation requirements, so they belong in a separate crate
- Minimal Dependencies:
tools_coreonly includes runtime dependencies (serde, tokio, etc.) - Proc-macro Dependencies:
tools_macrosincludes proc-macro specific dependencies (syn, quote, proc-macro2) - No Circular Dependencies: The macros reference the core crate, but not vice versa
- Single Entry Point: Users import from
tools-rsand get everything they need - Flexible Usage: Advanced users can depend directly on
tools_coreortools_macrosif needed - Clear API: The macro and trait names are consistent and intuitive
- Root: Core trait definitions (
ToolSchema) and implementations - Error handling:
ToolError,DeserializationErrorwith proper error chaining - Models: Data structures for function calls, registrations, and metadata
- Schema generation:
FunctionDeclfor LLM consumption - Tool collection:
ToolCollectionwith registration and execution logic
- Derive macro:
ToolSchemaimplementation generation - Attribute macro:
#[tool]for automatic registration - Utilities: Helper functions for path resolution and type analysis
- Clear Ownership: Each crate has a single, well-defined responsibility
- Minimal API Surface: Users only need to import from one crate
- Type Safety: Strong typing with JSON conversion at boundaries
- Error Handling: Comprehensive error types with proper context
- Async-First Design: Built around tokio and async/await patterns
- Macro Hygiene: Proper path resolution and name collision avoidance
- Workspace Management: Shared dependencies and consistent versioning
- Documentation: Clear examples and comprehensive tests
- New Tools: Use
#[tool]attribute on async functions - Core Changes: Modify
tools_corefor fundamental functionality - Schema Changes: Update
tools_corefor new type support - Macro Changes: Update
tools_macrosfor new derive capabilities - Examples: Add to the
examplescrate for documentation
- Unit Tests: Each crate has its own test suite
- Integration Tests: Workspace-level tests verify end-to-end functionality
- Example Tests: Examples serve as both documentation and integration tests
The reorganization consolidated 4 crates into 2:
Before:
tool_schema→ Merged intotools_coretool_schema_derive→ Merged intotools_macrostools→ Becametools_coretools_macros→ Merged intotools_macros
Benefits:
- Reduced complexity from 4 interdependent crates to 2 focused crates
- Eliminated confusion about which crate provides which functionality
- Simplified dependency management and circular dependency issues
- Better alignment with Rust ecosystem conventions
use tools_rs::{tool, ToolSchema};
#[derive(serde::Serialize, serde::Deserialize, ToolSchema)]
struct MyInput {
value: String,
}
#[tool]
async fn my_function(input: MyInput) -> String {
format!("Hello, {}!", input.value)
}let tools = tools_rs::collect_tools();
let declarations = tools_rs::function_declarations()?;
// Send declarations to LLM, receive function calls, execute with tools.call()use tools_core::ToolCollection;
let mut collection = ToolCollection::new();
collection.register("name", "description", |input: String| async move {
// tool implementation
}).unwrap();The workspace uses semantic versioning:
- Major version: Breaking API changes in public interfaces
- Minor version: New features maintaining backward compatibility
- Patch version: Bug fixes and internal improvements
Both tools_core and tools_macros follow the same version as the main tools-rs crate to ensure compatibility.