Skip to content

自定义工具

创建自己的工具

学会了工具接口,现在来创建自定义工具。

基本模板

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 循环的实现:循环结构 →

通过实际源码学习 AI Agent 开发