Files
agcore/docs/5-tool-system.md
T
2026-06-03 23:16:59 +08:00

25 KiB
Raw Blame History

Phase 2: Tool System — 方案设计

定稿日期:2026-06-03

背景与目标

AG Core Phase 0Foundation)已完成 LLM 调用周期基础设施,Phase 1Prompt Engineering)已完成提示词组合与模板化。Phase 2 的目标是补齐工具系统能力,实现 LLM 驱动的工具定义、注册、调用、权限控制,以及 MCP 协议集成。

核心目标:让 LLM 能通过 FinishReason::ToolCalls 触发工具自动执行,并将结果回传至对话上下文,形成完整的"思考 → 调用 → 反馈"闭环。


需求分析

功能需求

模块 需求 验收条件
BaseTool trait 工具抽象接口:名称、描述、参数、执行、权限声明 实现 trait 后可注册到 Registry
ToolRegistry 工具注册、发现、按名称调用 注册 3 个工具后能按名称查找到并执行
McpClient MCP 协议客户端(stdio transport 能启动 MCP 服务器子进程、列出工具、调用工具
PermissionChecker 工具执行前权限校验 禁止无权限的工具执行,返回结构化错误
自动 Tool 循环 LlmCycle 收到 ToolCalls 后自动执行工具并回传 一个包含工具调用的对话能完整执行 2+ 轮
流式 Tool 事件 流式模式下发射 ToolExecutionCompleted 事件 流式调用中工具执行完成后触发对应事件
工具调用历史持久化 自动工具循环产生的 Tool/Assistant 消息正确追加到 messages 查看 cycle.messages() 能获取完整工具交互轨迹

非功能需求

  • 所有公开 API 必须带 /// 文档注释
  • 无新增 unwrap() 调用
  • BaseToolexecute() 必须为 async
  • 工具执行错误必须结构化为 ToolError,不允许 panic
  • MCP 客户端超时默认 30 秒,可配置
  • 自定义工具与 MCP 工具通过同一 ToolRegistry 管理,对 LlmCycle 透明
  • 权限检查在工具执行之前,阻断后返回错误而非静默跳过

方案设计

模块结构

src/
  tools.rs                # tools 模块根:声明子模块 + 重导出公共 API
  tools/
    base.rs               # BaseTool trait — 工具抽象接口
    registry.rs           # ToolRegistry — 工具注册表
    permission.rs         # PermissionChecker — 权限校验器
    mcp.rs                # McpClient — MCP 协议客户端
    error.rs              # ToolError — 工具系统错误类型

tools.rs 根模块声明:

// tools.rs
pub mod base;
pub mod error;
pub mod mcp;
pub mod permission;
pub mod registry;

pub use base::BaseTool;
pub use error::ToolError;
pub use mcp::McpClient;
pub use permission::{Permission, PermissionChecker, PermissionConfig};
pub use registry::{ToolInvocation, ToolRegistry};

lib.rs 添加:

 pub mod llm;
 pub mod prompt;
+pub mod tools;

1. BaseTool trait — 工具抽象接口

// tools/base.rs

use async_trait::async_trait;
use serde_json::Value;

use crate::tools::error::ToolError;
use crate::tools::permission::Permission;

/// 工具抽象接口 —— 所有工具(自定义或 MCP)最终都实现此 trait。
#[async_trait]
pub trait BaseTool: Send + Sync {
    /// 工具名称(唯一标识,用于 LLM 的 tool_calls.name 匹配)。
    fn name(&self) -> &str;

    /// 工具描述(LLM 据此决定是否调用此工具)。
    fn description(&self) -> &str;

    /// 工具参数定义(JSON Schema 格式,传递给 LLM 的 tool.parameters)。
    fn parameters(&self) -> Value;

    /// 声明工具所需的权限列表。
    fn required_permissions(&self) -> Vec<Permission> {
        Vec::new()
    }

    /// 执行工具调用。
    async fn execute(&self, args: Value) -> Result<Value, ToolError>;
}

设计说明

  • name() 返回 &str 而非 String,避免每次调用克隆
  • parameters() 返回 serde_json::Value,与现有 OpenaiToolDefinition.parameters 类型一致
  • required_permissions() 提供默认空实现,简化无敏感操作的工具定义
  • execute() 接收 ValueJSON 对象)作为参数,返回 Value 作为结果,与 OpenAI API 的 arguments/output 格式一致

2. ToolRegistry — 工具注册表

