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)]
95pub enum ToolError {
96 #[error("Invalid input: {0}")]
98 InvalidInput(String),
99
100 #[error("Execution failed: {0}")]
102 ExecutionFailed(String),
103
104 #[error("Timeout: {0} seconds")]
106 Timeout(u64),
107
108 #[error("Tool not found: {0}")]
110 ToolNotFound(String),
111
112 #[error("MCP error [{code}]: {message}")]
117 McpError {
118 code: i32,
119 message: String,
120 data: Option<Value>,
121 },
122}
123
124use super::ToolDefinition;
125
126pub fn to_tool_definition(tool: &dyn BaseTool) -> ToolDefinition {
143 ToolDefinition::new(tool.name(), tool.description()).with_parameters(
144 tool.args_schema()
145 .unwrap_or(serde_json::json!({"type": "object"})),
146 )
147}
148
149#[async_trait]
152impl Runnable<String, String> for Arc<dyn BaseTool> {
153 type Error = LcelError;
154
155 async fn invoke(
156 &self,
157 input: String,
158 _config: Option<RunnableConfig>,
159 ) -> Result<String, LcelError> {
160 BaseTool::run(&**self, input).await.map_err(LcelError::from)
161 }
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167 use std::sync::Arc;
168
169 struct EchoTool;
171
172 #[async_trait]
173 impl BaseTool for EchoTool {
174 fn name(&self) -> &str {
175 "echo"
176 }
177 fn description(&self) -> &str {
178 "回显输入"
179 }
180 async fn run(&self, input: String) -> Result<String, ToolError> {
181 Ok(format!("echo: {input}"))
182 }
183 }
184
185 #[tokio::test]
186 async fn arc_tool_is_runnable() {
187 let tool: Arc<dyn BaseTool> = Arc::new(EchoTool);
188 let result = tool.invoke("hi".to_string(), None).await.unwrap();
189 assert_eq!(result, "echo: hi");
190 }
191
192 #[tokio::test]
193 async fn arc_tool_pipes() {
194 use crate::runnables::{RunnableExt, RunnableLambda};
195
196 let tool: Arc<dyn BaseTool> = Arc::new(EchoTool);
197 let chain = tool.pipe(RunnableLambda::new_sync(|s: String| s.to_uppercase()));
198 let result = chain.invoke("hi".to_string(), None).await.unwrap();
199 assert_eq!(result, "ECHO: HI");
200 }
201
202 #[tokio::test]
203 async fn arc_tool_error_maps_to_lcel() {
204 struct FailingTool;
205 #[async_trait]
206 impl BaseTool for FailingTool {
207 fn name(&self) -> &str {
208 "fail"
209 }
210 fn description(&self) -> &str {
211 "总是失败"
212 }
213 async fn run(&self, _input: String) -> Result<String, ToolError> {
214 Err(ToolError::ExecutionFailed("boom".to_string()))
215 }
216 }
217
218 let tool: Arc<dyn BaseTool> = Arc::new(FailingTool);
219 let err = tool.invoke("x".to_string(), None).await.unwrap_err();
220 assert!(matches!(err, LcelError::Tool(ref msg) if msg.contains("boom")));
221 }
222}