Skip to content

工具使用与 Function Calling 🔧

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

1. 工具使用基础

1.1 什么是 Tool Use

Tool Use 让 LLM 能够:

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

1.2 工作流程

1. 用户发送消息
2. LLM 分析是否需要工具
   ├── 不需要 → 直接回答
   └── 需要 → 生成 tool_use 响应
3. 应用程序执行工具
4. 将结果返回给 LLM
5. LLM 整合结果(可能需要更多工具)

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'"
          },
          max_results: {
            type: "integer",
            description: "返回的最大结果数",
            default: 10
          }
        },
        required: ["query"]
      }
    }
  }
];

2.2 Anthropic 格式

javascript
const tools = [
  {
    name: "search_codebase",
    description: "在代码库中搜索代码片段。当用户询问代码相关问题时使用。",
    input_schema: {
      type: "object",
      properties: {
        query: {
          type: "string",
          description: "搜索查询"
        },
        file_pattern: {
          type: "string",
          description: "文件名模式"
        }
      },
      required: ["query"]
    }
  }
];

2.3 好的工具描述

javascript
// ❌ 差的描述
{
  name: "get_weather",
  description: "获取天气"
}

// ✅ 好的描述
{
  name: "get_weather",
  description: `
    获取指定城市的当前天气信息。

    使用场景:
    - 用户询问天气情况
    - 需要天气数据做决策(如出行建议)

    返回内容:
    - 温度、湿度、风速
    - 天气状况描述
    - 未来 24 小时预报

    不适用场景:
    - 历史天气数据(使用 get_historical_weather)
    - 长期预报(使用 get_forecast)
  `
}

3. 实现工具调用

3.1 OpenAI 实现

javascript
import OpenAI from 'openai';

const openai = new OpenAI();

async function chat(messages) {
  const response = await openai.chat.completions.create({
    model: 'gpt-4o',
    messages,
    tools
  });

  const message = response.choices[0].message;

  // 检查是否需要调用工具
  if (message.tool_calls) {
    // 执行工具
    const toolResults = await Promise.all(
      message.tool_calls.map(async (toolCall) => {
        const result = await executeFunction(
          toolCall.function.name,
          JSON.parse(toolCall.function.arguments)
        );

        return {
          tool_call_id: toolCall.id,
          role: 'tool',
          content: JSON.stringify(result)
        };
      })
    );

    // 继续对话
    messages.push(message);
    messages.push(...toolResults);

    return chat(messages);
  }

  return message.content;
}

3.2 Anthropic 实现

javascript
import Anthropic from '@anthropic-ai/sdk';

const anthropic = new Anthropic();

async function chat(messages) {
  const response = await anthropic.messages.create({
    model: 'claude-3-5-sonnet-20241022',
    max_tokens: 4096,
    messages,
    tools
  });

  // 检查是否需要调用工具
  const toolUse = response.content.find(block => block.type === 'tool_use');

  if (toolUse) {
    // 执行工具
    const result = await executeFunction(toolUse.name, toolUse.input);

    // 继续对话
    messages.push({ role: 'assistant', content: response.content });
    messages.push({
      role: 'user',
      content: [{
        type: 'tool_result',
        tool_use_id: toolUse.id,
        content: JSON.stringify(result)
      }]
    });

    return chat(messages);
  }

  return response.content.find(block => block.type === 'text')?.text;
}

4. 工具实现示例

4.1 文件系统工具

javascript
const fileTools = [
  {
    name: 'read_file',
    description: '读取文件内容',
    input_schema: {
      type: 'object',
      properties: {
        path: { type: 'string', description: '文件路径' }
      },
      required: ['path']
    }
  },
  {
    name: 'write_file',
    description: '写入文件内容',
    input_schema: {
      type: 'object',
      properties: {
        path: { type: 'string', description: '文件路径' },
        content: { type: 'string', description: '文件内容' }
      },
      required: ['path', 'content']
    }
  }
];

async function executeFunction(name, args) {
  switch (name) {
    case 'read_file':
      return await fs.readFile(args.path, 'utf-8');

    case 'write_file':
      await fs.writeFile(args.path, args.content);
      return { success: true };

    default:
      throw new Error(`Unknown function: ${name}`);
  }
}

4.2 API 调用工具

javascript
const apiTools = [
  {
    name: 'search_web',
    description: '搜索网页内容',
    input_schema: {
      type: 'object',
      properties: {
        query: { type: 'string', description: '搜索查询' },
        limit: { type: 'integer', description: '结果数量', default: 5 }
      },
      required: ['query']
    }
  }
];

async function executeFunction(name, args) {
  if (name === 'search_web') {
    const response = await fetch(
      `https://api.search.com/search?q=${encodeURIComponent(args.query)}&limit=${args.limit}`
    );
    const data = await response.json();

    return data.results.map(r => ({
      title: r.title,
      url: r.url,
      snippet: r.snippet
    }));
  }
}

4.3 数据库工具

javascript
const dbTools = [
  {
    name: 'query_database',
    description: '查询数据库',
    input_schema: {
      type: 'object',
      properties: {
        sql: { type: 'string', description: 'SQL 查询语句' }
      },
      required: ['sql']
    }
  }
];

