一个月前我在内部做了一轮技术分享,主题只有一个:当你的智能体框架开始支持 A2A 协议,服务注册中心 Nacos 到底能帮上什么忙。台下有人问了一句:AgentScope 不是已经有自己的多智能体消息传递机制了吗,为什么还要把 A2A 和 Nacos 引进来?这个问题其实问到了点子上——单个框架内的多智能体协作,和开放生态下的跨框架智能体协作,是两码事。本文就从 AgentScope 2.0 的 A2A 模式讲起,聊聊我们是怎么用 Nacos 把多个 Agent 服务串成一张可动态发现的协作网络。
1. 智能体之间为什么需要 A2A:没有“通用语”之前的协作乱象
1.1 MCP 解决的是“智能体找工具”,不是“智能体找智能体”
过去两年大家聊得最多的协议是 MCP(Model Context Protocol),它的价值在于把工具调用标准化:智能体不需要为每个 API 写一套适配层,统一走 MCP 协议就能访问文件、数据库、第三方服务。但 MCP 的边界很清晰,它描述的是“智能体 -> 工具”这条单向链路。
真正做多智能体系统的时候你会发现,卡脖子的问题不在工具接入,而在智能体与智能体之间怎么互相发现、怎么下发任务、怎么传结果。每个框架都有自己的内部消息格式,AgentScope 的消息是 Message 对象,LangGraph 用的 graph state,AutoGen 走的是 ConversableAgent 的对话流。框架内部跑得再顺,出了框架边界大家都只能说“方言”,没法互通。
我们早期做过一个跨团队的协作场景,A 组用 AgentScope 做了个售前咨询智能体,B 组用自研框架做了个库存查询智能体,两边要协作就得靠人工对接口:A 组给我们拉一个 HTTP 接口文档,我们写调用代码,参数一变又要重新联调。这不叫智能体协作,这叫手工对接。
1.2 A2A 的核心抽象:Agent Card、Task、Artifact
A2A(Agent2Agent)协议要解决的就是这个互操作问题。它把每个智能体看作一个可通过 HTTP 访问的服务,定义了三个核心抽象:
| 抽象概念 | 作用 | 类比理解 |
|---|---|---|
| Agent Card | 描述智能体的能力、技能、endpoint 地址 | 智能体的“简历 + 名片” |
| Task | 一次从下发到完成的工作单元,带状态机 | 工单系统里的一个工单 |
| Artifact | 任务产出的文件、文本、结构化数据 | 工单交付物 |
协议的底层传输是 JSON-RPC over HTTP,所以只要你的 Agent 能暴露一个 HTTP 端点,就能接入 A2A 生态,跟你内部用什么框架、什么语言没有关系。Task 的状态机非常明确:submitted -> working -> input-required -> completed / failed / canceled,这让调用方可以稳定地轮询或接收回调,不用猜对方的执行进度。
当初看到这套抽象的时候我最大的感受是:它没有试图去规定“智能体内部该怎么思考”,只管“外部怎么协作”。这个克制感很重要。业界已经有很多血的教训,协议一旦管到内部实现,大家就不愿意接了。
1.3 AgentScope 2.0 对 A2A 的定位:不做封闭平台,做开放协议
AgentScope 本身是个产研落地向的多智能体框架,2.0 最大的变化之一就是原生支持 A2A 模式。它没有把智能体锁在自己的通信层里,而是让你既可以把 Agent 发布成一个符合 A2A 规范的服务端,也可以作为客户端去调用其他符合 A2A 规范的智能体。
这样布局的意义在于:AgentScope 不只是给自己生态里的智能体用,而是能够和 Google 的 GenAI Agents、Salesforce、以及各种支持 A2A 的平台直接互通。对做平台的同学来说,这是个好消息——你不用再为每个上游框架写一次适配,协议兼容一次,后续全通。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. AgentScope 2.0 的 A2A 实现是怎么落地的
2.1 服务端视角:把任意 Agent 暴露成 A2A 服务
在 AgentScope 2.0 里暴露 A2A 服务,核心逻辑是把一个已有的 Agent 或 Agent 编排流程包装成消息处理函数,然后挂到 A2A 服务端上。伪代码层面大概是这样的:
python复制import agentscope
# 定义一个处理 A2A Task 的函数
async def handle_task(task, context):
# task.message 里是对方发来的用户请求
result = await my_agent.run(task.message)
return {
"artifacts": [
{
"name": "reply",
"type": "text",
"text": result.text,
}
]
}
server = agentscope.a2a.A2AServer(
agent_card={
"name": "customer-service-agent",
"description": "处理售前咨询与产品问答",
"url": "http://agent-svc:8080/a2a",
"skills": [
{
"id": "product_info",
"name": "产品信息查询",
}
],
},
handler=handle_task,
)
server.start(host="0.0.0.0", port=8080)
Agent Card 里的 skills 字段特别有用,A2A 协议不要求你提前约定每个能力的方法签名,而是让调用方通过 Agent Card 里的描述来判断“这个智能体能干什么”。所以在设计 Agent Card 的时候,描述文字要写得让机器也容易理解,不能太艺术化。我们内部就吃过亏,写了“帮客户解决各种问题”这种废话,结果对方智能体模型读完根本不知道该不该把任务转发过来。
2.2 客户端视角:通过 Agent Card 发现并调用远端智能体
客户端侧的逻辑更简单:拿一个 Agent Card 的 URL,解析出能力信息,然后创建 Task,把用户问题传过去,等待执行结果。
python复制from agentscope.a2a import A2AClient
agent_card_url = "http://agent-svc:8080/.well-known/agent-card.json"
client = A2AClient(agent_card_url=agent_card_url)
# 创建任务
task = client.create_task(
message={"role": "user", "content": "请查一下这款产品是否支持海外发货"},
)
# 轮询任务状态
result = client.poll_task(task_id=task.id)
for artifact in result.artifacts:
print(artifact.text)
实际使用中 create_task 和 poll_task 是最常用的两个方法,但如果你做的是实时对话类场景,A2A 也支持 message_stream 的流式模式,task 会处于 working 状态,同时不断返回增量 artifact。这个后面讲坑的时候会再展开。
2.3 任务生命周期与流式消息的处理
A2A 的 Task 状态机看起来简单,真正落地时你会发现它像一个分布式任务队列的状态机:任务可能被派发到多个 worker,可能中途需要向调用方索取补充信息(input-required),需要你定义好回调逻辑。AgentScope 2.0 在框架层面把这个状态流转封装了,你只要关注 handler 的返回结构就行。
一个值得强调的设计是:handler 的返回值不一定是一次性的最终结果,它可以是多个 artifact 的列表,配合任务状态使用。比如一个做数据分析的智能体,可以先返回一个中间表格,再返回一段文字结论。调用方可以各自按需消费,这就是 A2A 对富结果的支持。
3. Nacos 在整个架构里充当什么角色
3.1 智能体地址的动态发现与故障转移
A2A 协议本身只解决“格式互通”,不解决“服务在哪里”。Agent Card 里写死的 URL 在 Demo 阶段没问题,一旦智能体服务扩容、迁移、故障重启,手写 URL 就变成了一堆坑。Nacos 在这里的角色,就是给所有 A2A 服务做注册与发现。
我们把每个 Agent 服务启动时用一个唯一标识(比如 agentscope://customer-service)注册到 Nacos,服务 IP 和端口由注册中心动态维护。客户端需要调用某个智能体时,先向 Nacos 查询当前可用的地址列表,再选一个发起 A2A 请求。这样智能体从 1 个副本扩到 10 个副本,客户端代码完全不用改。
实际测试下来,Nacos 临时实例的默认心跳是 5 秒一次,健康检查失败后自动剔除,这在大多数智能体场景下够用。服务重启造成的地址漂移,注册中心也能在十几秒内收敛,比手工改配置文件强了不止一个量级。
3.2 配置中心能力:把系统提示词和模型参数做成可热更配置
Nacos 的第二个价值容易被忽略——配置管理。多智能体系统里,每个 Agent 的行为高度依赖系统提示词、模型版本、temperature 参数。这些参数如果写在代码里,每次调整都要发版;如果放在本地配置文件,分布式部署时要同步到每台机器,非常痛苦。
我们的做法是:把系统提示词和模型调用参数放到 Nacos 配置中心,Agent 启动时加载一份,同时监听配置变更事件。业务方调整某个 Agent 的“人设”或模型参数时,直接用 Nacos 控制台改配置,配置发布后所有节点自动生效,连服务都不用重启。
3.3 安全基线:命名空间隔离与鉴权配置
Nacos 在使用上有个老生常谈的坑,就是未授权访问问题。默认配置下 Nacos 的 Open API 是裸露的,如果网络策略没收紧,命名空间、服务实例信息都能被外部拉走。最近很多人问的“Nacos namespaces 未授权访问”就是这么来的。
这里提一个我们内部强制要求的安全基线:
- Nacos 版本升到 2.2.1 以上,开启鉴权(
nacos.core.auth.enabled=true) - 生产环境一定要配置强密钥,不要用默认的
nacos字符串 - 按环境拆命名空间(dev/staging/prod),服务之间用白名单隔离
- 控制台暴露范围收窄到运维跳板机或内网,不直接对公网开放
安全配置不做好,注册中心暴露的信息可能让攻击者直接摸清你的 Agent 拓扑,这比单个接口被暴力破解更麻烦,因为拓扑信息是全局性的。
4. 实战:AgentScope A2A + Nacos 从零接入
4.1 环境准备与依赖安装
下面这部分是我们实际跑通的接入过程,照着做基本能复现。环境信息先列一下:
| 组件 | 版本/说明 |
|---|---|
| Python | 3.10+ |
| AgentScope | 2.0.x |
| Nacos Server | 2.3.2,单机或集群均可 |
| nacos-sdk-python | 1.0.7(官方 Python SDK) |
| 模型后端 | 通义千问或 OpenAI 兼容接口 |
安装依赖没什么特别,两条命令的事:
bash复制pip install agentscope[all]
pip install nacos-sdk-python
Nacos Server 的部署直接用官方 release 包解压运行即可,我用的是 standalone 模式做联调,生产环境建议至少 3 节点。
4.2 把 A2A 服务注册进 Nacos
Agent 服务启动时,除了启动 A2A 服务端,还要向 Nacos 注册一个临时实例。需要注意:注册的 IP 和端口必须填客户端能访问到的地址,不能想当然地填 localhost,否则跨节点调用必挂。
python复制import nacos
nacos_client = nacos.NacosClient(
server_addresses="nacos-server:8848",
namespace="public",
username="nacos",
password="your-password",
)
# 健康检查回调:Agent 进程存活时返回 True
def healthy():
return True
nacos_client.add_naming_instance(
service_name="agentscope/customer-service",
ip="10.0.0.5", # 外部可达的 IP,生产环境建议从环境变量读取
port=8080,
metadata={
"protocol": "a2a",
"agent_card_path": "/.well-known/agent-card.json",
"version": "2.0.0",
},
ephemeral=True,
healthy_check_callback=healthy,
)
service_name 这里我建议用 agentscope/{agent_name} 的命名规范,后面在 Nacos 控制台上看服务列表会非常清晰,按前缀就可以过滤出所有 A2A 服务。
有一个细节要注意:add_naming_instance 传的是同步的 SDK 调用,如果注册成功但心跳线程没起来,实例会在 15 秒后被标记为不健康。排查症状就是 Nacos 控制台看到服务在线但状态异常,这时候优先检查 SDK 的 beat 线程是不是被所在容器环境限制了。
4.3 客户端侧:注册中心发现 + 任务下发
客户端这边,不再硬编码 Agent Card 的 URL,而是先通过 Nacos 拿到服务实例,再拼出 Agent Card 地址。
python复制import nacos
from agentscope.a2a import A2AClient
nacos_client = nacos.NacosClient(
server_addresses="nacos-server:8848",
namespace="public",
)
instances = nacos_client.list_naming_instance("agentscope/customer-service")
# 简单轮询选一个可用实例,生产环境建议加负载策略
instance = instances[0]
agent_card_url = f"http://{instance['ip']}:{instance['port']}/.well-known/agent-card.json"
client = A2AClient(agent_card_url=agent_card_url)
task = client.create_task(
message={"role": "user", "content": "推荐两款适合跨境电商企业的产品"},
)
result = client.poll_task(task_id=task.id)
print(result.artifacts[0].text)
这套链路跑通之后,加新 Agent 的流程就从“双方联调”退化成“服务注册 + Agent Card 声明”,协作成本降了一个数量级。
5. 我在实际接入中踩过的坑
5.1 Nacos 心跳与 A2A 长连接的双重保活
第一次上测试环境时,我们遇到一个诡异现象:A2A 任务执行到一半,客户端突然报连接重置。查了一圈发现是负载均衡器的空闲超时把连接掐了,而我们的 Agent 处理一个任务有时要两三分钟,远超过默认的空闲时间。
解决思路是两个方向并行:HTTP 层把 Agent 服务侧的 keep-alive 时间调长,同时 Nacos 服务实例的 metadata 里带上 timeout 字段,方便客户端提前设置更合理的 socket 超时。更稳妥的方案是让 A2A 调用方走轮询模式而不是一个长连接等到底,尤其对耗时长任务,轮询 + task 状态查询比长连接更抗网络抖动。
5.2 Agent Card 的 endpoint 写错导致跨节点调用失败
这个问题非常隐蔽。AgentScope 的 A2A 服务端在生成 Agent Card 时,默认会用本机 hostname 或 127.0.0.1 拼出 url 字段。如果你在容器里运行,这个 url 对集群里的其他节点来说就是不可达地址。客户端拿到 Agent Card 后去调 url 里的地址,直接超时。
我们的修复方案是:服务启动时显式传入外部可达的地址,从环境变量或 Nacos 注册后的实例信息里取,不要依赖 AgentScope 默认的 hostname 推断。
python复制server = agentscope.a2a.A2AServer(
agent_card={
"name": "customer-service-agent",
"url": os.getenv("AGENT_PUBLIC_URL", "http://127.0.0.1:8080/a2a"),
# ...
},
handler=handle_task,
host="0.0.0.0",
port=8080,
)
因为这个问题我们耗费了整整一个下午,后来想一想,本质原因就是 Agent Card 里的 url 既是“身份标识”也是“调用地址”,一旦两者混在一起,环境差异就会放大成故障。
5.3 流式响应在 JSON-RPC 下的超时问题
A2A 的流式模式走的是 JSON-RPC 的 message_stream 方法,客户端和服务端之间会保持一个较长时间的连接,持续返回 artifact。我们当时直接把前端的 SSE 超时设置成 30 秒,结果 Agent 思考时间稍微一长,前端就报错。
这里给两个实用建议:
- 流式模式下,服务端要尽快返回一个初始的消息块,哪怕内容是空文本,目的是维持连接活性,让网关不判定为超时。
- 客户端侧不要依赖单次响应完成,而是累积 artifact,按业务语义判断结束。AgentScope 的 A2A 客户端对这种情况有处理,但你如果自己封装 HTTP 层,就一定要处理半包的情况。
5.4 版本兼容:AgentScope 2.0 与旧版模型配置迁移
从 AgentScope 1.x 升到 2.0 的过程中,最麻烦的不是 A2A 部分,而是模型配置的格式变化。2.0 把模型初始化方式重构得更统一,但旧项目里散落的各种 agentscope.init 写法需要逐个适配。我们的建议是:先在测试环境把模型读写接口包一层适配器,再套 A2A handler,否则你根本分不清报错是来自 A2A 层还是模型调用层。
如果你手上有存量 AgentScope 1.x 项目,升级前先跑一遍官方迁移文档里的示例,再动老代码,能省掉很多排查时间。
6. 从协议到生态:我的一点后续思考
AgentScope 支持 A2A 只是第一步,真正把“开放智能体生态”做起来,还有很多工程问题要处理。比如跨信任域的智能体之间怎么互相鉴权,A2A 协议的 client 凭证怎么在 Nacos 配置中心里安全分发;再比如任务级的路由策略,当同一类职能有多个智能体实例时,怎么按负载和技能匹配度做更聪明的选择。这些我和团队还在持续探索,方向对了,剩下的就是执行问题。
如果你最近也在做 AgentScope 2.0 的多智能体接入,建议先别急着上复杂编排。先用 A2A + Nacos 把两个最简单的 Agent 联通,再逐步加策略。协议的价值只有在跨边界时才会真正体现出来,单机单进程里你感受不到它的必要,一旦服务规模上来,你就能体会到一套好协议加一个可靠的注册中心,能让协作省多少事。
