你好,我是黄佳。
上节课我们完成了快速搭建MCP RAG服务,这节课我们继续进行协议实战,感受一下A2A的协议用法。
下面我将逐步给你展示一个通过A2A协议搭建的智能体平台,让你的智能体不仅能够相互对话,还能够协作协力来完成一个任务。

在这个基于 A2A 协议的智能体平台中,用户在浏览器端发出指令后,前端会将该请求传给 Host Agent,由它负责解析用户意图、拆解具体子任务,并且并行触发多个 Remote Agent;每个 Remote Agent 通过 A2A Client 将子任务封装为标准的 JSON-RPC 请求,发送给远端对应的 A2A Server,再由后者调用各自擅长的智能体模块(如 LangGraph Agent负责外汇兑换、Google ADK Agent负责报销收据、Crew AI Agent负责根据文字内容来生成图片等)执行并返回结果;最后,Host Agent 汇总并格式化各路反馈,一并呈现给用户,实现多智能体的分工协作与能力互补。
项目准备工作
我们先来搞定一些必要的准备工作。
安装Python3.12版本
A2A协议非常新,所以它对环境的要求也是很高的。因此,要确保我们已经安装了Python 3.12版本。如果你的系统没有这个版本,那就要更新一下。
下面是在Ubuntu系统中更新Python的代码示例:
sudo apt update
sudo apt install software-properties-common
sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt update
sudo apt install -y python3.12 python3.12-venv python3.12-dev
在Ubuntu系统中,检查 Python 3.12 是否安装成功:
python3.12 --version
Python 3.12.10
其他各种系统的版本安装我就不赘述了。
克隆项目代码库
安装好Python包之后,就可以来到这里下载我为你准备的这套A2A Demo代码,并用下面的命令把它Clone到你的本机。
git clone https://github.com/huangjia2019/a2a-in-action.git
这一套代码,是我基于Google官方A2A仓库复制出来并简单改造过的教学版本,我会基于这套代码为你详细讲解A2A协议的实现细节。

项目克隆到本机之后呢,你会看下面的目录结构。

其中agents目录中,就包含后续课程中要讲解的一系列A2A Agents示例。而Demo目录则是我们下面要演示的协议实战示例。

我们打开demo目录,再打开其中的ui目录,你会注意到其中有一个pyproject.toml文件。这是一个供UV使用的 Python 项目的环境配置文件,主要用于定义项目的元数据、依赖、构建方式等(其实,每一个Agent目录中,以及项目的根目录中,都会有这样一个配置文件,因为每一个Agent 服务所需要的环境各不相同)。
Google API key
这个项目以及后续要学习的许多Agents都需要一个Google API key,我们可以通过这个链接创建一个,并通过.env来配置这个密钥,提供给程序使用。

可以把这个.env文件放置在A2A-IN-ACTION项目根目录下。
好,下面就可以启动这个A2A项目并展示Agents之间的通信能力了。
启动A2A Demo项目
先通过cd demo/ui命令进入A2A Demo项目的目录。
使用下面的命令来启动这个Demo。
uv run main.py
在第一次启动的同时,uv命令会安装一系列所需的包。

同时,应用程序将成功在12000端口启动。

你将看到如下所示的和Agent交互对话的窗口。

而你的A2A Demo应用也能够接收到你的对话,服务顺畅地跑起来了。

选择最下面的Setting按钮,我们先设置一下Google API Key(相当于又手工设置了环境变量GOOGLE_API_KEY),同时选择接收图文输出。

此时,你可以顺畅地和Host进行对话,在这个Chat窗口后面,系统内部其实是有一个名叫“Simple Agent”的简单智能体(Host Agent或叫做Local Agent)负责和我们对话。

当然,这个简单智能体的能力有限。比如当我问他“如何把20欧元转换成美元”,他说他没有能力查找汇率。当我让它列出智能体的列表,它说目前这个应用中找不到任何外部智能体。其中,目前应用程序中只有它这一个Local Agent,看起来它的能力也仅限于陪聊。
就像MCP Host也要进行一些配置,才能够连接到外部的MCP服务,看来我们也需要进行一些配置,才能够调用其他Agent的能力。
下面,我们就选择Agents选项卡,来配置一系列的Remote Agents,也就是外部Agents。我们先来添加一个Currency Agent。
首先,进入agents/langgraph目录,然后通过 uv run . 命令把这个Agent服务跑起来。—— 这是一个专门负责汇率计算的Agent,能够实时的访问各种国家的货币汇率信息。

