Skip to content

工具使用与 Function Calling 🔧

"LLM 的真正力量在于与外部世界的连接。"

1. 工具使用基础

1.1 什么是 Tool Use / Function Calling

Tool Use 让 LLM 能够:

  • 读取外部数据: 文件、数据库、API
  • 执行操作: 发送邮件、创建文件、运行代码
  • 获取实时信息: 天气、股价、搜索结果
传统 LLM:
  用户问 → LLM 回答(仅靠训练知识)

Tool Use LLM:
  用户问 → LLM 决定调用工具 → 执行工具 → LLM 整合结果 → 回答

1.2 工作流程

┌─────────────────────────────────────────────────────────────────┐
│                        Tool Use Flow                             │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  1. 用户发送消息                                                  │
│     ↓                                                            │
│  2. LLM 分析是否需要工具                                          │
│     ├── 不需要 → 直接回答                                         │
│     └── 需要 → 生成 tool_use 响应                                 │
│                ↓                                                 │
│  3. 应用程序执行工具                                              │
│     ↓                                                            │
│  4. 将结果返回给 LLM                                              │
│     ↓                                                            │
│  5. LLM 整合结果,可能需要更多工具                                 │
│     ├── 需要更多 → 回到步骤 3                                     │
│     └── 完成 → 生成最终回答                                       │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘

2. 工具定义

2.1 OpenAI 格式

javascript
const tools = [
  {
    type: "function",
    function: {
      name: "search_codebase",
      description: "在代码库中搜索代码片段。当用户询问代码相关问题时使用。",
      parameters: {
        type: "object",
        properties: {
          query: {
            type: "string",
            description: "搜索查询,如函数名、类名或代码模式"
          },
          file_pattern: {
            type: "string",
            description: "文件名模式,如 '*.tsx' 或 'src/**/*.ts'"
          },
          max_results: {
            type: "integer",
            description: "返回的最大结果数",
            default: 10
          }
        },
        required: ["query"]
      }
    }
  }
];

const response = await openai.chat.completions.create({
  model: "gpt-4o",
  messages: [...],
  tools: tools,
  tool_choice: "auto"  // 或 "required" 强制使用工具
});

2.2 Anthropic 格式

javascript
const tools = [
  {
    name: "search_codebase",
    description: "在代码库中搜索代码片段。当用户询问代码相关问题时使用。",
    input_schema: {
      type: "object",
      properties: {
        query: {
          type: "string",
          description: "搜索查询,如函数名、类名或代码模式"
        },
        file_pattern: {
          type: "string",
          description: "文件名模式,如 '*.tsx' 或 'src/**/*.ts'"
        },
        max_results: {
          type: "integer",
          description: "返回的最大结果数",
          default: 10
        }
      },
      required: ["query"]
    }
  }
];

const response = await anthropic.messages.create({
  model: "claude-sonnet-4-20250514",
  max_tokens: 1024,
  tools: tools,
  messages: [...]
});

2.3 工具描述最佳实践

javascript
// ❌ 差的描述
{
  name: "get_weather",
  description: "获取天气"  // 太简单,模型不知道何时使用
}

// ✅ 好的描述
{
  name: "get_weather",
  description: `获取指定城市的当前天气信息。
  
使用场景:
- 用户询问某个地方的天气
- 用户计划出行需要天气参考
- 用户询问是否需要带伞/外套

返回内容:
- 温度(摄氏度)
- 天气状况(晴/多云/雨等)
- 湿度
- 风速

不适用场景:
- 历史天气查询
- 天气预报(未来多天)`,
  input_schema: {
    type: "object",
    properties: {
      city: {
        type: "string",
        description: "城市名称,如 '北京'、'上海'、'New York'"
      }
    },
    required: ["city"]
  }
}

3. 处理工具调用

3.1 基础处理流程

