自定义工具
创建自己的工具
学会了工具接口,现在来创建自定义工具。
基本模板
typescript
import { ServerTool, ToolResult } from './types'
export const myTool: ServerTool = {
name: 'my_tool',
schema: {
name: 'my_tool',
description: '工具描述',
parameters: {
type: 'object',
properties: {
// 定义参数
},
required: []
}
},
async execute(params): Promise<ToolResult> {
// 实现逻辑
return { content: '结果' }
}
}示例:HTTP 请求工具
typescript
export const httpTool: ServerTool = {
name: 'http_request',
schema: {
name: 'http_request',
description: `发送 HTTP 请求。
用于调用 REST API、获取 JSON 数据等。
支持 GET、POST、PUT、DELETE 方法。`,
parameters: {
type: 'object',
properties: {
url: {
type: 'string',
description: '请求 URL'
},
method: {
type: 'string',
enum: ['GET', 'POST', 'PUT', 'DELETE'],
description: '请求方法,默认 GET'
},
headers: {
type: 'object',
description: '请求头'
},
body: {
type: 'string',
description: '请求体(POST/PUT 时使用)'
}
},
required: ['url']
}
},
async execute(params: {
url: string
method?: string
headers?: Record<string, string>
body?: string
}) {
const { url, method = 'GET', headers = {}, body } = params
try {
const response = await fetch(url, {
method,
headers: {
'Content-Type': 'application/json',
...headers
},
body: body ? body : undefined
})
const data = await response.text()
return {
content: JSON.stringify({
status: response.status,
statusText: response.statusText,
body: data.slice(0, 5000) // 限制返回大小
}, null, 2)
}
} catch (error) {
return {
error: `请求失败: ${error.message}`
}
}
}
}示例:数据库查询工具
typescript
import Database from 'better-sqlite3'
export const sqliteTool: ServerTool = {
name: 'sqlite_query',
schema: {
name: 'sqlite_query',
description: `执行 SQLite 查询。
只支持 SELECT 查询,不允许修改数据。
用于查看数据库内容和结构。`,
parameters: {
type: 'object',
properties: {
database: {
type: 'string',
description: '数据库文件路径'
},
query: {
type: 'string',
description: 'SQL 查询语句'
}
},
required: ['database', 'query']
}
},
async execute(params: { database: string; query: string }) {
const { database, query } = params
// 安全检查:只允许 SELECT
if (!query.trim().toUpperCase().startsWith('SELECT')) {
return { error: '只允许 SELECT 查询' }
}
try {
const db = new Database(database, { readonly: true })
const rows = db.prepare(query).all()
db.close()
return {
content: JSON.stringify(rows, null, 2)
}
} catch (error) {
return {
error: `查询失败: ${error.message}`
}
}
}
}示例:带确认的工具
typescript
export const deleteFileTool: ServerTool = {
name: 'delete_file',
schema: {
name: 'delete_file',
description: '删除文件或目录',
parameters: {
type: 'object',
properties: {
path: {
type: 'string',
description: '要删除的文件或目录路径'
},
recursive: {
type: 'boolean',
description: '是否递归删除目录'
}
},
required: ['path']
}
},
async execute(params: { path: string; recursive?: boolean }) {
const { path, recursive = false } = params
try {
await fs.rm(path, { recursive })
return { content: `已删除: ${path}` }
} catch (error) {
return { error: `删除失败: ${error.message}` }
}
},
// 删除操作必须确认
async shouldConfirmExecute(params: { path: string; recursive?: boolean }) {
return {
title: '删除文件',
description: `将删除: ${params.path}${params.recursive ? ' (递归)' : ''}`,
risk: 'high',
warning: params.recursive ? '这将删除目录下的所有内容!' : undefined
}
}
}集成到 Agent
创建工具后,需要注册到 Agent:
typescript
// 1. 收集所有工具
const tools: ServerTool[] = [
readFileTool,
writeFileTool,
httpTool, // 自定义
sqliteTool, // 自定义
deleteFileTool // 自定义
]
// 2. 生成工具定义给 LLM
const functionDeclarations = tools.map(t => t.schema)
// 3. 初始化模型时传入
const model = genAI.getGenerativeModel({
model: 'gemini-2.5-flash',
tools: [{ functionDeclarations }]
})
// 4. 处理工具调用
async function handleToolCall(name: string, args: unknown) {
const tool = tools.find(t => t.name === name)
if (!tool) {
return { error: `未知工具: ${name}` }
}
return tool.execute(args)
}最佳实践
1. 参数验证
typescript
async execute(params) {
// 验证必填参数
if (!params.url) {
return { error: '缺少必填参数: url' }
}
// 验证参数格式
try {
new URL(params.url)
} catch {
return { error: '无效的 URL 格式' }
}
// 执行逻辑...
}2. 输出限制
typescript
async execute(params) {
const result = await fetchLargeData()
// 限制输出大小,避免超出 token 限制
const truncated = result.slice(0, 10000)
const isTruncated = result.length > 10000
return {
content: truncated + (isTruncated ? '\n...(输出已截断)' : '')
}
}3. 超时处理
typescript
async execute(params, signal?: AbortSignal) {
const controller = new AbortController()
// 结合外部取消信号
signal?.addEventListener('abort', () => controller.abort())
// 设置超时
const timeout = setTimeout(() => controller.abort(), 30000)
try {
const result = await fetch(params.url, {
signal: controller.signal
})
return { content: await result.text() }
} catch (error) {
if (error.name === 'AbortError') {
return { error: '请求超时或被取消' }
}
throw error
} finally {
clearTimeout(timeout)
}
}4. 有意义的错误信息
typescript
// 好
return { error: `文件不存在: ${path}` }
return { error: `权限不足,无法读取: ${path}` }
return { error: `JSON 解析失败 (行 ${line}): ${message}` }
// 不好
return { error: 'Error' }
return { error: error.message } // 可能不够清晰小结
- 遵循
ServerTool接口创建工具 - 参数验证、输出限制、超时处理
- 危险操作实现
shouldConfirmExecute - 返回有意义的错误信息
下一步
工具系统讲完了,接下来学习 Agent 循环的实现:循环结构 →