async function executeFunction(name, args) {
  if (name === 'query_database') {
    // 安全检查:只允许 SELECT
    if (!args.sql.trim().toLowerCase().startsWith('select')) {
      throw new Error('Only SELECT queries are allowed');
    }

    const results = await db.query(args.sql);
    return results.rows;
  }
}

5. 并行工具调用

5.1 OpenAI 并行调用

javascript
// LLM 可能一次返回多个工具调用
{
  tool_calls: [
    {
      id: "call_1",
      function: { name: "read_file", arguments: '{"path": "src/App.tsx"}' }
    },
    {
      id: "call_2",
      function: { name: "read_file", arguments: '{"path": "src/utils.ts"}' }
    }
  ]
}

// 并行执行
const results = await Promise.all(
  message.tool_calls.map(async (call) => {
    const result = await executeFunction(
      call.function.name,
      JSON.parse(call.function.arguments)
    );

    return {
      tool_call_id: call.id,
      role: 'tool',
      content: JSON.stringify(result)
    };
  })
);

5.2 Anthropic 并行调用

javascript
// Claude 可能返回多个 tool_use 块
{
  content: [
    { type: "text", text: "我需要读取这两个文件:" },
    { type: "tool_use", id: "1", name: "read_file", input: { path: "src/App.tsx" } },
    { type: "tool_use", id: "2", name: "read_file", input: { path: "src/utils.ts" } }
  ]
}

// 并行执行
const toolUses = response.content.filter(block => block.type === 'tool_use');

const results = await Promise.all(
  toolUses.map(async (toolUse) => {
    const result = await executeFunction(toolUse.name, toolUse.input);

    return {
      type: 'tool_result',
      tool_use_id: toolUse.id,
      content: JSON.stringify(result)
    };
  })
);

6. 错误处理

6.1 工具执行错误

javascript
async function executeFunction(name, args) {
  try {
    // 执行工具
    const result = await tools[name](args);
    return { success: true, data: result };
  } catch (error) {
    // 返回错误信息给 LLM
    return {
      success: false,
      error: error.message,
      suggestion: '请检查参数是否正确,或尝试其他方法'
    };
  }
}

6.2 参数验证

javascript
function validateArgs(schema, args) {
  for (const [key, prop] of Object.entries(schema.properties)) {
    // 检查必需参数
    if (schema.required?.includes(key) && !(key in args)) {
      throw new Error(`Missing required parameter: ${key}`);
    }

    // 检查类型
    if (key in args) {
      const value = args[key];
      if (prop.type === 'string' && typeof value !== 'string') {
        throw new Error(`Parameter ${key} must be a string`);
      }
      if (prop.type === 'integer' && !Number.isInteger(value)) {
        throw new Error(`Parameter ${key} must be an integer`);
      }
    }
  }
}

7. 安全考虑

7.1 权限控制

javascript
const dangerousOperations = ['delete_file', 'execute_command', 'modify_database'];

async function executeFunction(name, args, context) {
  // 危险操作需要用户确认
  if (dangerousOperations.includes(name)) {
    const confirmed = await askUserConfirmation(
      `Allow ${name} with args: ${JSON.stringify(args)}?`
    );

    if (!confirmed) {
      return { success: false, error: 'User denied permission' };
    }
  }

  return await tools[name](args);
}

7.2 输入清理

javascript
async function executeCommand(args) {
  // 禁止危险命令
  const dangerous = ['rm -rf', 'dd', 'mkfs', ':(){:|:&};:'];

  for (const pattern of dangerous) {
    if (args.command.includes(pattern)) {
      throw new Error('Dangerous command detected');
    }
  }

  // 限制命令白名单
  const allowed = ['ls', 'cat', 'grep', 'find'];
  const cmd = args.command.split(' ')[0];

  if (!allowed.includes(cmd)) {
    throw new Error(`Command ${cmd} is not allowed`);
  }

  return await exec(args.command);
}

8. 优化技巧

8.1 减少工具调用次数

javascript
// ❌ 多次调用
{
  name: "get_user",
  description: "获取单个用户信息"
}

// ✅ 批量调用
{
  name: "get_users",
  description: "获取多个用户信息",
  input_schema: {
    properties: {
      user_ids: {
        type: "array",
        items: { type: "string" }
      }
    }
  }
}

8.2 返回摘要而非全量数据

javascript
async function searchCodebase(args) {
  const results = await search(args.query);

  // 返回摘要
  return results.slice(0, 10).map(r => ({
    file: r.file,
    line: r.line,
    snippet: r.content.slice(0, 200) + '...'  // 只返回前 200 字符
  }));
}

8.3 缓存工具结果

javascript
const cache = new Map();

async function executeFunction(name, args) {
  const key = `${name}:${JSON.stringify(args)}`;

  if (cache.has(key)) {
    return cache.get(key);
  }

  const result = await tools[name](args);
  cache.set(key, result);

  return result;
}

9. 关键要点

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

前端面试知识库