javascript
async function chat(userMessage) {
  const messages = [{ role: "user", content: userMessage }];
  
  while (true) {
    const response = await anthropic.messages.create({
      model: "claude-sonnet-4-20250514",
      max_tokens: 4096,
      tools: tools,
      messages: messages
    });
    
    // 检查是否结束
    if (response.stop_reason === "end_turn") {
      return response.content;
    }
    
    // 处理工具调用
    if (response.stop_reason === "tool_use") {
      const assistantMessage = { role: "assistant", content: response.content };
      messages.push(assistantMessage);
      
      // 执行所有工具调用
      const toolResults = [];
      for (const block of response.content) {
        if (block.type === "tool_use") {
          const result = await executeToolCall(block.name, block.input);
          toolResults.push({
            type: "tool_result",
            tool_use_id: block.id,
            content: JSON.stringify(result)
          });
        }
      }
      
      messages.push({ role: "user", content: toolResults });
    }
  }
}

async function executeToolCall(name, input) {
  switch (name) {
    case "search_codebase":
      return await searchCodebase(input.query, input.file_pattern);
    case "read_file":
      return await readFile(input.path);
    case "run_command":
      return await runCommand(input.command);
    default:
      throw new Error(`Unknown tool: ${name}`);
  }
}

3.2 并行工具调用

现代 LLM 支持一次响应中调用多个工具:

javascript
// LLM 响应可能包含多个 tool_use
{
  content: [
    { type: "text", text: "让我同时搜索这两个文件..." },
    { type: "tool_use", id: "call_1", name: "read_file", input: { path: "src/App.tsx" } },
    { type: "tool_use", id: "call_2", name: "read_file", input: { path: "src/index.tsx" } }
  ]
}

// 并行执行提高效率
const toolResults = await Promise.all(
  toolUseCalls.map(async (call) => {
    const result = await executeToolCall(call.name, call.input);
    return {
      type: "tool_result",
      tool_use_id: call.id,
      content: JSON.stringify(result)
    };
  })
);

3.3 错误处理

javascript
async function executeToolCall(name, input) {
  try {
    const result = await toolImplementations[name](input);
    return {
      success: true,
      data: result
    };
  } catch (error) {
    // 返回错误信息给 LLM,让它调整策略
    return {
      success: false,
      error: error.message,
      suggestion: getSuggestion(error)  // 可选:提供建议
    };
  }
}

function getSuggestion(error) {
  if (error.code === 'ENOENT') {
    return "文件不存在,请检查路径或先列出目录内容";
  }
  if (error.code === 'EACCES') {
    return "权限不足,请检查文件权限";
  }
  return null;
}

4. 工具设计模式

4.1 CRUD 工具集

javascript
const crudTools = [
  {
    name: "list_files",
    description: "列出目录中的文件和子目录",
    input_schema: {
      type: "object",
      properties: {
        path: { type: "string", description: "目录路径" },
        recursive: { type: "boolean", default: false }
      },
      required: ["path"]
    }
  },
  {
    name: "read_file",
    description: "读取文件内容",
    input_schema: {
      type: "object",
      properties: {
        path: { type: "string" },
        start_line: { type: "integer", description: "起始行(可选)" },
        end_line: { type: "integer", description: "结束行(可选)" }
      },
      required: ["path"]
    }
  },
  {
    name: "write_file",
    description: "写入或覆盖文件",
    input_schema: {
      type: "object",
      properties: {
        path: { type: "string" },
        content: { type: "string" }
      },
      required: ["path", "content"]
    }
  },
  {
    name: "edit_file",
    description: "编辑文件的特定部分",
    input_schema: {
      type: "object",
      properties: {
        path: { type: "string" },
        old_text: { type: "string", description: "要替换的原文本" },
        new_text: { type: "string", description: "新文本" }
      },
      required: ["path", "old_text", "new_text"]
    }
  }
];

4.2 搜索与分析工具

