Skip to main content

lellm_core/
tool.rs

1//! 工具系统 — 协议层 + 可执行工具描述。
2//!
3//! 本模块定义了工具的基础类型,不依赖任何运行时(tokio/futures)。
4//!
5//! **分层:**
6//! - 协议层:`ToolArgs`, `ToolDefinition`, `ParallelSafety`, `ToolCategory`
7//! - 可执行描述:`ExecutableTool`(定义 + 执行器,但不负责调度/重试/目录)
8//!
9//! `ExecutableTool` 是 `#[tool]` 宏的产物,可在 graph 层直接使用。
10//! 真正的运行时(lookup, dispatch, retry, parallel, snapshot)全部留给 lellm-agent。
11
12use std::borrow::Cow;
13use std::future::Future;
14use std::ops::Deref;
15use std::pin::Pin;
16use std::sync::Arc;
17
18use crate::ToolResult;
19
20// ─── ToolSchema ─────────────────────────────────────────────────
21
22/// 工具参数 Schema — 清洗后的 JSON Schema(不含 $schema, title 等元数据噪音)。
23///
24/// Newtype 封装,预留扩展空间(schema hash、version、validation 等)。
25/// 通过 `Deref` 透明访问内部 `serde_json::Value`。
26#[repr(transparent)]
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub struct ToolSchema(pub serde_json::Value);
29
30impl ToolSchema {
31    pub fn new(value: serde_json::Value) -> Self {
32        Self(value)
33    }
34}
35
36impl Deref for ToolSchema {
37    type Target = serde_json::Value;
38
39    fn deref(&self) -> &Self::Target {
40        &self.0
41    }
42}
43
44impl PartialEq<serde_json::Value> for ToolSchema {
45    fn eq(&self, other: &serde_json::Value) -> bool {
46        &self.0 == other
47    }
48}
49
50impl From<serde_json::Value> for ToolSchema {
51    fn from(value: serde_json::Value) -> Self {
52        Self(value)
53    }
54}
55
56impl From<ToolSchema> for serde_json::Value {
57    fn from(schema: ToolSchema) -> Self {
58        schema.0
59    }
60}
61
62impl serde::Serialize for ToolSchema {
63    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
64    where
65        S: serde::Serializer,
66    {
67        self.0.serialize(serializer)
68    }
69}
70
71impl<'de> serde::Deserialize<'de> for ToolSchema {
72    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
73    where
74        D: serde::Deserializer<'de>,
75    {
76        serde_json::Value::deserialize(deserializer).map(ToolSchema)
77    }
78}
79
80// ─── ToolArgs ───────────────────────────────────────────────────
81
82/// 工具参数 trait — 由 `#[tool]` 宏自动生成。
83///
84/// 实现了此 trait 的结构体,即可通过 `tool_definition()` 方法
85/// 自动获得 `ToolDefinition`(含 JSON Schema)。
86///
87/// # 约束
88/// - `DeserializeOwned` — 可从任意 JSON Value 反序列化
89/// - `JsonSchema` — 可生成参数 Schema
90/// - `Send + Sync + 'static` — 可在异步运行时中安全传递
91///
92/// # 示例
93/// ```ignore
94/// use lellm_derive::tool;
95///
96/// #[tool(name = "search", description = "搜索互联网信息")]
97/// async fn search(query: String, limit: u32) -> String {
98///     format!("results for {}", query)
99/// }
100/// // 生成 SearchArgs struct + search_tool() 工厂函数
101/// ```
102pub trait ToolArgs:
103    serde::de::DeserializeOwned + schemars::JsonSchema + Send + Sync + 'static
104{
105    /// 工具名称(蛇形命名)
106    const NAME: &'static str;
107    /// 工具描述
108    const DESCRIPTION: &'static str;
109    /// JSON Schema(LazyLock 缓存)
110    fn schema() -> ToolSchema;
111
112    /// 从原始 JSON Value 反序列化工具参数。
113    ///
114    /// 默认实现调用 `serde_json::from_value()`,通常无需重写。
115    fn parse(value: serde_json::Value) -> Result<Self, serde_json::Error>
116    where
117        Self: Sized,
118    {
119        serde_json::from_value(value)
120    }
121
122    /// 自动生成 ToolDefinition(含 JSON Schema)。
123    ///
124    /// 默认实现无缓存。`#[tool]` 宏会生成带 `LazyLock` 缓存的覆盖版本。
125    fn tool_definition() -> ToolDefinition {
126        ToolDefinition {
127            name: Self::NAME.to_string(),
128            description: Self::DESCRIPTION.to_string(),
129            parameters: Self::schema().0,
130            cache_control: None,
131        }
132    }
133}
134
135// ─── ParallelSafety ─────────────────────────────────────────────
136
137/// 工具并行安全分级
138#[derive(Debug, Clone, PartialEq, Eq)]
139pub enum ParallelSafety {
140    /// 可并行执行(默认)
141    Safe,
142    /// 同类别内互斥,类别间可并行
143    CategoryExclusive,
144    /// 全局互斥
145    Exclusive,
146}
147
148// ─── ToolCategory ───────────────────────────────────────────────
149
150/// 工具类别 — 用于 `CategoryExclusive` 的分组
151#[derive(Debug, Clone, PartialEq, Eq, Hash)]
152pub struct ToolCategory(pub Cow<'static, str>);
153
154impl ToolCategory {
155    pub const FILE_IO: Self = Self(Cow::Borrowed("file_io"));
156    pub const NETWORK: Self = Self(Cow::Borrowed("network"));
157    pub const DATABASE: Self = Self(Cow::Borrowed("database"));
158
159    pub fn custom(name: impl Into<Cow<'static, str>>) -> Self {
160        Self(name.into())
161    }
162}
163
164// ─── ToolDefinition ─────────────────────────────────────────────
165
166/// 工具定义(纯数据,协议层)。
167///
168/// Schema 由 `schemars` 在编译期生成,经清洗后存入 `parameters` 字段。
169/// Provider 将此结构序列化后发送给 LLM。
170///
171/// **与 `ExecutableTool` 的区别:**
172/// - `ToolDefinition`(core):纯数据,Provider 序列化发送给 LLM
173/// - `ExecutableTool`(core):可执行,Agent 调用时查找并执行
174#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
175pub struct ToolDefinition {
176    /// 工具名称
177    pub name: String,
178    /// 工具描述
179    pub description: String,
180    /// JSON Schema 参数定义
181    pub parameters: serde_json::Value,
182    /// 缓存控制标记。Anthropic 支持 Tool Definition 级别的缓存。
183    #[serde(skip_serializing_if = "Option::is_none")]
184    pub cache_control: Option<crate::message::CacheControl>,
185}
186
187impl ToolDefinition {
188    /// 克隆并设置缓存标记。
189    pub fn with_cache(self, cache: crate::message::CacheControl) -> Self {
190        Self {
191            cache_control: Some(cache),
192            ..self
193        }
194    }
195
196    /// 从 `schemars::JsonSchema` 类型计算并清洗 JSON Schema。
197    ///
198    /// 供 `#[tool]` 宏生成的 `LazyLock` 调用,不在泛型函数中使用 `LazyLock`。
199    ///
200    /// **清洗规则:** 去除 `$schema`, `$id`, `title`, `description` 等根部元数据,
201    /// 保留 `type`, `properties`, `required`, `definitions` 等核心 JSON Schema 字段。
202    pub fn compute_and_clean_schema<S: schemars::JsonSchema>() -> ToolSchema {
203        let root = schemars::schema_for!(S);
204        let val = serde_json::to_value(&root)
205            .expect("Failed to serialize JsonSchema; this is a bug in schemars");
206        ToolSchema(Self::clean_schema(val))
207    }
208
209    /// 清洗 schemars 生成的 RootSchema,去除根部元数据噪音。
210    ///
211    /// 保留 `type`, `properties`, `required`, `definitions`, `additionalProperties`
212    /// 等核心 JSON Schema 字段。Codec 层在此基础上进行 Provider 特定的二次适配。
213    fn clean_schema(mut value: serde_json::Value) -> serde_json::Value {
214        if let Some(obj) = value.as_object_mut() {
215            // 去除标准 JSON Schema 根部的噪声元数据
216            obj.remove("$schema");
217            obj.remove("$id");
218            obj.remove("title");
219            obj.remove("description");
220        }
221        value
222    }
223}
224
225// ─── ToolFn ─────────────────────────────────────────────────────
226
227/// 异步工具执行函数类型 — 接受 JSON 参数,返回 boxed future。
228pub type ToolFn = Arc<
229    dyn Fn(&serde_json::Value) -> Pin<Box<dyn Future<Output = ToolResult> + Send>> + Send + Sync,
230>;
231
232/// 内部辅助 — 将 concrete future coerc 到 trait object。
233///
234/// 用于 `#[tool]` 宏生成的代码,解决 `Box::pin(async move { ... })`
235/// 无法自动 coerc 到 `Pin<Box<dyn Future>>` 的问题。
236#[doc(hidden)]
237pub fn __tool_box<F>(f: F) -> Pin<Box<dyn Future<Output = ToolResult> + Send>>
238where
239    F: Future<Output = ToolResult> + Send + 'static,
240{
241    Box::pin(f)
242}
243
244// ─── ExecutableTool ─────────────────────────────────────────────
245
246/// 可执行的工具 — 定义 + 安全元数据 + 执行器。
247///
248/// **与 `ToolDefinition` 的区别:**
249/// - `ToolDefinition`:纯数据,Provider 序列化发送给 LLM
250/// - `ExecutableTool`:可执行,Agent 调用时查找并执行
251///
252/// **与运行时(lellm-agent)的区别:**
253/// - `ExecutableTool`:描述"这个工具能做什么 + 怎么执行",但不负责调度
254/// - `ToolExecutor` / `ToolCatalog` / `ToolSnapshot`:负责 lookup, dispatch, retry, parallel
255///
256/// 用户通过 `ExecutableTool::safe()` 等工厂方法构造,
257/// 或由 `#[tool]` 宏自动生成。
258#[derive(Clone)]
259pub struct ExecutableTool {
260    /// 工具定义(纯元数据,可被 Provider 序列化)
261    pub definition: ToolDefinition,
262    /// 并行安全级别
263    pub safety: ParallelSafety,
264    /// 工具类别(仅 `CategoryExclusive` 时使用)
265    pub category: Option<ToolCategory>,
266    /// 执行函数(运行时,不被序列化)
267    executor: ToolFn,
268}
269
270impl ExecutableTool {
271    // ─── 访问器 ───────────────────────────────────────────────
272
273    /// 获取工具定义的引用。
274    pub fn definition(&self) -> &ToolDefinition {
275        &self.definition
276    }
277
278    /// 获取并行安全级别。
279    pub fn safety(&self) -> &ParallelSafety {
280        &self.safety
281    }
282
283    /// 获取工具类别(如果有)。
284    pub fn category(&self) -> Option<&ToolCategory> {
285        self.category.as_ref()
286    }
287
288    /// 执行工具调用,返回未来对象。
289    pub fn execute(
290        &self,
291        args: &serde_json::Value,
292    ) -> Pin<Box<dyn Future<Output = ToolResult> + Send>> {
293        (self.executor)(args)
294    }
295
296    // ─── 低层构造 — 接受原始 ToolFn(用于 MCP bridge 等场景) ──
297
298    /// 从原始执行函数构造。
299    ///
300    /// 用于 MCP bridge 等需要直接控制执行函数的场景。
301    pub fn from_fn(
302        def: ToolDefinition,
303        safety: ParallelSafety,
304        category: Option<ToolCategory>,
305        f: ToolFn,
306    ) -> Self {
307        Self {
308            definition: def,
309            safety,
310            category,
311            executor: f,
312        }
313    }
314
315    // ─── 高层构造 — 原始 JSON 输入 ────────────────────────────
316
317    /// 并行安全(Safe)工具注册。
318    pub fn safe<F, Fut>(def: ToolDefinition, f: F) -> Self
319    where
320        F: Fn(&serde_json::Value) -> Fut + Send + Sync + 'static,
321        Fut: Future<Output = ToolResult> + Send + 'static,
322    {
323        Self {
324            definition: def,
325            safety: ParallelSafety::Safe,
326            category: None,
327            executor: Arc::new(move |args: &serde_json::Value| Box::pin(f(args))),
328        }
329    }
330
331    /// 分类内互斥(CategoryExclusive)工具注册。
332    pub fn category_exclusive<F, Fut>(def: ToolDefinition, category: ToolCategory, f: F) -> Self
333    where
334        F: Fn(&serde_json::Value) -> Fut + Send + Sync + 'static,
335        Fut: Future<Output = ToolResult> + Send + 'static,
336    {
337        Self {
338            definition: def,
339            safety: ParallelSafety::CategoryExclusive,
340            category: Some(category),
341            executor: Arc::new(move |args: &serde_json::Value| Box::pin(f(args))),
342        }
343    }
344
345    /// 全局互斥(Exclusive)工具注册。
346    pub fn exclusive<F, Fut>(def: ToolDefinition, f: F) -> Self
347    where
348        F: Fn(&serde_json::Value) -> Fut + Send + Sync + 'static,
349        Fut: Future<Output = ToolResult> + Send + 'static,
350    {
351        Self {
352            definition: def,
353            safety: ParallelSafety::Exclusive,
354            category: None,
355            executor: Arc::new(move |args: &serde_json::Value| Box::pin(f(args))),
356        }
357    }
358
359    // ─── 高层构造 — 强类型输入(自动反序列化) ─────────────────
360
361    /// 强类型便捷构造 — 自动反序列化参数(Safe)。
362    ///
363    /// 闭包接收反序列化后的 `T`,而非原始 `serde_json::Value`。
364    /// 反序列化失败时返回 `ToolErrorKind::InvalidInput`。
365    pub fn safe_fn<T, F, Fut>(def: ToolDefinition, f: F) -> Self
366    where
367        T: ToolArgs + Send + 'static,
368        F: Fn(T) -> Fut + Send + Sync + 'static,
369        Fut: Future<Output = ToolResult> + Send + 'static,
370    {
371        let f = Arc::new(f);
372        Self::safe(def, move |value| {
373            let f = Arc::clone(&f);
374            let result = T::parse(value.clone());
375            async move {
376                match result {
377                    Ok(parsed) => f(parsed).await,
378                    Err(e) => Err(crate::ToolError::invalid_input(format!(
379                        "invalid tool arguments: {e}"
380                    ))),
381                }
382            }
383        })
384    }
385
386    /// 强类型便捷构造 — 自动反序列化参数(CategoryExclusive)。
387    pub fn category_exclusive_fn<T, F, Fut>(
388        def: ToolDefinition,
389        category: ToolCategory,
390        f: F,
391    ) -> Self
392    where
393        T: ToolArgs + Send + 'static,
394        F: Fn(T) -> Fut + Send + Sync + 'static,
395        Fut: Future<Output = ToolResult> + Send + 'static,
396    {
397        let f = Arc::new(f);
398        Self::category_exclusive(def, category, move |value| {
399            let f = Arc::clone(&f);
400            let result = T::parse(value.clone());
401            async move {
402                match result {
403                    Ok(parsed) => f(parsed).await,
404                    Err(e) => Err(crate::ToolError::invalid_input(format!(
405                        "invalid tool arguments: {e}"
406                    ))),
407                }
408            }
409        })
410    }
411
412    /// 强类型便捷构造 — 自动反序列化参数(Exclusive)。
413    pub fn exclusive_fn<T, F, Fut>(def: ToolDefinition, f: F) -> Self
414    where
415        T: ToolArgs + Send + 'static,
416        F: Fn(T) -> Fut + Send + Sync + 'static,
417        Fut: Future<Output = ToolResult> + Send + 'static,
418    {
419        let f = Arc::new(f);
420        Self::exclusive(def, move |value| {
421            let f = Arc::clone(&f);
422            let result = T::parse(value.clone());
423            async move {
424                match result {
425                    Ok(parsed) => f(parsed).await,
426                    Err(e) => Err(crate::ToolError::invalid_input(format!(
427                        "invalid tool arguments: {e}"
428                    ))),
429                }
430            }
431        })
432    }
433}