1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
use std::error::Error;
use std::string::String;
use async_trait::async_trait;
use serde_json::{json, Value};
use crate::agent::Command;
use crate::error::ToolError;
use super::runtime::ToolRuntime;
#[async_trait]
pub trait Tool: Send + Sync {
/// Returns the name of the tool.
fn name(&self) -> String;
/// Provides a description of what the tool does and when to use it.
fn description(&self) -> String;
/// This are the parametters for OpenAi-like function call.
/// You should return a jsnon like this one
/// ```json
/// {
/// "type": "object",
/// "properties": {
/// "command": {
/// "type": "string",
/// "description": "The raw command you want executed"
/// }
/// },
/// "required": ["command"]
/// }
///
/// If there s no implementation the defaul will be the self.description()
///```
fn parameters(&self) -> Value {
json!({
"type": "object",
"properties": {
"input": {
"type": "string",
"description":self.description()
}
},
"required": ["input"]
})
}
/// Processes an input string and executes the tool's functionality, returning a `Result`.
///
/// This function utilizes `parse_input` to parse the input and then calls `run`.
/// Its used by the Agent
async fn call(&self, input: &str) -> Result<String, ToolError> {
let input = self.parse_input(input).await;
self.run(input).await
}
/// Executes the core functionality of the tool.
///
/// Example implementation:
/// ```rust,ignore
/// async fn run(&self, input: Value) -> Result<String, Box<dyn Error>> {
/// let input_str = input.as_str().ok_or("Input should be a string")?;
/// self.simple_search(input_str).await
/// }
/// ```
async fn run(&self, input: Value) -> Result<String, ToolError>;
/// Executes the tool with runtime access (state, context, store, etc.).
///
/// This method is called when the tool needs access to runtime information.
/// The default implementation calls `run()` for backward compatibility.
///
/// Tools that need runtime access should override this method.
/// Tools can return a `Command` to update agent state.
///
/// Example implementation:
/// ```rust,ignore
/// async fn run_with_runtime(
/// &self,
/// input: Value,
/// runtime: &ToolRuntime,
/// ) -> Result<ToolResult, Box<dyn Error>> {
/// let state = runtime.state().await;
/// let messages = &state.messages;
/// // Use runtime information
/// Ok(ToolResult::Text("result".to_string()))
/// }
/// ```
async fn run_with_runtime(
&self,
input: Value,
_runtime: &ToolRuntime,
) -> Result<ToolResult, Box<dyn Error>> {
// Default implementation calls run() for backward compatibility
let result = self.run(input).await?;
Ok(ToolResult::Text(result))
}
/// Check if this tool requires runtime access.
///
/// Returns `true` if the tool needs access to runtime information
/// (state, context, store, etc.). Default is `false`.
fn requires_runtime(&self) -> bool {
false
}
/// Parses the input string, which could be a JSON value or a raw string, depending on the LLM model.
///
/// Implement this function to extract the parameters needed for your tool. If a simple
/// string is sufficient, the default implementation can be used.
async fn parse_input(&self, input: &str) -> Value {
log::info!("Using default implementation: {}", input);
match serde_json::from_str::<Value>(input) {
Ok(input) => {
if input["input"].is_string() {
Value::String(input["input"].as_str().unwrap().to_string())
} else {
Value::String(input.to_string())
}
}
Err(_) => Value::String(input.to_string()),
}
}
}
/// Result type for tool execution that can return either text or a command.
#[derive(Debug)]
pub enum ToolResult {
/// Simple text result (backward compatible)
Text(String),
/// Result with a command to update state
WithCommand {
text: String,
command: Option<Command>,
},
}
impl ToolResult {
pub fn text(text: String) -> Self {
Self::Text(text)
}
pub fn with_command(text: String, command: Command) -> Self {
Self::WithCommand {
text,
command: Some(command),
}
}
pub fn into_string(self) -> String {
match self {
Self::Text(s) => s,
Self::WithCommand { text, .. } => text,
}
}
}
impl From<String> for ToolResult {
fn from(s: String) -> Self {
Self::Text(s)
}
}