pub struct Tool { /* private fields */ }Expand description
Tool definition for OpenAI-compatible function calling.
A Tool encapsulates everything needed for an LLM to understand and execute
a function: its identity, purpose, expected inputs, and implementation.
§Design Philosophy
Tools are immutable by design. Once created, their metadata and handler cannot be changed. This ensures:
- Thread safety through simple cloning (all fields are cheaply cloned)
- Predictable behavior - a tool’s signature never changes mid-execution
- Safe concurrent access without locks
§Cloning Behavior
The Clone implementation is efficient:
nameanddescription: String clones (heap allocation)input_schema: JSON Value clone (reference counted internally in some cases)handler: Arc clone (only increments atomic counter, shares same handler)
This means cloning a tool is relatively cheap and won’t duplicate the actual handler implementation.
§Thread Safety
Tools are fully thread-safe:
- All fields are
Send + Sync - Handler is wrapped in
Arcfor shared ownership - Can be stored in agent registries accessed by multiple threads
- Can be cloned and sent across thread boundaries
§Examples
use open_agent::Tool;
use serde_json::json;
// Create a tool using the constructor
let calculator = Tool::new(
"multiply",
"Multiply two numbers together",
json!({
"a": "number",
"b": "number"
}),
|args| Box::pin(async move {
let a = args["a"].as_f64().unwrap_or(1.0);
let b = args["b"].as_f64().unwrap_or(1.0);
Ok(json!({"result": a * b}))
})
);
// Access tool metadata
println!("Tool: {}", calculator.name());
println!("Description: {}", calculator.description());
println!("Schema: {}", calculator.input_schema());Implementations§
Source§impl Tool
impl Tool
Sourcepub fn new<F, Fut>(
name: impl Into<String>,
description: impl Into<String>,
input_schema: Value,
handler: F,
) -> Self
pub fn new<F, Fut>( name: impl Into<String>, description: impl Into<String>, input_schema: Value, handler: F, ) -> Self
Create a new tool with flexible schema definition.
This constructor handles schema conversion automatically, accepting multiple formats:
§Schema Formats
§1. Simple Type Notation
{
"location": "string",
"temperature": "number"
}All parameters are marked as required by default.
§2. Extended Property Schema
{
"query": {
"type": "string",
"description": "Search query"
},
"limit": {
"type": "integer",
"optional": true
}
}Use "optional": true or "required": false to mark parameters as optional.
§3. Full JSON Schema
{
"type": "object",
"properties": {
"name": {"type": "string"}
},
"required": ["name"]
}Already valid JSON Schema - passed through as-is.
§Handler Requirements
The handler must satisfy several trait bounds:
Fn(Value) -> Fut: Takes JSON arguments, returns a futureSend + Sync: Can be shared across threads safely'static: No non-static references (must own all data)Fut: Future<Output = Result<Value>> + Send: Future is sendable and produces Result
The constructor automatically wraps the handler in Arc<...> and boxes the futures,
so you don’t need to do this manually.
§Generic Parameters
F: The handler function typeFut: The future type returned by the handler
These are inferred automatically from the handler you provide.
§Examples
§Simple Calculator Tool
use open_agent::Tool;
use serde_json::json;
let add_tool = Tool::new(
"add",
"Add two numbers together",
json!({
"a": "number",
"b": "number"
}),
|args| {
Box::pin(async move {
let a = args.get("a")
.and_then(|v| v.as_f64())
.ok_or_else(|| open_agent::Error::invalid_input("Parameter 'a' must be a number"))?;
let b = args.get("b")
.and_then(|v| v.as_f64())
.ok_or_else(|| open_agent::Error::invalid_input("Parameter 'b' must be a number"))?;
Ok(json!({"result": a + b}))
})
}
);§Tool with Optional Parameters
use open_agent::Tool;
use serde_json::json;
let search_tool = Tool::new(
"search",
"Search for information",
json!({
"query": {
"type": "string",
"description": "What to search for"
},
"max_results": {
"type": "integer",
"description": "Maximum results to return",
"optional": true,
"default": 10
}
}),
|args| Box::pin(async move {
let query = args["query"].as_str().unwrap_or("");
let max = args.get("max_results")
.and_then(|v| v.as_i64())
.unwrap_or(10);
// Perform search...
Ok(json!({"results": [], "query": query, "limit": max}))
})
);§Tool with External State
use open_agent::Tool;
use serde_json::json;
use std::sync::Arc;
// State that needs to be shared
let api_key = Arc::new("secret-key".to_string());
let tool = Tool::new(
"api_call",
"Make an API call",
json!({"endpoint": "string"}),
move |args| {
// Clone Arc to move into async block
let api_key = api_key.clone();
Box::pin(async move {
let endpoint = args["endpoint"].as_str().unwrap_or("");
// Use api_key in async operation
println!("Calling {} with key {}", endpoint, api_key);
Ok(json!({"status": "success"}))
})
}
);Sourcepub async fn execute(&self, arguments: Value) -> Result<Value>
pub async fn execute(&self, arguments: Value) -> Result<Value>
Execute the tool with the provided arguments.
This method invokes the tool’s handler asynchronously, passing the arguments and awaiting the result. It’s the primary way to run a tool’s logic.
§Execution Flow
- Call the handler function (stored in
Arc) with arguments - The handler returns a
Pin<Box<dyn Future>> - Await the future to get the
Result<Value> - Return the result (success value or error)
§Arguments
Arguments should be a JSON object matching the tool’s input_schema:
{
"param1": "value1",
"param2": 42
}The handler is responsible for extracting and validating these arguments.
§Error Handling
If the handler returns an error, it’s propagated directly. The agent calling this method should handle errors appropriately (e.g., retry logic, error reporting to the LLM).
§Examples
let calculator = Tool::new(
"add",
"Add numbers",
json!({"a": "number", "b": "number"}),
|args| Box::pin(async move {
let sum = args["a"].as_f64().unwrap() + args["b"].as_f64().unwrap();
Ok(json!({"result": sum}))
})
);
// Execute the tool
let result = calculator.execute(json!({"a": 5.0, "b": 3.0})).await?;
assert_eq!(result["result"], 8.0);Sourcepub fn to_openai_format(&self) -> Value
pub fn to_openai_format(&self) -> Value
Convert the tool definition to OpenAI’s function calling format.
This method generates the JSON structure expected by OpenAI’s Chat Completion API when using function calling. The format is also compatible with other LLM providers that follow OpenAI’s conventions.
§Output Format
Returns a JSON structure like:
{
"type": "function",
"function": {
"name": "tool_name",
"description": "Tool description",
"parameters": {
"type": "object",
"properties": { ... },
"required": [ ... ]
}
}
}§Usage in API Calls
This format is typically used when constructing the tools array for
API requests:
{
"model": "gpt-4",
"messages": [...],
"tools": [
// Output of to_openai_format() for each tool
]
}§Examples
let my_tool = tool("search", "Search for information")
.param("query", "string")
.build(|_| async { Ok(json!({})) });
let openai_format = my_tool.to_openai_format();
// Verify the structure
assert_eq!(openai_format["type"], "function");
assert_eq!(openai_format["function"]["name"], "search");
assert_eq!(openai_format["function"]["description"], "Search for information");
assert!(openai_format["function"]["parameters"].is_object());Sourcepub fn description(&self) -> &str
pub fn description(&self) -> &str
Returns the tool’s description.
Sourcepub fn input_schema(&self) -> &Value
pub fn input_schema(&self) -> &Value
Returns a reference to the tool’s input schema.
Trait Implementations§
Source§impl Debug for Tool
Custom Debug implementation for Tool.
impl Debug for Tool
Custom Debug implementation for Tool.
The handler field is omitted from debug output because:
- Function pointers/closures don’t have meaningful debug representations
- The
Arc<dyn Fn...>type is complex and not useful to display - Showing the handler would just print something like “Arc { … }”
Only the metadata fields (name, description, input_schema) are shown, which are the most useful for debugging tool definitions.