javascript
const searchTools = [
  {
    name: "grep_search",
    description: "使用正则表达式在文件中搜索",
    input_schema: {
      type: "object",
      properties: {
        pattern: { type: "string", description: "正则表达式" },
        path: { type: "string", description: "搜索路径" },
        include: { type: "string", description: "包含的文件模式,如 *.ts" }
      },
      required: ["pattern"]
    }
  },
  {
    name: "semantic_search",
    description: "语义搜索,根据含义查找相关代码",
    input_schema: {
      type: "object",
      properties: {
        query: { type: "string", description: "自然语言查询" },
        top_k: { type: "integer", default: 5 }
      },
      required: ["query"]
    }
  },
  {
    name: "find_references",
    description: "查找符号的所有引用",
    input_schema: {
      type: "object",
      properties: {
        symbol: { type: "string" },
        file: { type: "string", description: "符号定义所在文件" }
      },
      required: ["symbol"]
    }
  }
];

4.3 执行工具

javascript
const execTools = [
  {
    name: "run_terminal_command",
    description: "在终端执行命令",
    input_schema: {
      type: "object",
      properties: {
        command: { type: "string" },
        cwd: { type: "string", description: "工作目录" },
        timeout: { type: "integer", default: 30000 }
      },
      required: ["command"]
    }
  },
  {
    name: "run_tests",
    description: "运行测试",
    input_schema: {
      type: "object",
      properties: {
        test_pattern: { type: "string", description: "测试文件模式" },
        watch: { type: "boolean", default: false }
      }
    }
  }
];

5. 高级模式

5.1 工具选择策略

javascript
const response = await anthropic.messages.create({
  model: "claude-sonnet-4-20250514",
  tools: tools,
  // tool_choice 选项:
  
  // 1. auto - 模型自己决定是否使用工具
  tool_choice: { type: "auto" },
  
  // 2. any - 必须使用某个工具
  tool_choice: { type: "any" },
  
  // 3. tool - 必须使用指定工具
  tool_choice: { type: "tool", name: "search_codebase" },
  
  messages: [...]
});

5.2 工具结果优化

javascript
// ❌ 返回过多信息
const result = await readFile(path);
return result;  // 可能是整个大文件

// ✅ 智能截断
async function readFileOptimized(path, options = {}) {
  const content = await fs.readFile(path, 'utf-8');
  const lines = content.split('\n');
  
  // 如果太长,返回摘要
  if (lines.length > 500) {
    const { start_line = 1, end_line = 100 } = options;
    return {
      total_lines: lines.length,
      showing: `${start_line}-${end_line}`,
      content: lines.slice(start_line - 1, end_line).join('\n'),
      note: `文件有 ${lines.length} 行,只显示了 ${start_line}-${end_line} 行。使用 start_line/end_line 参数查看其他部分。`
    };
  }
  
  return { content, total_lines: lines.length };
}

5.3 工具组合模式

javascript
// 元工具:组合多个基础工具
{
  name: "analyze_component",
  description: "分析 React 组件的完整信息,包括代码、测试和使用情况",
  input_schema: {
    type: "object",
    properties: {
      component_path: { type: "string" }
    },
    required: ["component_path"]
  }
}

async function analyzeComponent(componentPath) {
  // 组合多个操作
  const [code, tests, usages] = await Promise.all([
    readFile(componentPath),
    findTests(componentPath),
    findReferences(getComponentName(componentPath))
  ]);
  
  return {
    code,
    tests: tests.map(t => ({ path: t.path, summary: t.summary })),
    usages: usages.slice(0, 10),  // 限制数量
    metrics: {
      lines: code.split('\n').length,
      imports: extractImports(code).length,
      exports: extractExports(code).length
    }
  };
}

6. 安全考量

6.1 权限控制

