如何使用 A2A + AG-UI 让代理之间(以及与您的应用程序)进行通信
太长不看
在本指南中,您将学习如何使用 A2A 协议、AG-UI 协议和 CopilotKit,在来自不同 AI 代理框架的 AI 代理之间构建全栈代理到代理 (A2A) 通信。
在正式开始之前,我们先来看看我们将要涵盖的内容:
-
什么是A2A协议?
-
使用 CLI 设置 A2A 多代理通信
-
将来自不同代理框架的 AI 代理与 A2A 协议集成
-
使用 CopilotKit 为 AG-UI 和 A2A 多代理通信构建前端
以下是我们将要构建的内容预览:
什么是A2A协议?
A2A (代理对代理)协议 是谷歌开发的一种标准化通信框架,它使不同的 AI 代理能够在分布式系统中发现、通信和协作,而无需考虑框架。
A2A 协议旨在促进代理之间的通信,代理可以将其他代理作为工具或服务调用,从而创建一个专门的 AI 能力网络。
A2A协议的主要特点包括:
-
A2A 客户端:这是主代理(我们称之为“客户端代理”),它启动一切。它会确定需要完成的任务,找到合适的辅助代理,并将任务分配给它们。你可以把它想象成代码中的项目经理。
-
A2A 代理:一种 AI 代理,它会根据 A2A 规则设置一个简单的网址(HTTP 端点)。它会监听传入的请求,处理任务,并将结果或更新发送回服务器。对于让您的代理“公开”并随时准备协作非常有用。
-
代理卡:想象一下,一张 JSON 格式的数字身份证——易于阅读和共享。它包含 A2A 代理的基本信息,例如姓名、职责和联系方式。
-
经纪人技能:这些就像是您经纪人的职位描述。每项技能都概述了其擅长的某项具体内容(例如,“文章摘要”或“图像生成”)。客户阅读这些技能,就能确切地知道该分配哪些任务——无需猜测!
-
A2A 执行器:幕后核心。它是代码中的一个函数,负责繁重的工作:接收请求,运行逻辑来解决任务,并输出响应或触发事件。
-
A2A 服务器:这是整个系统的 Web 服务器端。它将您代理的技能转化为可在互联网上共享的内容。您需要使用 A2A 的请求处理程序进行设置,使用 Starlette(一个 Python Web 框架)构建一个轻量级的 Web 应用程序,并使用 Uvicorn(一个快速服务器运行器)启动它。搞定——您的代理已上线并准备就绪!
如果您想深入了解 A2A 协议的工作原理及其设置,请查看此处的文档:A2A 协议文档。
既然您已经了解了 A2A 协议是什么,让我们来看看如何将其与 AG-UI 和 CopilotKit 一起使用来构建全栈 A2A AI 代理。
先决条件
要完全理解本教程,您需要对 React 或 Next.js 有一定的了解。
我们还将利用以下资源:
-
Python 是一种流行的编程语言,可用于使用 AI 代理框架构建 AI 代理;请确保您的计算机上已安装 Python。
-
AG-UI 协议 - 由 CopilotKit 开发的代理用户交互协议 (AG-UI) 是一种开源、轻量级、基于事件的协议,可促进前端和 AI 代理后端之间丰富的实时交互。
-
Google ADK是 Google 设计的开源框架,旨在简化构建复杂且可用于生产环境的 AI 代理的过程。
-
LangGraph 是一个用于创建和部署 AI 代理的框架。它还有助于定义代理要执行的控制流和动作。
-
Gemini API 密钥 - 一个 API 密钥,使您能够使用 ADK 代理的 Gemini 模型执行各种任务。
-
CopilotKit - 一个开源的辅助驾驶框架,用于构建自定义 AI 聊天机器人、应用内 AI 代理和文本区域。
使用 CLI 设置 A2A 多代理通信
在本节中,您将学习如何使用 CLI 命令设置 A2A 客户端(编排代理)+ A2A 代理,该命令使用 Google ADK 和 AG-UI 协议设置后端,并使用 CopilotKit 设置前端。
我们开始吧。
步骤 1:运行 CLI 命令
如果您还没有预配置的 AG-UI 代理,您可以通过在终端中运行以下 CLI 命令快速设置一个。
npx copilotkit@latest create -f a2a
然后按照下面的示例给你的项目命名。
步骤 2:安装前端依赖项
项目创建成功后,请使用您首选的包管理器安装依赖项:
npm install
步骤 3:安装后端依赖项
安装完前端依赖项后,再安装后端依赖项:
cd agents
python3 -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
pip install -r requirements.txt
cd ..
步骤 4:设置环境变量
安装完后端依赖项后,设置环境变量:
cp .env.example .env
# Edit .env and add your API keys:
# GOOGLE_API_KEY=your_google_api_key
# OPENAI_API_KEY=your_openai_api_key
步骤 5:启动所有服务
设置好环境变量后,启动包括后端和前端在内的所有服务。
npm run dev
开发服务器运行后,访问http://localhost:3000/,您应该可以看到您的 A2A 多代理前端已启动并运行。
恭喜!您已成功设置 A2A 多智能体通信。尝试让您的智能体研究某个主题,例如“请研究量子计算”。您会看到它会向研究智能体和分析智能体发送消息。然后,它会将完整的研究和分析结果呈现给用户。
将编排代理与后端 Google ADK 和 AG-UI 协议集成
在本节中,您将学习如何将编排器代理与 Google ADK 和 AG-UI 协议集成,以便将其作为 ASGI 应用程序公开给前端。
让我们开始吧。
步骤 1:设置后端
首先,克隆A2A-Travel 存储库,该存储库包含一个基于 Python 的后端(代理)和一个 Next.js 前端。
接下来,导航到后端目录:
cd agents
然后创建一个新的Python虚拟环境:
python -m venv .venv
之后,激活虚拟环境:
source .venv/bin/activate # On Windows: .venv\Scripts\activate
最后,安装文件中列出的所有 Python 依赖项 requirements.txt 。
pip install -r requirements.txt
步骤 2:配置 Orchestrator ADK 代理
设置好后端后,通过定义代理名称、指定 Gemini 2.5 Pro 作为大型语言模型 (LLM) 以及定义代理的指令来配置 Orchestrator ADK 代理,如下面的文件中所示agents/orchestrator.py。
# Import Google ADK components for LLM agent creation
from google.adk.agents import LlmAgent
# === ORCHESTRATOR AGENT CONFIGURATION ===
# Create the main orchestrator agent using Google ADK's LlmAgent
# This agent coordinates all travel planning activities and manages the workflow
orchestrator_agent = LlmAgent(
name="OrchestratorAgent",
model="gemini-2.5-pro", # Use the more powerful Pro model for complex orchestration
instruction="""
You are a travel planning orchestrator agent. Your role is to coordinate specialized agents
to create personalized travel plans.
AVAILABLE SPECIALIZED AGENTS:
1. **Itinerary Agent** (LangGraph) - Creates day-by-day travel itineraries with activities
2. **Restaurant Agent** (LangGraph) - Recommends restaurants for breakfast, lunch, and dinner by day
3. **Weather Agent** (ADK) - Provides weather forecasts and packing advice
4. **Budget Agent** (ADK) - Estimates travel costs and creates budget breakdowns
CRITICAL CONSTRAINTS:
- You MUST call agents ONE AT A TIME, never make multiple tool calls simultaneously
- After making a tool call, WAIT for the result before making another tool call
- Do NOT make parallel/concurrent tool calls - this is not supported
RECOMMENDED WORKFLOW FOR TRAVEL PLANNING:
// ...
""",
)
步骤 3:创建 ADK 中间件代理实例
配置好 Orchestrator ADK 代理后,创建一个 ADK 中间件代理实例,该实例封装 Orchestrator ADK 代理,以便将其与 AG-UI 协议集成,如下面的文件中所示agents/orchestrator.py。
# Import AG-UI ADK components for frontend integration
from ag_ui_adk import ADKAgent
// ...
# === AG-UI PROTOCOL INTEGRATION ===
# Wrap the orchestrator agent with AG-UI Protocol capabilities
# This enables frontend communication and provides the interface for user interactions
adk_orchestrator_agent = ADKAgent(
adk_agent=orchestrator_agent, # The core LLM agent we created above
app_name="orchestrator_app", # Unique application identifier
user_id="demo_user", # Default user ID for demo purposes
session_timeout_seconds=3600, # Session timeout (1 hour)
use_in_memory_services=True # Use in-memory storage for simplicity
)
步骤 4:配置 FastAPI 端点
创建 ADK 中间件代理实例后,配置一个 FastAPI 端点,将 AG-UI 封装的编排器 ADK 代理暴露给前端,如下面的文件中所示agents/orchestrator.py。
# Import necessary libraries for web server and environment variables
import os
import uvicorn
# Import FastAPI for creating HTTP endpoints
from fastapi import FastAPI
// ...
# === FASTAPI WEB APPLICATION SETUP ===
# Create the FastAPI application that will serve the orchestrator agent
# This provides HTTP endpoints for the AG-UI Protocol communication
app = FastAPI(title="Travel Planning Orchestrator (ADK)")
# Add the ADK agent endpoint to the FastAPI application
# This creates the necessary routes for AG-UI Protocol communication
add_adk_fastapi_endpoint(app, adk_orchestrator_agent, path="/")
# === MAIN APPLICATION ENTRY POINT ===
if __name__ == "__main__":
"""
Main entry point when the script is run directly.
This function:
1. Checks for required environment variables (API keys)
2. Configures the server port
3. Starts the uvicorn server with the FastAPI application
"""
# Check for required Google API key
if not os.getenv("GOOGLE_API_KEY"):
print("⚠️ Warning: GOOGLE_API_KEY environment variable not set!")
print(" Set it with: export GOOGLE_API_KEY='your-key-here'")
print(" Get a key from: https://aistudio.google.com/app/apikey")
print()
# Get server port from environment variable, default to 9000
port = int(os.getenv("ORCHESTRATOR_PORT", 9000))
# Start the server with detailed information
print(f"🚀 Starting Orchestrator Agent (ADK + AG-UI) on http://localhost:{port}")
# Run the FastAPI application using uvicorn
# host="0.0.0.0" allows external connections
# port is configurable via the environment variable
uvicorn.run(app, host="0.0.0.0", port=port)
恭喜!您已成功将编排器 ADK Agent 与 AG-UI 协议集成,并且可通过http://localhost:9000(或指定端口)端点访问。
将来自不同代理框架的 AI 代理与 A2A 协议集成
在本节中,您将学习如何将来自不同代理框架的 AI 代理与 A2A 集成。
协议。
我们开始吧!
步骤 1:配置 A2A 远程代理
首先,配置您的 A2A 远程代理,例如使用 LangGraph 框架的行程代理,如agents/itinerary_agent.py文件中所示。
import json
from langgraph.graph import StateGraph, END
from langchain_openai import ChatOpenAI
# === MAIN AGENT CLASS ===
class ItineraryAgent:
"""
Main agent class that handles itinerary generation using LangGraph workflow.
"""
def __init__(self):
self.llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.7)
# Build and compile the LangGraph workflow
self.graph = self._build_graph()
def _build_graph(self):
workflow = StateGraph(ItineraryState)
workflow.add_node("parse_request", self._parse_request)
workflow.add_node("create_itinerary", self._create_itinerary)
workflow.set_entry_point("parse_request")
workflow.add_edge("parse_request", "create_itinerary")
workflow.add_edge("create_itinerary", END)
# Compile the workflow into an executable graph
return workflow.compile()
def _parse_request(self, state: "ItineraryState") -> "ItineraryState":
message = state["message"]
# Create a focused prompt for the extraction task
prompt = f"""
Extract the destination and number of days from this travel request.
Return ONLY a JSON string with 'destination' and 'days' fields.
Request: {message}
Example output: {{"destination": "Tokyo", "days": 3}}
"""
# Get LLM response for parsing
response = self.llm.invoke(prompt)
print(response.content) # Debug: print LLM response
try:
parsed = json.loads(response.content)
state["destination"] = parsed.get("destination", "Unknown")
state["days"] = int(parsed.get("days", 3))
except Exception as e:
print(f"⚠️ Failed to parse request: {e}")
state["destination"] = "Unknown"
state["days"] = 3
return state
def _create_itinerary(self, state: "ItineraryState") -> "ItineraryState":
destination = state["destination"]
days = state["days"]
prompt = f"""
Create a detailed {days}-day travel itinerary for {destination}.
Make it realistic, interesting, and include specific place names.
Return ONLY valid JSON, no markdown, no other text.
"""
# Generate itinerary using LLM
response = self.llm.invoke(prompt)
content = response.content.strip()
# Clean up response (remove code fences if present)
if "```
json" in content:
content = content.split("
```json")[1].split("```
")[0].strip()
elif "
```" in content:
content = content.split("```
")[1].split("
```")[0].strip()
try:
structured_data = json.loads(content)
validated_itinerary = StructuredItinerary(**structured_data)
state["structured_itinerary"] = validated_itinerary.model_dump()
state["itinerary"] = json.dumps(
validated_itinerary.model_dump(), indent=2
)
print("✅ Successfully created structured itinerary")
except Exception as e:
print(f"⚠️ Failed to parse itinerary JSON: {e}")
state["itinerary"] = json.dumps(
{"error": "Failed to generate valid itinerary"}, indent=2
)
return state
async def invoke(self, message: "Message") -> str:
message_text = message.parts[0].root.text
print("Invoking itinerary agent with message:", message_text)
result = self.graph.invoke({
"message": message_text,
"destination": "",
"days": 3,
"itinerary": ""
})
return result["itinerary"]
步骤 2:设置 A2A 远程代理技能和代理卡
配置好 A2A 远程代理后,设置该代理以便其他代理可以发现和调用它。
为此,请定义每个代理提供的具体技能,以及其他代理可以发现的公共代理卡,如agents/itinerary_agent.py文件中所示。
from a2a.types import ( AgentCapabilities, AgentCard, AgentSkill)
// ...
# Define the specific skill this agent provides
skill = AgentSkill(
id='itinerary_agent',
name='Itinerary Planning Agent',
description='Creates detailed day-by-day travel itineraries using LangGraph',
tags=['travel', 'itinerary', 'langgraph'],
examples=[
'Create a 3-day itinerary for Tokyo',
'Plan a week-long trip to Paris',
'What should I do in New York for 5 days?'
],
)
# Define the public agent card that other agents can discover
public_agent_card = AgentCard(
name='Itinerary Agent',
description='LangGraph-powered agent that creates detailed day-by-day travel itineraries in plain text format with activities and meal recommendations.',
url=f'http://localhost:{port}/',
version='1.0.0',
defaultInputModes=['text'], # Accepts text input
defaultOutputModes=['text'], # Returns text output
capabilities=AgentCapabilities(streaming=True), # Supports streaming responses
skills=[skill], # List of skills this agent provides
supportsAuthenticatedExtendedCard=False, # No authentication required
)
步骤 3:配置 A2A 代理执行器
在设置好每个代理的技能以及代理卡之后,按照文件中所示,为每个代理配置一个 A2A 代理执行器来处理 A2A 请求和响应agents/itinerary_agent.py。
from a2a.server.agent_execution import AgentExecutor, RequestContext
// ...
# === A2A PROTOCOL EXECUTOR ===
class ItineraryAgentExecutor(AgentExecutor):
"""
An executor class that bridges the A2A Protocol with our ItineraryAgent.
This class handles the A2A Protocol lifecycle:
- Receives execution requests from other agents
- Delegates to our ItineraryAgent for processing
- Sends results back through the event queue
"""
def __init__(self):
"""Initialize the executor with an instance of our agent"""
self.agent = ItineraryAgent()
async def execute(
self,
context: RequestContext,
event_queue: EventQueue,
) -> None:
"""
Execute an itinerary generation request.
This method:
1. Calls our agent with the incoming message
2. Formats the result as an A2A text message
3. Sends the response through the event queue
Args:
context: Request context containing the message and metadata
event_queue: Queue for sending response events back to caller
"""
# Generate itinerary using our agent
result = await self.agent.invoke(context.message)
# Send result back through A2A Protocol event queue
await event_queue.enqueue_event(new_agent_text_message(result))
async def cancel(
self, context: RequestContext, event_queue: EventQueue
) -> None:
"""
Handle cancellation requests (not implemented).
For this agent, we don't support cancellation since the itinerary
Generation is typically fast and non-interruptible.
"""
raise Exception('cancel not supported')
步骤 4:设置 A2A 代理服务器
为每个远程代理配置 A2A 代理执行器后,设置每个代理的 A2A 代理服务器,该服务器配置 A2A 协议请求处理程序、创建 Starlette Web 应用程序并启动 uvicorn 服务器,如文件中所示agents/itineray_agent.py。
# Import A2A Protocol components for inter-agent communication
from a2a.server.apps import A2AStarletteApplication
from a2a.server.request_handlers import DefaultRequestHandler
from a2a.server.tasks import InMemoryTaskStore
// ...
# Get port from environment variable, default to 9001
port = int(os.getenv("ITINERARY_PORT", 9001))
# === MAIN APPLICATION SETUP ===
def main():
"""
Main function that sets up and starts the A2A Protocol server.
This function:
1. Checks for required environment variables
2. Sets up the A2A Protocol request handler
3. Creates the Starlette web application
4. Starts the uvicorn server
"""
# Check for required OpenAI API key
if not os.getenv("OPENAI_API_KEY"):
print("⚠️ Warning: OPENAI_API_KEY environment variable not set!")
print(" Set it with: export OPENAI_API_KEY='your-key-here'")
print()
# Create the A2A Protocol request handler
# This handles incoming requests and manages the task lifecycle
request_handler = DefaultRequestHandler(
agent_executor=ItineraryAgentExecutor(), # Our custom executor
task_store=InMemoryTaskStore(), # Simple in-memory task storage
)
# Create the A2A Starlette web application
# This provides the HTTP endpoints for A2A Protocol communication
server = A2AStarletteApplication(
agent_card=public_agent_card, # Public agent information
http_handler=request_handler, # Request processing logic
extended_agent_card=public_agent_card, # Extended agent info (same as public)
)
# Start the server
print(f"🗺️ Starting Itinerary Agent (LangGraph + A2A) on http://localhost:{port}")
uvicorn.run(server.build(), host='0.0.0.0', port=port)
# === ENTRY POINT ===
if __name__ == '__main__':
"""
Entry point when the script is run directly.
This allows the agent to be started as a standalone service:
python itinerary_agent.py
"""
main()
恭喜!您已成功将远程代理与 A2A 协议集成,现在协调代理可以将任务委派给代理。
使用 CopilotKit 为 AG-UI 和 A2A 多代理通信构建前端
在本节中,您将学习如何使用 CopilotKit 为 AG-UI 和 A2A 多代理通信添加前端,CopilotKit 可以在 React 运行的任何地方运行。
我们开始吧。
步骤 1:设置前端
首先,请在您之前克隆的A2A-Travel 存储库中安装前端依赖项。
npm install
然后配置环境变量并编辑 .env 文件,并添加您的 GOOGLE_API_KEY 和 OPENAI_API_KEY。
cp .env.example .env
最后,启动所有后端和前端服务器:
npm run dev
该命令会依次启动用户界面http://localhost:3000、行程协调器http://localhost:9000、行程代理http://localhost:9001、预算代理http://localhost:9002、餐厅代理http://localhost:9003和天气代理。http://localhost:9005
如果您访问http://localhost:3000/,您应该会看到旅行规划 A2A 多代理前端已启动并运行。
步骤 2:使用 A2A 中间件配置 CopilotKit API 路由
设置好前端后,按照文件中所示,配置 CopilotKit API 路由和 A2A 中间件,以建立前端、AG-UI + ADK Orchestrator 代理和 A2A 代理之间的连接app/api/copilotkit/route.ts。
import {
CopilotRuntime,
ExperimentalEmptyAdapter,
copilotRuntimeNextJSAppRouterEndpoint,
} from "@copilotkit/runtime";
import { HttpAgent } from "@ag-ui/client";
import { A2AMiddlewareAgent } from "@ag-ui/a2a-middleware";
import { NextRequest } from "next/server";
export async function POST(request: NextRequest) {
// STEP 1: Define A2A agent URLs
const itineraryAgentUrl =
process.env.ITINERARY_AGENT_URL || "http://localhost:9001";
const budgetAgentUrl =
process.env.BUDGET_AGENT_URL || "http://localhost:9002";
const restaurantAgentUrl =
process.env.RESTAURANT_AGENT_URL || "http://localhost:9003";
const weatherAgentUrl =
process.env.WEATHER_AGENT_URL || "http://localhost:9005";
// STEP 2: Define orchestrator URL (speaks AG-UI Protocol)
const orchestratorUrl =
process.env.ORCHESTRATOR_URL || "http://localhost:9000";
// STEP 3: Wrap orchestrator with HttpAgent (AG-UI client)
// the orchestrator agent we pass to the middleware needs to be an instance of a derivative of an ag-ui `AbstractAgent`
// In this case, we have access to the agent via url, so we can gain an instance using the `HttpAgent` class
const orchestrationAgent = new HttpAgent({
url: orchestratorUrl,
});
// STEP 4: Create A2A Middleware Agent
// This bridges AG-UI and A2A protocols by:
// 1. Wrapping the orchestrator
// 2. Registering all A2A agents
// 3. Injecting send_message_to_a2a_agent tool
// 4. Routing messages between orchestrator and A2A agents
const a2aMiddlewareAgent = new A2AMiddlewareAgent({
description:
"Travel planning assistant with 4 specialized agents: Itinerary and Restaurant (LangGraph), Weather and Budget (ADK)",
agentUrls: [
itineraryAgentUrl, // LangGraph + OpenAI
restaurantAgentUrl, // ADK + Gemini
budgetAgentUrl, // ADK + Gemini
weatherAgentUrl, // ADK + Gemini
],
orchestrationAgent,
});
// STEP 5: Create CopilotKit Runtime
const runtime = new CopilotRuntime({
agents: {
a2a_chat: a2aMiddlewareAgent, // Must match frontend: <CopilotKit agent="a2a_chat">
},
});
// STEP 6: Set up Next.js endpoint handler
const { handleRequest } = copilotRuntimeNextJSAppRouterEndpoint({
runtime,
serviceAdapter: new ExperimentalEmptyAdapter(),
endpoint: "/api/copilotkit",
});
return handleRequest(request);
}
步骤 3:设置 CopilotKit 提供程序
使用 A2A 中间件配置 CopilotKit API 路由后,设置管理 A2A 多代理会话的 CopilotKit 提供程序组件。
要设置 CopilotKit 提供程序, [<CopilotKit>](https://docs.copilotkit.ai/reference/components/CopilotKit) 组件必须包装应用程序中与 Copilot 相关的部分,如components/travel-chat.tsx文件中所示。
import { CopilotKit } from "@copilotkit/react-core";
// ...
/**
* MAIN COMPONENT: CopilotKit Provider Wrapper
*
* This is the main export that wraps the chat component with the CopilotKit provider.
* The provider configuration enables:
* - Runtime connection to backend agents via /api/copilotkit endpoint
* - A2A agent communication protocol
* - Development console for debugging (disabled in production)
*/
export default function TravelChat({
onItineraryUpdate,
onBudgetUpdate,
onWeatherUpdate,
onRestaurantUpdate,
}: TravelChatProps) {
return (
<CopilotKit
runtimeUrl="/api/copilotkit" // Backend endpoint for agent communication
showDevConsole={false} // Disable dev console in production
agent="a2a_chat" // Specify A2A agent protocol
>
<ChatInner
onItineraryUpdate={onItineraryUpdate}
onBudgetUpdate={onBudgetUpdate}
onWeatherUpdate={onWeatherUpdate}
onRestaurantUpdate={onRestaurantUpdate}
/>
</CopilotKit>
);
}
步骤 4:配置 Copilot UI 组件
设置好 CopilotKit 提供程序后,设置一个 Copilot UI 组件,以便与 AG-UI + A2A 代理进行交互。CopilotKit 附带几个内置聊天组件,包括CopilotPopup、 CopilotSidebar和CopilotChat。
要设置 Copilot UI 组件,请将其与核心页面组件一起定义,如components/travel-chat.tsx文件中所示。
import { CopilotChat } from "@copilotkit/react-ui";
// ...
const ChatInner = ({
onItineraryUpdate,
onBudgetUpdate,
onWeatherUpdate,
onRestaurantUpdate,
}: TravelChatProps) => {
// ...
/**
* COPILOTKIT CHAT COMPONENT: Main chat interface
*
* The CopilotChat component provides the core chat interface with:
* - Message history and real-time conversation
* - Integration with all registered actions
* - Customizable labels and instructions
* - Built-in support for generative UI and HITL workflows
*/
return (
<div className="h-full">
<CopilotChat
className="h-full"
labels={{
initial:
"👋 Hi! I'm your travel planning assistant.\n\nAsk me to plan a trip and I'll coordinate with specialized agents to create your perfect itinerary!",
}}
instructions="You are a helpful travel planning assistant. Help users plan their trips by coordinating with specialized agents."
/>
</div>
);
};
步骤 5:使用生成式 UI 渲染代理之间的通信
设置好 Copilot UI 组件后,在聊天组件中使用生成式 UI渲染代理之间的通信。
要在聊天组件中实时渲染代理之间的通信,请定义一个useCopilotAction()名为 的钩子send_message_to_a2a_agent,如components/travel-chat.tsx文件中所示。
import { useCopilotAction } from "@copilotkit/react-core";
//A2A Communication visualization components
import { MessageToA2A } from "./a2a/MessageToA2A";
import { MessageFromA2A } from "./a2a/MessageFromA2A";
// ...
const ChatInner = ({
onItineraryUpdate,
onBudgetUpdate,
onWeatherUpdate,
onRestaurantUpdate,
}: TravelChatProps) => {
// ...
useCopilotAction({
name: "send_message_to_a2a_agent",
description: "Sends a message to an A2A agent",
available: "frontend", // This action runs on frontend only - no backend processing
parameters: [
{
name: "agentName",
type: "string",
description: "The name of the A2A agent to send the message to",
},
{
name: "task",
type: "string",
description: "The message to send to the A2A agent",
},
],
// Custom render function creates visual A2A communication components
render: (actionRenderProps: MessageActionRenderProps) => {
return (
<>
{/* MessageToA2A: Shows outgoing message (green box) */}
<MessageToA2A {...actionRenderProps} />
{/* MessageFromA2A: Shows agent response (blue box) */}
<MessageFromA2A {...actionRenderProps} />
</>
);
},
});
// ...
return (
<div className="h-full">
{*/ ... /*}
</div>
);
};
当协调代理向 A2A 代理发送请求或接收响应时,您应该会在聊天组件上看到如下所示的通信。
步骤 6:在前端实现人机交互(HITL)
人机交互(HITL)允许智能体在执行过程中请求人类输入或批准,从而提高人工智能系统的可靠性和可信度。对于需要处理复杂决策或需要人类判断的行动的人工智能应用而言,这种模式至关重要。
您可以在CopilotKit 文档中了解更多关于“人机交互”的信息。
要在前端实现人机交互(HITL),您需要使用 CopilotKit 的 useCopilotKitAction 钩子renderAndWaitForResponse方法,该方法允许从渲染函数异步返回值,如components/travel-chat.tsx文件中所示。
const ChatInner = ({
onItineraryUpdate,
onBudgetUpdate,
onWeatherUpdate,
onRestaurantUpdate,
}: TravelChatProps) => {
// State management for HITL budget approval workflow
// Tracks approval/rejection status for different budget proposals
const [approvalStates, setApprovalStates] = useState<
Record<string, { approved: boolean; rejected: boolean }>
>({});
/**
* HITL FEATURE: Budget approval workflow with renderAndWaitForResponse
*
* This useCopilotAction demonstrates CopilotKit's Human-in-the-Loop (HITL)
* capabilities, which pause agent execution and wait for user interaction
* before continuing the workflow.
*
* Key features:
* - renderAndWaitForResponse: Blocks agent until user provides input
* - State management: Tracks approval/rejection status across re-renders
* - Business logic integration: Only proceeds with approved budgets
* - Custom UI: Renders an interactive approval card with approve/reject buttons
* - Response handling: Sends the user's decision back to the agent
*/
useCopilotAction(
{
name: "request_budget_approval",
description: "Request user approval for the travel budget",
parameters: [
{
name: "budgetData",
type: "object",
description: "The budget breakdown data requiring approval",
},
],
// renderAndWaitForResponse pauses agent execution until the user responds
renderAndWaitForResponse: ({ args, respond }) => {
// Step 1: Validate budget data structure
if (!args.budgetData || typeof args.budgetData !== "object") {
return (
<div className="text-xs text-gray-500 p-2">
Loading budget data...
</div>
);
}
const budget = args.budgetData as BudgetData;
if (!budget.totalBudget || !budget.breakdown) {
return (
<div className="text-xs text-gray-500 p-2">
Loading budget data...
</div>
);
}
// Step 2: Create a unique key for this budget to track approval state
const budgetKey = `budget-${budget.totalBudget}`;
const currentState = approvalStates[budgetKey] || {
approved: false,
rejected: false,
};
// Step 3: Define approval handler - updates state and responds to agent
const handleApprove = () => {
setApprovalStates((prev) => ({
...prev,
[budgetKey]: { approved: true, rejected: false },
}));
// Send approval response back to the agent to continue the workflow
respond?.({ approved: true, message: "Budget approved by user" });
};
// Step 4: Define rejection handler - updates state and responds to agent
const handleReject = () => {
setApprovalStates((prev) => ({
...prev,
[budgetKey]: { approved: false, rejected: true },
}));
// Send rejection response back to the agent to handle accordingly
respond?.({ approved: false, message: "Budget rejected by user" });
};
// Step 5: Render interactive budget approval card
return (
<BudgetApprovalCard
budgetData={budget}
isApproved={currentState.approved}
isRejected={currentState.rejected}
onApprove={handleApprove}
onReject={handleReject}
/>
);
},
},
[approvalStates] // Re-register when approval states change
);
// ...
return (
<div className="h-full">
{/* ... */}
</div>
);
当代理通过工具/操作名称触发前端操作,以在执行过程中请求人工输入或反馈时,最终用户会看到一个选择提示(显示在聊天界面中)。然后,用户可以通过按下聊天界面中的按钮进行选择,如下图所示。
步骤 6:在前端流式传输代理之间的响应
要在前端流式传输代理到代理的响应,定义一个 useEffect() 钩子,该钩子解析 AI 代理的响应以提取结构化数据(如行程、预算、天气或餐厅推荐),如文件中所示components/travel-chat.tsx。
const ChatInner = ({
onItineraryUpdate,
onBudgetUpdate,
onWeatherUpdate,
onRestaurantUpdate,
}: TravelChatProps) => {
// State management for HITL budget approval workflow
// Tracks approval/rejection status for different budget proposals
const [approvalStates, setApprovalStates] = useState<
Record<string, { approved: boolean; rejected: boolean }>
>({});
// CopilotKit hook to access chat messages for data extraction
// visibleMessages contains all messages currently displayed in the chat
const { visibleMessages } = useCopilotChat();
/**
* GENERATIVE UI FEATURE: Auto-extract structured data from agent responses
*
* This useEffect demonstrates CopilotKit's ability to automatically parse and
* extract structured data from AI agent responses, converting them into
* interactive UI components.
*
* Process:
* 1. Monitor all visible chat messages for agent responses
* 2. Parse JSON data from the A2A agent message results
* 3. Identify data type (itinerary, budget, weather, restaurant)
* 4. Update parent component state to render corresponding UI components
* 5. Apply business logic (e.g., budget approval checks)
*/
useEffect(() => {
const extractDataFromMessages = () => {
// Step 1: Iterate through all visible messages in the chat
for (const message of visibleMessages) {
const msg = message as any;
// Step 2: Filter for A2A agent response messages specifically
if (
msg.type === "ResultMessage" &&
msg.actionName === "send_message_to_a2a_agent"
) {
try {
const result = msg.result;
let parsed;
// Step 3: Parse the agent response data (handle both string and object formats)
if (typeof result === "string") {
let cleanResult = result;
// Remove A2A protocol prefix if present
if (result.startsWith("A2A Agent Response: ")) {
cleanResult = result.substring("A2A Agent Response: ".length);
}
parsed = JSON.parse(cleanResult);
} else if (typeof result === "object" && result !== null) {
parsed = result;
}
// Step 4: Identify data type and trigger appropriate UI updates
if (parsed) {
// Itinerary data: destination + itinerary array
if (
parsed.destination &&
parsed.itinerary &&
Array.isArray(parsed.itinerary)
) {
onItineraryUpdate?.(parsed as ItineraryData);
}
// Budget data: requires user approval before displaying
else if (
parsed.totalBudget &&
parsed.breakdown &&
Array.isArray(parsed.breakdown)
) {
const budgetKey = `budget-${parsed.totalBudget}`;
const isApproved = approvalStates[budgetKey]?.approved || false;
// Step 5: Apply HITL approval check - only show if user approved
if (isApproved) {
onBudgetUpdate?.(parsed as BudgetData);
}
}
// Weather data: destination + forecast array
else if (
parsed.destination &&
parsed.forecast &&
Array.isArray(parsed.forecast)
) {
const weatherDataParsed = parsed as WeatherData;
onWeatherUpdate?.(weatherDataParsed);
}
// Restaurant data: destination + meals array
else if (
parsed.destination &&
parsed.meals &&
Array.isArray(parsed.meals)
) {
onRestaurantUpdate?.(parsed as RestaurantData);
}
}
} catch (e) {
// Silently handle parsing errors - not all messages contain structured data
}
}
}
};
extractDataFromMessages();
}, [
visibleMessages,
approvalStates,
onItineraryUpdate,
onBudgetUpdate,
onWeatherUpdate,
onRestaurantUpdate,
]);
// ...
return (
<div className="h-full">
{/* ... */}
</div>
);
};
然后,提取出的结构化数据触发 UI 更新以渲染交互式组件,如app/page.tsx文件中所示。
"use client";
import { useState } from "react";
import TravelChat from "@/components/travel-chat";
import { ItineraryCard, type ItineraryData } from "@/components/ItineraryCard";
import { BudgetBreakdown, type BudgetData } from "@/components/BudgetBreakdown";
import { WeatherCard, type WeatherData } from "@/components/WeatherCard";
import { type RestaurantData } from "@/components/ItineraryCard";
export default function Home() {
const [itineraryData, setItineraryData] = useState<ItineraryData | null>(null);
const [budgetData, setBudgetData] = useState<BudgetData | null>(null);
const [weatherData, setWeatherData] = useState<WeatherData | null>(null);
const [restaurantData, setRestaurantData] = useState<RestaurantData | null>(null);
return (
<div className="relative flex h-screen overflow-hidden bg-[#DEDEE9] p-2">
// ...
<div className="flex flex-1 overflow-hidden z-10 gap-2">
// ...
<div className="flex-1 overflow-hidden">
<TravelChat
onItineraryUpdate={setItineraryData}
onBudgetUpdate={setBudgetData}
onWeatherUpdate={setWeatherData}
onRestaurantUpdate={setRestaurantData}
/>
</div>
</div>
<div className="flex-1 overflow-y-auto rounded-lg bg-white/30 backdrop-blur-sm">
<div className="max-w-5xl mx-auto p-8">
// ...
{itineraryData && (
<div className="mb-4">
<ItineraryCard data={itineraryData} restaurantData={restaurantData} />
</div>
)}
{(weatherData || budgetData) && (
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
{weatherData && (
<div>
<WeatherCard data={weatherData} />
</div>
)}
{budgetData && (
<div>
<BudgetBreakdown data={budgetData} />
</div>
)}
</div>
)}
</div>
</div>
</div>
</div>
);
}
如果您向客服人员提出询问并批准其反馈请求,您应该会在用户界面中看到客服人员的回复或结果,如下所示。
结论
特别鸣谢:
我要特别感谢Mark Morgan,他构建了 A2A 旅行演示,包括其所有后端多代理功能以及美观的前端设计。他做得非常出色!请在GitHub
上关注他!
让我快速回顾一下我们已经构建的内容:
在本指南中,我们逐步介绍了如何使用 A2A + AG-UI 协议和 CopilotKit 构建全栈 Agent-to-Agent 通信。
虽然我们已经探索了一些功能,但我们仅仅触及了 CopilotKit 无数用例的冰山一角,从构建交互式 AI 聊天机器人到构建代理解决方案——本质上,CopilotKit 可以让您在几分钟内为您的产品添加大量有用的 AI 功能。
希望本指南能帮助您更轻松地将人工智能驱动的副驾驶功能集成到您现有的应用程序中。
在Twitter上关注 CopilotKit 并打个招呼,如果你想开发一些很酷的东西,请加入 Discord 社区。
文章来源:https://dev.to/copilotkit/how-to-make-agents-talk-to-each-other-and-your-app-using-a2a-ag-ui-4amo