好,此时远程Agent服务中,我们可以在应用中添加这个Currency Agent了!

选择Agent的名称和端口,添加好的Agent如下图所示。

重新开始对话,你就能发现,这时候Local Agent遇到货币兑换这样的难题就不会再捉襟见肘了。

几轮对话过后,选择Task List选项卡,可以看到一系列的对话列表。

此时,在服务器的后台,我们也可以看到Local Host和远程服务的交互过程。

好,那么现在我们看到了一个Local Agent(也叫Host Agent,即本地Agent),通过A2A协议调用了外部Agent的能力。底层是如何实现的?让我们来一起剖析一下关键设计。
A2A Demo的设计实现
从整体架构上看,一个A2A 系统由以下主要组件构成:
-
Agent Card: 代理的身份标识和能力声明。
-
Skill: 代理可执行的具体能力。
-
Task Manager: 处理任务流和状态管理。
-
Server: 提供HTTP接口让其他代理/客户端访问。
而在这些关键组件实现的基础之上,还需要为Demo应用创建UI,并注册服务,还要通过一系列的通信机制让外部Agent和Host Agent(本地Agent)能够相互对话,进行能力的发现。
UI 层入口与服务注册
首先我们简单介绍UI层的实现。在文件夹demo/ui/pages.py中,就这个应用的前端页面。

文件demo/ui/main.py中通过 FastAPI 启动后端服务,并用 Mesop 框架组织前端页面。
from service.server.server import ConversationServer
...
app = FastAPI()
router = APIRouter()
agent_server = ConversationServer(router)
app.include_router(router)
这段代码将所有与智能体对话相关的API路由注册到 FastAPI 应用,核心逻辑都在 ConversationServer 里。
Host Agent 服务实现
前端服务需要首先和本地Agent建立连接,才能实现和用户的交互对话。文件demo/ui/service/server/server.py中的ConversationServer 类是 UI 与 Host Agent 之间的桥梁,负责路由注册和请求分发。初始化时会根据环境变量选择 ADKHostManager(真实多智能体调度)或 InMemoryFakeAgentManager(假数据)。
此处的关键方法是:
-
async def _send_message(self, request: Request),它接收前端消息,调用 self.manager.process_message(message) 进行处理(异步线程),并返回消息ID。
-
async def _register_agent(self, request: Request),支持动态注册远程Agent,调用 self.manager.register_agent(url)。
-
async def _list_agents(self),返回当前已注册的所有Agent信息。
这些API接口就是A2A协议在本地Host侧的“落地实现”,所有对话、任务、Agent管理都通过这里转发。
Host Agent 调度与A2A协议实现
下面我们来分析本地Agent是如何实现A2A协议的。
在文件demo/ui/service/server/adk_host_manager.py中,ADKHostManager 继承自 ApplicationManager,是真正的“智能体大脑”。
此处的关键属性和方法如下:
-
self._host_agent = HostAgent([], self.task_callback),这里的 HostAgent 是多智能体调度的核心,负责多智能体的注册、能力发现、任务分发、回调等,是 Host 侧的“大脑”。这个类的定义位于hosts/multiagent/host_agent.py 文件。
-
async def process_message(self, message: Message),处理用户消息,维护会话、消息、事件等,并通过 self._host_runner.run_async(…) 触发智能体推理和任务流转。
-
def register_agent(self, url),支持通过URL动态注册远程Agent,Agent信息会被加入 _agents 列表,供Host调度。
-
@property def agents(self),返回所有已注册的AgentCard(能力描述卡片),用于能力发现和展示。
这里的 process_message 方法会将用户输入转为标准的A2A消息格式,交由 HostAgent 进行多智能体协作处理,最终结果再通过事件流返回。
HostAgent 通过 remote_agent_connection.py 维护与每个远程 Agent 的连接和能力卡片(AgentCard)—— 这也是A2A协议的核心交互机制。
Remote Agents和Agent Card
在agent目录中,有一系列可以配置到Demo应用中的外部(Remote)Agents,每个子目录就是一个外部 Agent 的实现。在刚才的Demo环节,我们以通过LangGraph 实现的货币转换Agent为例,启动并手动注册了它。

