Skip to main content

lellm_agent/runtime/tools/
mod.rs

1//! 工具系统 — 注册、定义、执行、目录抽象。
2//!
3//! 独立的工具子系统,被 runtime 层使用。
4
5mod args;
6mod executor;
7
8pub use args::ToolArgs;
9pub use executor::{
10    BatchExecutionResult, ParallelSafety, ToolCategory, ToolExecutor, ToolRegistration,
11    execute_batch_with,
12};
13
14/// 异步工具函数类型(executor 内部使用)
15pub(crate) type ToolFn = std::sync::Arc<
16    dyn Fn(
17            &serde_json::Value,
18        )
19            -> std::pin::Pin<Box<dyn std::future::Future<Output = lellm_core::ToolResult> + Send>>
20        + Send
21        + Sync,
22>;
23
24// ─── 工具快照 ────────────────────────────────────────────────────
25
26/// 工具快照 — 冻结视图(Frozen View)。
27///
28/// 一旦创建,快照内容不再变化。通过 `version` 区分不同时刻的快照。
29/// `definitions` 通过 `OnceLock` 懒构建——大部分轮次不需要定义列表。
30pub struct ToolSnapshot {
31    version: u64,
32    tools: std::sync::Arc<indexmap::IndexMap<String, ToolRegistration>>,
33    definitions: std::sync::OnceLock<Vec<lellm_core::ToolDefinition>>,
34}
35
36impl ToolSnapshot {
37    /// 从工具映射构建快照。
38    pub fn new(tools: indexmap::IndexMap<String, ToolRegistration>, version: u64) -> Self {
39        Self {
40            version,
41            tools: std::sync::Arc::new(tools),
42            definitions: std::sync::OnceLock::new(),
43        }
44    }
45
46    /// 按名称查找工具注册信息。
47    pub fn get(&self, name: &str) -> Option<&ToolRegistration> {
48        self.tools.get(name)
49    }
50
51    /// 获取所有工具定义(懒构建)。
52    pub fn definitions(&self) -> &[lellm_core::ToolDefinition] {
53        self.definitions
54            .get_or_init(|| self.tools.values().map(|t| t.definition.clone()).collect())
55    }
56
57    /// 是否有工具。
58    pub fn has_tools(&self) -> bool {
59        !self.tools.is_empty()
60    }
61
62    /// 快照版本号。
63    pub fn version(&self) -> u64 {
64        self.version
65    }
66
67    /// 工具数量。
68    pub fn len(&self) -> usize {
69        self.tools.len()
70    }
71
72    /// 是否为空。
73    pub fn is_empty(&self) -> bool {
74        self.tools.is_empty()
75    }
76}
77
78// ─── 工具目录抽象 ────────────────────────────────────────────────
79
80/// 工具目录 — 静态或动态的工具集合。
81///
82/// **设计目标:**
83/// - 让 `ToolExecutor` 不关心工具来源(静态注册 vs MCP 发现)
84/// - 每轮迭代调用 `snapshot()` 一次,固定本轮工具集(避免同轮不一致)
85/// - `ToolRegistration` 必须 `Clone + Send + Sync`(快照在内存中传递)
86///
87/// **快照时机:**
88/// - `ToolUseLoop::execute()` — 每轮迭代开始前调用一次
89/// - `ToolUseLoop::execute_stream()` — 每轮迭代开始前调用一次
90/// - **禁止**在 `execute_batch` 内部调用(会导致同轮工具集漂移)
91#[async_trait::async_trait]
92pub trait ToolCatalog: Send + Sync {
93    /// 获取当前所有工具注册的快照。
94    ///
95    /// 返回的快照在调用瞬间冻结。
96    /// 后续调用可能返回不同的工具集(动态目录刷新)。
97    async fn snapshot(&self) -> std::sync::Arc<ToolSnapshot>;
98}
99
100/// 静态工具目录 — 构建后不可变的工具集合。
101pub struct StaticCatalog {
102    snapshot: std::sync::Arc<ToolSnapshot>,
103}
104
105impl StaticCatalog {
106    /// 从工具注册列表构建静态目录。
107    pub fn from_tools(tools: Vec<ToolRegistration>) -> Self {
108        let mut map = indexmap::IndexMap::with_capacity(tools.len());
109        for reg in tools {
110            map.insert(reg.definition.name.clone(), reg);
111        }
112        Self {
113            snapshot: std::sync::Arc::new(ToolSnapshot::new(map, 0)),
114        }
115    }
116
117    /// 空目录。
118    pub fn empty() -> Self {
119        Self {
120            snapshot: std::sync::Arc::new(ToolSnapshot::new(indexmap::IndexMap::new(), 0)),
121        }
122    }
123}
124
125#[async_trait::async_trait]
126impl ToolCatalog for StaticCatalog {
127    async fn snapshot(&self) -> std::sync::Arc<ToolSnapshot> {
128        self.snapshot.clone()
129    }
130}
131
132/// 冲突解决策略
133#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
134pub enum ConflictPolicy {
135    /// 默认:前面优先级高,同名工具被遮蔽
136    #[default]
137    Shadow,
138    /// 严格模式:冲突即报错
139    Error,
140}
141
142/// 工具冲突详情
143#[derive(Debug, Clone)]
144pub struct CatalogConflict {
145    /// 冲突的工具名称
146    pub tool_name: String,
147    /// 获胜的 catalog 名称(优先级高)
148    pub winner: String,
149    /// 被覆盖的 catalog 名称(优先级低)
150    pub loser: String,
151    /// 使用的冲突策略
152    pub policy: ConflictPolicy,
153}
154
155/// 组合目录构建器
156#[derive(Default)]
157pub struct CompositeCatalogBuilder {
158    sources: Vec<(String, std::sync::Arc<dyn ToolCatalog>)>,
159    conflict_policy: ConflictPolicy,
160}
161
162impl CompositeCatalogBuilder {
163    /// 创建新的构建器
164    pub fn new() -> Self {
165        Self::default()
166    }
167
168    /// 设置冲突策略
169    pub fn conflict_policy(mut self, policy: ConflictPolicy) -> Self {
170        self.conflict_policy = policy;
171        self
172    }
173
174    /// 添加工具源(按优先级从高到低)
175    pub fn add(
176        mut self,
177        name: impl Into<String>,
178        catalog: std::sync::Arc<dyn ToolCatalog>,
179    ) -> Self {
180        self.sources.push((name.into(), catalog));
181        self
182    }
183
184    /// 构建组合目录
185    pub fn build(self) -> CompositeCatalog {
186        let sources: Vec<_> = self.sources.into_iter().map(|(_, c)| c).collect();
187        CompositeCatalog {
188            sources,
189            conflict_policy: self.conflict_policy,
190            version_counter: std::sync::atomic::AtomicU64::new(0),
191            conflicts: std::sync::Mutex::new(Vec::new()),
192        }
193    }
194}
195
196/// 组合目录 — 按优先级合并多个工具源。
197///
198/// **遮蔽策略(Shadowing):** 靠前的源优先级高,同名工具被遮蔽。
199/// 遮蔽发生时通过 `tracing::warn!` 记录结构化日志。
200pub struct CompositeCatalog {
201    sources: Vec<std::sync::Arc<dyn ToolCatalog>>,
202    conflict_policy: ConflictPolicy,
203    version_counter: std::sync::atomic::AtomicU64,
204    conflicts: std::sync::Mutex<Vec<CatalogConflict>>,
205}
206
207impl CompositeCatalog {
208    /// 创建组合目录(Builder 模式)。
209    pub fn builder() -> CompositeCatalogBuilder {
210        CompositeCatalogBuilder::new()
211    }
212
213    /// 创建组合目录(简单模式,默认 Shadow 策略)。
214    ///
215    /// `sources` 按优先级从高到低排列。
216    pub fn new(sources: Vec<std::sync::Arc<dyn ToolCatalog>>) -> Self {
217        Self {
218            sources,
219            conflict_policy: ConflictPolicy::default(),
220            version_counter: std::sync::atomic::AtomicU64::new(0),
221            conflicts: std::sync::Mutex::new(Vec::new()),
222        }
223    }
224
225    /// 获取所有冲突详情
226    pub fn conflicts(&self) -> Vec<CatalogConflict> {
227        self.conflicts.lock().unwrap().clone()
228    }
229}
230
231#[async_trait::async_trait]
232impl ToolCatalog for CompositeCatalog {
233    async fn snapshot(&self) -> std::sync::Arc<ToolSnapshot> {
234        let mut merged = indexmap::IndexMap::new();
235        let mut conflicts = Vec::new();
236
237        // 反向遍历(从低优先级到高优先级),高优先级自然覆盖低优先级
238        for (idx, source) in self.sources.iter().rev().enumerate() {
239            let snap = source.snapshot().await;
240            let snap_tools = &snap.tools;
241            let source_name = format!("source_{}", idx);
242            for (name, tool) in snap_tools.iter() {
243                if merged.contains_key(name) {
244                    tracing::warn!(
245                        tool_name = %name,
246                        "Tool conflict detected in CompositeCatalog. Higher priority tool shadows the lower one."
247                    );
248                    conflicts.push(CatalogConflict {
249                        tool_name: name.clone(),
250                        winner: source_name.clone(),
251                        loser: format!("source_{}", idx + 1),
252                        policy: self.conflict_policy,
253                    });
254                }
255                merged.insert(name.clone(), tool.clone());
256            }
257        }
258
259        // 存储冲突信息
260        if !conflicts.is_empty() {
261            *self.conflicts.lock().unwrap() = conflicts;
262        }
263
264        let version = self
265            .version_counter
266            .fetch_add(1, std::sync::atomic::Ordering::SeqCst)
267            + 1;
268        std::sync::Arc::new(ToolSnapshot::new(merged, version))
269    }
270}