4.2 ToolRegistry 工具注册表

`ToolRegistry` 是 RAF 工具系统的中央注册中心。它管理工具的生命周期(注册、查找、列表),是 AgentBuilder 和 FunctionInvokingChatClient 之间的桥梁。

数据结构

/// ToolRegistry — 管理工具注册和查找,遵循 MAF 模式。
pub struct ToolRegistry {
    tools: HashMap<String, Arc<dyn ITool>>,
}

**设计要点:**

- 使用 `HashMap<String, Arc<dyn ITool>>`——键是工具名称(`ITool::name()`),值是多所有权智能指针,支持跨组件共享 - `Arc<dyn ITool>` 是 trait object,通过虚表调用,允许注册任意实现了 `ITool` 的类型 - 名称是唯一键:后注册的工具会覆盖同名的先注册工具

完整 API

impl ToolRegistry {
    /// 创建空的工具注册表
    pub fn new() -> Self {
        Self { tools: HashMap::new() }
    }

    /// 注册具体类型的工具(自动包装为 Arc)
    pub fn register(&mut self, tool: impl ITool + 'static) {
        self.tools.insert(tool.name().to_string(), Arc::new(tool));
    }

    /// 注册已经包装为 Arc 的工具
    pub fn register_arc(&mut self, tool: Arc<dyn ITool>) {
        self.tools.insert(tool.name().to_string(), tool);
    }

    /// 根据名称查找工具
    pub fn get(&self, name: &str) -> Option<&Arc<dyn ITool>> {
        self.tools.get(name)
    }

    /// 获取所有已注册工具的列表
    pub fn list(&self) -> Vec<&Arc<dyn ITool>> {
        self.tools.values().collect()
    }

    /// 返回已注册工具的数量
    pub fn len(&self) -> usize {
        self.tools.len()
    }

    /// 检查注册表是否为空
    pub fn is_empty(&self) -> bool {
        self.tools.is_empty()
    }
}

impl Default for ToolRegistry {
    fn default() -> Self {
        Self::new()
    }
}

方法详解

| 方法 | 签名 | 说明 | |------|------|------| | `register()` | `fn register(&mut self, tool: impl ITool + 'static)` | 接收任何具体类型的工具,内部用 `Arc::new()` 包装。`+ 'static` 约束确保类型不包含非静态引用。 |

**为什么有两个注册方法?**

- `register()`:用于注册具体类型(编译期已知大小),如 `registry.register(ReadFile { scope: None })` - `register_arc()`:用于注册已包装的工具,如 `registry.register_arc(approval_tool)`(当工具已通过装饰器处理)

两者最终都维护 `HashMap<String, Arc<dyn ITool>>`,仅在参数类型上不同。

使用示例

基本注册与查找

use rust_agent_core::{ToolRegistry, ToolResult};

// 创建注册表
let mut registry = ToolRegistry::new();

// 注册内置工具
registry.register(ReadFile { scope: None });
registry.register(WriteFile { scope: None });
registry.register(RunCommand { scope: None, timeout_secs: Some(60) });

assert_eq!(registry.len(), 3);
assert!(!registry.is_empty());

// 按名称查找
let read_tool = registry.get("ReadFile").expect("ReadFile should be registered");
assert_eq!(read_tool.name(), "ReadFile");

// 列出所有工具
for tool in registry.list() {
    println!("Tool: {} — {}", tool.name(), tool.description());
}

AgentBuilder 集成

在 AgentBuilder 中,`with_tool()` 内部调用 `ToolRegistry::register()`:

AgentBuilder::new()
    .with_tool(ReadFile { scope: None })
    .with_tool(WriteFile { scope: None })
    .with_tool(RunCommand { scope: None, timeout_secs: None })
    .build()?;

审批工具注册

审批包装后的工具通过 `register_arc()` 注册:

let mut registry = ToolRegistry::new();

// 直接注册(自动执行)
registry.register(RunCommand {
    scope: None,
    timeout_secs: None,
});

// 审批包装后注册(需人工确认)
let approved = Arc::new(ApprovalRequiredTool::new(Arc::new(RunCommand {
    scope: None,
    timeout_secs: None,
})));
registry.register_arc(approved);

在 Agent 运行流程中的位置

flowchart TD
    AB["AgentBuilder::with_tool()"] --> TR["ToolRegistry::register()"]
    TR -->|"aggregate"| TOOLS["Vec<Arc<dyn ITool>>"]
    TOOLS --> FICC["FunctionInvokingChatClient"]

    FICC -->|"list() for function defs"| TOOLS
    FICC -->|"get(name) on tool_call"| TOOLS
    FICC --> EXEC["execute(arguments)"]

1. **构建阶段**:`AgentBuilder` 将所有 `with_tool()` 调用的工具注册到 `ToolRegistry` 2. **每次 run()**:`FunctionInvokingChatClient` 遍历注册表生成 function definitions 发送给 LLM 3. **工具调用时**:LLM 返回 `tool_calls` 后,框架通过 `get(tool_call.name)` 查找对应工具并调用 `execute()`

线程安全

`ToolRegistry` 使用 `HashMap<String, Arc<dyn ITool>>`:

- `Arc` 提供多所有权,允许多个组件同时持有工具引用 - `ITool: Send + Sync` 约束确保工具可以安全地跨线程共享 - `execute()` 内部自行管理可变状态(如需),框架不做额外同步

关键要点

1. **名称是唯一键**——同一名称的工具后注册会覆盖先注册的,确保工具名全局唯一 2. **Arc trait object 模式**——通过虚表实现多态,所有注册工具对框架是同质的 `Arc<dyn ITool>` 3. **两个注册方法满足不同场景**——具体类型用 `register()`,已包装类型用 `register_arc()` 4. **O(1) 查找**——HashMap 保证按名称查找的高效性