用 VoltAgent 将 AI Agent 暴露为 A2A 服务:with-a2a-server 示例全解析
2026/9/24 14:57:54 网站建设 项目流程
  • 人工智能
  • 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 是一个开源的 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/sendmessage/streamtasks/gettasks/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中定义了devtsx watch --env-file=.env ./src)、buildtsc)、starttest:smoke等脚本,并声明了对@voltagent/a2a-server@voltagent/core@voltagent/internal@voltagent/logger@voltagent/server-honoaizod的依赖,具体可见 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 dev

dev脚本实际执行的是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 的idsupportagent,这个 id 会出现在后续的发现路径(/.well-known/supportagent/agent-card.json)和 JSON-RPC 路径(/a2a/supportagent)中;
  • 工具参数使用zodz.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");

装配逻辑分三步:

  1. 创建A2AServer:通过构造参数声明服务器元信息(nameversiondescription)。A2AServerConfig还支持idprovider(组织与官网 URL)、agents(直接在服务器上挂 Agent)以及filterAgents(按请求上下文过滤可暴露的 Agent),详见 packages/a2a-server/src/types.ts;
  2. VoltAgent中登记:通过a2aServers字段以serverId -> A2AServer的形式注册(键supportAgent就是路径中的 serverId);
  3. 挂载 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)包括:

字段说明示例值来源
nameAgent 名称agent.id ?? agent.name,即supportagent
description描述默认取agent.purpose
urlJSON-RPC 端点绝对地址基于请求 URL 解析/a2a/:serverId
provider/version服务器元信息A2AServer配置
capabilities能力声明默认streaming: truepushNotifications: falsestateTransitionHistory: 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);
  • 消息可选携带taskIdcontextId,若缺失则由服务端生成(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

请求的上下文(userIdsessionIdmetadata)可通过context查询参数(JSON 编码)或请求体中的context字段传入,由 Hono 路由解析合并(见 packages/server-hono/src/routes/a2a.routes.ts),最终在调用 Agent 时转换为conversationIdabortSignalcontext等执行选项(见 server.ts 的 buildAgentCallOptions)。

任务生命周期与消息映射

A2A 的核心抽象是Task(任务):每次对话都对应一个任务,任务有明确的状态机。TaskState定义在 packages/a2a-server/src/types.ts:

submitted → working → completed ↘ failed ↘ canceled

任务记录TaskRecord(types.ts)包含idcontextIdstatus(状态 + 时间戳 + 关联消息)、history(完整消息历史)、可选的artifactsmetadata。状态迁移由 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终帧;若中途被取消或出错,则分别产出canceledfailed终帧(见 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-32700JSON 解析失败
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.codeerror.messageerror.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覆盖)依次断言以下环节:

  1. Agent Card 发现:抓取/.well-known/supportagent/agent-card.json,断言name === "supportagent"url/a2a/supportagent的绝对地址、skills是数组;
  2. message/send:发送一条纯文本消息,断言响应为 JSON-RPC 2.0、无error,且任务状态为completed
  3. tasks/get:用返回的task.id查询历史,断言history.length >= 2(用户消息 + Agent 回复);
  4. message/stream:以Accept: text/event-stream发起流式请求,逐帧解析data: <RS>...负载,断言至少收到一个working状态更新帧,且最终帧completed、最后一条历史消息role === "agent"且文本非空;
  5. tasks/cancel:先发起一个长任务流,在流打开(onOpen)时立即调用tasks/cancel,断言取消传播到流(最终状态为canceledcompleted);
  6. 取消传播的单元级验证:用内置的 stub Agent 与 stub 注册表直接驱动A2AServer.handleRequest,在消费第一个流事件后调用tasks/cancel,断言最终帧为canceled——这验证了AbortController注册/注销机制(registerActiveOperation/abortActiveOperation,见 server.ts)确实能把取消信号传递给正在流式生成的 Agent。

全部通过后脚本输出🎉 All smoke tests passed。这说明该示例不仅是"能跑",而且覆盖了发现、同步、流式、查询、取消五大核心能力。

进一步扩展:多 Agent、流式与持久化

示例 README 的 Next steps 给出了三条明确的扩展路径:

  1. 接入多个 Agent:在a2aServers映射中继续添加条目即可。每个条目以 serverId 为键,VoltAgent会为每个服务器注册独立的发现与 JSON-RPC 路由;由于agentRegistry是全局共享的,所有已注册的 Agent 都可被 A2A 服务器解析到。
  2. 流式支持A2AServer已内置message/stream(卡片中capabilities.streaming默认为true),当 Agent 通过streamText产生增量输出时,任务帧会逐段更新,外部可以实时渲染。
  3. 持久化任务:默认的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/sendhandleMessageSendtoVoltAgentMessageagent.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

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

相关推荐

上一篇:插件自动化测试框架:markdown-preview.nvim的单元测试与集成测试实现
下一篇:phar-io/manifest云存储集成:从S3读取PHAR文件元数据

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

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

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

立即咨询