☰
VoltAgent 工具体系实战指南:从 createTool 到 Toolkit 的完整工具箱构建
2026/9/25 17:26:40 网站建设 项目流程
  • 人工智能
  • AI Agent
  • Agent 框架
  • 后端
  • 多智能体
  • RAG
  • 工具调用
  • Agent 记忆

【免费下载链接】voltagent

AI Agent Engineering Platform built on an Open Source TypeScript AI Agent Framework

项目地址:https://gitcode.com/gh_mirrors/vo/voltagent
点击查看免费下载

本篇技术指南以 VoltAgent 官方文档 Tools & Toolkits 概览 为主体,系统讲解如何通过@voltagent/core为 AI Agent 定义单个工具(createTool)、在工具内访问操作上下文、启用输出 Schema 校验,以及使用新一代Toolkit概念对相关工具进行分组管理与指令注入。读完本文,你将能够独立编写可接入外部 API、数据库或任意自定义代码的工具,并以 Toolkit 形式组织复杂 Agent 的工具集,同时理解底层 ToolManager 与系统提示词组装机制。

为什么需要 Tools 与 Toolkits

VoltAgent 允许你通过Tools扩展 AI Agent 的能力边界:工具让 Agent 能够调用外部 API、执行计算、访问数据库,或运行几乎任何自定义代码。而在实际工程中,若干工具往往在逻辑上协同工作(例如用于逐步推理的think、analyze,或与同一 API 交互的一组工具),为此 VoltAgent 引入了Toolkit概念来统一管理相关工具。整体 API 入口集中在 packages/core/src/tool/index.ts,工具管理逻辑位于 packages/core/src/tool/manager。

定义一个单一工具

定义工具最基本的方式是使用createTool辅助函数(也可以直接实例化Tool类)。一个工具必须包含以下要素:

  • name:工具的唯一名称(LLM 通过它来调用工具)。
  • description:对工具功能的清晰描述(LLM 据此决定何时使用它)。
  • parameters:定义工具输入参数的 Zod Schema。
  • execute:包含工具逻辑的异步函数,接收校验后的参数作为输入。
  • outputSchema(可选):定义预期输出格式的 Zod Schema,提供后工具输出将按此校验。
import { Agent, createTool } from "@voltagent/core"; import { z } from "zod"; // Define a simple weather tool const getWeatherTool = createTool({ name: "get_weather", description: "Fetches the current weather for a given location.", parameters: z.object({ location: z.string().describe("The city and state, e.g., San Francisco, CA"), }), execute: async ({ location }) => { // In a real scenario, you would call a weather API here console.log(`Fetching weather for ${location}...`); if (location.toLowerCase().includes("tokyo")) { return { temperature: "15°C", condition: "Cloudy" }; } return { temperature: "22°C", condition: "Sunny" }; }, }); const agent = new Agent({ name: "WeatherAgent", instructions: "An agent that can fetch weather information.", model: "openai/gpt-4o-mini", tools: [getWeatherTool], // Add the tool to the agent }); // Now the agent can use the 'get_weather' tool when asked about weather.

Tool 构造器的完整字段(源码视角)

从源码 packages/core/src/tool/index.ts 可以看到,createTool的底层ToolOptions除上述四个核心字段外还支持以下可选项:

  • id:工具唯一标识符,默认回退为name(构造器中this.id = options.id ?? options.name)。
  • tags:用于组织或标注工具的用户自定义标签数组,会写入 OpenTelemetry 的tool.tags属性。
  • needsApproval:是否需要在执行前获得审批;设为函数时可针对每次调用动态决策,其类型为boolean | ToolNeedsApprovalFunction。
  • providerOptions:透传给特定 Provider 的选项(例如 Anthropic 的cacheControl: { type: 'ephemeral' }缓存控制)。
  • mcp:当工具通过@voltagent/mcp-server暴露时使用的 MCP 注解与元数据(如readOnlyHint、destructiveHint、idempotentHint等)。
  • toModelOutput:将工具输出转换为多模态内容的函数(Anthropic、OpenAI 支持),可返回文本 + 图片等content类型结果。
  • hooks:工具生命周期钩子,包括onStart与onEnd(onEnd可返回{ output }覆盖最终输出)。

