- 人工智能
- AI Agent
- Agent 框架
- 后端
- 多智能体
- RAG
- 工具调用
- Agent 记忆
【免费下载链接】voltagent
AI Agent Engineering Platform built on an Open Source TypeScript AI Agent Framework
VoltAgent 是一个开源的 TypeScript AI Agent 工程化框架,而@voltagent/a2a-server是它提供的Agent-to-Agent(A2A)协议服务端实现。本篇文章以仓库中的 with-a2a-server 示例 为主体,完整讲解如何把一个普通的 VoltAgent Agent 通过 A2A 的 JSON-RPC 端点暴露给外部,让其他 Agent、IDE 或编排框架可以通过.well-known发现它、通过/a2a/:serverId向它发送消息并管理任务。读完本文,你将掌握 A2A 服务的装配方式、Agent Card 发现机制、四大 JSON-RPC 方法(message/send、message/stream、tasks/get、tasks/cancel)的调用与内部实现,以及如何用冒烟测试端到端验证整个链路。
什么是 A2A 协议,为什么 VoltAgent 要支持它
A2A(Agent-to-Agent)是一套让 AI Agent 之间相互发现、通信与协作的开放协议。与 MCP(Model Context Protocol,面向 Agent 与工具/数据源)不同,A2A 解决的是Agent 与 Agent之间的互操作问题:一个 Agent 需要调用另一个 Agent 的能力,而双方不需要共享同一套运行时。
VoltAgent 在@voltagent/a2a-server包中提供了这套协议的服务端实现。根据 packages/a2a-server/README.md 的说明,该包把 VoltAgent 的 Agent 暴露到 A2A JSON-RPC 协议之上,使其他 Agent、IDE 或编排框架可以通过定义良好的端点与之交互(目前该包仍标记为Experimental,API 在首个稳定版发布前可能发生变化)。
with-a2a-server示例展示的正是这一能力的落地形态:一个极简 VoltAgent 项目,包含一个SupportAgent和一个status工具,通过@voltagent/a2a-server与@voltagent/server-hono的集成,对外提供:
- 一个Agent Card(发现文档),说明该 Agent 的能力与端点地址;
- 一个JSON-RPC 消息端点,接收符合 A2A 规范的请求并驱动内部 Agent 执行;
- 完整的任务生命周期(提交、执行中、完成、失败、取消)与内存任务存储。
示例结构总览
examples/with-a2a-server ├── src/ │ ├── agents/assistant.ts # 示例 Agent 定义(含 status 工具) │ └── index.ts # VoltAgent 启动引导 + A2A 服务器注册 ├── scripts/ │ └── smoke-test.mjs # 端到端冒烟测试脚本 ├── package.json ├── tsconfig.json └── README.md与仓库中其他示例一样,package.json中定义了dev(tsx watch --env-file=.env ./src)、build(tsc)、start、test:smoke等脚本,并声明了对@voltagent/a2a-server、@voltagent/core、@voltagent/internal、@voltagent/logger、@voltagent/server-hono、ai、zod的依赖,具体可见 examples/with-a2a-server/package.json。
环境准备与本地运行
示例 README 明确给出了前置条件与运行方式,完整继承如下:
前置条件
- Node.js 20+
pnpm- 环境变量
OPENAI_API_KEY(示例 Agent 使用 OpenAI 模型)
创建项目(若尚未创建)
npm create voltagent-app@latest -- --example with-a2a-server安装依赖并启动
pnpm install pnpm --filter voltagent-example-with-a2a-server devdev脚本实际执行的是tsx watch --env-file=.env ./src,即监听模式启动,并会加载项目根目录的.env文件(OPENAI_API_KEY可放在其中)。Hono 服务器监听在http://localhost:3141。
从 Agent 定义到 A2A 暴露:两个关键源码文件
1. Agent 与工具定义
examples/with-a2a-server/src/agents/assistant.ts 中定义了一个带status工具的SupportAgent:
import { Agent, createTool } from "@voltagent/core"; import { z } from "zod"; const statusTool = createTool({ name: "status", description: "Return the current time in ISO format", parameters: z.object({}), async execute() { return { timestamp: new Date().toISOString(), }; }, }); export const assistant = new Agent({ id: "supportagent", name: "SupportAgent", instructions: "Reply with helpful answers and include the current time when relevant.", model: "openai/gpt-4o-mini", tools: [statusTool], }); export const tools = { status: statusTool };要点:
- Agent 的
id为supportagent,这个 id 会出现在后续的发现路径(/.well-known/supportagent/agent-card.json)和 JSON-RPC 路径(/a2a/supportagent)中; - 工具参数使用
zod的z.object({})描述,说明该工具不需要额外参数; execute()返回 ISO 格式时间戳,用于演示 Agent 在回复中引用工具结果。
2. A2A 服务器创建与 VoltAgent 装配
examples/with-a2a-server/src/index.ts 是核心装配入口:
import { A2AServer } from "@voltagent/a2a-server"; import { VoltAgent } from "@voltagent/core"; import { createPinoLogger } from "@voltagent/logger"; import { honoServer } from "@voltagent/server-hono"; import { assistant } from "./agents/assistant"; const logger = createPinoLogger({ name: "with-a2a-server", level: "debug", }); const a2aServer = new A2AServer({ name: "SupportAgent", version: "0.1.0", description: "Expose VoltAgent over the Agent-to-Agent protocol", }); new VoltAgent({ agents: { assistant, }, a2aServers: { supportAgent: a2aServer, }, server: honoServer({ port: 3141 }), logger, }); logger.info("VoltAgent A2A example is running on http://localhost:3141");装配逻辑分三步:
- 创建
A2AServer:通过构造参数声明服务器元信息(name、version、description)。A2AServerConfig还支持id、provider(组织与官网 URL)、agents(直接在服务器上挂 Agent)以及filterAgents(按请求上下文过滤可暴露的 Agent),详见 packages/a2a-server/src/types.ts; - 在
VoltAgent中登记:通过a2aServers字段以serverId -> A2AServer的形式注册(键supportAgent就是路径中的 serverId); - 挂载 Hono 服务器:
honoServer({ port: 3141 })提供 HTTP 传输层,端口为 3141。
从源码看,VoltAgent在初始化时会对a2aServers逐项调用initializeA2AServer(packages/core/src/voltagent.ts),其内部把实例注册进 A2A 注册表并注入依赖:
private initializeA2AServer(server: A2AServerLike | A2AServerFactory): A2AServerLike { const instance: A2AServerLike = typeof server === "function" ? server() : server; this.a2aServerRegistry.register(instance, this.getA2ADependencies()); this.a2aServers.add(instance); return instance; }这里A2AServerLike | A2AServerFactory意味着除了直接传实例,还可以传一个返回实例的工厂函数(便于懒加载)。注入的依赖来自getA2ADependencies()(packages/core/src/voltagent.ts),即 VoltAgent 全局的agentRegistry——这让 A2A 服务器可以访问框架中注册的所有 Agent,而不只是构造时传入的那几个。A2A 服务器的依赖契约定义在 packages/internal/src/a2a/types.ts:agentRegistry.getAgent(id)与agentRegistry.getAllAgents()加上可选的taskStore。
发现机制:Agent Card 与.well-known
A2A 协议要求 Agent 通过标准位置暴露其能力描述文档(Agent Card)。示例中,Hono 服务器启动后即可直接抓取发现文档:
curl http://localhost:3141/.well-known/supportagent/agent-card.json | jq返回的卡片通过url字段公布 JSON-RPC 端点:
{ "url": "http://localhost:3141/a2a/supportagent" }这个路由的规范定义在 packages/server-core/src/routes/definitions.ts:GET /.well-known/:serverId/agent-card.json(OpenAPI 摘要为 "Get A2A agent card")。Hono 适配层在 packages/server-hono/src/routes/a2a.routes.ts 中实现:先按serverId从注册表解析出 Agent,再调用resolveAgentCard生成卡片,找不到时返回 404,参数非法时返回 400。
卡片本身由buildAgentCard生成(packages/a2a-server/src/adapters/agent.ts),其结构(对应 types.ts 中的 AgentCard)包括:
| 字段 | 说明 | 示例值来源 |
|---|---|---|
name | Agent 名称 | agent.id ?? agent.name,即supportagent |
description | 描述 | 默认取agent.purpose |
url | JSON-RPC 端点绝对地址 | 基于请求 URL 解析/a2a/:serverId |
provider/version | 服务器元信息 | A2AServer配置 |
capabilities | 能力声明 | 默认streaming: true、pushNotifications: false、stateTransitionHistory: false |
defaultInputModes/defaultOutputModes | 输入输出模式 | 均为["text"] |
skills | 技能列表 | 由agent.getTools()映射,tags: ["tool"] |
一个值得注意的实现细节:Agent 的每个工具会被自动映射为卡片中的一项skill,外部 Agent 据此即可感知该 Agent 具备哪些能力。
消息端点:向 Agent 发送 JSON-RPC 请求
拿到卡片中的url后,即可向 JSON-RPC 端点发消息。示例 README 给出的请求体:
curl -X POST http://localhost:3141/a2a/supportagent \ -H "Content-Type: application/json" \ -d '{ "jsonrpc": "2.0", "id": "1", "method": "message/send", "params": { "message": { "kind": "message", "role": "user", "messageId": "msg-1", "parts": [{ "kind": "text", "text": "What time is it?" }] } } }'协议层对请求的约束:
jsonrpc必须为"2.0",且method必须是字符串(校验见 protocol.ts 的 isJsonRpcRequest);params.message必须存在、parts必须为非空数组;当前实现只支持kind: "text"的纯文本消息部件,其他类型会被拒绝(校验见 server.ts 的 validateMessageSendParams);- 消息可选携带
taskId、contextId,若缺失则由服务端生成(randomUUID)。
服务端入口handleRequest(packages/a2a-server/src/server.ts)是一个按method分发的 JSON-RPC 处理器,支持四种方法:
| 方法 | 作用 | 返回 |
|---|---|---|
message/send | 同步发送一条消息并等待执行完成 | TaskRecord |
message/stream | 以 SSE 流式发送消息,逐段推送任务状态 | 异步流,逐帧TaskRecord |
tasks/get | 按任务 id 查询历史与状态 | TaskRecord |
tasks/cancel | 取消正在执行的任务 | TaskRecord(状态为canceled) |
请求的上下文(userId、sessionId、metadata)可通过context查询参数(JSON 编码)或请求体中的context字段传入,由 Hono 路由解析合并(见 packages/server-hono/src/routes/a2a.routes.ts),最终在调用 Agent 时转换为conversationId、abortSignal、context等执行选项(见 server.ts 的 buildAgentCallOptions)。
任务生命周期与消息映射
A2A 的核心抽象是Task(任务):每次对话都对应一个任务,任务有明确的状态机。TaskState定义在 packages/a2a-server/src/types.ts:
submitted → working → completed ↘ failed ↘ canceled任务记录TaskRecord(types.ts)包含id、contextId、status(状态 + 时间戳 + 关联消息)、history(完整消息历史)、可选的artifacts与metadata。状态迁移由 packages/a2a-server/src/tasks.ts 中的纯函数完成:createTaskRecord(初始为submitted)、appendMessage(追加消息)、updateLastMessage(更新最后一条,用于流式增量)、transitionStatus(迁移状态并打时间戳)、ensureCancelable(终态任务不可再取消)、upsertArtifact(按名字写入或合并产物)。
A2A 消息与 VoltAgent 内部消息的转换发生在适配层 packages/a2a-server/src/adapters/message.ts:
toVoltAgentMessage:取第一条 text 部件作为内容,把role: "agent"映射为assistant;fromVoltAgentMessage:反向映射回 A2A 消息并生成新的messageId。
任务存储默认使用InMemoryTaskStore(packages/a2a-server/src/store.ts),它以agentId::taskId为键在内存Map中保存任务快照,读写时做structuredClone,并维护一个activeCancellations集合用于取消传播。这意味着重启进程后任务即丢失——示例 README 的 Next steps 也明确指出,生产场景应提供自定义TaskStore实现以持久化任务。
流式场景的实现细节:message/stream返回的流会先产出初始记录(submitted/working),随后在for await消费streamResult.textStream的每个 chunk 时,把累积文本写入responseMessage.parts[0].text并产出working状态的更新帧,最后产出completed终帧;若中途被取消或出错,则分别产出canceled或failed终帧(见 server.ts 的 createMessageStreamGenerator)。Hono 路由把该异步流包装为 SSE(text/event-stream)响应,帧格式为data: <RS>payload\n\n(见 packages/server-hono/src/routes/a2a.routes.ts)。
错误处理与 JSON-RPC 错误码
协议错误统一由VoltA2AError表达(packages/a2a-server/src/types.ts),错误码对应A2AErrorCode常量(types.ts):
| 错误码 | 值 | 含义 |
|---|---|---|
PARSE_ERROR | -32700 | JSON 解析失败 |
INVALID_REQUEST | -32600 | 请求结构非法(如未知 Agent) |
METHOD_NOT_FOUND | -32601 | 未知方法 |
INVALID_PARAMS | -32602 | 参数非法(如消息缺少 parts) |
INTERNAL_ERROR | -32603 | 内部错误 |
TASK_NOT_FOUND | -32001 | 任务不存在 |
TASK_NOT_CANCELABLE | -32002 | 任务已处于终态,不可取消 |
UNSUPPORTED_OPERATION | -32004 | 不支持的操作 |
任何处理器抛出的错误都会在handleRequest的 catch 中被normalizeError(packages/a2a-server/src/protocol.ts)统一转换为符合 JSON-RPC 2.0 的错误响应(error.code、error.message、error.data)。Hono 层据此映射 HTTP 状态码:错误响应返回 400,未知 serverId 返回 404(见 a2a.routes.ts)。
端到端验证:冒烟测试脚本做了什么
仓库为示例提供了完整的端到端冒烟测试脚本 examples/with-a2a-server/scripts/smoke-test.mjs。先在一个终端启动 dev server,再在另一个终端运行:
pnpm --filter voltagent-example-with-a2a-server test:smoke脚本(默认目标http://localhost:3141,可用环境变量BASE_URL覆盖)依次断言以下环节:
- Agent Card 发现:抓取
/.well-known/supportagent/agent-card.json,断言name === "supportagent"、url是/a2a/supportagent的绝对地址、skills是数组; message/send:发送一条纯文本消息,断言响应为 JSON-RPC 2.0、无error,且任务状态为completed;tasks/get:用返回的task.id查询历史,断言history.length >= 2(用户消息 + Agent 回复);message/stream:以Accept: text/event-stream发起流式请求,逐帧解析data: <RS>...负载,断言至少收到一个working状态更新帧,且最终帧completed、最后一条历史消息role === "agent"且文本非空;tasks/cancel:先发起一个长任务流,在流打开(onOpen)时立即调用tasks/cancel,断言取消传播到流(最终状态为canceled或completed);- 取消传播的单元级验证:用内置的 stub Agent 与 stub 注册表直接驱动
A2AServer.handleRequest,在消费第一个流事件后调用tasks/cancel,断言最终帧为canceled——这验证了AbortController注册/注销机制(registerActiveOperation/abortActiveOperation,见 server.ts)确实能把取消信号传递给正在流式生成的 Agent。
全部通过后脚本输出🎉 All smoke tests passed。这说明该示例不仅是"能跑",而且覆盖了发现、同步、流式、查询、取消五大核心能力。
进一步扩展:多 Agent、流式与持久化
示例 README 的 Next steps 给出了三条明确的扩展路径:
- 接入多个 Agent:在
a2aServers映射中继续添加条目即可。每个条目以 serverId 为键,VoltAgent会为每个服务器注册独立的发现与 JSON-RPC 路由;由于agentRegistry是全局共享的,所有已注册的 Agent 都可被 A2A 服务器解析到。 - 流式支持:
A2AServer已内置message/stream(卡片中capabilities.streaming默认为true),当 Agent 通过streamText产生增量输出时,任务帧会逐段更新,外部可以实时渲染。 - 持久化任务:默认的
InMemoryTaskStore仅适合开发与演示。实现TaskStore接口(load({ agentId, taskId })与save({ agentId, data }),见 types.ts)并作为taskStore注入依赖后,即可把任务状态持久化到数据库,实现跨重启的任务恢复。
小结
with-a2a-server示例用极少的代码展示了 VoltAgent 的完整 A2A 集成路径:Agent + createTool定义能力,A2AServer暴露协议端点,VoltAgent.a2aServers完成装配,honoServer提供 HTTP 传输。配合.well-known发现文档、四种 JSON-RPC 方法与规范错误码,外部任何遵循 A2A 的 Agent、IDE 或编排框架都能与 VoltAgent 生态互操作。如果你想深入底层,可以从 packages/a2a-server/src/server.ts 的handleRequest开始,沿着message/send→handleMessageSend→toVoltAgentMessage→agent.generateText的调用链逐行阅读;想验证协议行为,packages/a2a-server/src/server.spec.ts 与 packages/server-hono/src/routes/a2a.routes.spec.ts 是更系统的测试参考。
- 人工智能
- AI Agent
- Agent 框架
- 后端
- 多智能体
- RAG
- 工具调用
- Agent 记忆
【免费下载链接】voltagent
AI Agent Engineering Platform built on an Open Source TypeScript AI Agent Framework
相关推荐
Thorium 浏览器指南:Chromium 优化分支的选版、安装与进阶配置
Thorium 浏览器指南:Chromium 优化分支的选版、安装与进阶配置 Thorium 是一个以元素周期表 90 号元素命名的 Chromium 分支浏览
桌面应用跨平台VoltAgent A2A Server 实战指南:用 JSON-RPC 把 VoltAgent Agent 暴露给外部 Agent
VoltAgent A2A Server 实战指南:用 JSON RPC 把 VoltAgent Agent 暴露给外部 Agent 本篇技术指南围绕 @vol
人工智能AI AgentAgent 框架后端多智能体RAG工具调用Agent 记忆Agent 工作流AI 评测MCP 服务MCP Clients语音OmniRoute A2A Server 技术解析:以 JSON-RPC 2.0 将 AI 网关暴露为 Agent-to-Agent 协议端点
OmniRoute A2A Server 技术解析:以 JSON RPC 2.0 将 AI 网关暴露为 Agent to Agent 协议端点 OmniRout
后端API网关LLM 网关人工智能大模型MCP 服务桌面应用
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考