Agent2Agent 协议实战
一方面,我们正处在一个令人兴奋的 AI 代理时代,但另一方面,这有点像框架的丛林!LangGraph、CrewAI、Google ADK……我们如何让用这些不同工具构建的代理真正“交谈”并协作完成复杂任务?
进入Agent2Agent(A2A)协议,这是由 Google 发起的一项开放标准,旨在建立这些桥梁。与其只是谈论互操作性,我更想看到它在实际中的应用。因此,我深入研究了官方的 A2A 存储库,并设置了他们的多代理演示界面。
在这篇教程中,我将逐步介绍整个设置过程。它涉及启动多个基于 Python 的代理(使用 LangGraph、CrewAI 和 ADK),并通过中央 Web 界面连接它们。
本文将回顾那些设置步骤(方便复制粘贴!),然后深入探讨使这种跨框架通信成为可能的关键 A2A 概念。
我们为什么应该关心?
如果我们想要构建的不仅仅是单代理玩具项目,那么我们迟早会遇到这个互操作性问题。想象一下,你希望你的数据分析代理(也许在 LangGraph 中)将结果交给报告撰写代理(可能是 CrewAI)。他们如何协调?
A2A 提供了一种潜在的标准方式:
- 组合专家: 通过结合在特定任务上表现最佳的代理来构建复杂的流程,而不管它们内部使用的框架是什么。
- 避免锁定: 通过定义一个通用的通信层来促进灵活性。
- 标准交互: 提供可预测的方式让代理发现能力、管理任务并交换不同类型的数据(不仅仅是文本)!
这个演示使这些想法变得具体化。
好吧,现在让我们卷起袖子运行这个多代理演示。确保你安装了 Git、Python 3.12 和 uv。目标是让 Web 界面通过 A2A 协议与三个不同的 Python 代理(LangGraph、CrewAI、ADK)进行通信。
1、克隆存储库并设置虚拟环境
首先,获取代码并使用 uv 在项目根目录创建一个隔离的 Python 环境。在虚拟环境中工作对于保持依赖项整洁并避免与其他项目的冲突至关重要。
# 克隆存储库
git clone https://github.com/google/A2A.git
cd A2A
# 在项目根目录创建虚拟环境(命名为 .venv)
uv venv
# 激活环境(以下命令将从此处使用 Python/pip)
# Linux/macOS:
source .venv/bin/activate
# Windows CMD: .\.venv\Scripts\activate.bat
# Windows PowerShell: .\.venv\Scripts\Activate.ps1
我们获取了项目代码,然后使用 uv venv
在 .venv 文件夹内创建了一个专用的 Python 沙盒。source 命令使我们的终端会话在此沙盒中使用所有后续的 Python 相关命令。我们的命令提示符现在应该有一个 (A2A) 前缀(或类似内容):
2、安装依赖项
现在,我们需要安装必要的 Python 包。A2A 示例有一个稍微复杂一点的结构:common 依赖项位于 samples/python 中,但 CrewAI 代理有其自己的特定要求,定义在其子目录中。我们将使用 uv sync 两次来处理这一点,将所有内容安装到同一个已激活的 .venv 中。
# 切换到主要 Python 样例目录
cd samples/python
# 安装 common 依赖项 + LangGraph + ADK 要求
# (读取 samples/python/pyproject.toml)
uv sync
# 现在,进入 CrewAI 代理的目录
cd agents/crewai
# 将 CrewAI 特定依赖项安装到相同的已激活环境
# (读取 samples/python/agents/crewai/pyproject.toml)
uv sync
# 重要:返回到主要 Python 样例目录。
# 我们将从这里运行 agent start 命令。
cd ../..
# (你现在应该回到 A2A/samples/python 目录)
我们首先在 samples/python 中运行 uv sync 来安装 fastapi、mesop、langgraph、google-generativeai 等包。然后,我们专门进入 agents/crewai 并再次运行 uv sync。这读取了它的 pyproject.toml 并将 crewai 和 crewai-tools 添加到我们已经激活的同一个 .venv 中。现在,我们的单一环境中包含了所有 Python 组件所需的包。最后,我们返回到 samples/python,这是从 uv run 启动代理的预期位置。
3、配置 API 密钥(用于代理)
示例代理使用 Google 的 Gemini 模型,因此它们需要一个 API 密钥。我们将在 A2A 项目目录的根目录下创建一个 .env 文件:
# 在 A2A 项目的根目录创建 .env 文件
# (我们现在在 A2A/samples/python 中,所以使用 ../../)
echo "GOOGLE_API_KEY=YOUR_API_KEY_HERE" > ../../.env
我们在 A2A 项目顶层创建了一个简单的文本文件名为 .env。Python 代理脚本在启动时会自动加载此文件,使其能够访问 GOOGLE_API_KEY。我们将它放在这里是因为 uv run agents/... 命令上下文似乎能正确地从相对于 pyproject.toml 的根目录中拾取它。
请确保将 YOUR_API_KEY_HERE 替换为您的实际 API 密钥(您可以在 https://aistudio.google.com/app/apikey 上免费获取)
4、运行 A2A 代理(每个都在自己的终端中!)
这就是“多代理”部分的生动体现。每个代理都作为一个独立的 Web 服务器运行。我们需要为每个代理打开单独的终端窗口。
关键点: 在每个新打开的终端中,我们必须:
- 导航到 A2A/samples/python 目录。
- 激活相同的虚拟环境:
source ../../.venv/bin/activate
# --- 🖥️ 终端 1: LangGraph 代理(端口 10000) ---
# (假设你在 A2A/samples/python 中且 venv 已激活)
echo "启动 LangGraph 代理于端口 10000..."
uv run agents/langgraph
# (保持此终端运行)
# --- 🖥️ 终端 2: CrewAI 代理(端口 10001) ---
# (在新的终端中: cd /path/to/A2A/samples/python && source ../../.venv/bin/activate)
echo "启动 CrewAI 代理于端口 10001..."
uv run agents/crewai
# (保持此终端运行)
# --- 🖥️ 终端 3: ADK 代理(端口 10002) ---
# (在新的终端中: cd /path/to/A2A/samples/python && source ../../.venv/bin/activate)
echo "启动 ADK 代理于端口 10002..."
uv run agents/google_adk
# (保持此终端运行)
我们可能会看到的警告只是指出激活的路径(/path/to/A2A/.venv)与默认相对路径(A2A/samples/python/.venv)不同。对于这个特定的设置流程(在根目录激活 venv,然后从子目录运行 uv sync),通常可以安全忽略此警告。 依赖项确实被安装到了正确的共享虚拟环境中(A2A/.venv)
5、运行演示 UI 应用程序
最后,让我们启动作为客户端和协调器的 Web 界面。
# --- 🖥️ 终端 4: 演示 UI(端口 12000) ---
# 打开另一个新的终端窗口
# 切换到 A2A 项目的根目录
cd /path/to/A2A # 如果需要,请调整路径
# 激活相同的虚拟环境
source .venv/bin/activate
# 特别是切换到演示 UI 目录
cd demo/ui
# 在这里创建 .env 文件 - UI 后端需要自己的密钥访问
echo "GOOGLE_API_KEY=YOUR_API_KEY_HERE" > .env
# 运行演示 UI 应用程序(FastAPI 后端 + Mesop 前端)
uv run main.py
# (保持此终端运行)
我们启动了第四个服务器进程。这个进程运行演示/ui/main.py 脚本,其中包含 Mesop Web 前端逻辑和 FastAPI 后端。此后端服务作为我们的主要“主机代理”——它将接收来自 Web UI 的命令,然后使用其内部的 A2A 客户端与我们之前启动的代理服务器进行通信。注意此处单独的 .env 文件放置,这是由 UI 的代码结构决定的。
6、连接并交互!
现在是收获的时候了:
让我们打开浏览器并导航到 http://localhost:12000.
我们应该现在看到这个界面:
注册代理: 这是至关重要的!UI 并不知道有关代理的信息。
- 单击侧边栏中的 机器人图标(🤖)。
- 单击 向上箭头图标(⬆️)以添加代理。
- 输入 localhost:10000(不包括 <http://),然后单击 “读取”(等待详细信息),然后单击 “保存”。
- 对于 localhost:10001 重复上述步骤。
- 对于 localhost:10002 重复上述步骤。
开始聊天:
- 单击 消息图标(💬)和 “+” 按钮。
- 发送像前面提到的示例那样的提示,并查看不同的代理如何在同一界面内响应!
我们启动了 Web UI,并手动告知其后端在哪里可以找到正在运行的 A2A 代理服务器。现在,当我们输入聊天内容时,后端可以通过注册期间获取的 AgentCards 发现功能(capabilities),并使用 A2A 协议适当路由我们的请求。
我们现在成功通过 A2A 标准连接了使用 LangGraph、CrewAI 和 ADK 构建的代理!从这里开始,我们可以探索演示中的不同视图(任务列表、事件列表),以更详细地了解协议的实际操作。
7、实践一下
好的,设置完成了!我们有三个独立运行的本地 AI 代理(LangGraph、CrewAI、ADK),以及作为中央枢纽的 Demo UI,它们都通过 A2A 协议相互连接。现在,让我们实际与它们对话,看看“幕后”发生了什么。
让我们在浏览器中启动 Demo UI(http://localhost:12000),单击消息图标(💬),开始新的对话(+),并尝试以下提示:
7.1 查询货币代理(LangGraph)
这个代理是用 LangGraph 构建的,并使用工具获取实时数据。我们可以尝试这个提示:
50 美元等于多少日元?
我们输入消息并发送。由于该代理支持流式传输(我们在其 AgentCard 中看到了 streaming: true),Demo UI 的后端可能会发起流式连接(tasks/sendSubscribe)。如果它这样做,我们可能会短暂看到类似“正在工作…”或“查找汇率…”的中间状态消息来自代理。最后,我们会收到一个包含转换结果的文本响应(例如,“50 美元等于 7162.5 日元”)。
A2A 概念的实际应用:
- 任务启动: 我们的 UI 后端通过 HTTP 发送包含提示的 tasks/send 或 tasks/sendSubscribe 请求。
- 流式传输(可选但支持): 如果使用了 tasks/sendSubscribe,则这些“正在工作…”的消息会作为 TaskStatusUpdateEvents 通过 Server-Sent Events (SSE) 到达。最终答案可能在最后一个 TaskStatusUpdateEvent 或单独的 TaskArtifactUpdateEvent 中包含的 TextPart 中。
- 基本文本输入输出: 我们的输入和代理的最终输出都是 A2A 消息/工件中的简单 TextParts。
我们可以在 UI 的事件列表中看到这一点:
让我们尝试另一个提示(多轮对话):
加拿大元的汇率是多少?
我们将看到: 代理很可能无法直接完成此操作。它应该会回应我们需要更多信息,比如:“您想将加元兑换成哪种货币?”
A2A 概念的实际应用:
- input-required 状态: 代理意识到需要更多信息。它将 A2A 任务状态设置为 input-required,并发送回其澄清问题。任务在代理端暂停,等待我们的回复。
- 继续任务: 当我们回复(例如,“兑欧元”)时,我们的 UI 后端使用与原始请求相同的 Task ID 发送我们的回答。代理收到此信息后,现在有足够的信息来完成任务。这展示了 A2A 如何在单个任务中管理对话上下文。
7.2 命令图像代理(CrewAI)
这个代理使用 CrewAI 和 Google Gemini 来生成图像。其 AgentCard 告诉我们它不支持流式传输。
我们的提示:
生成一张宇航员骑自行车在月球上的图片。
我们将看到: 在我们发送提示后,可能会有一小段延迟(图像生成不是即时的!)。我们可能会看到来自 UI 主代理的通用“正在工作…”指示符(而不是通过 A2A 流式传输来自 CrewAI 代理本身)。然后,生成的图像应直接嵌入到聊天窗口中:
A2A 概念的实际应用:
- 请求/响应(tasks/send): 由于不支持流式传输,UI 后端使用标准的 tasks/send 方法。它会在代理完全完成图像生成后,通过 HTTP 接收单次响应。
- FilePart 工件: 这里很酷的部分是,图像不仅仅是用文字描述的;CrewAI 代理将图像数据(可能是 base64 编码的字节)打包到包含 FilePart 的工件中。这部分包含字节和 mimeType(如 image/png)。我们的 Demo UI 收到此工件后,知道如何解码字节并渲染图像,因为有 mimeType。
7.3 提交费用代理(Google ADK)
这个代理模拟了一个费用流程,并展示了使用表单进行结构化数据交换。
我们的提示:
我需要提交咖啡的费用。
我们将看到: 代理可能会回应说需要更多细节。关键是我们应该看到一个交互式表单直接出现在聊天中,询问“日期”、“金额”和“用途”。我们可以直接在 UI 中填写这些字段,并点击“提交”按钮(由 UI 根据代理的响应渲染)。提交后,代理应该确认费用已提交:
A2A 概念的实际应用:
- DataPart 工件(表单请求): 代理通过返回包含 DataPart 的工件表明需要结构化输入。此部分中的 JSON 数据描述了表单结构(字段、类型、标签)。我们的 Demo UI 特别编码以识别此 DataPart 结构,并将其呈现为交互式 HTML 表单。
- input-required 状态(隐式): 任务实际上进入了一个等待我们通过表单输入的状态。
- DataPart 消息(表单响应): 当我们在 UI 中点击“提交”时,UI 后端收集我们的输入,创建一个包含 DataPart(其中的数据字段包含我们填写的值,如 {“date”: “2023-10-27”, “amount”: “5.00”, ...})的新消息,并使用相同的 Task ID 将此消息发送回代理。
- 结构化数据交换: 这展示了 A2A 超越简单的文本或不透明文件的能力,允许进行结构化数据交换,从而实现更丰富的交互,如表单。
7.4 进一步实验!
现在轮到你了!尝试不同的提示。向货币代理查询历史汇率,向图像代理请求不同风格的图像,或者尝试给费用代理提供初始部分信息。当你这样做的时候,在演示 UI 中点击“任务列表”(✔️)和“事件列表”(📝)视图。它们揭示了每个交互背后的 A2A 状态和消息流。看到它运作后再一窥幕后,这是真正理解 A2A 如何连接这些不同代理世界的好方法。
8、底层:A2A 实际上发生了什么?
好吧,它有效,但是如何实现的呢? 这不是魔法;这是 A2A 协议在促成交互:
- 代理发现(AgentCard 📇): 当我们在注册 localhost:10000 时点击“读取”,UI 发送了一个 HTTP GET 请求到 http://localhost:10000/.well-known/agent.json。代理服务器基于其代码配置动态生成了 JSON 响应(这些样本中的不是静态文件!)。这个 AgentCard 就像一张名片,告诉 UI 代理的名称、URL、技能和关键能力(如“是否支持流式传输?”)。此发现步骤对于协调器知道与谁交谈至关重要。
- 任务生命周期(➡️⏳❓✅): 每次我们启动的交互都会成为 A2A 任务。协议定义了提交、工作、需要输入(如货币代理需要澄清)和完成/失败等状态。我们可以在演示的“任务列表”视图(勾选图标 ✔️)中看到这些状态的变化。这种结构化的生命周期对于管理可能较长的代理操作至关重要。
- 丰富内容(Parts & Artefacts 📄🖼️📊): A2A 不限于文本。通信通过包含 Parts 的 Message 对象进行。我们看到了 TextPart(聊天消息)、FilePart(CrewAI 代理返回图像)和 DataPart(ADK 代理发送费用表单)。代理输出通常包装在 Artefacts 中。这种灵活性对处理多样化数据的代理至关重要。
- 流式传输(SSE 📨): LangGraph 代理支持流式传输(在其卡中 capabilities.streaming: true)。当演示后端调用其 tasks/sendSubscribe 方法时,代理通过标准的 Server-Sent Events (SSE) 使用 HTTP 返回更新(如“查找汇率...”)。这允许在长时间任务期间进行实时反馈,而 CrewAI 代理则使用简单的请求/响应(tasks/send)。
- “协商”(能力检查与澄清): 虽然没有深入谈判,A2A 允许:(1) 通过 AgentCard 进行初始能力检查。(2) 客户端可能告知代理其接受的输出格式(acceptedOutputModes)。(3) 明显的需要输入状态,代理通过 UI 与用户协商更多信息。
9、结束语
恭喜,我们亲自动手进行了 Agent2Agent (A2A) 示例演示,成功设置了一个本地环境,其中使用完全不同框架构建的代理——LangGraph、CrewAI 和 Google ADK——通过单一 Web UI 进行通信和协作。我们不只是阅读了互操作性的概念;我们构建了一个小型示例。
这个演示虽然相对简单,但它为我们提供了 A2A 潜力的一个实用视角。它解决的核心挑战——使不同的 AI 系统能够相互通信——是许多人在构建过程中面临的挑战。A2A协议正在逐步支持越来越复杂的应用程序。A2A的方法,提供了一个基于熟悉Web技术(HTTP、JSON-RPC、Server-Sent Events)的标准通信层,感觉非常务实。
对于我们这些构建者来说,这次练习的关键收获是:
- 可组合性: 我们看到一个中央“主机”如何通过AgentCard发现并委派任务给专门的代理(货币转换、图像生成、表单处理)。这预示着未来我们可以通过拼接最佳的代理来构建更强大的系统,而不是尝试构建庞大的单一系统或陷入某个单一框架的生态系统。
- 标准化交互: 不需要为每个代理之间的链接发明自定义API,A2A提供了定义好的方法(tasks/send、tasks/get、tasks/sendSubscribe),明确的任务生命周期,以及处理不同类型数据的标准方式(TextPart、FilePart、DataPart)。这种可预测性对于构建可靠的多代理系统至关重要。
- 灵活性: 该协议适应各种交互模式——简单的请求/响应、通过SSE进行实时流式更新,甚至像表单这样的交互元素,正如不同代理所展示的那样。
下一步我们该怎么做?
A2A是一个新兴的开放标准,围绕它的生态系统仍在发展。演示有效展示了核心概念,但自然还有更多深度需要探索。
深入研究代码: 现在你已经看到它运行了,我鼓励你进一步探索A2A GitHub仓库。查看:
- specification/json/a2a.json: 协议结构的真实来源。
- samples/python/common: 可重用的Python客户端/服务器库和类型定义(使用Pydantic)。
- 特定代理适配器(例如samples/python/agents/langgraph/task_manager.py),以了解它们如何将框架逻辑桥接到A2A。
更多实验: 尝试修改代理或主机逻辑。你能添加另一个代理吗?你能在主机代理中创建一个更复杂的流程吗?
考虑你的项目: 思考一下像A2A这样的标准如何简化你在构建或计划构建的不同AI组件之间的通信。
参与社区: 该项目有GitHub讨论和问题反馈区。
虽然A2A并不是解决所有多代理挑战的万能药,但它代表了迈向更加开放和互操作生态系统的重要一步。通过提供一种通用语言,它让我们作为开发者可以更多地专注于构建智能、专业的代理,而不用过多关注连接它们所需的管道。旅程才刚刚开始,但这个动手演示表明基础是稳固的,潜力无疑是令人兴奋的。
原文链接:Getting Started with Google A2A: A Hands-on Tutorial for the Agent2Agent Protocol
汇智网翻译整理,转载请标明出处