1#![allow(clippy::uninlined_format_args)]
2use openai_ergonomic::{
13 builders::chat::tool_function, responses::chat::ToolCallExt, Client, Result,
14};
15use serde::{Deserialize, Serialize};
16use serde_json::json;
17
18#[derive(Debug, Serialize, Deserialize)]
19struct CalculatorParams {
20 operation: String,
21 a: f64,
22 b: f64,
23}
24
25#[derive(Debug, Serialize, Deserialize)]
26struct MemoryParams {
27 key: String,
28 value: Option<String>,
29}
30
31use std::collections::HashMap;
33use std::sync::{Arc, Mutex};
34
35fn get_calculator_tool() -> openai_client_base::models::ChatCompletionTool {
36 tool_function(
37 "calculator",
38 "Perform basic arithmetic operations: add, subtract, multiply, divide",
39 json!({
40 "type": "object",
41 "properties": {
42 "operation": {
43 "type": "string",
44 "enum": ["add", "subtract", "multiply", "divide"],
45 "description": "The arithmetic operation to perform"
46 },
47 "a": {
48 "type": "number",
49 "description": "The first number"
50 },
51 "b": {
52 "type": "number",
53 "description": "The second number"
54 }
55 },
56 "required": ["operation", "a", "b"]
57 }),
58 )
59}
60
61fn get_memory_tool() -> openai_client_base::models::ChatCompletionTool {
62 tool_function(
63 "memory",
64 "Store or retrieve a value from memory. If value is provided, store it. Otherwise, retrieve it.",
65 json!({
66 "type": "object",
67 "properties": {
68 "key": {
69 "type": "string",
70 "description": "The key to store or retrieve"
71 },
72 "value": {
73 "type": "string",
74 "description": "The value to store (omit to retrieve)"
75 }
76 },
77 "required": ["key"]
78 }),
79 )
80}
81
82fn execute_calculator(params: &CalculatorParams) -> String {
83 let result = match params.operation.as_str() {
84 "add" => params.a + params.b,
85 "subtract" => params.a - params.b,
86 "multiply" => params.a * params.b,
87 "divide" => {
88 if params.b == 0.0 {
89 return json!({ "error": "Division by zero" }).to_string();
90 }
91 params.a / params.b
92 }
93 _ => return json!({ "error": "Unknown operation" }).to_string(),
94 };
95
96 json!({
97 "operation": params.operation,
98 "a": params.a,
99 "b": params.b,
100 "result": result
101 })
102 .to_string()
103}
104
105fn execute_memory(params: &MemoryParams, storage: &Arc<Mutex<HashMap<String, String>>>) -> String {
106 let mut store = storage.lock().unwrap();
107
108 if let Some(value) = ¶ms.value {
109 store.insert(params.key.clone(), value.clone());
111 json!({
112 "action": "stored",
113 "key": params.key,
114 "value": value
115 })
116 .to_string()
117 } else {
118 store.get(¶ms.key).map_or_else(
120 || {
121 json!({
122 "action": "not_found",
123 "key": params.key,
124 "message": "Key not found in memory"
125 })
126 .to_string()
127 },
128 |value| {
129 json!({
130 "action": "retrieved",
131 "key": params.key,
132 "value": value
133 })
134 .to_string()
135 },
136 )
137 }
138}
139
140fn execute_tool(
142 tool_name: &str,
143 arguments: &str,
144 storage: &Arc<Mutex<HashMap<String, String>>>,
145) -> Result<String> {
146 match tool_name {
147 "calculator" => {
148 let params: CalculatorParams = serde_json::from_str(arguments)?;
149 Ok(execute_calculator(¶ms))
150 }
151 "memory" => {
152 let params: MemoryParams = serde_json::from_str(arguments)?;
153 Ok(execute_memory(¶ms, storage))
154 }
155 _ => Ok(json!({ "error": format!("Unknown tool: {}", tool_name) }).to_string()),
156 }
157}
158
159async fn handle_tool_loop(
161 client: &Client,
162 mut chat_builder: openai_ergonomic::builders::chat::ChatCompletionBuilder,
163 tools: &[openai_client_base::models::ChatCompletionTool],
164 storage: &Arc<Mutex<HashMap<String, String>>>,
165) -> Result<String> {
166 const MAX_ITERATIONS: usize = 10; let mut iteration = 0;
168
169 loop {
170 iteration += 1;
171 if iteration > MAX_ITERATIONS {
172 return Err(std::io::Error::other("Max iterations reached in tool loop").into());
173 }
174
175 println!("\n [Iteration {}]", iteration);
176
177 let request = chat_builder.clone().tools(tools.to_vec());
179 let response = client.send_chat(request).await?;
180
181 let tool_calls = response.tool_calls();
183 if tool_calls.is_empty() {
184 if let Some(content) = response.content() {
186 return Ok(content.to_string());
187 }
188 return Err(std::io::Error::other("No content in final response").into());
189 }
190
191 println!(" Tool calls: {}", tool_calls.len());
193
194 chat_builder = chat_builder.assistant_with_tool_calls(
197 response.content().unwrap_or(""),
198 tool_calls.iter().map(|tc| (*tc).clone()).collect(),
199 );
200
201 for tool_call in tool_calls {
203 let tool_name = tool_call.function_name();
204 let tool_args = tool_call.function_arguments();
205 let tool_id = tool_call.id();
206
207 println!(" → {}: {}", tool_name, tool_args);
208
209 let result = match execute_tool(tool_name, tool_args, storage) {
210 Ok(result) => {
211 println!(" ✓ Result: {}", result);
212 result
213 }
214 Err(e) => {
215 let error_msg = format!("Error: {}", e);
216 eprintln!(" ✗ {}", error_msg);
217 error_msg
218 }
219 };
220
221 chat_builder = chat_builder.tool(tool_id, result);
223 }
224 }
225}
226
227#[tokio::main]
228async fn main() -> Result<()> {
229 println!("=== Multi-turn Tool Calling Example ===\n");
230
231 let client = Client::from_env()?.build();
233
234 let storage = Arc::new(Mutex::new(HashMap::new()));
236
237 let tools = vec![get_calculator_tool(), get_memory_tool()];
239
240 println!("Available tools:");
241 println!(" - calculator: Perform arithmetic operations");
242 println!(" - memory: Store and retrieve values");
243 println!();
244
245 println!("Example 1: Single Tool Call");
247 println!("User: What is 15 + 27?");
248 {
249 let chat_builder = client
250 .chat()
251 .system("You are a helpful assistant with access to a calculator and memory storage.")
252 .user("What is 15 + 27?");
253
254 let result = handle_tool_loop(&client, chat_builder, &tools, &storage).await?;
255 println!("Assistant: {}", result);
256 }
257
258 println!("\n\nExample 2: Multiple Sequential Tool Calls");
260 println!("User: Calculate 10 * 5 and store the result in memory as 'product'");
261 {
262 let chat_builder = client
263 .chat()
264 .system("You are a helpful assistant with access to a calculator and memory storage.")
265 .user("Calculate 10 * 5 and store the result in memory as 'product'");
266
267 let result = handle_tool_loop(&client, chat_builder, &tools, &storage).await?;
268 println!("Assistant: {}", result);
269 }
270
271 println!("\n\nExample 3: Retrieve from Memory");
273 println!("User: What did I store in 'product'?");
274 {
275 let chat_builder = client
276 .chat()
277 .system("You are a helpful assistant with access to a calculator and memory storage.")
278 .user("What did I store in 'product'?");
279
280 let result = handle_tool_loop(&client, chat_builder, &tools, &storage).await?;
281 println!("Assistant: {}", result);
282 }
283
284 println!("\n\nExample 4: Complex Multi-step Task");
286 println!("User: Calculate 100 / 4, multiply that by 3, and tell me the final result");
287 {
288 let chat_builder = client
289 .chat()
290 .system("You are a helpful assistant with access to a calculator and memory storage.")
291 .user("Calculate 100 / 4, multiply that by 3, and tell me the final result");
292
293 let result = handle_tool_loop(&client, chat_builder, &tools, &storage).await?;
294 println!("Assistant: {}", result);
295 }
296
297 println!("\n\nExample 5: Conversation with History");
299 {
300 let mut chat_builder = client
301 .chat()
302 .system("You are a helpful assistant with access to a calculator and memory storage.");
303
304 println!("User: What is 8 + 7?");
306 chat_builder = chat_builder.user("What is 8 + 7?");
307 let result = handle_tool_loop(&client, chat_builder.clone(), &tools, &storage).await?;
308 println!("Assistant: {}", result);
309
310 chat_builder = chat_builder.assistant(&result);
312
313 println!("\nUser: Now multiply that by 3");
315 chat_builder = chat_builder.user("Now multiply that by 3");
316 let result = handle_tool_loop(&client, chat_builder.clone(), &tools, &storage).await?;
317 println!("Assistant: {}", result);
318 }
319
320 println!("\n\n=== All examples completed successfully ===");
321 println!("\nKey Takeaway:");
322 println!(" When implementing multi-turn tool calling, ALWAYS use");
323 println!(" assistant_with_tool_calls() to maintain proper conversation");
324 println!(" history. This is essential for the model to understand the");
325 println!(" tool results and continue the conversation correctly.");
326
327 Ok(())
328}