具体来说:
-
agents/langgraph/agent.py中的CurrencyAgent,用 LangGraph 框架和 Google Gemini API 实现了货币兑换的智能体逻辑,支持流式和同步调用。
-
agents/langgraph/main.py中通过 A2AServer(A2A协议服务端实现)将 CurrencyAgent 以 HTTP 服务形式暴露出来,并注册了自己的 AgentCard,即能力卡片。
Agent Card是A2A协议的关键内核概念之一,它负责定义代理的元数据(名称、描述、URL、版本等),声明支持的输入/输出模态(如文本、图像等)同时列出代理提供的技能清单(skill),其目的是让你的代理能够被发现,并让其他系统知道如何与它交互。这是实现“代理之间互相通信”(Agent-to-Agent)的基础。
agent_card = AgentCard(
name='Currency Agent',
description='Helps with exchange rates for currencies',
url=f'http://{host}:{port}/',
version='1.0.0',
defaultInputModes=CurrencyAgent.SUPPORTED_CONTENT_TYPES,
defaultOutputModes=CurrencyAgent.SUPPORTED_CONTENT_TYPES,
capabilities=capabilities,
skills=[skill],
)
你可以把它想象成你的 AI 代理(Agent)的 “名片” 或 “说明书”。它是一个标准化的信息包(通常是 JSON 格式),用来告诉其他代理或系统这个代理叫什么名字(name)、它是干什么的(description)、在哪里可以找到它并与之通信(url,包含主机和端口)、它支持哪些基本能力(capabilities)、它具体能完成哪些任务或技能(skills,比如货币兑换服务),以及版本号、默认沟通方式等其他元数据。
A2AServer则是A2A的协议服务端实现,其中的Task Manager负责任务生命周期管理,状态追踪和更新(WORKING、COMPLETED、ERROR等),处理同步/异步请求(on_send_task 和 on_send_task_subscribe),生成适当的响应格式以及错误处理和恢复。
server = A2AServer(
agent_card=agent_card,
task_manager=AgentTaskManager(
agent=CurrencyAgent(),
notification_sender_auth=notification_sender_auth,
),
host=host,
port=port,
)
这样,langgraph 目录下的 Agent 就成为了一个标准的 A2A 协议远程 Agent,具备独立服务能力和能力自描述。
A2A Client 端实现
最后我们来看一下A2A Client 端的实现逻辑。文件demo/ui/service/client/client.py中的ConversationClient 类封装了与远程Agent的HTTP通信,所有请求都以 JSON-RPC 格式发送。
此处的关键方法如下:
-
async def send_message(self, payload: SendMessageRequest),通过HTTP POST将消息发送到远程Agent的 /message/send 接口。
-
async def register_agent(self, payload: RegisterAgentRequest),远程注册Agent。
其它如 list_agents、list_tasks、get_events 等,均为A2A协议的标准接口,负责A2A协议“客户端”的标准实现,并与远端Agent进行协议对接。
总结一下
好,总结一下。Google A2A 框架提供了一个灵活而标准化的通信机制,让各个 AI 代理能够基于面向技能的设计,通过清晰的身份标识和能力声明,以统一的协议实现同步或异步的交互,并在此过程中依赖完善的状态管理与错误处理,以模块化、可扩展的架构共同构建更复杂的应用场景。
在具体实现上,每个 Agent 启动时会生成自己的 AgentCard,内含名称、描述、服务 URL 以及所支持的能力和技能等关键信息;启动后,Agent 通过 A2A Server 将自己的 AgentCard 向注册中心登记,而 Host 端则通过注册/发现机制(如调用 demo/ui/utils/agent_card.py 中的 get_agent_card 接口)远程拉取所有 AgentCard,实现能力路由与展示,从而完成代理之间的能力发现与互操作。