构造器本身也有守护逻辑:缺少name会直接抛出"Tool name is required";缺少parameters会抛出"parameters schema is required";缺少description时仅记录警告。Tool类还带有type = "user-defined"判别字段(源码 Tool 类定义),供 ToolManager 在运行时可靠地区分 VoltAgent 自有工具与外部工具,避免跨模块instanceof失效问题。另外,execute字段是可选的——当不提供服务端execute时,isClientSide()返回true,表明该工具应在客户端侧执行。

在工具中访问操作上下文

工具可以通过execute的第二个参数访问操作元数据、用户上下文与控制机制:

import { Agent, createTool } from "@voltagent/core"; import { z } from "zod"; // Tool that uses operation context const contextAwareWeatherTool = createTool({ name: "get_weather", description: "Fetches weather with user preferences", parameters: z.object({ location: z.string().describe("The city name"), }), execute: async ({ location }, options) => { // Access user-defined context const units = options?.context?.get("preferredUnits") || "celsius"; const userId = options?.userId; // Use operation-scoped logger options?.logger?.info(`Fetching weather for ${location} in ${units}`); // Check abort signal if (options?.abortSignal?.aborted) { throw new Error("Request was cancelled"); } // Call weather API with user preferences const response = await fetch( `https://api.weather.com/current?city=${location}&units=${units}`, { signal: options?.abortSignal } ); return await response.json(); }, }); // Use the tool with context const context = new Map(); context.set("preferredUnits", "fahrenheit"); const agent = new Agent({ name: "WeatherAgent", instructions: "An agent that respects user preferences", model: "openai/gpt-4o-mini", tools: [contextAwareWeatherTool], }); const response = await agent.generateText("What's the weather in Paris?", { userId: "user123", context, });

options 参数的完整构成

options参数(即ToolExecuteOptions,定义于 packages/core/src/agent/providers/base/types.ts)是Partial<OperationContext>的扩展,包含:

  • 操作元数据:operationId(操作唯一标识)、userId、conversationId
  • 用户上下文:context(Map<string | symbol, unknown>,用于存放自定义数据)
  • 控制机制:abortController、abortSignal
  • 日志:logger(操作作用域 logger)
  • AI SDK 数据:toolCallId、messages,二者封装在toolContext字段中(另有name、abortSignal)
  • 以及OperationContext中的resolvedMemory、workspace、requestHeaders、systemContext、isActive、parentAgentId、elicitation等字段(见 packages/core/src/agent/types.ts)

注意,abortController是"向后兼容"的冗余字段,源码注释明确建议优先使用toolContext.abortSignal。当工具由 VoltAgent 的 Agent 内部调用时,toolContext总是被填充(见下文执行工厂实现);而由外部调用方(如 MCP Server)调用时该字段是可选的。

工具输出 Schema 校验

VoltAgent 支持工具的可选输出 Schema 校验。这一特性确保工具输出符合预定义结构,带来多重收益:

  • 类型安全(Type Safety):工具输出基于 Schema 获得类型推断
  • 运行时校验(Runtime Validation):无效输出会被立即捕获
  • 错误恢复(Error Recovery):校验失败时 LLM 会收到错误信息,并可用修正后的输出重试
  • 一致性(Consistency):所有工具响应遵循相同结构
  • 文档化(Documentation):输出 Schema 即 API 契约

带输出 Schema 的示例

