使用agentscope自动注册agent应用到nacos以及对a2a协议的思考
参考资料
- https://java.agentscope.io/zh/task/a2a.html#a2a-server
- https://mp.weixin.qq.com/s/-pp43gOTkTtkuxAt_szFIw
本文主要记录了在测试agent自动注册nacos过程中对a2a的一些思考,可能存在一些理解的偏差,请审慎自主思考阅读。
借助A2A 对接能力可以实现像调用本地工具一样自然地调用远端 A2A Agent,实现跨语言、跨框架的协同。agentscope能够将应用包装成符合 A2A 规范的服务端点。
这里使用agentscope runtime启动agent,并同时注册在nacos中,如下示例
# 创建 Nacos Registry 实例
registry = NacosRegistry(
nacos_client_config=ClientConfigBuilder()
.server_address("localhost:8848")
# 其他可选配置项
.build()
)
agent_app = AgentApp(
app_name="TourGuideAgent",
app_description="专业导游智能体,提供旅游咨询、路线规划和景点介绍服务",
# 在 a2a_config 中配置 registry 和技能
a2a_config=AgentCardWithRuntimeConfig(
port=8091,
registry=registry,
agent_card={
"skills": [
AgentSkill(
id="attraction_recommendation",
name="景点推荐",
description="根据旅游目的地和时间,推荐当地热门景点和小众景点",
inputModes=["text"],
outputModes=["text"],
tags=["景点", "推荐", "旅游"]
),
AgentSkill(
id="travel_consultation",
name="旅游咨询",
description="解答关于交通、住宿、美食、天气等旅游相关问题",
inputModes=["text"],
outputModes=["text"],
tags=["旅游咨询", "交通", "住宿", "美食"]
)
]
}
),
)
@agent_app.query(framework="agentscope")
async def query_func(
self,
msgs,
request: AgentRequest = None,
**kwargs,
):
# 创建ReAct智能体实例
agent = ReActAgent(
name="TourGuide",
model=OpenAIChatModel(
model_name="gpt-4o-mini",
api_key="sk-uzpq0u0n5FN14HorW45hUw",
client_kwargs={"base_url": "http://172.31.14.46:4000"},
stream=True, # 启用流式响应
),
sys_prompt="你是一位专业的导游,熟悉各地的旅游景点、历史文化和风土人情。请为游客提供详细的旅游咨询、路线规划和景点介绍。回答时要热情友好,信息准确,并尽可能提供实用的旅游建议。",
memory=InMemoryMemory(),
formatter=OpenAIChatFormatter(),
)
agent.set_console_output_enabled(enabled=False)
# 执行智能体并流式返回消息
async for msg, last in stream_printing_messages(
agents=[agent],
coroutine_task=agent(msgs),
):
yield msg, last
if __name__ == "__main__":
import asyncio
async def main():
result = await agent_app.deploy(
LocalDeployManager(host="0.0.0.0", port=8091), mode=DeploymentMode.DAEMON_THREAD
)
stop_event = asyncio.Event()
await stop_event.wait()
asyncio.run(main())
启动服务后,可以观察到nacos中已经自动注册了a2a服务

