02_with_tools/
02_with_tools.rs

1//! Agent with Custom Tools Example
2//!
3//! This example demonstrates how to create custom tools and register them
4//! with an agent, allowing the agent to use those tools to accomplish tasks.
5//!
6//! Run with: cargo run --example 02_with_tools --manifest-path ceylon/Cargo.toml
7
8use ceylon_next::agent::Agent;
9use ceylon_next::tasks::{OutputData, TaskRequest};
10use ceylon_next::tools::ToolTrait;
11use serde_json::{json, Value};
12
13// Define a custom tool for mathematical calculations
14struct CalculatorTool;
15
16impl ToolTrait for CalculatorTool {
17    fn name(&self) -> String {
18        "calculator".to_string()
19    }
20
21    fn description(&self) -> String {
22        "A calculator tool that can perform basic mathematical operations. \
23         Accepts 'operation' (add, subtract, multiply, divide) and 'numbers' (array of numbers)."
24            .to_string()
25    }
26
27    fn input_schema(&self) -> Value {
28        json!({
29            "type": "object",
30            "properties": {
31                "operation": {
32                    "type": "string",
33                    "description": "The operation to perform: add, subtract, multiply, divide"
34                },
35                "numbers": {
36                    "type": "array",
37                    "items": { "type": "number" },
38                    "description": "Array of numbers to perform the operation on"
39                }
40            },
41            "required": ["operation", "numbers"]
42        })
43    }
44
45    fn execute(&self, input: Value) -> Value {
46        let operation = input["operation"].as_str().unwrap_or("add");
47        let numbers: Vec<f64> = input["numbers"]
48            .as_array()
49            .unwrap_or(&vec![])
50            .iter()
51            .filter_map(|v| v.as_f64())
52            .collect();
53
54        let result = match operation {
55            "add" => numbers.iter().sum(),
56            "subtract" => {
57                numbers.iter().enumerate().fold(0.0, |acc, (i, &num)| {
58                    if i == 0 {
59                        num
60                    } else {
61                        acc - num
62                    }
63                })
64            }
65            "multiply" => numbers.iter().product(),
66            "divide" => {
67                numbers.iter().enumerate().fold(1.0, |acc, (i, &num)| {
68                    if i == 0 {
69                        num
70                    } else {
71                        acc / num
72                    }
73                })
74            }
75            _ => 0.0,
76        };
77
78        json!({
79            "operation": operation,
80            "numbers": numbers,
81            "result": result
82        })
83    }
84}
85
86// Define a custom tool for string manipulation
87struct StringToolTool;
88
89impl ToolTrait for StringToolTool {
90    fn name(&self) -> String {
91        "string_tool".to_string()
92    }
93
94    fn description(&self) -> String {
95        "A string manipulation tool that can uppercase, lowercase, or reverse strings."
96            .to_string()
97    }
98
99    fn input_schema(&self) -> Value {
100        json!({
101            "type": "object",
102            "properties": {
103                "operation": {
104                    "type": "string",
105                    "description": "The operation to perform: uppercase, lowercase, reverse, length"
106                },
107                "text": {
108                    "type": "string",
109                    "description": "The text to manipulate"
110                }
111            },
112            "required": ["operation", "text"]
113        })
114    }
115
116    fn execute(&self, input: Value) -> Value {
117        let operation = input["operation"].as_str().unwrap_or("uppercase");
118        let text = input["text"].as_str().unwrap_or("");
119
120        let result = match operation {
121            "uppercase" => text.to_uppercase(),
122            "lowercase" => text.to_lowercase(),
123            "reverse" => text.chars().rev().collect(),
124            "length" => text.len().to_string(),
125            _ => "Unknown operation".to_string(),
126        };
127
128        json!({
129            "operation": operation,
130            "input": text,
131            "result": result
132        })
133    }
134}
135
136#[tokio::main]
137async fn main() {
138    println!("🤖 Ceylon Agent - Tools Example\n");
139
140    // Step 1: Create an agent
141    let mut agent = Agent::new("MathAssistant", "ollama::gemma3:latest");
142
143    agent.with_system_prompt(
144        "You are a helpful math and text assistant. \
145         Use the available tools to help the user. \
146         Be clear about what tools you're using and why."
147    );
148
149    // Step 2: Register custom tools with the agent
150    println!("🔧 Registering tools...");
151    agent.add_tool(CalculatorTool);
152    agent.add_tool(StringToolTool);
153    println!("✓ Tools registered\n");
154
155    // Step 3: Create a task that requires tool usage
156    let mut task = TaskRequest::new(
157        "Please calculate 25 + 15 + 10, and then tell me the uppercase version of the word 'hello'"
158    );
159    task.with_name("Complex Task")
160        .with_description("A task requiring multiple tools")
161        .with_priority(8);
162
163    // Step 4: Run the agent
164    println!("📋 Task: {}\n", task.id());
165    let response = agent.run(task).await;
166
167    // Step 5: Display the result
168    match response.result() {
169        OutputData::Text(answer) => {
170            println!("📝 Agent Response:\n{}\n", answer);
171        }
172        _ => {
173            println!("❌ Unexpected response type");
174        }
175    }
176
177    println!("✅ Example completed successfully!");
178}