// tools/registry.rs

use std::collections::HashMap;
use std::sync::Arc;

use async_trait::async_trait;
use serde_json::Value;

use crate::llm::types::{OpenaiToolDefinition, ToolDefinition};
use crate::tools::base::BaseTool;
use crate::tools::error::ToolError;
use crate::tools::permission::{Permission, PermissionChecker};

/// 工具调用记录 —— 用于追踪和调试。
pub struct ToolInvocation {
    pub tool_name: String,
    pub input: Value,
    pub output: Result<Value, ToolError>,
}

/// 工具注册表 —— 管理工具注册、发现、调用。
pub struct ToolRegistry {
    tools: HashMap<String, Arc<dyn BaseTool>>,
    permission_checker: Option<Arc<PermissionChecker>>,
}

impl ToolRegistry {
    pub fn new() -> Self;

    /// 设置权限检查器(可选,不设置则不检查权限)。
    pub fn with_permission_checker(mut self, checker: PermissionChecker) -> Self;

    /// 注册一个工具。
    pub fn register(&mut self, tool: Arc<dyn BaseTool>) -> Result<(), ToolError>;

    /// 批量注册工具。
    pub fn register_all(&mut self, tools: Vec<Arc<dyn BaseTool>>) -> Result<(), ToolError>;

    /// 注销一个工具。
    pub fn unregister(&mut self, name: &str) -> Option<Arc<dyn BaseTool>>;

    /// 按名称查找工具。
    pub fn get(&self, name: &str) -> Option<Arc<dyn BaseTool>>;

    /// 获取所有已注册工具的名称列表。
    pub fn list_tools(&self) -> Vec<String>;

    /// 获取所有工具的 ToolDefinition 列表(用于传递给 LLM)。
    pub fn definitions(&self) -> Vec<ToolDefinition>;

    /// 调用一个工具(含权限检查)。
    pub async fn invoke(&self, name: &str, args: Value) -> Result<ToolInvocation, ToolError>;

    /// 批量执行工具调用(并行执行互不依赖的工具)。
    pub async fn invoke_all(
        &self,
        calls: Vec<(String, Value)>,
    ) -> Vec<ToolInvocation>;
}

核心逻辑

  • invoke():查找工具 → 权限检查 → 执行 → 返回 ToolInvocation
  • invoke_all():对多个工具调用并行执行(使用 tokio::join!futures::join_all),适用于 LLM 同时发出多个 tool_calls 的场景
  • definitions():将注册的工具批量转换为 Vec<ToolDefinition>,供 LlmCycle 传递 LLM
  • ToolRegistry 不持有 PermissionChecker 的生命周期(使用 Arc),允许多个 Registry 共享同一个 Checker

使用示例

let mut registry = ToolRegistry::new()
    .with_permission_checker(checker);

registry.register(Arc::new(WeatherTool))?;
registry.register(Arc::new(FileReadTool))?;

// 获取 ToolDefinitions 传递给 LLM
let tools = registry.definitions();

// 收到 LLM 的 tool_calls 后执行
let calls = vec![
    ("get_weather".into(), json!({"city": "Beijing"})),
    ("read_file".into(), json!({"path": "/tmp/data.txt"})),
];
let results = registry.invoke_all(calls).await;

3. PermissionChecker — 权限校验器

// tools/permission.rs

/// 权限级别枚举。
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Permission {
    /// 只读(读取文件、查询数据库等)。
    Read,
    /// 写入(创建/修改文件、插入数据等)。
    Write,
    /// 删除(删除文件、记录等)。
    Delete,
    /// 网络访问(HTTP 请求等)。
    Network,
    /// Shell 命令执行。
    Shell,
    /// 文件系统操作(除读/写/删之外的 FS 操作)。
    FileSystem,
    /// 自定义权限(可通过 namespaced 字符串扩展)。
    Custom(String),
}

/// 权限配置。
pub struct PermissionConfig {
    /// 允许的权限列表(空 = 全部允许)。
    pub allowed: Vec<Permission>,
    /// 拒绝的权限列表(优先级高于 allowed)。
    pub denied: Vec<Permission>,
    /// 是否允许未声明权限的工具执行(默认为 true)。
    pub allow_unspecified: bool,
}

impl Default for PermissionConfig {
    fn default() -> Self {
        Self {
            allowed: vec![Permission::Read, Permission::Network],
            denied: vec![Permission::Delete, Permission::Shell],
            allow_unspecified: true,
        }
    }
}