import { createTool } from "@voltagent/core"; import { z } from "zod"; // Define the output schema const weatherOutputSchema = z.object({ location: z.string(), temperature: z.number(), condition: z.enum(["sunny", "cloudy", "rainy", "snowy"]), humidity: z.number().min(0).max(100), forecast: z.object({ high: z.number(), low: z.number(), description: z.string(), }), }); // Create a tool with output validation const weatherTool = createTool({ name: "get_weather", description: "Get current weather with forecast", parameters: z.object({ location: z.string().describe("City name"), }), outputSchema: weatherOutputSchema, // Optional output schema execute: async ({ location }) => { // This output will be validated against weatherOutputSchema return { location, temperature: 22, condition: "sunny", humidity: 65, forecast: { high: 25, low: 18, description: "Clear skies throughout the day", }, }; }, });

校验机制的源码实现

输出校验的底层实现在 packages/core/src/agent/agent.ts 的validateToolOutput方法中:若工具提供了outputSchema,执行结果会经过outputSchema.safeParse(result)校验;校验失败时抛出带有validationErrors(Zod 原始错误数组)与actualOutput属性的 Error。该错误随后经 error-utils.ts 的 buildToolErrorResult 序列化为可回传给模型的可序列化错误对象。整体流程如下:

  1. 工具执行后,若提供了outputSchema,其输出即被校验。
  2. 校验成功,返回校验后的输出(parseResult.data)。
  3. 校验失败,向 LLM 返回错误对象:
    { "error": true, "message": "Output validation failed: Expected number, received string", "validationErrors": [...], "actualOutput": {...} }
  4. LLM 可以看到校验错误,并可能通过再次调用工具来修复问题。

值得留意的是,校验同样适用于异步生成器工具:在 createToolExecutionFactory 中,对于execute为 async generator 的工具,其每个yield的中间值都会先经validateToolOutput校验再透传,最后一个值作为最终结果。真实示例可参考 examples/with-tools/src/tools/weather.ts,它使用z.discriminatedUnion("status", ...)定义了loading/success两种状态的输出 Schema,并先用yield返回"正在获取"的初步更新,再返回最终天气数据(该工具还演示了needsApproval的注释用法)。

最佳实践

  • 为需要稳定响应格式的工具使用输出 Schema
  • 保持 Schema 聚焦,避免过度复杂的嵌套结构
  • 在 Schema 中使用.describe()提供描述性错误信息
  • 考虑用.optional()让部分字段可选以增加灵活性
  • 输出 Schema 完全可选——不带它的工具行为与之前完全一致

用 Toolkit 分组管理相关工具

Toolkit允许你:

  1. 分组相关工具:让工具管理保持组织化
  2. 定义共享指令:向 LLM 提供如何使用该 Toolkit 内全部工具的通用指导
  3. 控制指令注入:决定 Toolkit 的共享指令是否自动加入 Agent 的系统提示词

定义一个 Toolkit

Toolkit是一个具有如下结构的对象(createToolkit实现见 packages/core/src/tool/toolkit.ts):

