open_agent/tools/factory.rs
1/// Create a tool using the builder pattern (convenience function).
2///
3/// This is the recommended way to create tools. It returns a [`ToolBuilder`] that
4/// allows you to fluently configure the tool's schema and handler.
5///
6/// ## Typical Usage Pattern
7///
8/// ```text
9/// tool(name, description)
10/// .param(name, type) // Add parameters (optional, can repeat)
11/// .build(handler) // Provide handler and create Tool
12/// ```
13///
14/// ## Why Use This Instead of Tool::new?
15///
16/// - **More readable**: The builder pattern reads like natural language
17/// - **Incremental schema building**: Add parameters one at a time
18/// - **Flexible**: Can conditionally add parameters or use `.schema()` for complex cases
19/// - **Type-safe**: Method chaining ensures you can't forget the handler
20///
21/// ## Parameters
22///
23/// - `name`: Unique identifier for the tool (snake_case recommended)
24/// - `description`: Human-readable explanation of what the tool does
25///
26/// Both parameters accept any type that implements `Into<String>`, so you can
27/// pass string literals, `String` values, or anything else convertible to String.
28///
29/// ## Examples
30///
31/// ### Basic Calculator Tool
32///
33/// ```rust,no_run
34/// use open_agent::tool;
35/// use serde_json::json;
36///
37/// let add_tool = tool("add", "Add two numbers")
38/// .param("a", "number")
39/// .param("b", "number")
40/// .build(|args| async move {
41/// let a = args.get("a")
42/// .and_then(|v| v.as_f64())
43/// .ok_or_else(|| open_agent::Error::invalid_input("Parameter 'a' must be a number"))?;
44/// let b = args.get("b")
45/// .and_then(|v| v.as_f64())
46/// .ok_or_else(|| open_agent::Error::invalid_input("Parameter 'b' must be a number"))?;
47/// Ok(json!({"result": a + b}))
48/// });
49/// ```
50///
51/// ### Tool with External HTTP Client
52///
53/// ```rust,no_run
54/// use open_agent::{tool, Error};
55/// use serde_json::json;
56/// # use std::sync::Arc;
57///
58/// // Shared HTTP client (example - use your actual HTTP client)
59/// # struct HttpClient;
60/// # impl HttpClient {
61/// # fn new() -> Self { HttpClient }
62/// # async fn get(&self, url: &str) -> Result<String, Box<dyn std::error::Error>> {
63/// # Ok("response".to_string())
64/// # }
65/// # }
66/// let http_client = Arc::new(HttpClient::new());
67///
68/// let fetch_tool = tool("fetch_url", "Fetch content from a URL")
69/// .param("url", "string")
70/// .build(move |args| {
71/// let client = http_client.clone();
72/// async move {
73/// let url = args["url"].as_str().unwrap_or("");
74/// let content = client.get(url).await
75/// .map_err(|e| Error::tool(format!("Failed to fetch: {}", e)))?;
76/// Ok(json!({"content": content}))
77/// }
78/// });
79/// ```
80///
81/// ### Tool with Complex Schema
82///
83/// ```rust,no_run
84/// use open_agent::tool;
85/// use serde_json::json;
86///
87/// let search_tool = tool("search", "Search for information")
88/// .schema(json!({
89/// "query": {
90/// "type": "string",
91/// "description": "Search query"
92/// },
93/// "filters": {
94/// "type": "object",
95/// "description": "Optional filters",
96/// "optional": true,
97/// "properties": {
98/// "date_from": {"type": "string"},
99/// "date_to": {"type": "string"}
100/// }
101/// },
102/// "max_results": {
103/// "type": "integer",
104/// "default": 10,
105/// "optional": true
106/// }
107/// }))
108/// .build(|args| async move {
109/// // Implementation
110/// Ok(json!({"results": []}))
111/// });
112/// ```
113///
114/// ### Conditional Parameter Addition
115///
116/// ```rust,no_run
117/// use open_agent::tool;
118/// use serde_json::json;
119///
120/// # let enable_advanced = true;
121/// let mut builder = tool("process", "Process data")
122/// .param("input", "string");
123///
124/// // Conditionally add parameters
125/// if enable_advanced {
126/// builder = builder.param("advanced_mode", "boolean");
127/// }
128///
129/// let my_tool = builder.build(|args| async move {
130/// Ok(json!({"status": "processed"}))
131/// });
132/// ```
133///
134/// ### Integration with Agent
135///
136/// ```rust,no_run
137/// use open_agent::{Client, AgentOptions, tool};
138/// use serde_json::json;
139///
140/// # async fn example() -> open_agent::Result<()> {
141/// let weather_tool = tool("get_weather", "Get weather for a location")
142/// .param("location", "string")
143/// .build(|args| async move {
144/// Ok(json!({"temp": 72, "conditions": "sunny"}))
145/// });
146///
147/// let options = AgentOptions::builder()
148/// .model("gpt-4")
149/// .base_url("http://localhost:1234/v1")
150/// .tool(weather_tool)
151/// .build()?;
152///
153/// let client = Client::new(options)?;
154/// // Client can now use the tool when responding to queries
155/// # Ok(())
156/// # }
157/// ```
158///
159/// ## See Also
160///
161/// - [`Tool::new`] - Direct constructor if you prefer not using the builder
162/// - [`ToolBuilder`] - The builder type returned by this function
163/// - [`Tool`] - The final tool type produced by `.build()`
164pub fn tool(name: impl Into<String>, description: impl Into<String>) -> ToolBuilder {
165 ToolBuilder::new(name, description)
166}