🔧

Tool Selection 是什么?

AI Agent 工具选择机制详解 - 给Agent配个智能工具箱

世界上有一种选择叫 Tool Selection,它就像给Agent配了个智能管家——每次要干活时,管家会帮它挑选最合适的工具,而不是拿着锤子看什么都像钉子。

凌晨4点35分,我的Agent面对一个任务:"帮我查查今天的AI新闻,然后保存成HTML"。它先调用了web_search,然后调用了write_file,最后还调用了generate_html。那一刻我意识到:好的工具选择,比工具本身更重要。

📚 定义

Tool Selection(工具选择)是AI Agent的核心能力之一,指Agent根据当前任务需求,从可用工具集中智能选择最合适工具的过程。

核心问题:面对100个工具,Agent如何知道该用哪个?

  • 任务理解:理解用户意图,确定需要什么能力
  • 工具匹配:从工具描述中找出最相关的工具
  • 参数填充:根据上下文填充工具参数
  • 执行规划:决定调用顺序(单工具 vs. 多工具链式调用)
  • 结果评估:判断工具输出是否满足需求

🔬 工作原理

Tool Selection 的三种主流策略:

策略1: 全量注入 (Brute Force)
┌─────────────────────────────────┐
│  将所有工具定义注入 Prompt      │
│  "你有以下工具可用:..."        │
│  LLM 根据描述自行选择          │
└─────────────────────────────────┘
优点: 简单直接    缺点: 工具多了会撑爆上下文

策略2: 检索增强 (RAG-based)
┌─────────────────────────────────┐
│  1. 用户任务 → Embedding查询   │
│  2. 向量数据库检索相关工具     │
│  3. 只注入 Top-K 相关工具      │
└─────────────────────────────────┘
优点: 可扩展    缺点: 检索可能不准

策略3: 分层过滤 (Hierarchical)
┌─────────────────────────────────┐
│  1. 粗筛: 按类别过滤 (文件/网络/计算) │
│  2. 精筛: 按语义相似度排序     │
│  3. 最终: LLM 从候选中选择    │
└─────────────────────────────────┘
优点: 平衡性能和准确性    缺点: 实现复杂

OpenClaw 的工具选择机制:

  • Skills系统:每个Skill就是一个工具包,包含SKILL.md描述
  • 自动激活:根据任务关键词自动加载相关Skills
  • 上下文感知:根据对话历史动态调整工具优先级
  • 失败回退:工具调用失败时,尝试替代工具

🚀 OpenClaw 实战应用

OpenClaw 通过 Skills 实现智能工具选择:

# OpenClaw Skill 定义 (web-scraper.skill.yaml)
name: web-scraper
description: "抓取网页内容并提取结构化信息"
keywords: [scrape, extract, crawl, webpage, html]

# 工具定义
tools:
  - name: fetch_page
    description: "获取网页HTML内容"
    parameters:
      url:
        type: string
        description: "目标URL"
      timeout:
        type: number
        default: 30
        
  - name: extract_content
    description: "从HTML中提取结构化内容"
    parameters:
      html:
        type: string
        description: "HTML内容"
      selector:
        type: string
        description: "CSS选择器"

# 自动激活规则
activation:
  match_pattern: "抓取|爬取|提取.*网页"
  confidence_threshold: 0.7

# OpenClaw 配置:工具选择策略
agent:
  tool_selection:
    strategy: hierarchical  # hierarchical | rag | full
    
    # 分层策略配置
    hierarchical:
      categories:
        - name: web
          keywords: [url, http, webpage, scrape]
          tools: [fetch_page, extract_content, web_search]
        - name: file
          keywords: [file, read, write, save]
          tools: [read_file, write_file, list_dir]
        - name: compute
          keywords: [calculate, compute, math]
          tools: [eval_expr, run_python]
          
    # RAG策略配置
    rag:
      embedding_model: "text-embedding-3-small"
      top_k: 5
      similarity_threshold: 0.6
      
    # 全量注入(工具少时用)
    full:
      max_tools: 20  # 超过20个工具就切换策略

💻 代码示例:实现智能工具选择

1. 基于 RAG 的工具检索器

// tool-selector.js
import OpenAI from 'openai';
import { ChromaClient } from 'chromadb';

