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