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