1 unstable release
new 0.1.0 | Apr 28, 2025 |
---|
#111 in #schema
23 downloads per month
28KB
189 lines
tool_calling
A procedural-macro framework for defining, registering, and invoking Rust functions as tools with automatic JSON Schema validation and error handling.
Features
- Attribute-based: Annotate Rust functions with
#[tool]
to register them automatically. - Automatic Schema Generation: Generates a JSON Schema from function signature and doc comments.
- Synchronous & Asynchronous: Support both sync and async functions out of the box.
- Optional Parameters: Use
Option<T>
for optional arguments;#[default = ...]
for defaults. - Type Safety: Denies reference types (
&T
) to ensure tools use owned types likeString
andVec<T>
. - Error Handling: Provides clear errors for missing tools, argument validation failures, and execution errors (including panics).
Installation
Add this to your Cargo.toml
:
[dependencies]
tool_calling = "0.1"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
Or use cargo:
cargo add tool_calling
cargo add tokio --features macros,rt-multi-thread
Note: The
tokio
dependency is only required if you are calling tools asynchronously (most CLI or server contexts).
Quickstart
use tool_calling::{tool, ToolHandler};
use serde_json::json;
#[tool]
/// Get user info from database
fn get_user_info(user_id: u32) -> String {
match user_id {
1 => "User 1 info...".to_string(),
_ => "User not found".to_string(),
}
}
#[tokio::main]
async fn main() {
// Initialize handler
let handler = ToolHandler::default();
// Call the tool with a JSON payload
let payload = json!({
"type": "function",
"function": {
"name": "get_user_info",
"arguments": { "user_id": 1 }
}
});
let result = handler.call_tool(&payload).await.unwrap();
println!("Result: {}", result);
}
Defining Tools
Use the #[tool]
attribute to mark any free function as a tool. The macro will:
- Collect the doc comment (
///
) as the tool's description. - Inspect parameters to generate a JSON Schema (
u32
/i*
/usize
→integer
,f32
/f64
→number
,bool
→boolean
,String
→string
). - Treat
Option<T>
parameters as optional fields in the schema (allowingnull
). - Enforce owned types (no
&T
).
use tool_calling::tool;
#[tool]
/// Get user info from database
fn get_user_info(user_id: u32) -> String {
match user_id {
1 => "User 1 info...".to_string(),
_ => "User not found".to_string(),
}
}
Optionally provide a default literal for Option<T>
parameters:
use tool_calling::{tool, default};
#[tool]
/// Greet a user with optional punctuation
fn greet(
name: String,
#[default = "?" ] punctuation: Option<String>
) -> String {
let punct = punctuation.unwrap_or_else(|| "!".to_string());
format!("Hello, {}{}", name, punct)
}
Calling Tools
1. Initialize a handler
use tool_calling::ToolHandler;
#[tokio::main]
async fn main() {
let handler = ToolHandler::default();
// ...
}
2. Discover tools & schemas
// List a single tool
if let Some(tool) = handler.get_tool("get_user_info") {
println!("Description: {}", tool.description);
println!("Schema: {}", serde_json::to_string_pretty(&tool.parameter_schema).unwrap());
}
// List all tools for LLM function-calling
println!("{}", serde_json::to_string_pretty(&handler.all_tools_schema()).unwrap());
3. Call by JSON payload
use serde_json::json;
let payload = json!({
"type": "function",
"function": {
"name": "get_user_info",
"arguments": { "user_id": 1 }
}
});
match handler.call_tool(&payload).await {
Ok(res) => println!("Result: {}", res),
Err(e) => eprintln!("Error: {}", e),
}
4. Call directly with string args
let result = handler
.call_with_args("get_user_info", &["1".to_string()])
.await
.unwrap();
println!("Direct call result: {}", result);
Examples
Explore the examples directory for more usage scenarios:
examples/simple_example.rs
— Basic sync tool.examples/async_example.rs
— Async tool withtokio
.examples/optional_arguments_example.rs
— Tool with optional parameters and defaults.
Testing
Run the full test suite:
cargo test
API Reference
Macros
#[tool]
— Marks a function as a tool, generating registration code and JSON Schema.#[default = <literal>]
— Attach toOption<T>
parameters for default values.
ToolHandler
ToolHandler::default()
— Initializes and registers all annotated tools.get_tool(name: &str) -> Option<&Tool>
— Retrieve metadata for a single tool.all_tools_schema() -> serde_json::Value
— A JSON array of all tools (for LLM introspection).call_tool(input: &serde_json::Value) -> Result<String, ToolError>
— Parse a function-call payload and execute.call_with_args(name: &str, args: &[String]) -> Result<String, ToolError>
— Directly invoke a tool by name.
Error Handling
ToolError
variants:
NotFound(String)
— Tool name not registered.BadArgs(String)
— Arguments missing or failed JSON Schema validation.Execution(String)
— Underlying function panicked or returned an execution error.
Contributing
Contributions, issues, and feature requests are welcome! Please open a GitHub issue or submit a pull request.
License
This project is licensed under either of:
- Apache License, Version 2.0
- MIT license
at your option.
Dependencies
~13–22MB
~312K SLoC