aagt_core/skills/tool/
mod.rs1use async_trait::async_trait;
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::sync::Arc;
9
10use crate::error::Error;
11
12pub mod code_interpreter;
13pub mod cron;
14pub mod delegation;
15pub mod memory;
16
17pub use cron::CronTool;
18pub use delegation::DelegateTool;
19pub use memory::{RememberThisTool, SearchHistoryTool, TieredSearchTool, FetchDocumentTool};
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct ToolDefinition {
24 pub name: String,
26 pub description: String,
28 pub parameters: serde_json::Value,
30 pub parameters_ts: Option<String>,
32}
33
34#[async_trait]
36pub trait Tool: Send + Sync {
37 fn name(&self) -> String;
40
41 async fn definition(&self) -> ToolDefinition;
43
44 async fn call(&self, arguments: &str) -> anyhow::Result<String>;
46}
47
48#[derive(Clone)]
49pub struct ToolSet {
50 tools: HashMap<String, Arc<dyn Tool>>,
51}
52
53impl Default for ToolSet {
54 fn default() -> Self {
55 Self::new()
56 }
57}
58
59impl ToolSet {
60 pub fn new() -> Self {
62 Self {
63 tools: HashMap::new(),
64 }
65 }
66
67 pub fn add<T: Tool + 'static>(&mut self, tool: T) -> &mut Self {
69 self.tools.insert(tool.name().to_string(), Arc::new(tool));
70 self
71 }
72
73 pub fn add_shared(&mut self, tool: Arc<dyn Tool>) -> &mut Self {
75 self.tools.insert(tool.name().to_string(), tool);
76 self
77 }
78
79 pub fn get(&self, name: &str) -> Option<&Arc<dyn Tool>> {
81 self.tools.get(name)
82 }
83
84 pub fn contains(&self, name: &str) -> bool {
86 self.tools.contains_key(name)
87 }
88
89 pub async fn definitions(&self) -> Vec<ToolDefinition> {
91 let mut defs = Vec::new();
92 for tool in self.tools.values() {
93 defs.push(tool.definition().await);
94 }
95 defs
96 }
97
98 pub async fn call(&self, name: &str, arguments: &str) -> anyhow::Result<String> {
100 let tool = self
101 .tools
102 .get(name)
103 .ok_or_else(|| Error::ToolNotFound(name.to_string()))?;
104
105 tool.call(arguments).await
106 }
107
108 pub fn len(&self) -> usize {
110 self.tools.len()
111 }
112
113 pub fn is_empty(&self) -> bool {
115 self.tools.is_empty()
116 }
117
118 pub fn iter(&self) -> impl Iterator<Item = (&String, &Arc<dyn Tool>)> {
120 self.tools.iter()
121 }
122}
123
124impl crate::agent::context::ContextInjector for ToolSet {
125 fn inject(&self) -> crate::error::Result<Vec<crate::agent::message::Message>> {
126 if self.tools.is_empty() {
127 return Ok(Vec::new());
128 }
129
130 let mut content = String::from("## Tool Definitions (TypeScript)\n\n");
131 content.push_str("You have access to the following tools. Use them to fulfill the user's request.\n\n");
132
133 let mut sorted_tools: Vec<_> = self.tools.iter().collect();
135 sorted_tools.sort_by_key(|(k, _)| *k);
136
137 for (name, tool) in sorted_tools {
138 let def = futures::executor::block_on(tool.definition());
142
143 content.push_str(&format!("### {}\n{}\n", name, def.description));
144 if let Some(ts) = def.parameters_ts {
145 content.push_str("```typescript\n");
146 content.push_str(&ts);
147 if !ts.ends_with('\n') {
148 content.push('\n');
149 }
150 content.push_str("```\n\n");
151 } else {
152 content.push_str("```json\n");
154 content.push_str(&serde_json::to_string_pretty(&def.parameters).unwrap_or_default());
155 content.push_str("\n```\n\n");
156 }
157 }
158
159 Ok(vec![crate::agent::message::Message::system(content)])
160 }
161}
162
163pub struct ToolSetBuilder {
165 tools: Vec<Arc<dyn Tool>>,
166}
167
168impl Default for ToolSetBuilder {
169 fn default() -> Self {
170 Self::new()
171 }
172}
173
174impl ToolSetBuilder {
175 pub fn new() -> Self {
177 Self { tools: Vec::new() }
178 }
179
180 pub fn tool<T: Tool + 'static>(mut self, tool: T) -> Self {
182 self.tools.push(Arc::new(tool));
183 self
184 }
185
186 pub fn shared_tool(mut self, tool: Arc<dyn Tool>) -> Self {
188 self.tools.push(tool);
189 self
190 }
191
192 pub fn build(self) -> ToolSet {
194 let mut toolset = ToolSet::new();
195 for tool in self.tools {
196 toolset.add_shared(tool);
197 }
198 toolset
199 }
200}
201
202#[macro_export]
215macro_rules! simple_tool {
216 (
217 name: $name:expr,
218 description: $desc:expr,
219 parameters: $params:expr,
220 handler: $handler:expr
221 ) => {{
222 struct SimpleTool;
223
224 #[async_trait::async_trait]
225 impl $crate::tool::Tool for SimpleTool {
226 fn name(&self) -> String {
227 $name.to_string()
228 }
229
230 async fn definition(&self) -> $crate::tool::ToolDefinition {
231 $crate::tool::ToolDefinition {
232 name: $name.to_string(),
233 description: $desc.to_string(),
234 parameters: $params,
235 }
236 }
237
238 async fn call(&self, arguments: &str) -> anyhow::Result<String> {
239 let handler = $handler;
240 handler(arguments).await
241 }
242 }
243
244 SimpleTool
245 }};
246}
247
248#[cfg(test)]
249mod tests {
250 use super::*;
251
252 struct EchoTool;
253
254 #[async_trait]
255 impl Tool for EchoTool {
256 fn name(&self) -> String {
257 "echo".to_string()
258 }
259
260 async fn definition(&self) -> ToolDefinition {
261 ToolDefinition {
262 name: "echo".to_string(),
263 description: "Echo back the input".to_string(),
264 parameters: serde_json::json!({
265 "type": "object",
266 "properties": {
267 "message": {
268 "type": "string",
269 "description": "Message to echo"
270 }
271 },
272 "required": ["message"]
273 }),
274 }
275 }
276
277 async fn call(&self, arguments: &str) -> anyhow::Result<String> {
278 #[derive(Deserialize)]
279 struct Args {
280 message: String,
281 }
282 let args: Args = serde_json::from_str(arguments)
283 .map_err(|e| Error::ToolArguments {
284 tool_name: "echo".to_string(),
285 message: e.to_string(),
286 })?;
287 Ok(args.message)
288 }
289 }
290
291 #[tokio::test]
292 async fn test_toolset() {
293 let mut toolset = ToolSet::new();
294 toolset.add(EchoTool);
295
296 assert!(toolset.contains("echo"));
297 assert_eq!(toolset.len(), 1);
298
299 let result = toolset
300 .call("echo", r#"{"message": "hello"}"#)
301 .await
302 .expect("call should succeed");
303 assert_eq!(result, "hello");
304 }
305}