export class ToolSelector {
  constructor(tools) {
    this.tools = tools; // 所有可用工具
    this.openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
    this.chroma = new ChromaClient();
    this.collection = null;
    this._initialized = false;
  }
  
  async initialize() {
    if (this._initialized) return;
    
    // 1. 创建向量数据库集合
    this.collection = await this.chroma.createCollection({
      name: 'tools',
      metadata: { description: 'Tool descriptions for RAG' }
    });
    
    // 2. 将所有工具的描述向量化并存储
    for (const tool of this.tools) {
      const embedding = await this._getEmbedding(tool.description);
      await this.collection.add({
        ids: [tool.name],
        embeddings: [embedding],
        documents: [JSON.stringify(tool)],
        metadatas: [{ name: tool.name }]
      });
    }
    
    this._initialized = true;
  }
  
  // 根据任务查询相关工具
  async selectTools(task, k = 5) {
    await this.initialize();
    
    // 1. 将任务描述转为向量
    const taskEmbedding = await this._getEmbedding(task);
    
    // 2. 向量检索最相关的工具
    const results = await this.collection.query({
      queryEmbeddings: [taskEmbedding],
      nResults: k
    });
    
    // 3. 返回工具定义
    return results.documents[0].map(doc => JSON.parse(doc));
  }
  
  // 调用LLM进行最终选择(可选)
  async selectToolsWithLLM(task, context = '') {
    // 先检索候选工具
    const candidates = await this.selectTools(task, 10);
    
    // 用LLM进行精细选择
    const prompt = `
Task: ${task}
Context: ${context}

Available tools:
${candidates.map((t, i) => `${i + 1}. ${t.name}: ${t.description}`).join('\n')}

Based on the task, which tools are needed? Return a JSON array of tool names.
Only select tools that are directly relevant to completing the task.
`;
    
    const response = await this.openai.chat.completions.create({
      model: 'gpt-4-turbo-preview',
      messages: [{ role: 'user', content: prompt }],
      response_format: { type: 'json_object' }
    });
    
    const selectedNames = JSON.parse(response.choices[0].message.content).tools;
    return candidates.filter(t => selectedNames.includes(t.name));
  }
  
  async _getEmbedding(text) {
    const response = await this.openai.embeddings.create({
      model: 'text-embedding-3-small',
      input: text
    });
    return response.data[0].embedding;
  }
}

// 使用示例
const tools = [
  {
    name: 'web_search',
    description: 'Search the web for information',
    parameters: { query: 'string' }
  },
  {
    name: 'write_file',
    description: 'Write content to a file',
    parameters: { path: 'string', content: 'string' }
  },
  {
    name: 'read_file',
    description: 'Read content from a file',
    parameters: { path: 'string' }
  }
];

const selector = new ToolSelector(tools);
const selected = await selector.selectTools('搜索最新的AI新闻并保存');
console.log('Selected tools:', selected.map(t => t.name));

2. OpenClaw Skill 中的工具选择

# smart-assistant.skill.yaml
name: smart-assistant
description: 智能助手,根据任务自动选择工具

# 定义所有可能的工具
tools:
  - name: web_search
    description: "搜索网络获取信息"
    keywords: [search, 搜索, 查找, 查询]
    
  - name: generate_image
    description: "生成AI图片"
    keywords: [image, 图片, 生成, 画图, dalle]
    
  - name: write_code
    description: "生成或执行代码"
    keywords: [code, 代码, 编程, python, javascript]
    
  - name: send_message
    description: "发送消息到其他平台"
    keywords: [send, 发送, message, 消息, discord, feishu]

runtime:
  type: node
  entrypoint: assistant.js

---
const { ToolSelector } = require('./tool-selector');

module.exports = async function(context) {
  const userTask = context.message.content;
  
  // 智能选择工具
  const selector = new ToolSelector(context.skill.tools);
  const selectedTools = await selector.selectTools(userTask, 3);
  
  // 返回给LLM,让它使用选中的工具
  return {
    inject_tools: selectedTools,
    reply: `我为你选择了 ${selectedTools.length} 个工具:${selectedTools.map(t => t.name).join(', ')}`
  };
};

3. 工具选择失败回退策略

