什么是A2A协议?
**A2A(代理到代理)**协议是由谷歌开发的一个开放协议,用于跨越组织或技术边界的代理之间的通信。
使用A2A,代理可以为最终用户完成任务,而无需共享内存、想法或工具。相反,代理会交换上下文、状态、指令和数据,这些数据以它们的原生模态存在。
- 简单:重用现有标准
- 企业级:认证、安全、隐私、追踪、监控
- 异步优先:(非常)长运行任务和人工介入
- 模态不可知:文本、音频/视频、表单、iframe等
- 不透明执行:代理不必共享想法、计划或工具
A2A与MCP——关键区别
- MCP能让企业API不依赖特定工具,还能很好地和大语言模型配合使用。
- A2A允许分布式代理(使用ADK、LangChain、CrewAI等构建)无缝地相互交流。
A2A是代理的API,而MCP是工具的API
A2A协议——核心概念
A2A(代理到代理)协议围绕几个基本概念构建,这些概念使得代理之间的互操作性变得无缝。在本文中,我们将使用多代理旅行规划应用程序来更好地理解核心概念。
1. 基于任务的通信
代理之间的每次交互都被视为一个任务——一个具有明确开始和结束的清晰工作单元。这使得通信变得有结构且可追踪。示例:
基于任务的通信(请求)
{
"jsonrpc": "2.0",
"method": "tasks/send",
"params": {
"taskId": "20250615123456",
"message": {
"role": "user",
"parts": [
{
"text": "Find flights from New York to Miami on 2025-06-15"
}
]
}
},
"id": "12345"
}
基于任务的通信(响应)
{
"jsonrpc": "2.0",
"id": "12345",
"result": {
"taskId": "20250615123456",
"state": "completed",
"messages": [
{
"role": "user",
"parts": [
{
"text": "Find flights from New York to Miami on 2025-06-15"
}
]
},
{
"role": "agent",
"parts": [
{
"text": "I found the following flights from New York to Miami on June 15, 2025:\n\n1. Delta Airlines DL1234: Departs JFK 08:00, Arrives MIA 11:00, $320\n2. American Airlines AA5678: Departs LGA 10:30, Arrives MIA 13:30, $290\n3. JetBlue B9101: Departs JFK 14:45, Arrives MIA 17:45, $275"
}
]
}
],
"artifacts": []
}
}
2. 代理发现
代理可以通过从标准位置(/.well-known/agent.json)读取其他代理的agent.json文件来自动发现其他代理的功能。无需手动设置。以下是一个代理卡示例:
- 目的:公开可访问的发现端点
- 位置:根据A2A协议的标准化网络可访问路径
- 用途:由其他代理/客户端进行外部发现
- 作用:启用自动代理发现
{
"name": "Travel Itinerary Planner",
"displayName": "Travel Itinerary Planner",
"description": "An agent that coordinates flight and hotel information to create comprehensive travel itineraries",
"version": "1.0.0",
"contact": "code.aicloudlab@gmail.com",
"endpointUrl": "http://localhost:8005",
"authentication": {
"type": "none"
},
"capabilities": ["streaming"],
"skills": [
{
"name": "createItinerary",
"description": "Create a comprehensive travel itinerary including flights and accommodations",
"inputs": [
{
"name": "origin",
"type": "string",
"description": "Origin city or airport code"
},
{
"name": "destination",
"type": "string",
"description": "Destination city or area"
},
{
"name": "departureDate",
"type": "string",
"description": "Departure date in YYYY-MM-DD format"
},
{
"name": "returnDate",
"type": "string",
"description": "Return date in YYYY-MM-DD format (optional)"
},
{
"name": "travelers",
"type": "integer",
"description": "Number of travelers"
},
{
"name": "preferences",
"type": "object",
"description": "Additional preferences like budget, hotel amenities, etc."
}
],
"outputs": [
{
"name": "itinerary",
"type": "object",
"description": "Complete travel itinerary with flights, hotels, and schedule"
}
]
}
]
}
3. 框架不可知的互操作性
A2A可以在不同的代理框架之间工作——比如ADK、CrewAI、LangChain——因此使用不同工具构建的代理仍然可以协同工作。
4. 多模态消息传递
A2A通过部件系统支持各种内容类型,允许代理在统一的消息格式内交换文本、结构化数据和文件。
5. 标准化消息结构
A2A使用干净的JSON-RPC风格来发送和接收消息,使得实现具有一致性和易于解析。
6. 技能和能力
代理会发布它们能做的事情(“技能”)——以及它们需要的输入和提供的输出——这样其他代理就知道如何与它们合作。示例:
// 技能声明在代理卡中
"skills": [
{
"name": "createItinerary",
"description": "Creates a travel itinerary",
"inputs": [
{"name": "origin", "type": "string", "description": "Origin city or airport"},
{"name": "destination", "type": "string", "description": "Destination city or airport"},
{"name": "departureDate", "type": "string", "description": "Date of departure (YYYY-MM-DD)"},
{"name": "returnDate", "type": "string", "description": "Date of return (YYYY-MM-DD)"}
]
}
]
// 在消息中调用技能
{
"role": "user",
"parts": [
{
"text": "Create an itinerary for my trip from New York to Miami"
},
{
"type": "data",
"data": {
"skill": "createItinerary",
"parameters": {
"origin": "New York",
"destination": "Miami",
"departureDate": "2025-06-15",
"returnDate": "2025-06-20"
}
}
}
]
}
7. 任务生命周期
每个任务都会经历明确定义的阶段:提交 → 正在处理 → 完成(或失败/取消)。我们总是可以追踪任务所处的状态。
8. 实时更新(流式传输)
长时间运行的任务可以使用服务器发送事件(SSE)流式传输更新,以便代理能够实时接收进度。
9. 推送通知
代理可以使用网络钩子主动通知其他代理任务更新,支持安全通信(JWT、OAuth等)。
10. 结构化表单
代理可以使用DataPart请求或提交结构化表单,这使得处理输入(如JSON或配置)变得简单。
架构
我们将使用相同的**旅行规划器**架构,扩展A2A + MCP协议。
下面的演示只是为了说明多个代理之间的A2A协议,仅供理解。
上图由作者提供
上述架构使用了模块化的多代理AI系统,其中每个代理都是独立可部署的,并使用谷歌的A2A(代理到代理)协议进行通信。
架构中的核心组件
- 用户界面层 — 向前端服务器发送HTTP请求
- 代理层 — 主代理、代理1和代理2之间的协调
- 协议层 — 代理之间的A2A协议通信
- 外部数据层 — 使用MCP访问外部API
代理角色:
- 行程规划代理 — 作为中央协调器 — 主代理,协调用户与专业代理之间的交互。
- 航班搜索代理 — 一个专门的代理,负责根据用户输入获取航班选项。
- 酒店搜索代理 — 一个专门的代理,负责根据用户偏好获取酒店住宿。
MCP在本项目中的实现:
航班搜索MCP服务器
- 连接:航班搜索(代理1)连接到MCP航班服务器
- 功能:连接到航班预订API和数据库
酒店搜索MCP服务器
- 连接:酒店搜索(代理2)连接到MCP酒店服务器
- 功能:连接到酒店预订系统和聚合器
代理通信流程
- 用户通过Streamlit UI提交旅行查询
- 旅行规划器解析查询以提取关键信息
- 旅行规划器向航班搜索代理请求航班信息
- 航班搜索代理通过调用MCP服务器返回可用航班
- 旅行规划器提取目的地详情
- 旅行规划器向酒店搜索代理请求酒店信息
- 酒店搜索代理返回住宿选项
- 旅行规划器将所有数据综合成一份完整的行程
实现
让我们深入了解一下如何使用ADK + MCP + Gemini AI构建这个多代理系统,将其分解为关键的实现步骤。
先决条件
-
安装Python 3.11+
-
通过API密钥访问谷歌**Gemini**生成式AI
-
一个有效的**SerpAPI密钥**
-
一个有效的**OpenAI GPT密钥**
项目文件夹结构
├── common
│ ├── __init__.py
│ ├── client
│ │ ├── __init__.py
│ │ ├── card_resolver.py
│ │ └── client.py
│ ├── server
│ │ ├── __init__.py
│ │ ├── server.py
│ │ ├── task_manager.py
│ │ └── utils.py
│ ├── types.py
│ └── utils
│ ├── in_memory_cache.py
│ └── push_notification_auth.py
├── flight_search_app
│ ├── a2a_agent_card.json
│ ├── agent.py
│ ├── main.py
│ ├── static
│ │ └── .well-known
│ │ └── agent.json
│ └── streamlit_ui.py
├── hotel_search_app
│ ├── README.md
│ ├── a2a_agent_card.json
│ ├── langchain_agent.py
│ ├── langchain_server.py
│ ├── langchain_streamlit.py
│ ├── static
│ │ └── .well-known
│ │ └── agent.json
│ └── streamlit_ui.py
└── itinerary_planner
├── __init__.py
├── a2a
│ ├── __init__.py
│ └── a2a_client.py
├── a2a_agent_card.json
├── event_log.py
├── itinerary_agent.py
├── itinerary_server.py
├── run_all.py
├── static
│ └── .well-known
│ └── agent.json
└── streamlit_ui.py
步骤 1:设置虚拟环境
安装依赖项
# 设置虚拟环境
python -n venv .venv
# 激活虚拟环境
source .venv/bin/activate
# 安装依赖项
pip install fastapi uvicorn streamlit httpx python-dotenv pydantic
pip install google-generativeai google-adk langchain langchain-openai
步骤 2:安装MCP服务器包
mcp酒店服务器 — https://pypi.org/project/mcp-hotel-search/
mcp航班服务器 — https://pypi.org/project/mcp-flight-search/
# 安装mcp酒店搜索
pip install mcp-hotel-search
# 安装mcp航班搜索
pip install mcp-flight-search
步骤 3:设置Gemini、OpenAI、SerpAI的环境变量
设置上述先决条件中的环境变量
GOOGLE_API_KEY=your_google_api_key
OPENAI_API_KEY=your_openai_api_key
SERP_API_KEY=your_serp_api_key
步骤 4:使用ADK作为MCP客户端设置航班搜索(代理)并使用Gemini 2.0 Flash
以下设置与上一篇文章中使用ADK的设置相同,但增加了A2A协议,并使用了从https://github.com/google/A2A/tree/main/samples/python/common中获取的可重用模块。
├── common/ # 共享A2A协议组件
│ ├── __init__.py
│ ├── client/ # 客户端实现
│ │ ├── __init__.py
│ │ └── client.py # 基础A2A客户端
│ ├── server/ # 服务器实现
│ │ ├── __init__.py
│ │ ├── server.py # A2A服务器实现
│ │ └── task_manager.py # 任务管理工具
│ └── types.py # A2A共享类型定义
├── flight_search_app/ # 航班搜索代理(代理1)
│ ├── __init__.py
│ ├── a2a_agent_card.json # 代理能力声明
│ ├── agent.py # ADK代理实现
│ ├── main.py # ADK服务器入口点Gemini LLM
│ └── static/ # 静态文件
│ └── .well-known/ # 代理发现目录
│ └── agent.json # 标准化代理发现文件
ADK代理实现作为MCP客户端从MCP服务器获取工具
from google.adk.agents.llm_agent import LlmAgent
from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset, StdioServerParameters
..
..
# 从MCP服务器获取工具
server_params = StdioServerParameters(
command="mcp-flight-search",
args=["--connection_type", "stdio"],
env={"SERP_API_KEY": serp_api_key},)
tools, exit_stack = await MCPToolset.from_server(
connection_params=server_params)
..
..
使用通用A2A服务器组件和类型以及谷歌ADK运行器、会话和代理定义ADK服务器入口点
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.adk.agents import Agent
from .agent import get_agent_async
# 导入通用A2A服务器组件和类型
from common.server.server import A2AServer
from common.server.task_manager import InMemoryTaskManager
from common.types import (
AgentCard,
SendTaskRequest,
SendTaskResponse,
Task,
TaskStatus,
Message,
TextPart,
TaskState,
)
# --- 自定义航班搜索任务管理器 ---
class FlightAgentTaskManager(InMemoryTaskManager):
"""特定于ADK航班搜索代理的任务管理器。"""
def __init__(self, agent: Agent, runner: Runner, session_service: InMemorySessionService):
super().__init__()
self.agent = agent
self.runner = runner
self.session_service = session_service
logger.info("FlightAgentTaskManager初始化完成。")
...
...
使用代理卡创建A2A服务器实例
# --- 主执行块 ---
async def run_server():
"""初始化服务并启动A2AServer。"""
logger.info("开始初始化航班搜索A2A服务器...")
session_service = None
exit_stack = None
try:
session_service = InMemorySessionService()
agent, exit_stack = await get_agent_async()
runner = Runner(
app_name='flight_search_a2a_app',
agent=agent,
session_service=session_service,
)
# 创建特定的任务管理器
task_manager = FlightAgentTaskManager(
agent=agent,
runner=runner,
session_service=session_service
)
# 定义代理卡
port = int(os.getenv("PORT", "8000"))
host = os.getenv("HOST", "localhost")
listen_host = "0.0.0.0"
agent_card = AgentCard(
name="Flight Search Agent (A2A)",
description="根据用户查询提供航班信息。",
url=f"http://{host}:{port}/",
version="1.0.0",
defaultInputModes=["text"],
defaultOutputModes=["text"],
capabilities={"streaming": False},
skills=[
{
"id": "search_flights",
"name": "Search Flights",
"description": "根据出发地、目的地和日期搜索航班。",
"tags": ["flights", "travel"],
"examples": ["Find flights from JFK to LAX tomorrow"]
}
]
)
# 创建A2AServer实例
a2a_server = A2AServer(
agent_card=agent_card,
task_manager=task_manager,
host=listen_host,
port=port
)
# 程序化配置Uvicorn
config = uvicorn.Config(
app=a2a_server.app, # 传递A2AServer的Starlette应用
host=listen_host,
port=port,
log_level="info"
)
server = uvicorn.Server(config)
...
...
让我们启动航班搜索应用
步骤 5:使用LangChain作为MCP客户端设置酒店搜索(代理)并使用OpenAI(GPT-4o)
├── hotel_search_app/ # 酒店搜索代理(代理2)
│ ├── __init__.py
│ ├── a2a_agent_card.json # 代理能力声明
│ ├── langchain_agent.py # LangChain代理实现
│ ├── langchain_server.py # 服务器入口点
│ └── static/ # 静态文件
│ └── .well-known/ # 代理发现目录
│ └── agent.json # 标准化代理发现文件
使用OpenAI LLM的LangChain代理实现作为MCP客户端
from langchain_openai import ChatOpenAI
from langchain.agents import AgentExecutor, create_openai_functions_agent
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_mcp_adapters.client import MultiServerMCPClient
# MCP客户端配置
MCP_CONFIG = {
"hotel_search": {
"command": "mcp-hotel-search",
"args": ["--connection_type", "stdio"],
"transport": "stdio",
"env": {"SERP_API_KEY": os.getenv("SERP_API_KEY")},
}
}
class HotelSearchAgent:
"""使用LangChain MCP适配器的酒店搜索代理。"""
def __init__(self):
self.llm = ChatOpenAI(model="gpt-4o", temperature=0)
def _create_prompt(self):
"""创建带有自定义系统消息的提示模板。"""
system_message = """你是一个有帮助的酒店搜索助手。
"""
return ChatPromptTemplate.from_messages([
("system", system_message),
("human", "{input}"),
MessagesPlaceholder(variable_name="agent_scratchpad"),
])
..
..
async def process_query(self, query):
...
# 为此次查询创建MCP客户端
async with MultiServerMCPClient(MCP_CONFIG) as client:
# 从这个客户端实例获取工具
tools = client.get_tools()
# 创建提示
prompt = self._create_prompt()
# 使用这些工具创建代理
agent = create_openai_functions_agent(
llm=self.llm,
tools=tools,
prompt=prompt
)
# 使用这些工具创建执行器
executor = AgentExecutor(
agent=agent,
tools=tools,
verbose=True,
handle_parsing_errors=True,
)
使用通用A2A服务器组件和类型,类似于步骤4,创建A2AServer实例
让我们启动酒店搜索应用(Langchain服务器)服务器入口点以调用MCP服务器
步骤 6:使用A2A协议实现主机代理作为代理之间的协调器
行程规划器是上述旅行规划的核心组件,使用A2A协议在航班和酒店服务之间进行通信。
├── itinerary_planner/ # 旅行规划主机代理(代理3)
│ ├── __init__.py
│ ├── a2a/ # A2A客户端实现
│ │ ├── __init__.py
│ │ └── a2a_client.py # 航班和酒店代理的客户端
│ ├── a2a_agent_card.json # 代理能力声明
│ ├── event_log.py # 事件记录工具
│ ├── itinerary_agent.py # 主规划器实现
│ ├── itinerary_server.py # FastAPI服务器
│ ├── run_all.py # 运行所有组件的脚本
│ ├── static/ # 静态文件
│ │ └── .well-known/ # 代理发现目录
│ │ └── agent.json # 标准化代理发现文件
│ └── streamlit_ui.py # 主用户界面
A2A协议实现,使用航班和酒店API URL
- 包含与其他服务通信的客户端代码
- 实现了代理到代理协议
- 包含调用航班和酒店搜索服务的模块
# A2A兼容代理API的根端点
FLIGHT_SEARCH_API_URL = os.getenv("FLIGHT_SEARCH_API_URL", "http://localhost:8000")
HOTEL_SEARCH_API_URL = os.getenv("HOTEL_SEARCH_API_URL", "http://localhost:8003")
class A2AClientBase:
"""通过根端点与A2A兼容代理通信的基础客户端。"""
async def send_a2a_task(self, user_message: str, task_id: Optional[str] = None, agent_type: str = "generic") -> Dict[str, Any]:
...
....
# 构造带有A2A方法和修正后的参数结构的JSON-RPC负载
payload = {
"jsonrpc": "2.0",
"method": "tasks/send",
"params": {
"id": task_id,
"taskId": task_id,
"message": {
"role": "user",
"parts": [
{"type": "text", "text": user_message}
]
}
},
"id": task_id
}
代理卡 — 描述代理能力、端点、认证要求和技能的JSON元数据文件。用于A2A协议中的服务发现
{
"name": "Travel Itinerary Planner",
"displayName": "Travel Itinerary Planner",
"description": "一个协调航班和酒店信息以创建综合旅行行程的代理",
"version": "1.0.0",
"contact": "code.aicloudlab@gmail.com",
"endpointUrl": "http://localhost:8005",
"authentication": {
"type": "none"
},
"capabilities": ["streaming"],
"skills": [
{
"name": "createItinerary",
"description": "创建包含航班和住宿的综合旅行行程",
"inputs": [
{
"name": "origin",
"type": "string",
"description": "出发城市或机场代码"
},
{
"name": "destination",
"type": "string",
"description": "目的地城市或地区"
},
{
"name": "departureDate",
"type": "string",
"description": "出发日期,格式为YYYY-MM-DD"
},
{
"name": "returnDate",
"type": "string",
"description": "返回日期,格式为YYYY-MM-DD(可选)"
},
{
"name": "travelers",
"type": "integer",
"description": "旅行者数量"
},
{
"name": "preferences",
"type": "object",
"description": "其他偏好,如预算、酒店设施等"
}
],
"outputs": [
{
"name": "itinerary",
"type": "object",
"description": "完整的旅行行程,包括航班、酒店和时间表"
}
]
}
]
}
行程代理 — 系统的核心主机代理,协调与航班和酒店搜索服务的通信,使用语言模型解析自然语言请求
import google.generativeai as genai # 直接使用SDK
..
..
from itinerary_planner.a2a.a2a_client import FlightSearchClient, HotelSearchClient
# 配置谷歌生成式AI SDK
genai.configure(api_key=api_key)
class ItineraryPlanner:
"""一个使用谷歌生成式AI SDK协调航班和酒店搜索代理以创建行程的规划器。"""
def __init__(self):
"""初始化行程规划器。"""
logger.info("使用谷歌生成式AI SDK初始化行程规划器")
self.flight_client = FlightSearchClient()
self.hotel_client = HotelSearchClient()
# 使用SDK创建Gemini模型实例
self.model = genai.GenerativeModel(
model_name="gemini-2.0-flash",
)
..
..
行程服务器 — 一个FastAPI服务器,暴露行程规划器的端点,处理传入的HTTP请求并将请求路由到行程代理
streamlit_ui — 使用Streamlit构建的用户界面,为旅行规划提供表单,并以用户友好的格式显示结果
最终演示
在每个终端中启动服务器代理
# 启动航班搜索代理 - 1端口8000
python -m flight_search_app.main
# 启动酒店搜索代理 - 2端口8003
python -m hotel_search_app.langchain_server
# 启动行程主机代理 - 端口8005
python -m itinerary_planner.itinerary_server
# 启动前端UI - 端口8501
streamlit run itinerary_planner/streamlit_ui.py
由主机代理发起任务ID的航班搜索日志
由主机代理发起任务的酒店搜索日志
行程规划器 — 主机代理的所有请求/响应
代理事件日志
这个演示实现了**谷歌A2A协议**的核心原则,使代理能够以结构化、可互操作的方式进行通信。在上述演示中实现的组件如下:
- 代理卡 — 所有代理都暴露
.well-known/agent.json
文件用于发现。 - A2A服务器 — 每个代理都运行一个A2A服务器:
flight_search_app
、hotel_search_app
和itinerary_planner
。 - A2A客户端 —
itinerary_planner
包含针对航班和酒店代理的专用A2A客户端。 - 任务管理 — 每个请求/响应都被建模为一个A2A任务,状态包括已提交、正在处理和已完成。
- 消息结构 — 使用标准的JSON-RPC格式,带有角色(用户/代理)和部件(主要是
TextPart
)。
以下组件在我们的演示中尚未实现,但可以扩展为企业级代理:
- 流式传输(SSE) — A2A支持服务器发送事件用于长时间运行的任务,但我们的演示使用简单的请求/响应,耗时不到3-5秒。
- 推送通知 — 尚未使用网络钩子更新机制。
- 复杂部件 — 只使用了
TextPart
。可以添加对DataPart
、FilePart
等的支持,以实现更丰富的负载。 - 高级发现 — 实现了基本的
.well-known/agent.json
,但尚未实现高级认证、JWKS或授权流程。
A2A协议 — 官方文档
总结
在本文中,我们探索了如何使用可重用的A2A组件、ADK、LangChain和MCP构建一个功能齐全的多代理系统,用于旅行规划场景。通过结合这些开源工具和框架,我们的代理能够:
- 使用A2A动态发现并调用彼此
- 通过MCP以模型友好的方式连接到外部API
- 使用现代框架,如ADK和LangChain
- 以异步方式通信,具有清晰的任务生命周期和结构化结果
这些原则可以扩展到更多领域,如零售、客户服务自动化、运营工作流以及AI辅助的企业工具。