Skip to main content

lc_core/tools/
base.rs

1// src/core/tools/base.rs
2//! Tool base traits.
3//!
4//! Python's BaseTool uses a simplified run(input: str) -> str interface.
5
6use async_trait::async_trait;
7use schemars::JsonSchema;
8use serde::{de::DeserializeOwned, Serialize};
9use serde_json::Value;
10
11/// Base tool trait (object-safe version).
12///
13/// This is the base interface for tool registries and Agents.
14/// Uses string input/output to simplify LLM calls.
15///
16/// All tools must implement this interface to be used by Agents.
17#[async_trait]
18pub trait BaseTool: Send + Sync {
19    /// Returns the tool name.
20    ///
21    /// Name should be unique and clearly express the tool's purpose.
22    fn name(&self) -> &str;
23
24    /// Returns the tool description.
25    ///
26    /// Description should detail the tool's purpose, input format, and output format.
27    fn description(&self) -> &str;
28
29    /// Execute the tool (string version).
30    ///
31    /// This is the primary interface called by Agents.
32    /// Input is typically a JSON string, output is the execution result.
33    ///
34    /// # Arguments
35    /// * `input` - Tool input (typically JSON-formatted string).
36    ///
37    /// # Returns
38    /// String representation of execution result.
39    async fn run(&self, input: String) -> Result<String, ToolError>;
40
41    /// Returns the input JSON Schema.
42    ///
43    /// Used to describe the tool's input format to the LLM.
44    fn args_schema(&self) -> Option<Value> {
45        None
46    }
47
48    /// Whether to return result directly to user.
49    ///
50    /// If true, tool output is returned directly to user, not passed to Agent.
51    fn return_direct(&self) -> bool {
52        false
53    }
54
55    /// Handle execution error.
56    ///
57    /// Returns a friendly error message when tool execution fails.
58    async fn handle_error(&self, error: ToolError) -> String {
59        format!("Tool '{}' execution failed: {}", self.name(), error)
60    }
61}
62
63/// Generic tool trait (type-safe version).
64///
65/// For scenarios requiring type-safe input/output.
66/// Tools implementing this trait can be automatically wrapped as BaseTool.
67#[async_trait]
68pub trait Tool: Send + Sync {
69    /// Input type (must support deserialization and JSON Schema).
70    type Input: DeserializeOwned + JsonSchema + Send + Sync + 'static;
71
72    /// Output type (must support serialization).
73    type Output: Serialize + Send + Sync;
74
75    /// Execute the tool.
76    ///
77    /// # Arguments
78    /// * `input` - Tool input.
79    ///
80    /// # Returns
81    /// Tool output.
82    async fn invoke(&self, input: Self::Input) -> Result<Self::Output, ToolError>;
83
84    /// Returns the input JSON Schema.
85    fn args_schema(&self) -> Option<Value> {
86        use schemars::schema_for;
87        serde_json::to_value(schema_for!(Self::Input)).ok()
88    }
89}
90
91/// Tool error type.
92#[derive(Debug, thiserror::Error)]
93pub enum ToolError {
94    /// Input validation error.
95    #[error("Invalid input: {0}")]
96    InvalidInput(String),
97
98    /// Execution error.
99    #[error("Execution failed: {0}")]
100    ExecutionFailed(String),
101
102    /// Timeout.
103    #[error("Timeout: {0} seconds")]
104    Timeout(u64),
105
106    /// Tool not found.
107    #[error("Tool not found: {0}")]
108    ToolNotFound(String),
109
110    /// MCP 传输层错误(经 `MCPToolAdapter` 适配),保留 code/message/data(P1-6)。
111    ///
112    /// 不静默降级为 `ExecutionFailed`,上层可据 `code` 区分连接断开 / 方法不存在 /
113    /// 参数错误等场景。
114    #[error("MCP error [{code}]: {message}")]
115    McpError {
116        code: i32,
117        message: String,
118        data: Option<Value>,
119    },
120}
121
122use super::ToolDefinition;
123
124/// Converts BaseTool to ToolDefinition (for function calling).
125///
126/// # Arguments
127/// * `tool` - Tool implementing BaseTool trait.
128///
129/// # Returns
130/// ToolDefinition for bind_tools().
131///
132/// # Example
133/// ```ignore
134/// use langchainrust::{Calculator, BaseTool, to_tool_definition};
135/// use std::sync::Arc;
136///
137/// let calculator = Calculator::new();
138/// let tool_def = to_tool_definition(&calculator);
139/// ```
140pub fn to_tool_definition(tool: &dyn BaseTool) -> ToolDefinition {
141    ToolDefinition::new(tool.name(), tool.description()).with_parameters(
142        tool.args_schema()
143            .unwrap_or(serde_json::json!({"type": "object"})),
144    )
145}