javascript
const PERMISSION_LEVELS = {
  read: ['read_file', 'list_files', 'grep_search', 'semantic_search'],
  write: ['write_file', 'edit_file', 'delete_file'],
  execute: ['run_terminal_command', 'run_tests'],
  dangerous: ['run_as_admin', 'modify_system']
};

function filterToolsByPermission(tools, allowedLevel) {
  const allowedTools = new Set();
  
  for (const [level, toolNames] of Object.entries(PERMISSION_LEVELS)) {
    if (shouldAllow(level, allowedLevel)) {
      toolNames.forEach(t => allowedTools.add(t));
    }
  }
  
  return tools.filter(t => allowedTools.has(t.name));
}

6.2 输入验证

javascript
async function executeToolCall(name, input) {
  // 验证工具存在
  if (!toolImplementations[name]) {
    throw new Error(`Unknown tool: ${name}`);
  }
  
  // 路径安全检查
  if (input.path) {
    const resolvedPath = path.resolve(input.path);
    if (!resolvedPath.startsWith(ALLOWED_ROOT)) {
      throw new Error(`Path outside allowed directory: ${input.path}`);
    }
  }
  
  // 命令安全检查
  if (name === 'run_terminal_command') {
    if (DANGEROUS_COMMANDS.some(cmd => input.command.includes(cmd))) {
      throw new Error(`Dangerous command blocked: ${input.command}`);
    }
  }
  
  return await toolImplementations[name](input);
}

const DANGEROUS_COMMANDS = [
  'rm -rf /',
  'sudo rm',
  'mkfs',
  ':(){ :|:& };:',  // fork bomb
  'dd if=',
];

6.3 操作确认

javascript
const REQUIRES_CONFIRMATION = ['write_file', 'delete_file', 'run_terminal_command'];

async function executeWithConfirmation(name, input, confirmFn) {
  if (REQUIRES_CONFIRMATION.includes(name)) {
    const description = describeAction(name, input);
    const confirmed = await confirmFn(description);
    
    if (!confirmed) {
      return { 
        success: false, 
        error: "操作被用户取消",
        action_description: description
      };
    }
  }
  
  return await executeToolCall(name, input);
}

function describeAction(name, input) {
  switch (name) {
    case 'write_file':
      return `将写入文件: ${input.path} (${input.content.length} 字符)`;
    case 'delete_file':
      return `将删除文件: ${input.path}`;
    case 'run_terminal_command':
      return `将执行命令: ${input.command}`;
    default:
      return `将执行: ${name}`;
  }
}

7. 流式工具调用

7.1 处理流式响应

javascript
async function* streamChat(userMessage) {
  const stream = await anthropic.messages.stream({
    model: "claude-sonnet-4-20250514",
    max_tokens: 4096,
    tools: tools,
    messages: [{ role: "user", content: userMessage }]
  });
  
  let currentToolUse = null;
  let toolUseBuffer = '';
  
  for await (const event of stream) {
    switch (event.type) {
      case 'content_block_start':
        if (event.content_block.type === 'tool_use') {
          currentToolUse = {
            id: event.content_block.id,
            name: event.content_block.name,
            input: ''
          };
          yield { type: 'tool_start', tool: currentToolUse.name };
        }
        break;
        
      case 'content_block_delta':
        if (event.delta.type === 'text_delta') {
          yield { type: 'text', content: event.delta.text };
        } else if (event.delta.type === 'input_json_delta') {
          toolUseBuffer += event.delta.partial_json;
        }
        break;
        
      case 'content_block_stop':
        if (currentToolUse) {
          currentToolUse.input = JSON.parse(toolUseBuffer);
          yield { type: 'tool_call', ...currentToolUse };
          currentToolUse = null;
          toolUseBuffer = '';
        }
        break;
    }
  }
}

7.2 实时工具执行反馈

