目录
- 介绍
-
- [1. 什么是A2A?](#1. 什么是A2A?)
- [2. 为什么需要A2A?](#2. 为什么需要A2A?)
- 核心概念
-
- [1. 核心角色](#1. 核心角色)
- [2. 数据结构](#2. 数据结构)
- 框架集成
介绍
- 在人工智能飞速发展的今天,我们见证了无数强大的
AI Agent(智能体)的诞生。然而,当我们需要解决复杂问题的时候,往往发现这些Agent就像一个个孤岛,被禁锢在各自的框架和生态中,无法协同工作。接下来我们主要探讨下一个旨在打破这些壁垒的开放标准,A2A(Agent2Agent)协议。
1. 什么是A2A?
- A2A协议是一个开放标准,旨在实现AI Agent之间的无缝通信与协作。它提供了一种通用的语言,让基于不同框架,如
LangChain,CrewAI Google ADK等由不同供应商开发的Agent能够互相理解并协同工作,A2A的核心愿景是让Agent回归其本质 - 作为问题解决者,独立的在其环境中行动,跨越组织和技术的边界进行联合。
2. 为什么需要A2A?
- 在A2A出现之前,要实现Agent之间的协作面临着诸多挑战
痛点:Agent被"降级为"工具
- 当前的普遍做法是将Agent包装成"工具(Tools)"暴露给其他Agent(类似于MCP协议中的工具)。这种做法存在显著缺陷:
- 效率低下:Agent本应具备协商和推理能力,将其视为无状态的工具限制了其潜能
- 集成噩梦:每一次集成都需要定制化的点对点解决方案,工程量巨大
- 创新受阻:各自为政的开发模式导致系统难以扩展,且缺乏统一的安全标准
核心概念
1. 核心角色
- 用户(
User):一切的起点,可能是人类,也可能是自动化服务,它是提出需求的一方。 - A2A客户端(
Client Agent):用户的"代理人",它代表用户发起请求,负责与远端Agent进行沟通,相当于用户的私人助理。 - A2A服务端(
Remote Agent):提供服务的"专家"。它是一个实现了A2A协议HTTP接口的AI Agent。对于客户端来说,服务端是不透明(Opaque)的黑盒,客户端只知道它能做什么(能力),但不知道它怎么做(内部逻辑、模型、记忆),这最大程度保护了隐私和知识产权。
2. 数据结构
- A2A设计了一套标准化的数据结构,确保不同"语言的Agent能够顺畅交流"
Agent Card(名片):包含身份、能力、API地址、认证方式的json文档。通信的时候会和给对端agent交换名片,对端通过它来判断该Agent是否适合当前任务,以及如何建立安全连接。skills是Agent向外界声明其具体能力的元数据结构,可以理解为Agent的技能清单或者功能模块。Task(任务):一个有状态的工作单元,拥有唯一ID和生命周期,管理复杂工作,不仅仅是一问一答,而是支持多轮对话、长时运行操作的完整流程管理。Message(消息):客户端和Agent之间的一轮对话,包含角色(用户/Agent)和内容。Part(内容块):消息和产物中的基本内容容器(如文本块、文件块、数据块)。也提供了多模态支持,让Agent可以灵活的交换文本、文件、JSON数据等多种格式内容。Artifact(产物):Agent工作产生的具体、有形的成果(如生成的文档、图片、结构化数据)。与普通消息不同,它是任务的最终交付物,可被结构化存储和检索。
- 在A2A响应中,对于简单的任务,Agent可以直接返回Message(立即回复);如果任务复杂,Agent会创建一个Task,返回任务ID,客户端随后通过该ID追踪进度或接收流式更新。
其他概念:
Context(上下文):通过contextId将多个相关的任务逻辑上分组,确保多轮交互中的记忆连贯性Transport(传输层):基于HTTP(S)协议,使用JSON-RPC 2.0作为载荷格式,确保与现有Web基础设施的完美兼容Security(安全):企业级标准。认证信息(如Token)通过HTTP Header传递,与业务消息分离,确保安全且不侵入协议本身
框架集成
- 一般我们会需要把
a2a与现有框架集成,例如go-restful框架,下面重点讲解下如何实现这个效果
首先我们选择https://github.com/trpc-group/trpc-a2a-go?tab=readme-ov-file作为a2a协议的框架,下面创建了一个a2a的server实例
go
package agents
import (
"fmt"
"trpc.group/trpc-go/trpc-a2a-go/server"
"trpc.group/trpc-go/trpc-a2a-go/taskmanager"
)
/**
* @Author clarenceLiu
* Data 2026/1/7 18:01
**/
func NewA2AServer(a *MainAgent) (*server.A2AServer, error) {
mtm, err := taskmanager.NewMemoryTaskManager(a)
if err != nil {
return nil, err
}
card := newAgentCard()
return server.NewA2AServer(card, mtm)
}
func stringPtr(s string) *string {
return &s
}
func boolPtr(b bool) *bool {
return &b
}
func newAgentCard() server.AgentCard {
// 企业级智能客服系统
agentCard := server.AgentCard{
Name: "智能客服助手-Pro",
Description: "基于大语言模型的企业级智能客服系统,支持多轮对话、工单自动处理、客户意图识别和情感分析,7×24小时为客户提供专业服务",
URL: "https://api.enterprise.com/v2/agent/customer-service",
Version: "2.1.4",
Provider: &server.AgentProvider{
Organization: "腾讯云智能",
URL: stringPtr("https://cloud.tencent.com/product/ti-platform"),
},
IconURL: stringPtr("https://cdn.enterprise.com/icons/customer-service-bot.png"),
DocumentationURL: stringPtr("https://docs.enterprise.com/agents/customer-service"),
Capabilities: server.AgentCapabilities{
Streaming: boolPtr(true),
PushNotifications: boolPtr(true),
StateTransitionHistory: boolPtr(true),
Extensions: []server.AgentExtension{
{
URI: "https://schemas.enterprise.com/extensions/crm-integration",
Required: boolPtr(true),
Description: stringPtr("CRM系统集成扩展"),
Params: map[string]interface{}{
"supported_crm": []string{"Salesforce", "HubSpot", "Zendesk"},
"sync_interval": 300,
},
},
{
URI: "https://schemas.enterprise.com/extensions/sentiment-analysis",
Required: boolPtr(false),
Description: stringPtr("情感分析扩展"),
Params: map[string]interface{}{
"languages": []string{"zh-CN", "en-US", "ja-JP"},
"accuracy": 0.92,
},
},
},
},
Skills: []server.AgentSkill{
{
ID: "ticket-classification",
Name: "工单智能分类",
Description: stringPtr("自动识别客户问题类型,分类到相应的处理队列"),
Tags: []string{"classification", "automation"},
Examples: []string{
"我的订单什么时候发货?",
"如何申请退换货?",
"支付失败了怎么办?",
},
InputModes: []string{"text", "voice"},
OutputModes: []string{"text", "json"},
},
{
ID: "order-inquiry",
Name: "订单查询助手",
Description: stringPtr("帮助客户查询订单状态、物流信息等"),
Tags: []string{"order", "tracking", "logistics"},
Examples: []string{
"查询订单TF2024123456的状态",
"我的包裹到哪里了?",
"什么时候能收到货?",
},
InputModes: []string{"text", "voice", "image"},
OutputModes: []string{"text", "rich-message"},
},
{
ID: "complaint-handling",
Name: "投诉处理专员",
Description: stringPtr("专业处理客户投诉,提供解决方案和补偿建议"),
Tags: []string{"complaint", "escalation", "compensation"},
Examples: []string{
"我对你们的服务很不满意",
"产品质量有问题,要求退款",
"客服态度太差了",
},
InputModes: []string{"text", "voice"},
OutputModes: []string{"text", "escalation-form"},
},
{
ID: "technical-support",
Name: "技术支持专家",
Description: stringPtr("提供产品使用指导和技术问题解答"),
Tags: []string{"technical", "troubleshooting", "guidance"},
Examples: []string{
"如何设置账户?",
"APP闪退了怎么办?",
"这个功能怎么用?",
},
InputModes: []string{"text", "voice", "screenshot"},
OutputModes: []string{"text", "step-by-step", "video-guide"},
},
},
DefaultInputModes: []string{"text", "voice"},
DefaultOutputModes: []string{"text", "rich-message"},
SecuritySchemes: map[string]server.SecurityScheme{
"api_key": {
Type: server.SecuritySchemeTypeAPIKey,
Description: stringPtr("API密钥认证"),
Name: stringPtr("X-API-Key"),
In: server.SecuritySchemeInHeader.Ptr(),
},
"bearer_token": {
Type: server.SecuritySchemeTypeHTTP,
Description: stringPtr("JWT Bearer令牌认证"),
Scheme: stringPtr("bearer"),
BearerFormat: stringPtr("JWT"),
},
"oauth2": {
Type: server.SecuritySchemeTypeOAuth2,
Description: stringPtr("OAuth2认证"),
Flows: &server.OAuthFlows{
ClientCredentials: &server.OAuthFlow{
TokenURL: "https://auth.enterprise.com/oauth/token",
Scopes: map[string]string{
"read": "读取权限",
"write": "写入权限",
"admin": "管理权限",
},
},
},
},
},
Security: []map[string][]string{
{"api_key": []string{}},
{"bearer_token": []string{"read", "write"}},
{"oauth2": []string{"read", "write", "admin"}},
},
PreferredTransport: stringPtr("http2"),
ProtocolVersion: stringPtr(server.ProtocolVersion),
AdditionalInterfaces: []server.AgentInterface{
{
URL: "wss://ws.enterprise.com/v2/agent/customer-service",
Transport: "websocket",
},
{
URL: "grpc://grpc.enterprise.com:50051",
Transport: "grpc",
},
},
SupportsAuthenticatedExtendedCard: boolPtr(true),
Signatures: []server.AgentCardSignature{
{
Protected: "eyJhbGciOiJSUzI1NiIsImtpZCI6ImsyNTYifQ",
Signature: "signature-base64-encoded",
},
},
}
return agentCard
}
- 类似下面这样,使用
server实例提供一个对外服务
go
type agentResource struct {
MainAgent *agents.MainAgent `inject:""`
A2AServer *server.A2AServer `inject:""`
}
func (a *agentResource) A2A(req *restful.Request, resp *restful.Response) {
a.A2AServer.Handler().ServeHTTP(resp.ResponseWriter, req.Request)
}
func (a *agentResource) A2AStream(req *restful.Request, resp *restful.Response) {
a.A2AServer.Handler().ServeHTTP(resp.ResponseWriter, req.Request)
}
- 流传输核心代码如下所示(基于eino框架)
go
func (a *JoyAgent) runWithStream(ctx context.Context, input *adk.AgentInput, options ...adk.AgentRunOption) *adk.AsyncIterator[*adk.AgentEvent] {
iterator, generator := adk.NewAsyncIteratorPair[*adk.AgentEvent]()
go func() {
var err error
defer func() {
var panicError error
utilruntime.RecoverFromPanic(&panicError)
if panicError != nil {
generator.Send(&adk.AgentEvent{
Err: panicError,
})
} else if err != nil {
generator.Send(&adk.AgentEvent{
Err: err,
})
}
generator.Close()
}()
a2aMessage := protocol.Message{
Role: protocol.MessageRoleUser,
}
ln := len(input.Messages)
for i := ln - 1; i >= 0; i-- {
message := input.Messages[i]
if message.Role != schema.User {
continue
}
a2aMessage.Parts = append(a2aMessage.Parts, protocol.NewTextPart(message.Content))
break
}
if len(a2aMessage.Parts) == 0 {
log.Logger.Error(fmt.Errorf("no messages provided"), "JoyAgent input has no messages",
"input", gconv.String(input))
}
events, err := a.a2aStreamClient.StreamMessage(ctx, protocol.SendMessageParams{
Message: a2aMessage,
Metadata: a.metadata(ctx),
})
if err != nil {
log.Logger.Error(err, "[JoyAgent] failed to stream message")
return
}
reader, writer := schema.Pipe[*schema.Message](5)
defer writer.Close()
generator.Send(&adk.AgentEvent{
AgentName: a.Name(ctx),
Output: &adk.AgentOutput{
MessageOutput: &adk.MessageVariant{
IsStreaming: true,
Role: schema.Assistant,
MessageStream: reader,
},
},
})
for event := range events {
task, ok := event.Result.(*protocol.Task)
if !ok {
log.Logger.Error(fmt.Errorf("type: %T", event.Result), "JoyAgent event is not task")
continue
}
for _, artifact := range task.Artifacts {
for _, part := range artifact.Parts {
switch v := part.(type) {
case *protocol.TextPart:
writer.Send(&schema.Message{
Role: schema.Assistant,
Content: v.Text,
}, nil)
default:
log.Logger.Error(fmt.Errorf("type: %T", part), "JoyAgent event is not TextPart")
continue
}
}
}
}
log.Logger.Info("[JoyAgent]", "finished", len(events))
}()
return iterator
}