|
8 | 8 | //! This example simulates an e-commerce API with tools for users, orders, and |
9 | 9 | //! inventory. The ScriptedTool lets an agent compose these in one call. |
10 | 10 |
|
11 | | -use bashkit::{ScriptedTool, Tool, ToolDef, ToolRequest}; |
| 11 | +use bashkit::{ScriptedTool, Tool, ToolRequest}; |
12 | 12 |
|
13 | 13 | #[tokio::main] |
14 | 14 | async fn main() -> anyhow::Result<()> { |
15 | 15 | println!("=== Scripted Tool Demo ===\n"); |
16 | 16 |
|
17 | | - // Build the orchestrator with tool definitions + closures |
| 17 | + // Build the orchestrator with tool definitions + callbacks. |
| 18 | + // In production the callbacks would call real APIs. |
18 | 19 | let mut tool = ScriptedTool::builder("ecommerce_api") |
19 | 20 | .short_description("E-commerce API orchestrator with user, order, and inventory tools") |
20 | | - .tool( |
21 | | - ToolDef::new("get_user", "Fetch user by ID") |
22 | | - .with_schema(serde_json::json!({ |
23 | | - "type": "object", |
24 | | - "properties": { |
25 | | - "id": {"type": "integer", "description": "User ID"} |
26 | | - }, |
27 | | - "required": ["id"] |
28 | | - })), |
29 | | - |args| { |
30 | | - let id = args.param_i64("id").ok_or("missing --id")?; |
31 | | - |
32 | | - let users = [ |
33 | | - (1, "Alice", "alice@example.com", "premium"), |
34 | | - (2, "Bob", "bob@example.com", "basic"), |
35 | | - (3, "Charlie", "charlie@example.com", "premium"), |
36 | | - ]; |
37 | | - |
38 | | - match users.iter().find(|(uid, ..)| *uid == id) { |
39 | | - Some((uid, name, email, tier)) => Ok(format!( |
40 | | - "{{\"id\":{uid},\"name\":\"{name}\",\"email\":\"{email}\",\"tier\":\"{tier}\"}}\n" |
41 | | - )), |
42 | | - None => Err(format!("user {} not found", id)), |
43 | | - } |
44 | | - }, |
45 | | - ) |
46 | | - .tool( |
47 | | - ToolDef::new("list_orders", "List orders for a user") |
48 | | - .with_schema(serde_json::json!({ |
49 | | - "type": "object", |
50 | | - "properties": { |
51 | | - "user_id": {"type": "integer", "description": "User ID"} |
52 | | - }, |
53 | | - "required": ["user_id"] |
54 | | - })), |
55 | | - |args| { |
56 | | - let uid = args.param_i64("user_id").ok_or("missing --user_id")?; |
57 | | - |
58 | | - let orders = match uid { |
59 | | - 1 => r#"[{"order_id":101,"item":"Laptop","qty":1,"price":999.99},{"order_id":102,"item":"Mouse","qty":2,"price":29.99}]"#, |
60 | | - 2 => r#"[{"order_id":201,"item":"Keyboard","qty":1,"price":79.99}]"#, |
61 | | - 3 => r#"[]"#, |
62 | | - _ => return Err(format!("no orders for user {}", uid)), |
63 | | - }; |
64 | | - |
65 | | - Ok(format!("{orders}\n")) |
66 | | - }, |
67 | | - ) |
68 | | - .tool( |
69 | | - ToolDef::new("get_inventory", "Check inventory for an item") |
70 | | - .with_schema(serde_json::json!({ |
71 | | - "type": "object", |
72 | | - "properties": { |
73 | | - "item": {"type": "string", "description": "Item name"} |
74 | | - }, |
75 | | - "required": ["item"] |
76 | | - })), |
77 | | - |args| { |
78 | | - let item = args.param_str("item").ok_or("missing --item")?; |
79 | | - |
80 | | - let stock = match item.to_lowercase().as_str() { |
81 | | - "laptop" => 15, |
82 | | - "mouse" => 142, |
83 | | - "keyboard" => 67, |
84 | | - _ => 0, |
85 | | - }; |
86 | | - |
87 | | - Ok(format!( |
88 | | - "{{\"item\":\"{}\",\"in_stock\":{}}}\n", |
89 | | - item, stock |
90 | | - )) |
91 | | - }, |
92 | | - ) |
93 | | - .tool( |
94 | | - ToolDef::new("create_discount", "Create a discount code") |
95 | | - .with_schema(serde_json::json!({ |
96 | | - "type": "object", |
97 | | - "properties": { |
98 | | - "user_id": {"type": "integer", "description": "User ID"}, |
99 | | - "percent": {"type": "integer", "description": "Discount percentage"} |
100 | | - }, |
101 | | - "required": ["user_id", "percent"] |
102 | | - })), |
103 | | - |args| { |
104 | | - let uid = args.param_i64("user_id").ok_or("missing --user_id")?; |
105 | | - let pct = args.param_i64("percent").ok_or("missing --percent")?; |
106 | | - Ok(format!( |
107 | | - "{{\"code\":\"SAVE{pct}-U{uid}\",\"percent\":{pct},\"user_id\":{uid}}}\n" |
108 | | - )) |
109 | | - }, |
110 | | - ) |
| 21 | + .tool(fakes::get_user_def(), fakes::get_user) |
| 22 | + .tool(fakes::list_orders_def(), fakes::list_orders) |
| 23 | + .tool(fakes::get_inventory_def(), fakes::get_inventory) |
| 24 | + .tool(fakes::create_discount_def(), fakes::create_discount) |
111 | 25 | .env("STORE_NAME", "Bashkit Shop") |
112 | 26 | .build(); |
113 | 27 |
|
@@ -227,3 +141,121 @@ async fn main() -> anyhow::Result<()> { |
227 | 141 | println!("\n=== Demo Complete ==="); |
228 | 142 | Ok(()) |
229 | 143 | } |
| 144 | + |
| 145 | +// --------------------------------------------------------------------------- |
| 146 | +// Fake e-commerce API — tool definitions + handlers |
| 147 | +// |
| 148 | +// In a real application these callbacks would call actual HTTP APIs, databases, |
| 149 | +// etc. They live in a separate module so the main function can focus on the |
| 150 | +// ScriptedTool builder API and the demo scenarios. |
| 151 | +// --------------------------------------------------------------------------- |
| 152 | +mod fakes { |
| 153 | + use bashkit::{ToolArgs, ToolDef}; |
| 154 | + |
| 155 | + // -- get_user -------------------------------------------------------- |
| 156 | + |
| 157 | + pub fn get_user_def() -> ToolDef { |
| 158 | + ToolDef::new("get_user", "Fetch user by ID").with_schema(serde_json::json!({ |
| 159 | + "type": "object", |
| 160 | + "properties": { |
| 161 | + "id": {"type": "integer", "description": "User ID"} |
| 162 | + }, |
| 163 | + "required": ["id"] |
| 164 | + })) |
| 165 | + } |
| 166 | + |
| 167 | + pub fn get_user(args: &ToolArgs) -> Result<String, String> { |
| 168 | + let id = args.param_i64("id").ok_or("missing --id")?; |
| 169 | + |
| 170 | + let users = [ |
| 171 | + (1, "Alice", "alice@example.com", "premium"), |
| 172 | + (2, "Bob", "bob@example.com", "basic"), |
| 173 | + (3, "Charlie", "charlie@example.com", "premium"), |
| 174 | + ]; |
| 175 | + |
| 176 | + match users.iter().find(|(uid, ..)| *uid == id) { |
| 177 | + Some((uid, name, email, tier)) => Ok(format!( |
| 178 | + "{{\"id\":{uid},\"name\":\"{name}\",\"email\":\"{email}\",\"tier\":\"{tier}\"}}\n" |
| 179 | + )), |
| 180 | + None => Err(format!("user {} not found", id)), |
| 181 | + } |
| 182 | + } |
| 183 | + |
| 184 | + // -- list_orders ----------------------------------------------------- |
| 185 | + |
| 186 | + pub fn list_orders_def() -> ToolDef { |
| 187 | + ToolDef::new("list_orders", "List orders for a user").with_schema(serde_json::json!({ |
| 188 | + "type": "object", |
| 189 | + "properties": { |
| 190 | + "user_id": {"type": "integer", "description": "User ID"} |
| 191 | + }, |
| 192 | + "required": ["user_id"] |
| 193 | + })) |
| 194 | + } |
| 195 | + |
| 196 | + pub fn list_orders(args: &ToolArgs) -> Result<String, String> { |
| 197 | + let uid = args.param_i64("user_id").ok_or("missing --user_id")?; |
| 198 | + |
| 199 | + let orders = match uid { |
| 200 | + 1 => { |
| 201 | + r#"[{"order_id":101,"item":"Laptop","qty":1,"price":999.99},{"order_id":102,"item":"Mouse","qty":2,"price":29.99}]"# |
| 202 | + } |
| 203 | + 2 => r#"[{"order_id":201,"item":"Keyboard","qty":1,"price":79.99}]"#, |
| 204 | + 3 => r#"[]"#, |
| 205 | + _ => return Err(format!("no orders for user {}", uid)), |
| 206 | + }; |
| 207 | + |
| 208 | + Ok(format!("{orders}\n")) |
| 209 | + } |
| 210 | + |
| 211 | + // -- get_inventory --------------------------------------------------- |
| 212 | + |
| 213 | + pub fn get_inventory_def() -> ToolDef { |
| 214 | + ToolDef::new("get_inventory", "Check inventory for an item").with_schema( |
| 215 | + serde_json::json!({ |
| 216 | + "type": "object", |
| 217 | + "properties": { |
| 218 | + "item": {"type": "string", "description": "Item name"} |
| 219 | + }, |
| 220 | + "required": ["item"] |
| 221 | + }), |
| 222 | + ) |
| 223 | + } |
| 224 | + |
| 225 | + pub fn get_inventory(args: &ToolArgs) -> Result<String, String> { |
| 226 | + let item = args.param_str("item").ok_or("missing --item")?; |
| 227 | + |
| 228 | + let stock = match item.to_lowercase().as_str() { |
| 229 | + "laptop" => 15, |
| 230 | + "mouse" => 142, |
| 231 | + "keyboard" => 67, |
| 232 | + _ => 0, |
| 233 | + }; |
| 234 | + |
| 235 | + Ok(format!( |
| 236 | + "{{\"item\":\"{}\",\"in_stock\":{}}}\n", |
| 237 | + item, stock |
| 238 | + )) |
| 239 | + } |
| 240 | + |
| 241 | + // -- create_discount ------------------------------------------------- |
| 242 | + |
| 243 | + pub fn create_discount_def() -> ToolDef { |
| 244 | + ToolDef::new("create_discount", "Create a discount code").with_schema(serde_json::json!({ |
| 245 | + "type": "object", |
| 246 | + "properties": { |
| 247 | + "user_id": {"type": "integer", "description": "User ID"}, |
| 248 | + "percent": {"type": "integer", "description": "Discount percentage"} |
| 249 | + }, |
| 250 | + "required": ["user_id", "percent"] |
| 251 | + })) |
| 252 | + } |
| 253 | + |
| 254 | + pub fn create_discount(args: &ToolArgs) -> Result<String, String> { |
| 255 | + let uid = args.param_i64("user_id").ok_or("missing --user_id")?; |
| 256 | + let pct = args.param_i64("percent").ok_or("missing --percent")?; |
| 257 | + Ok(format!( |
| 258 | + "{{\"code\":\"SAVE{pct}-U{uid}\",\"percent\":{pct},\"user_id\":{uid}}}\n" |
| 259 | + )) |
| 260 | + } |
| 261 | +} |
0 commit comments