javascript
async function chatWithProgress(userMessage, onProgress) {
  for await (const event of streamChat(userMessage)) {
    switch (event.type) {
      case 'text':
        onProgress({ type: 'text', content: event.content });
        break;
        
      case 'tool_start':
        onProgress({ type: 'status', message: `调用工具: ${event.tool}` });
        break;
        
      case 'tool_call':
        onProgress({ type: 'status', message: `执行中: ${event.name}` });
        const result = await executeToolCall(event.name, event.input);
        onProgress({ type: 'tool_result', name: event.name, result });
        break;
    }
  }
}

// 使用示例
chatWithProgress("搜索所有使用 useState 的文件", (event) => {
  if (event.type === 'text') {
    process.stdout.write(event.content);
  } else if (event.type === 'status') {
    console.log(`\n[${event.message}]`);
  }
});

8. 实战案例

8.1 代码编辑助手工具集

javascript
const codeEditingTools = [
  {
    name: "read_file",
    description: "读取文件内容。对于大文件,建议指定行范围。",
    input_schema: {
      type: "object",
      properties: {
        path: { type: "string", description: "文件路径" },
        start_line: { type: "integer", description: "起始行(1-indexed)" },
        end_line: { type: "integer", description: "结束行(1-indexed)" }
      },
      required: ["path"]
    }
  },
  {
    name: "search_replace",
    description: "在文件中搜索并替换文本。old_text 必须完全匹配。",
    input_schema: {
      type: "object",
      properties: {
        path: { type: "string" },
        old_text: { type: "string", description: "要替换的原文本(必须完全匹配)" },
        new_text: { type: "string", description: "替换后的文本" }
      },
      required: ["path", "old_text", "new_text"]
    }
  },
  {
    name: "codebase_search",
    description: "语义搜索代码库,找到相关代码。",
    input_schema: {
      type: "object",
      properties: {
        query: { type: "string", description: "自然语言描述,如'处理用户登录的代码'" },
        file_type: { type: "string", description: "文件类型过滤,如 'tsx', 'ts'" }
      },
      required: ["query"]
    }
  },
  {
    name: "run_terminal_cmd",
    description: "执行终端命令。用于运行测试、安装依赖等。",
    input_schema: {
      type: "object",
      properties: {
        command: { type: "string" },
        cwd: { type: "string", description: "工作目录" }
      },
      required: ["command"]
    }
  }
];

8.2 完整对话示例

javascript
// 用户: "在 src/components/Button.tsx 中添加 loading 状态"

// LLM 响应 1: 读取文件
{
  content: [
    { type: "text", text: "让我先看看当前的 Button 组件实现:" },
    { type: "tool_use", id: "1", name: "read_file", input: { path: "src/components/Button.tsx" } }
  ]
}

// 执行工具,返回结果

// LLM 响应 2: 编辑文件
{
  content: [
    { type: "text", text: "我来添加 loading 状态和对应的样式:" },
    { 
      type: "tool_use", 
      id: "2", 
      name: "search_replace",
      input: {
        path: "src/components/Button.tsx",
        old_text: "interface ButtonProps {\n  children: React.ReactNode;\n  onClick?: () => void;\n}",
        new_text: "interface ButtonProps {\n  children: React.ReactNode;\n  onClick?: () => void;\n  loading?: boolean;\n}"
      }
    }
  ]
}

// ... 更多编辑

// LLM 最终响应
{
  content: [
    { type: "text", text: "我已经完成了以下修改:\n1. 添加了 `loading` prop\n2. 添加了加载时的 spinner\n3. 加载时禁用按钮点击\n\n要测试这个修改,可以运行 `npm test`。" }
  ]
}

9. 关键要点

  1. 工具描述要详细: 包含使用场景、返回内容、不适用场景
  2. 处理并行调用: 现代模型可一次调用多个工具
  3. 优雅处理错误: 返回有用的错误信息让模型调整策略
  4. 安全第一: 验证输入、限制权限、危险操作需确认
  5. 优化返回内容: 避免返回过大的数据,提供分页或摘要

前端面试知识库