1use crate::runnables::{LcelError, Runnable, RunnableConfig};
7use async_trait::async_trait;
8use schemars::JsonSchema;
9use serde::{de::DeserializeOwned, Serialize};
10use serde_json::Value;
11use std::sync::Arc;
12
13#[async_trait]
20pub trait BaseTool: Send + Sync {
21 fn name(&self) -> &str;
25
26 fn description(&self) -> &str;
30
31 async fn run(&self, input: String) -> Result<String, ToolError>;
42
43 fn args_schema(&self) -> Option<Value> {
47 None
48 }
49
50 fn return_direct(&self) -> bool {
54 false
55 }
56
57 async fn handle_error(&self, error: ToolError) -> String {
61 format!("Tool '{}' execution failed: {}", self.name(), error)
62 }
63}
64
65#[async_trait]
70pub trait Tool: Send + Sync {
71 type Input: DeserializeOwned + JsonSchema + Send + Sync + 'static;
73
74 type Output: Serialize + Send + Sync;
76
77 async fn invoke(&self, input: Self::Input) -> Result<Self::Output, ToolError>;
85
86 fn args_schema(&self) -> Option<Value> {
88 use schemars::schema_for;
89 serde_json::to_value(schema_for!(Self::Input)).ok()
90 }
91}
92
93#[derive(Debug, thiserror::Error)]
95#[non_exhaustive]
96pub enum ToolError {
97 #[error("Invalid input: {0}")]
99 InvalidInput(String),
100
101 #[error("Execution failed: {0}")]
103 ExecutionFailed(String),
104
105 #[error("Timeout: {0} seconds")]
107 Timeout(u64),
108
109 #[error("Tool not found: {0}")]
111 ToolNotFound(String),
112
113 #[error("MCP error [{code}]: {message}")]
118 McpError {
119 code: i32,
121 message: String,
123 data: Option<Value>,
125 },
126}
127
128use super::ToolDefinition;
129
130pub fn to_tool_definition(tool: &dyn BaseTool) -> ToolDefinition {
147 ToolDefinition::new(tool.name(), tool.description()).with_parameters(
148 tool.args_schema()
149 .unwrap_or(serde_json::json!({"type": "object"})),
150 )
151}
152
153#[async_trait]
156impl Runnable<String, String> for Arc<dyn BaseTool> {
157 type Error = LcelError;
158
159 async fn invoke(
160 &self,
161 input: String,
162 _config: Option<RunnableConfig>,
163 ) -> Result<String, LcelError> {
164 BaseTool::run(&**self, input).await.map_err(LcelError::from)
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171 use std::sync::Arc;
172
173 struct EchoTool;
175
176 #[async_trait]
177 impl BaseTool for EchoTool {
178 fn name(&self) -> &str {
179 "echo"
180 }
181 fn description(&self) -> &str {
182 "回显输入"
183 }
184 async fn run(&self, input: String) -> Result<String, ToolError> {
185 Ok(format!("echo: {input}"))
186 }
187 }
188
189 #[tokio::test]
190 async fn arc_tool_is_runnable() {
191 let tool: Arc<dyn BaseTool> = Arc::new(EchoTool);
192 let result = tool.invoke("hi".to_string(), None).await.unwrap();
193 assert_eq!(result, "echo: hi");
194 }
195
196 #[tokio::test]
197 async fn arc_tool_pipes() {
198 use crate::runnables::{RunnableExt, RunnableLambda};
199
200 let tool: Arc<dyn BaseTool> = Arc::new(EchoTool);
201 let chain = tool.pipe(RunnableLambda::new_sync(|s: String| s.to_uppercase()));
202 let result = chain.invoke("hi".to_string(), None).await.unwrap();
203 assert_eq!(result, "ECHO: HI");
204 }
205
206 #[tokio::test]
207 async fn arc_tool_error_maps_to_lcel() {
208 struct FailingTool;
209 #[async_trait]
210 impl BaseTool for FailingTool {
211 fn name(&self) -> &str {
212 "fail"
213 }
214 fn description(&self) -> &str {
215 "总是失败"
216 }
217 async fn run(&self, _input: String) -> Result<String, ToolError> {
218 Err(ToolError::ExecutionFailed("boom".to_string()))
219 }
220 }
221
222 let tool: Arc<dyn BaseTool> = Arc::new(FailingTool);
223 let err = tool.invoke("x".to_string(), None).await.unwrap_err();
224 assert!(matches!(err, LcelError::Tool(ref msg) if msg.contains("boom")));
225 }
226}