/// 权限检查器。
pub struct PermissionChecker {
    config: PermissionConfig,
}

impl PermissionChecker {
    pub fn new(config: PermissionConfig) -> Self;

    /// 检查指定权限是否允许执行。
    pub fn check(&self, tool_name: &str, permissions: &[Permission]) -> Result<(), ToolError>;
}

权限判定规则

  1. 如果权限在 denied 中 → 拒绝
  2. 如果权限在 allowed 中 → 允许
  3. 如果 allowed 非空且权限不在其中 → 拒绝(白名单模式)
  4. 如果 allowed 为空 → 按 allow_unspecified 判定

4. McpClient — MCP 协议客户端

MCPModel Context Protocol)是一种基于 JSON-RPC 的协议,用于 LLM 与外部工具系统通信。Phase 2 实现其 最小可行子集,专注于 stdio transport。

// tools/mcp.rs

use std::process::{Child, Command, Stdio};
use std::sync::atomic::{AtomicBool, Ordering};

use serde_json::Value;
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::process::{ChildStdin, ChildStdout, Command as TokioCommand};
use tokio::sync::Mutex;

use crate::tools::base::BaseTool;
use crate::tools::error::ToolError;

/// MCP 协议版本。
const MCP_VERSION: &str = "2025-03-26";

/// MCP 传输方式。
pub enum McpTransport {
    /// 通过子进程 stdin/stdout 通信。
    Stdio {
        command: String,
        args: Vec<String>,
    },
    // /// SSEServer-Sent Events)传输(未来支持)。
    // Sse { url: String },
}

/// MCP 客户端 —— 与 MCP 服务器通信。
pub struct McpClient {
    transport: McpTransport,
    server_name: String,
    /// 已初始化的工具列表(缓存)。
    tools: Vec<McpTool>,
    /// 是否已初始化。
    initialized: AtomicBool,
    /// 超时时间(秒)。
    timeout_secs: u64,
}

/// MCP 服务器暴露的工具(缓存结构)。
struct McpTool {
    name: String,
    description: Option<String>,
    input_schema: Value,
}

impl McpClient {
    /// 创建一个 MCP 客户端。
    pub fn new(server_name: impl Into<String>, transport: McpTransport) -> Self;

    /// 设置超时时间。
    pub fn with_timeout(mut self, secs: u64) -> Self;

    /// 连接并初始化(发送 initialize 请求,获取服务器能力声明)。
    pub async fn connect(&mut self) -> Result<(), ToolError>;

    /// 列出服务器支持的工具(调用 tools/list)。
    pub async fn list_tools(&mut self) -> Result<Vec<ToolDefinition>, ToolError>;

    /// 调用一个工具(调用 tools/call)。
    pub async fn call_tool(&self, name: &str, args: Value) -> Result<Value, ToolError>;

    /// 关闭连接(终止子进程)。
    pub async fn close(&mut self) -> Result<(), ToolError>;

    /// 将 MCP 客户端转换为 BaseTool 适配器列表(用于注册到 ToolRegistry)。
    pub fn into_tools(self) -> Vec<Arc<dyn BaseTool>>;
}

MCP 协议交互流程

客户端                          MCP 服务器
  │                                │
  ├── initialize request ──────►   │
  │   { protocolVersion, capabilities }
  │◄──── initialize response ──   │
  │   { protocolVersion, serverInfo, capabilities }
  │                                │
  ├── initialized notification ──► │
  │                                │
  ├── tools/list ──────────────►   │
  │◄── tools/list result ────────  │
  │   { tools: [{ name, description, inputSchema }] }
  │                                │
  ├── tools/call ─────────────►   │
  │   { name, arguments }
  │◄── tools/call result ────────  │
  │   { content: [{ type, text }] }
  │                                │
  ├── shutdown ───────────────►    │
  │◄── shutdown ─────────────       │

关于 stdio transport 实现

  • 使用 tokio::process::Command 启动子进程
  • stdin 写入 JSON-RPC 请求(每行一个 JSON 对象)
  • stdout 读取 JSON-RPC 响应(使用 BufReader 逐行读取)
  • 每个请求关联一个 id(递增整数),通过 id 匹配请求和响应
  • 进程退出时自动关闭