思考题
-
请你研究一下Agents目录中的外部Agents,每一个具有什么能力,能完成什么任务。
-
请你在这个应用程序中,添加更多的Agent,让多个Agent协作完成更为复杂的任务,比如发票的报销工作。
怎么样,这个应用像不像一个简版的Manus,那么在后续A2A每一课的讲解中我们将更深入的剖析具体agents的设计以及agents之间是怎样交互和协调工作的,敬请期待。
精选留言
2025-06-24 13:12:46
2025-06-19 17:54:32
第1题简答:从YouTube视频中提取隐藏字幕、按需生成惊艳的高质量图像、根据报销金额和用途,协助用户完成报销流程、解析文件、从文本中提取结构化的联系信息、行程规划。
第2题因报销 Agent 一直报错,暂未找到原因。还需要再研究下。
2025-07-10 20:30:05
◦ 相似点:主体及能力定义高度相似,A to A 中的“agent”类似传统架构中的“server”,agent 的“skill”对应 server 的接口/能力项;服务注册与发现机制相近。
◦ 差异点:最大差异在于调度方式,A to A 由“agent client”自动决策调用哪个 agent 的哪个技能;传统开发中通过在 server 里写死代码实现调度。
2. A to A 模式的优势
◦ 减少程序员编写“管道逻辑”的成本:以往整合多个能力(如地图查询、酒店/机票预订等)需跨团队协调并,手动编写大量串联逻辑,最后联调上线。A to A 中只需将这些能力注册到 host agent,由其自动决策调度以满足上层需求。
◦ 对企业级大型系统映射更友好:相比 MCP 模式,更适合现有大型系统切换,一个 server 可对应一个 agent,通过描述 agent 的“card”(能力载体)实现映射,概念更清晰,减少繁杂感。
3. A to A 模式的潜在问题
◦ 当注册的 agent 量级过大、对应的“card”(能力载体)繁多时,agent 的决策准确性和场景适应性存疑。
◦ 每个 agent 的“card”中技能点的描述需要较多转化工作。
4. 针对问题的解决方案设想
◦ 让 agent 引入“快照”机制:对已识别的服务决策规则(如两个服务的交互逻辑)进行快照并写死到代码中;新增agent时,经一段时间观察,识别决策规则,快照固化到代码。
◦ 效果:快照范围内的决策通过固定代码路由,避免 agent 和客户端的决策复杂度随服务数量增加而持续上升。
◦ autoagent :通过工具解析现有 server 的接口文档、业务逻辑,自动生成 agent 的能力描述,降低 A to A 协议落地门槛,实现现有 server 向 A to A 架构的快速转换。
2025-08-03 20:26:01
2025-07-30 11:11:45
2025-07-13 17:46:52
INFO: 127.0.0.1:45507 - "POST /__ui__ HTTP/1.1" 200 OK
Failed to list conversations: HTTP Error 502: Server error '502 Bad Gateway' for url 'http://localhost:12000/conversation/list'
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/502
Failed to list tasks HTTP Error 502: Server error '502 Bad Gateway' for url 'http://localhost:12000/task/list'
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/502
Failed to update state: 'NoneType' object is not iterable
Traceback (most recent call last):
File "D:\work\workspace\a2a-in-action\demo\ui\state\host_agent_service.py", line 142, in UpdateAppState
for task in await GetTasks():
^^^^^^^^^^^^^^^^
TypeError: 'NoneType' object is not iterable
INFO: 127.0.0.1:45507 - "POST /__ui__ HTTP/1.1" 200 OK
Failed to list conversations: HTTP Error 502: Server error '502 Bad Gateway' for url 'http://localhost:12000/conversation/list'
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/502
Failed to list tasks HTTP Error 502: Server error '502 Bad Gateway' for url 'http://localhost:12000/task/list'
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/502
Failed to update state: 'NoneType' object is not iterable
Traceback (most recent call last):
File "D:\work\workspace\a2a-in-action\demo\ui\state\host_agent_service.py", line 142, in UpdateAppState
for task in await GetTasks():
^^^^^^^^^^^^^^^^
2025-07-13 16:41:50
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/502
Failed to list conversations: HTTP Error 502: Server error '502 Bad Gateway' for url 'http://localhost:12000/conversation/list'
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/502
Failed to list tasks HTTP Error 502: Server error '502 Bad Gateway' for url 'http://localhost:12000/task/list'
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/502
Failed to update state: 'NoneType' object is not iterable
Traceback (most recent call last):
File "C:\Users\lyy\Desktop\llm-agent\a2a-in-action-master\demo\ui\state\host_agent_service.py", line 142, in UpdateAppState
for task in await GetTasks():
^^^^^^^^^^^^^^^^
TypeError: 'NoneType' object is not iterable,咖哥,我也出现了这个问题,不知道怎么解决
2025-07-04 12:21:52
可以搭个自己的vpn或者买台海外虚拟机跑agents
2025-07-02 16:24:36
INFO: Will watch for changes in these directories: ['D:\\13.Aigc_workspace\\a2a-in-action-master\\demo\\ui']
INFO: Uvicorn running on http://0.0.0.0:12000 (Press CTRL+C to quit)
WARNING: --reload-include and --reload-exclude have no effect unless watchfiles is installed.
INFO: Started reloader process [6992] using StatReload
INFO: Started server process [27908]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: 127.0.0.1:63396 - "GET / HTTP/1.1" 200 OK
INFO: 127.0.0.1:63396 - "GET /styles.css HTTP/1.1" 304 Not Modified
INFO: 127.0.0.1:63396 - "GET /zone.js/bundles/zone.umd.js HTTP/1.1" 304 Not Modified
INFO: 127.0.0.1:63397 - "GET /prod_bundle.js HTTP/1.1" 304 Not Modified
INFO: 127.0.0.1:63397 - "POST /__ui__ HTTP/1.1" 200 OK
INFO: 127.0.0.1:63397 - "GET /__web-components-module__/components/async_poller.js HTTP/1.1" 304 Not Modified
2025-07-01 17:07:32
需要再 demo/main.py 第 59 行,增加:dangerously_disable_trusted_types=True,
完整代码:
security_policy = me.SecurityPolicy(
allowed_script_srcs=[
'https://cdn.jsdelivr.net',
],
dangerously_disable_trusted_types=True, # 新增修复代码
)
错误:
⚠️ Content Security Policy Error ⚠️
━━━━━━━━━━━━━━━━━━
Directive: trusted-types
Blocked URL: trusted-types-policy
App path: /
ℹ️ If this is coming from your web component,
update your security policy like this:
@me.page(
security_policy=me.SecurityPolicy(
dangerously_disable_trusted_types=True
)
)
For more info:
https://mesop-dev.github.io/mesop/web-components/troubleshooting/
2025-06-27 17:39:27
2025-06-25 22:45:07
2025-06-22 10:50:01
老师,这个对国内用户感觉不是很友好,能替换模型,用其他模型实现a2a吗?
2025-06-21 22:03:56
[2025-06-21 22:00:44,107] ERROR in app: Exception on /.well-known/appspecific/com.chrome.devtools.json [GET]
Traceback (most recent call last):
File "C:\Soft\workplace\a2a-in-action\demo\ui\.venv\Lib\site-packages\flask\app.py", line 1511, in wsgi_app
response = self.full_dispatch_request()
File "C:\Soft\workplace\a2a-in-action\demo\ui\.venv\Lib\site-packages\flask\app.py", line 919, in full_dispatch_request
rv = self.handle_user_exception(e)
File "C:\Soft\workplace\a2a-in-action\demo\ui\.venv\Lib\site-packages\flask\app.py", line 917, in full_dispatch_request
rv = self.dispatch_request()
File "C:\Soft\workplace\a2a-in-action\demo\ui\.venv\Lib\site-packages\flask\app.py", line 902, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args) # type: ignore[no-any-return]
File "C:\Soft\workplace\a2a-in-action\demo\ui\.venv\Lib\site-packages\mesop\server\static_file_serving.py", line 141, in serve_file
return send_file_compressed(
get_path(path),
disable_gzip_cache=disable_gzip_cache,
)
File "C:\Soft\workplace\a2a-in-action\demo\ui\.venv\Lib\site-packages\mesop\server\static_file_serving.py", line 345, in send_file_compressed
response = send_file(path)
File "C:\Soft\workplace\a2a-in-action\demo\ui\.venv\Lib\site-packages\flask\helpers.py", line 511, in send_file
return werkzeug.utils.send_file( # type: ignore[return-value]
~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
**_prepare_send_file_kwargs(
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
...<9 lines>...
)
^
)
^
File "C:\Soft\workplace\a2a-in-action\demo\ui\.venv\Lib\site-packages\werkzeug\utils.py", line 428, in send_file
stat = os.stat(path)
FileNotFoundError: [WinError 3] 系统找不到指定的路径。: 'C:\\Soft\\workplace\\a2a-in-action\\demo\\ui\\.venv\\Lib\\site-packages
Failed to update state: 'NoneType' object is not iterable
2025-06-21 22:01:49
2025-06-21 11:30:34
2025-06-20 08:47:55
http error Server error '502 Bad Gateway' for url 'http://0.0.0.0:12000/task/list'
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/502
Failed to list tasks HTTP Error 502: Server error '502 Bad Gateway' for url 'http://0.0.0.0:12000/task/list'
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/502
Failed to update state: 'NoneType' object is not iterable
Traceback (most recent call last):
File "D:\pycharmProjects\a2a-samples\demo\ui\state\host_agent_service.py", line 154, in UpdateAppState
for task in await GetTasks():
^^^^^^^^^^^^^^^^
TypeError: 'NoneType' object is not iterable
http error Server error '502 Bad Gateway' for url 'http://0.0.0.0:12000/conversation/list'
2025-06-16 23:10:54