import { createTool, createToolkit, type Tool, type Toolkit } from "@voltagent/core"; const myCalculatorToolkit = createToolkit({ name: "calculator_toolkit", description: "Tools for performing basic arithmetic operations.", // Optional instructions for the LLM instructions: `Use these tools for calculations. Always use 'add' for addition, 'subtract' for subtraction.`, // Set to true to add the above instructions to the system prompt addInstructions: true, tools: [ createTool({ /* ... definition for 'add' tool ... */ }), createTool({ /* ... definition for 'subtract' tool ... */ }), // ... other calculator tools ], });

Toolkit类型定义(toolkit.ts 的 Toolkit 类型)包含五个字段:

  • name:Toolkit 的唯一标识名称,用于管理与日志
  • description(可选):Toolkit 作用或所含工具的简述,默认空字符串
  • instructions(可选):关于如何使用其中工具的共享指令,addInstructions为true时才注入系统提示词
  • addInstructions(可选):是否自动将instructions加入系统提示词,默认为false
  • tools:属于该 Toolkit 的Tool或 Vercel 工具数组(Tool<ToolSchema, ToolSchema | undefined> | VercelTool)

createToolkit同样有校验逻辑:name缺失会抛错,空tools数组会发出警告(但不会阻止创建)。类型定义中明确tools的数组元素类型为Tool | VercelTool,说明 Toolkit 内既可以放 VoltAgent 自建工具,也可以放 Provider 定义的工具。

重要:随着 Toolkit 的引入,单个Tool实例不再拥有自己的instructions或addInstructions属性——指令统一在 Toolkit 层级管理。从源码看,ToolOptions也确实没有这两个字段,这一设计避免了指令在多个层级重复冗余。

嵌套 Toolkit 的限制

从 ToolkitManager 源码 可以确认:Toolkit 内部不支持嵌套 Toolkit——ToolkitManager.addToolkit()是一个 no-op 实现,调用时会记录警告 "nested toolkits are not supported" 并返回false,因此Toolkit的tools数组只接受工具而非另一个 Toolkit。

向 Agent 添加工具与 Toolkit

Agent构造器的tools选项现在接受同时包含单个Tool对象与Toolkit对象的数组,ToolManager会对两者进行无缝处理:

import { openai } from "@ai-sdk/openai"; import { Agent, createTool, createToolkit, type Toolkit } from "@voltagent/core"; // ... import other tools and toolkits ... const agent = new Agent({ name: "MultiToolAgent", instructions: "An agent with various tools and toolkits.", model: "openai/gpt-4o-mini", tools: [ getWeatherTool, // Add an individual tool myCalculatorToolkit, // Add a toolkit openai.tools.webSearch(), // Add a provider-defined tool // ... other tools or toolkits ], });

ToolManager 的底层行为

ToolManager(packages/core/src/tool/manager/ToolManager.ts)继承自BaseToolManager(packages/core/src/tool/manager/BaseToolManager.ts),底层按三类容器管理工具:

  • baseTools:用户自定义、可在服务端执行或仅客户端执行的工具
  • providerTools:由 Provider 外部管理的工具
  • toolkits:ToolkitManager实例,内部再各自维护 baseTools 与 providerTools

关键行为包括:

  • 名称冲突检查:addToolkit时若 Toolkit 内任一工具与现有独立工具或其他 Toolkit 内工具重名(hasToolInAny检查),会记录警告并跳过添加(返回false);同名 Toolkit 会被替换并发出警告。
  • 运行时类型判别:isProviderTool(type === "provider")、isBaseTool(type === "user-defined")、isToolkit(存在数组型tools属性)三个类型守卫用于在addItems/addStandaloneTool中分派处理。
  • 扁平化视图:getAllTools()、getAllBaseTools()、getAllProviderTools()、getAllToolNames()都会展开 Toolkit 内部工具,形成 Agent 执行与 API 暴露(getToolsForApi)所需的统一视图;prepareToolsForExecution会把parameters(Zod Schema)转成 AI SDK 可消费的inputSchema,同时透传needsApproval、providerOptions、toModelOutput与outputSchema。
  • 动态增删:Agent 实例支持addTools运行时动态添加工具(examples/with-tools/src/index.ts 演示了agent.addTools([weatherTool])的用法),也支持removeTool、removeToolkit。

自动指令注入机制

Agent 初始化时,其getSystemMessage方法会检查tools数组中提供的所有Toolkit。若某个 Toolkit 的addInstructions: true且定义了instructions字符串,这些指令会被自动追加到 Agent 的基础描述之后,构成发送给 LLM 的最终系统提示词。

源码层面,这一逻辑实现在 agent.ts 的 addToolkitInstructions:它遍历this.toolManager.getToolkits(),对每个addInstructions && instructions的 Toolkit 以\n\n${toolkit.instructions}的形式拼接,最终通过enrichInstructions(agent.ts L5588-L5619)将其与markdown指令、检索上下文、工作记忆、子代理监督指令等合并为最终 system message。此外,运行时通过generateText等调用传入的runtimeToolkits也会参与拼接,同名时以运行时定义为优先,并保持静态顺序。

Provider 定义的工具

部分 Provider 通过 Vercel AI SDK 暴露自己的工具:

  • 它们可以作为独立工具存在,也可以放入 Toolkit 中;添加 Toolkit 时同样受名称冲突检查约束。
  • 它们不能通过通常的Tool.execute处理器在你的服务器上执行——执行由 Provider 管理。

例如openai.tools.webSearch()就是典型的 Provider 工具,它由 OpenAI 侧托管执行,Agent 只负责把它传入工具列表(见 examples/with-tools/src/index.ts)。ProviderTool类型(tool/index.ts L115-L121)通过type: "provider"判别字段与id: \${string}.${string}`模板类型(如openai.webSearch)加以标识;在prepareToolsForExecution中,Provider 工具会被原样透传给 AI SDK(tools[tool.name] = tool),而不会包装execute`。

进阶:内置的 reasoning_tools 推理工具 Toolkit

文档 Reasoning Tools 给出了一个开箱即用的 Toolkit 实例。createReasoningTools(实现见 packages/core/src/tool/reasoning/index.ts)返回名为"reasoning_tools"的 Toolkit,内含think(内部思维草稿纸)与analyze(评估结果并决定continue/validate/final_answer)两个工具,并可通过addInstructions(默认true)、think、analyze、addFewShot、fewShotExamples选项定制;默认指令要求 Agent 在任何工具调用或响应前先think,并按Think -> [Think -> ...] -> [Tool Calls] -> [Analyze] -> final_answer的迭代循环解题。这是理解"相关工具 + 共享指令 + 自动注入"三者如何协同的最佳范例——直接把一个 Toolkit 塞进tools数组,Agent 即获得结构化推理能力。

编写高质量工具的工程建议

综合上述机制,编写与组织工具时值得遵循以下原则:

  • 命名与描述优先:name是 LLM 的调用句柄,description决定 LLM 的调用时机,二者直接影响工具使用率;缺失name/parameters会在构造时直接抛错。
  • 用 Zod Schema 约束边界:parameters负责输入校验,outputSchema负责输出契约;借助describe()、.optional()、z.discriminatedUnion可以写出既严格又灵活的 Schema。
  • 善用操作上下文:通过options读取用户偏好、使用操作级logger、监听abortSignal实现优雅取消,并在长任务中通过 async generator 的yield推送初步进度(参考 with-tools 示例)。
  • 按领域组织 Toolkit:把访问同一 API、或服务于同一业务流程的工具收进一个 Toolkit,用共享instructions告诉 LLM 工具间的配合规则,并仅在需要时开启addInstructions(默认false)避免系统提示词膨胀。
  • 留意命名冲突:ToolManager 会在独立工具与 Toolkit 工具之间、以及 Toolkit 之间做名称冲突检查,重名工具会被跳过或覆盖,因此工具名应尽量全局唯一。
  • 区分执行边界:自建工具的execute在你自己的服务器上运行;Provider 工具的execute由 Provider 管理,不要试图为其编写服务端处理器。
  • 人工智能
  • AI Agent
  • Agent 框架
  • 后端
  • 多智能体
  • RAG
  • 工具调用
  • Agent 记忆

【免费下载链接】voltagent

AI Agent Engineering Platform built on an Open Source TypeScript AI Agent Framework

项目地址:https://gitcode.com/gh_mirrors/vo/voltagent
点击查看免费下载

相关推荐

上一篇:在 TodoAppTs 示例中实战 Wasp:CLAUDE.md 指引下的全栈开发、验证与调试工作流
下一篇:SkillSpector contrib/batch_scan 测试设计深度解析:如何用 164 个测试驯服并发池与 monkey-patch

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询