使用如下客户端请求
async def main() -> None:
# 1. 从 Nacos 获取 Agent Card
agent_card = await NacosAgentCardResolver(
remote_agent_name="TourGuideAgent",
nacos_client_config=ClientConfig(
server_addresses="http://127.0.0.1:8848",
),
).get_agent_card()
print(f"获取到 Agent Card: {agent_card.name}")
# 2. 调用 agent 获取回复
agent = A2AAgent(agent_card=agent_card)
msg = Msg(
name="user",
content="告诉我亚马逊有哪些计算服务?",
role="user",
)
await agent(msg)
if __name__ == "__main__":
import asyncio
asyncio.run(main())
这个时候我希望了解下获取a2a card已经agent之间通信的细节,于是通过抓包的方式来检查。
由于已经将agent card注册在nacos中,并且在客户端中通过NacosAgentCardResolver检索,因此客户端能够通过nacos获取a2a card信息,类似如下内容
$ curl localhost:8091/.well-known/agent-card.json
{"additionalInterfaces":[],"capabilities":{"pushNotifications":false,"stateTransitionHistory":false,"streaming":false},"defaultInputModes":["text"],"defaultOutputModes":["text"],"description":"专业导游智能体,提供旅游咨询、路线规划和景点介绍服务","name":"TourGuideAgent","preferredTransport":"JSONRPC","protocolVersion":"0.3.0","skills":[{"description":"根据旅游目的地和时间,推荐当地热门景点和小众景点","id":"attraction_recommendation","inputModes":["text"],"name":"景点推荐","outputModes":["text"],"tags":["景点","推荐","旅游"]},{"description":"解答关于交通、住宿、美食、天气等旅游相关问题","id":"travel_consultation","inputModes":["text"],"name":"旅游咨询","outputModes":["text"],"tags":["旅游咨询","交通","住宿","美食"]}],"url":"http://172.31.14.46:8091/a2a","version":"1.0.0"}
然而在操作过程中发现无法获取到访问8848端口的请求,
- 程序成功运行并获取到了 a2a card,但是抓包只看到 8091 端口的流量
- 但是程序访问了 Nacos,不存在缓存机制(已经重启了客户端)
通过strace我们发现client实际访问nacos使用的是9848端口
# strace -f -e trace=network python agentscope/a2a/client.py
[pid 1146518] connect(12, {sa_family=AF_INET, sin_port=htons(9848), sin_addr=inet_addr("127.0.0.1")}, 16) = 0
[pid 1146520] connect(12, {sa_family=AF_INET6, sin6_port=htons(9848), sin6_flowinfo=htonl(0), inet_pton(AF_INET6, "::ffff:127.0.0.1", &sin6_addr), sin6_scope_id=0}, 28) = -1 EINPROGRESS (Operation now in progress)
[pid 1146518] connect(13, {sa_family=AF_INET, sin_port=htons(9848), sin_addr=inet_addr("127.0.0.1")}, 16) = 0
[pid 1146519] connect(13, {sa_family=AF_INET6, sin6_port=htons(9848), sin6_flowinfo=htonl(0), inet_pton(AF_INET6, "::ffff:127.0.0.1", &sin6_addr), sin6_scope_id=0}, 28) = -1 EINPROGRESS (Operation now in progress)
[pid 1146475] connect(14, {sa_family=AF_INET, sin_port=htons(8091), sin_addr=inet_addr("172.31.14.46")}, 16) = -1 EINPROGRESS (Operation now in progress)
Nacos 的 gRPC 端口通常是 9848,也就是说客户端Nacos SDK 使用了 gRPC 协议来通信,具体代码入口为。关于这一点的说明见,https://nacos.io/blog/faq/nacos-user-question-history15295/
Nacos 从 2.0 版本开始引入了 gRPC 协议作为客户端与服务器之间的通信方式。Nacos 2.0 引入 gRPC 的主要目的是为了提升性能和扩展性
# agentscope/a2a/_nacos_resolver.py
client = await NacosAIService.create_ai_service(...)
return await client.get_agent_card(...)
# v2/nacos/ai/nacos_ai_service.py
self.grpc_client_proxy = AIGRPCClientProxy(...)
return await self.grpc_client_proxy.get_agent_card(...)
继续查看抓包结果,追踪HTTP流结果如下

可见客户端向agent的/a2a发起post请求
{
"id": "8725cad3-0d83-48d2-bed3-da456fca651e",
"jsonrpc": "2.0",
"method": "message/send",
"params": {
"configuration": {
"acceptedOutputModes": [],
"blocking": true
},
"message": {
"kind": "message",
"messageId": "3fef013a-842c-462c-9be6-d5bdf23a4b28",
"parts": [
{
"kind": "text",
"text": "告诉我亚马逊有哪些计算服务?"
}
],
"role": "user"
}
}
}
响应内容如下,响应内容和ADK的示例还是有区别的,可能是由于A2A协议本身也在变动中。
{
"id": "8725cad3-0d83-48d2-bed3-da456fca651e",
"jsonrpc": "2.0",
"result": {
"kind": "message",
"messageId": "msg_46df2210-d0f8-463c-a108-ae69bd62c2b5",
"parts": [
{
"kind": "text",
"text": "您提到服您推荐一个适合初学者的“第一个 AWS 计算项目”?😊"
}
],
"role": "agent"
}
}
这里有一个问题,a2a调用并没有提到需要使用那些技能,这一点和mcp tool中通过明确的schema定义支持的工具是有区别的,我理解由于a2a本身是供agent之间相互调用的协议,因此a2a card中携带的内容实际上起到的作用类似于对agent的描述。
所以,a2a更像是意图委托,Client并不关心 Server 具体用了什么函数和方法,只是发送一个自然语言的任务。Remote Agent 接到任务后,自己进行推理,决定是否需要使用其他工具来完成这个任务。
所以当我们将remote agent的系统提示定义为一个技术专家,但是skill中指定导游的技能时,会得到以下回复。因为skill只决定了client agent是否会发送相关的请求,而remote agent的输出并不会受到skill的影响。


浙公网安备 33010602011771号