关于 MCP Server 的管理

  • Phase 2 实现 MCP Server 框架,只实现 Client
  • MCP Server 由外部提供(如 npx @anthropic/mcp-server-filesystem
  • 用户需要提供 MCP Server 的启动命令和参数

5. ToolError — 错误类型

// tools/error.rs

use thiserror::Error;

#[derive(Error, Debug)]
pub enum ToolError {
    #[error("工具 '{0}' 未注册")]
    NotFound(String),

    #[error("工具 '{0}' 执行失败: {1}")]
    ExecutionFailed(String, String),

    #[error("工具 '{0}' 参数无效: {1}")]
    InvalidArguments(String, String),

    #[error("权限被拒绝: 工具 '{0}' 需要 {1:?} 权限")]
    PermissionDenied(String, String),

    #[error("MCP 协议错误: {0}")]
    McpError(String),

    #[error("MCP 未初始化: {0}")]
    McpNotInitialized(String),

    #[error("MCP 超时: {0}")]
    McpTimeout(String),

    #[error("IO 错误: {0}")]
    Io(#[from] std::io::Error),

    #[error("其他错误: {0}")]
    Other(String),
}

6. LlmCycle 扩展 — 自动 Tool 循环

核心设计:在 LlmCycle 中新增 submit_with_tools() 方法,自动处理 tool 执行循环。

// llm/cycle.rs 新增

use crate::tools::registry::ToolRegistry;
use crate::tools::error::ToolError;

impl LlmCycle {
    /// 提交消息并自动处理工具调用循环。
    ///
    /// 流程:
    /// 1. 发送请求(含工具定义)
    /// 2. 检查响应中的 finish_reason
    /// 3. 如果是 ToolCalls → 执行工具 → 回传结果 → 重复 1
    /// 4. 如果是 Stop/Length → 返回最终响应
    pub async fn submit_with_tools(
        &mut self,
        prompt: String,
        registry: &ToolRegistry,
    ) -> Result<ChatResponse, LlmError> {
        let tools = registry.definitions();
        let max_turns = self.config.max_turns.unwrap_or(10);
        let mut turn = 0;

        self.messages.push(OpenaiChatMessage::user_text(prompt));

        loop {
            turn += 1;
            if turn > max_turns {
                return Err(LlmError::Other(format!(
                    "达到最大工具循环轮次 ({})",
                    max_turns
                )));
            }

            // 发送请求
            let response = self.submit_request(&tools).await?;

            // 检查是否需要执行工具
            let should_execute = matches!(
                response.stop_reason,
                Some(FinishReason::ToolCalls) | None
            ) && has_tool_calls(&response.message);

            if !should_execute {
                return Ok(response);
            }

            // 解析 tool_calls 并执行
            let tool_calls = extract_tool_calls(&response.message);
            let results = registry.invoke_all(tool_calls).await;

            // 回传工具结果
            for result in results {
                let content = match &result.output {
                    Ok(value) => serde_json::to_string(value)
                        .unwrap_or_else(|_| "{}".to_string()),
                    Err(e) => format!("错误: {}", e),
                };

                self.messages.push(
                    OpenaiChatMessage::tool_result(result.tool_name.clone(), content)
                );
            }
        }
    }

    /// 内部请求方法(与 submit 共享重试逻辑,但不 push user message)。
    async fn submit_request(
        &mut self,
        tools: &[ToolDefinition],
    ) -> Result<ChatResponse, LlmError> {
        // ... 提取 submit() 中的 request → response 逻辑(不含 user prompt push
    }
}

关键设计决策

决策 选择 理由
循环方式 同步循环(单线程串行) 工具执行依赖前一轮结果,串行更安全
最大轮次 CycleConfig.max_turns,默认 10 防止无限循环(LLM 反复调用工具)
工具并行 invoke_all() 互不依赖的工具并行 LLM 可能一次发出多个 tool_callsparallel_tool_calls
错误处理 工具执行错误以文本回传 LLM,而非终止循环 LLM 可自行从错误中恢复
消息追踪 所有工具交互通过 self.messages 持久化 调用方能通过 cycle.messages() 查看完整轨迹

流式模式支持

submit_stream() 的增强方案:新增 submit_stream_with_tools(),在流式事件层面支持自动 tool 循环。

impl LlmCycle {
    pub async fn submit_stream_with_tools(
        &mut self,
        prompt: String,
        registry: &ToolRegistry,
    ) -> Result<Pin<Box<dyn Stream<Item = StreamEvent> + Send>>, LlmError> {
        // 1. 使用 submit_stream() 获取初始事件流
        // 2. 监听 TurnComplete { reason: ToolCalls }
        // 3. 触发时:通过 ToolRegistry 执行工具
        // 4. 发射 ToolExecutionCompleted 事件
        // 5. 将工具结果注入 messages
        // 6. 自动发起下一轮请求(递归)
        // 7. 直到 finish_reason 为 Stop
    }
}

事件发射时序

submit_stream_with_tools("查天气")
  │
  ├─ AssistantTextDelta "我来查一下北京的天气..."
  ├─ ToolExecutionStarted { tool_name: "get_weather", input: {city:"北京"}, id:"call_1" }
  ├─ TurnComplete { reason: ToolCalls }
  │
  ├── [自动] 执行工具 get_weather({city:"北京"})
  │
  ├─ ToolExecutionCompleted { tool_name: "get_weather", output: {temp:22}, is_error:false }
  │
  ├─ AssistantTextDelta "北京今天 22°C"
  ├─ TurnComplete { reason: Stop }
  │
  └─ (流结束)

7. 自定义工具示例

use agcore::tools::prelude::*;
use serde_json::Value;

struct WeatherTool;

#[async_trait]
impl BaseTool for WeatherTool {
    fn name(&self) -> &str { "get_weather" }
    fn description(&self) -> &str { "获取指定城市的当前天气" }
    fn parameters(&self) -> Value {
        serde_json::json!({
            "type": "object",
            "properties": {
                "city": { "type": "string", "description": "城市名称" }
            },
            "required": ["city"]
        })
    }

    async fn execute(&self, args: Value) -> Result<Value, ToolError> {
        let city = args["city"].as_str()
            .ok_or_else(|| ToolError::InvalidArguments(
                "get_weather".into(), "缺少 city 参数".into()
            ))?;
        // 模拟天气查询
        Ok(serde_json::json!({ "city": city, "temperature": 22, "unit": "°C" }))
    }
}

8. 模块依赖关系

tools/ 模块内部依赖:
  base.rs     → 无内部依赖(Permission 枚举 + ToolError
  permission  → 无内部依赖
  registry    → base, error, permission
  mcp         → base, error(需通过 registry 注册)
  error       → 无内部依赖

跨模块依赖:
  tools/  → llm/types (ToolDefinition 类型)
  llm/cycle → tools/registry (自动 tool 循环)
  llm/cycle → tools/error (ToolError 转换)

实现计划

Step 1: 创建方案文档

创建 docs/5-tool-system.md(即本文档)。

Step 2: ToolError

  • 创建 src/tools/error.rs
  • 定义 ToolError 枚举(NotFound / ExecutionFailed / InvalidArguments / PermissionDenied / McpError / McpTimeout / Io / Other
  • 运行 cargo check 验证

Step 3: Permission

  • 创建 src/tools/permission.rs
  • 定义 Permission 枚举 + PermissionConfig + PermissionChecker
  • 编写权限判定逻辑(白名单/黑名单/未指定策略)
  • 编写 5+ 边界测试覆盖:白名单模式、黑名单模式、空列表、自定义权限冲突
  • 运行 cargo test 验证

Step 4: BaseTool trait

  • 创建 src/tools/base.rs
  • 定义 BaseTool traitname / description / parameters / required_permissions / execute
  • 创建 src/tools.rs 模块根,声明子模块,重导出公共 API
  • lib.rs 添加 pub mod tools;
  • 编写 1 个 MockTool 测试工具并验证 trait 实现
  • 运行 cargo check 验证

Step 5: ToolRegistry

  • 创建 src/tools/registry.rs
  • 定义 ToolInvocation 结构体 + ToolRegistry
  • 实现核心方法:register / get / list / definitions / invoke / invoke_all
  • invoke_all() 使用 futures::future::join_all 并行执行互不依赖的工具
  • definitions()HashMap 中的工具转换为 Vec<ToolDefinition>
  • 编写 8+ 测试覆盖:注册冲突、空注册表查找、单次调用、批量并行调用、工具执行失败
  • 运行 cargo test 验证

Step 6: LlmCycle 扩展(自动 Tool 循环)

  • 新增 cycle_submit.rs 子模块(或直接在 cycle.rs 中扩增,取决于代码量)
  • 提取 submit_request() 内部方法(将 submit() 中的 request→response 逻辑独立)
  • 实现 submit_with_tools() 方法:
    • 循环:submit_request → 检查 finish_reason → 调用 registry.invoke_all → 回传结果
    • max_turns 控制,达到上限返回错误
    • 工具执行错误以文本回传(LLM 可恢复)
  • 实现 submit_stream_with_tools() 方法:
    • 组合流式事件流和自动 tool 循环
    • 在 TurnComplete(ToolCalls) 后发射 ToolExecutionCompleted
  • 更新 CycleConfig 文档注释
  • 编写 3+ 集成测试:单轮 tool 调用、多轮 tool 调用、达到 max_turns 终止
  • 运行 cargo test 验证

Step 7: McpClientMCP 协议客户端)

  • 创建 src/tools/mcp.rs
  • 实现 JSON-RPC 消息结构(Request / Response / Error / Notification
  • 实现 stdio transport
    • connect():启动子进程,发送 initialize 请求
    • list_tools():调用 tools/list,缓存结果
    • call_tool():调用 tools/call,解析响应
    • close():发送 shutdown 请求,终止子进程
  • 实现 into_tools():将 MCP 工具转换为 Vec<Arc<dyn BaseTool>> 适配器
  • 设置 30 秒默认超时
  • 编写 MCP 协议消息序列化/反序列化测试 + 模拟子进程集成测试
  • 运行 cargo test 验证

Step 8: 收尾

  • 更新 docs/roadmap.md 标记 Phase 2 完成
  • cargo clippy — 无警告
  • cargo build — 完整构建
  • 检查所有新公开 API 有 /// 文档注释
  • cargo test — 所有测试通过

术语表

术语 说明
BaseTool 工具抽象接口,所有工具需实现此 trait
ToolRegistry 工具注册表,管理工具注册、发现、调用
ToolInvocation 工具调用记录,包含输入、输出和执行结果
Permission 权限级别枚举(Read/Write/Delete/Network/Shell 等)
PermissionChecker 权限校验器,执行前判定是否允许
McpClient MCP 协议客户端,通过 stdio 与 MCP Server 通信
ToolDefinition 传递给 LLM 的工具定义(同 OpenaiToolDefinition
自动 Tool 循环 LlmCycle 自动执行 LLM 请求的工具调用并回传结果

风险评估

风险 概率 缓解措施
MCP 协议规范变化 只实现最小子集(initialize/list_tools/call_tool),封装在 mcp.rs 中便于适配
MCP 子进程异常退出 实现超时机制 + 错误恢复;进程退出时自动标记为不可用
工具执行死循环(LLM 反复调用工具) max_turns 硬限制 + 检测重复调用模式
JSON-RPC 消息竞争(stdio 双工) 请求和响应通过 id 字段匹配,使用 Mutex 保护写操作
权限配置过于复杂 PermissionConfig 提供合理默认值(允许 Read/Network,拒绝 Delete/Shell),简单场景无需自定义
工具调用参数类型不匹配 execute() 接收 Value,由实现方自行校验;通过 ToolError::InvalidArguments 返回结构化错误

验收标准

  1. cargo check 编译通过
  2. cargo clippy 无警告
  3. 模块文件路径正确:src/tools.rs + src/tools/{base,registry,permission,mcp,error}.rs
  4. BaseTool trait 可被自定义工具实现,name() / description() / parameters() / execute() 四个方法正常工作
  5. ToolRegistry 支持注册、查找、列出、注销操作
  6. ToolRegistry::definitions() 返回正确的 Vec<ToolDefinition>
  7. ToolRegistry::invoke() 执行工具前进行权限检查
  8. ToolRegistry::invoke_all() 并行执行多个工具调用
  9. PermissionChecker 根据配置正确判定权限(白名单/黑名单/默认策略)
  10. LlmCycle::submit_with_tools() 收到 FinishReason::ToolCalls 后自动执行工具并回传结果
  11. LlmCycle::submit_with_tools() 达到 max_turns 上限时终止并返回错误
  12. LlmCycle::submit_stream_with_tools() 在流式模式下发射 ToolExecutionCompleted 事件
  13. 自动 tool 循环产生的 Tool 消息正确追加到 cycle.messages()
  14. McpClient::connect() 能完成 MCP 协议握手(initialize
  15. McpClient::list_tools() 能获取 MCP Server 暴露的工具列表
  16. McpClient::call_tool() 能调用 MCP Server 的工具
  17. McpClient::into_tools() 能生成可供 ToolRegistry 注册的适配器
  18. 所有新公开 API 有文档注释
  19. 测试覆盖率:cargo test 全部通过