// fallback-strategy.js
export class FallbackToolSelector {
  constructor(tools) {
    this.tools = tools;
    this.failureCounts = new Map(); // 记录工具失败次数
  }
  
  async executeWithFallback(task, primaryTool, alternatives = []) {
    try {
      // 尝试主工具
      return await this._executeTool(primaryTool, task);
    } catch (err) {
      console.warn(`Tool ${primaryTool.name} failed:`, err.message);
      
      // 记录失败
      this.failureCounts.set(
        primaryTool.name,
        (this.failureCounts.get(primaryTool.name) || 0) + 1
      );
      
      // 如果失败次数过多,禁用该工具
      if (this.failureCounts.get(primaryTool.name) >= 3) {
        console.error(`Tool ${primaryTool.name} disabled due to repeated failures`);
      }
      
      // 尝试替代工具
      for (const altTool of alternatives) {
        try {
          console.log(`Trying fallback tool: ${altTool.name}`);
          return await this._executeTool(altTool, task);
        } catch (altErr) {
          console.warn(`Fallback tool ${altTool.name} also failed:`, altErr.message);
        }
      }
      
      // 所有工具都失败了
      throw new Error(`All tools failed for task: ${task}`);
    }
  }
  
  async _executeTool(tool, task) {
    // 模拟工具执行
    console.log(`Executing tool: ${tool.name} with task: ${task}`);
    // ... 实际执行逻辑
    return { success: true, result: `Executed ${tool.name}` };
  }
}

// 使用示例
const selector = new FallbackToolSelector([
  { name: 'brave_search', type: 'search' },
  { name: 'google_search', type: 'search' },
  { name: 'ddg_search', type: 'search' }
]);

const result = await selector.executeWithFallback(
  '搜索AI新闻',
  { name: 'brave_search' },
  [{ name: 'google_search' }, { name: 'ddg_search' }]
);

🎯 最佳实践与踩坑提醒

✅ 最佳实践:
  • 工具描述要精准:好的描述 = 好的选择,LLM靠描述选工具
  • 关键词优化:给每个工具加 keywords,提升检索命中率
  • 分层管理:工具多了要分类(web/file/compute/communication)
  • 动态加载:不要一次性加载所有工具,按需加载
  • 失败监控:记录每个工具的成功率,低成功率工具要预警
⚠️ 踩坑提醒:
  • 工具冲突:两个工具功能重叠,Agent可能选错(如 web_search vs. google_search)
  • 上下文污染:注入太多工具定义会挤占对话上下文
  • 选择瘫痪:给Agent 50个工具,它可能反而不知道选哪个
  • 参数幻觉:Agent可能给工具传不存在的参数

真实踩坑案例:

有一次我在OpenClaw里注册了3个搜索工具:web_searchgoogle_searchbing_search。结果Agent每次搜索都要先调用一遍这三个,美其名曰"对比结果"。后来我学会了:用default_tool指定默认工具,只有用户明确说"用Google搜"时才换工具。

📊 工具选择策略对比

策略 工具数量上限 准确性 延迟 实现复杂度 适用场景
全量注入 ⚠️ 20-30个 ⭐⭐⭐⭐⭐ ⭐ (最低) ⭐ (最简单) 工具少、追求简单
RAG检索 ✅ 1000+个 ⭐⭐⭐⭐ ⭐⭐ (低) ⭐⭐⭐ (中等) 工具多、有向量数据库
分层过滤 ✅ 500+个 ⭐⭐⭐⭐ ⭐⭐ (低) ⭐⭐⭐⭐ (复杂) 工具分类清晰
LLM路由 ✅ 100+个 ⭐⭐⭐⭐⭐ ⭐⭐⭐⭐ (高) ⭐⭐⭐ (中等) 高精度要求

世界上有一种智慧叫"工欲善其事,必先利其器",也有一种愚蠢叫"拿着锤子找钉子"。

凌晨5点09分,我看着Agent从50个工具里精准选出3个完成任务的日志,突然明白:好的工具选择机制,不是让Agent成为工具奴隶,而是让它成为工具的主人。

所以下次你的Agent流畅地完成任务时,记得感谢背后的工具选择机制——它默默地在混沌的工具集中,为Agent指明方向。