- 人工智能
- AI Agent
- Agent 框架
- 后端
- 多智能体
- RAG
- 工具调用
- Agent 记忆
【免费下载链接】voltagent
AI Agent Engineering Platform built on an Open Source TypeScript AI Agent Framework
本篇技术指南以 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 序列化为可回传给模型的可序列化错误对象。整体流程如下:
- 工具执行后,若提供了
outputSchema,其输出即被校验。 - 校验成功,返回校验后的输出(
parseResult.data)。 - 校验失败,向 LLM 返回错误对象:
{ "error": true, "message": "Output validation failed: Expected number, received string", "validationErrors": [...], "actualOutput": {...} } - 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允许你:
- 分组相关工具:让工具管理保持组织化
- 定义共享指令:向 LLM 提供如何使用该 Toolkit 内全部工具的通用指导
- 控制指令注入:决定 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加入系统提示词,默认为falsetools:属于该 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
相关推荐
VoltAgent 工具系统实战:用 createTool 构建可插拔 AI Agent 工具链
VoltAgent 工具系统实战:用 createTool 构建可插拔 AI Agent 工具链 导读 本文以 VoltAgent 官方示例 examples/
人工智能AI AgentAgent 框架后端多智能体RAG工具调用Agent 记忆Agent 工作流AI 评测MCP 服务MCP Clients语音VoltAgent 工具系统实战指南:用 createTool 为 Agent 添加自定义工具、动态工具与 Provider 内置工具
VoltAgent 工具系统实战指南:用 createTool 为 Agent 添加自定义工具、动态工具与 Provider 内置工具 本文基于官方配方文档 T
人工智能AI AgentAgent 框架后端多智能体RAG工具调用Agent 记忆Agent 工作流AI 评测MCP 服务MCP Clients语音PDFPatcher 使用教程:批量合并、重命名、页面提取的输出与排障
PDFPatcher 使用教程:批量合并、重命名、页面提取的输出与排障 PDFPatcher(PDF 补丁丁)是一款免费、免安装的 Windows PDF 批量
人工智能AI AgentAgent 框架后端多智能体RAG工具调用Agent 记忆Agent 工作流AI 评测MCP 服务MCP Clients语音
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考