摘要
从零实现 Agent 时,除了模型调用,还要处理工具注册、记忆、流式响应、工作流、人工确认、MCP 接入和运行观测。只用一个循环不断调用模型,原型可以运行,任务一复杂就容易失控。
VoltAgent 是一个开源 TypeScript Agent 工程平台,提供 Agent Runtime、工具、记忆、工作流、MCP、RAG 和多智能体等能力,同时配套可观测与运营能力。本文从创建项目开始,构建一个带工具和记忆的客服 Agent,再讨论工作流暂停恢复、权限边界和生产部署注意事项。
一、背景与问题
一个工具型 Agent 的基本循环如下:
用户问题
→ 模型理解任务
→ 判断是否调用工具
→ 执行工具
→ 把工具结果交给模型
→ 继续调用或生成最终答案
如果所有逻辑都手写在一个文件中,很快会遇到:
- 工具参数校验分散。
- 对话记忆无法持久化。
- 多步任务没有统一状态。
- 工具失败后难以重试或恢复。
- 客户端断开后任务状态不明确。
- Agent 的 Token、延迟和工具调用无法追踪。
VoltAgent 的设计重点是把 Agent 工程拆成运行时、工具、记忆、工作流和观测等可组合部分。官方仓库将其定位为 TypeScript Agent 工程平台,并提供 @voltagent/core、工作流、工具注册、MCP 和记忆等能力。
二、核心概念
1. Agent
Agent 通常由以下元素组成:
| Instructions | 角色、边界和行为规则 |
| Model | 实际调用的模型 |
| Tools | Agent 可以调用的外部能力 |
| Memory | 跨轮次保存的上下文 |
| Runtime | 执行模型与工具循环 |
| Guardrails | 输入输出和动作约束 |
Agent 的工具集合越多,模型需要处理的工具描述越多,Prompt 成本和误调用概率也会增加。
2. Tool
一个合格的工具不只是一个函数,还应包含:
- 明确的名称和描述。
- 可校验的输入 Schema。
- 权限要求。
- 超时和重试边界。
- 是否产生副作用。
- 审计信息。
查询工单和修改工单的安全级别不同,不能只因为它们都是 TypeScript 函数就采用同样的策略。
3. Memory
记忆至少分为:
- 短期记忆:当前会话消息。
- 长期记忆:用户偏好、历史事实和任务结果。
- 业务状态:订单、工单、审批等可验证数据。
业务状态不能只放在模型记忆中。模型记忆可以帮助理解上下文,但不能替代数据库中的真实状态。
4. Workflow
Workflow 适合描述可预测的多步流程,例如报销审批、文档处理和客服升级。它与自由规划的 Agent 不同:
| Agent | 模型动态决定下一步 |
| Workflow | 开发者预先定义步骤和分支 |
| Agent + Workflow | Agent 负责局部决策,Workflow 负责整体边界 |
高风险流程通常应让 Workflow 掌握主流程,把 Agent 限制在某个步骤内。
三、工作原理
1. VoltAgent 运行结构
VoltAgent 应用
├─ Agent
│ ├─ Instructions
│ ├─ Model Provider
│ ├─ Tools
│ └─ Memory
├─ Workflow
│ ├─ Step
│ ├─ Suspend
│ └─ Resume
├─ Server
└─ Logger / Observability
应用启动后,服务端暴露 Agent 和工作流的运行入口,业务系统可以通过 HTTP 或其他集成方式发起任务。
2. 工具调用循环
输入消息
→ 组装系统指令、历史消息和工具 Schema
→ 调用模型
→ 如果返回文本:结束
→ 如果返回工具调用:
校验参数
检查权限
执行工具
记录结果
回到模型
循环必须设置最大步数、最大总耗时和总 Token 预算。模型“认为还需要继续”不等于系统必须无限执行。
3. 工作流暂停与恢复
涉及人工审批的流程不能一直占用 HTTP 请求。更合理的方式是:
执行步骤
→ 发现需要人工确认
→ 持久化状态
→ suspend
→ 返回任务 ID
→ 人工提交决定
→ resume
→ 继续后续步骤
暂停状态要保存在可靠存储中,不能只存在 Node.js 进程内存。
四、实战示例
1. 创建项目
VoltAgent 官方仓库提供了创建项目的 CLI 入口:
npm create voltagent-app@latest
cd my-agent-app
npm install
创建时根据提示选择模型供应商和项目选项。实际命令和模板会随版本变化,建议以当前仓库 README 和官方文档为准。
2. 配置模型密钥
OPENAI_API_KEY=replace-with-development-key
开发环境可以使用 .env,但不要将真实密钥提交到仓库。生产环境应通过 Secret 管理系统注入,并限制应用进程读取范围。
3. 定义一个工具
import { z } from "zod";
export const queryTicketTool = {
name: "query_ticket",
description: "查询当前用户有权限访问的工单信息",
parameters: z.object({
ticketId: z.string().min(1),
}),
execute: async ({ ticketId }: { ticketId: string }) => {
const ticket = await ticketRepository.findVisible(ticketId);
if (!ticket) {
return { found: false };
}
return {
found: true,
id: ticket.id,
status: ticket.status,
summary: ticket.summary,
};
},
};
示例中的 findVisible 应在数据库查询层执行租户和用户权限过滤。不能先查出全部工单,再把权限判断交给模型。
4. 创建 Agent
import { Agent, Memory, VoltAgent } from "@voltagent/core";
import { LibSQLMemoryAdapter } from "@voltagent/libsql";
import { createPinoLogger } from "@voltagent/logger";
import { honoServer } from "@voltagent/server-hono";
import { openai } from "@ai-sdk/openai";
import { queryTicketTool } from "./tools/query-ticket";
const memory = new Memory({
storage: new LibSQLMemoryAdapter({
url: "file:./.voltagent/memory.db",
}),
});
const supportAgent = new Agent({
name: "support-agent",
instructions: `
你是客服工单助手。
只能查询当前用户有权限访问的工单。
无法确认的信息要明确说明,不要编造工单状态。
涉及修改、关闭或删除工单时,必须要求人工确认。
`,
model: openai("gpt-4o-mini"),
tools: [queryTicketTool],
memory,
});
const logger = createPinoLogger({
name: "support-agent",
level: "info",
});
new VoltAgent({
agents: {
supportAgent,
},
server: honoServer(),
logger,
});
这里使用了持久化记忆,但记忆数据库仍需要备份、加密和访问控制。对话记忆不应无限增长,应该设置会话过期和摘要策略。
5. 启动开发服务
npm run dev
启动后先用本地测试请求验证:
curl -X POST http://localhost:3141/agents/support-agent \\
-H "Content-Type: application/json" \\
-d '{"message":"请查询工单 INC-1001"}'
不同版本的服务路由可能存在差异,实际调用路径以启动日志和项目模板生成的路由为准。
6. 增加工作流边界
对于“查询后决定是否升级”的任务,可以将关键步骤显式建模:
import { createWorkflowChain } from "@voltagent/core";
import { z } from "zod";
export const escalationWorkflow = createWorkflowChain({
id: "ticket-escalation",
name: "Ticket Escalation",
purpose: "根据工单信息决定是否升级到人工团队",
input: z.object({
ticketId: z.string(),
reason: z.string(),
}),
result: z.object({
status: z.enum(["pending", "escalated", "rejected"]),
note: z.string(),
}),
});
生产实现中可以继续追加查询、规则判断、人工确认和结果写入步骤。涉及外部写操作时,不要只让模型输出“已升级”,必须由后端工具真正执行并返回可验证结果。
五、常见问题与实践建议
1. 为什么 Agent 总是调用错误的工具?
检查工具描述是否重叠、参数 Schema 是否清晰、工具数量是否过多,以及是否把不相关工具全部暴露给每个 Agent。可以按任务动态提供工具集合,并为工具名称使用稳定、具体的动词。
2. 记忆会不会泄露其他用户的信息?
会,尤其是在会话 ID、用户 ID 和租户 ID 没有严格绑定时。每次读取记忆都要使用服务端生成的作用域,不能直接信任客户端传来的 conversation ID。
3. 是否应该让 Agent 自动执行写操作?
默认不应该。写操作需要权限检查、参数校验、幂等键、审计和人工确认。只要操作涉及资金、删除、权限变更或对外发送消息,就应设置更严格的审批边界。
4. 如何限制 Agent 的成本?
设置单任务最大步数、输入和输出 Token 上限、工具调用次数、总耗时和模型级预算。长任务可以采用低成本模型做规划,高能力模型只处理关键步骤。
5. VoltOps 等观测能力能替代自建监控吗?
平台观测适合查看 Agent 执行过程和开发调试,但企业仍需将关键指标接入自己的日志、指标、审计和告警系统。尤其是租户费用、业务权限和敏感数据访问,不能只依赖第三方控制台。
六、进阶思考
1. Agent 与业务系统的边界
建议采用以下边界:
Agent:理解自然语言、规划、选择工具
业务服务:权限、事务、数据校验、状态变更
数据库:最终事实和一致性
观测系统:执行记录、成本和质量数据
Agent 可以提出动作,但真正的业务变更应由业务服务完成。
2. MCP 接入
VoltAgent 可以连接 MCP 服务,把外部工具和数据源纳入 Agent。接入时仍要建立工具白名单、服务器身份、传输安全、参数审计和超时策略。
MCP 解决的是工具连接协议,不会自动解决业务权限。一个 MCP Server 暴露的工具仍然需要由应用决定哪些用户和 Agent 可以使用。
3. 可恢复执行
长任务需要保存:
- taskId 和 parentTaskId。
- 当前步骤和输入摘要。
- 工具调用记录。
- 重试次数和错误分类。
- 暂停原因和恢复凭据。
- 最终结果与费用。
这样客户端断线、Pod 重启或模型超时后,任务仍然可以查询、重试或人工接管。
4. 评估 Agent 行为
应为每个重要 Agent 准备固定任务集,检查:
- 是否选择正确工具。
- 是否遵守权限边界。
- 是否在需要时请求确认。
- 是否能从工具错误中恢复。
- 是否产生多余调用。
- 是否控制 Token 和延迟。
Agent 的评估对象不只是最终文本,还包括决策轨迹和工具动作。
结论
VoltAgent 将 TypeScript Agent 开发拆分为 Agent、工具、记忆、工作流、MCP 和观测等能力,适合用来构建比简单聊天接口更复杂的任务型应用。它可以减少重复的运行时工程,但不会替代业务权限、数据一致性和安全审查。
实践时建议先实现只读工具,再加入持久化记忆和工作流;最后接入写操作、MCP 和多智能体协作。每一步都要设置最大步数、超时、预算、审计和可恢复状态。
网硕互联帮助中心




评论前必须登录!
注册