A2A协议:小白程序员轻松入门AI Agent协作,收藏学习必备指南
A2A协议旨在解决AI Agent之间协作的难题,突破传统工具函数调用的限制。本文从Agent与Tool的根本差异出发,阐述了N²集成困局和五大设计原则。深入解析了三角色模型、Agent Card发现机制、Task状态机、Message/Part/Artifact等核心元素,并详细介绍了多轮交互的两级标识设计、三种通信机制以及企业级实践中的多租户路由、四层授权和可观测性方案。文章最后探讨了A2A与MCP的互补关系及生态现状,为小白程序员学习大模型Agent协作提供了全面且易于理解的指南。

1. 为什么需要A2A协议
1.1 Agent ≠ Tool:一个被忽视的根本差异
假设你有两个AI智能体,一个擅长数据分析,另一个擅长报告生成,你想让它们合作出一份市场调研报告。在今天的技术条件下,大概率会把其中一个拆成工具,塞到另一个的Function Calling里。
看起来搞定了。但你做的事情本质上是把一个有自主推理能力、能多轮协商、有状态记忆的智能体,降维成了一个无状态的函数调用。Agent的核心能力,自主判断、动态规划、交互式澄清,在这一刻全丢了。
这就是A2A协议要解决的根本问题。
Agent和Tool是两种本质不同的交互对象。Tool是原始件,有明确的结构化输入输出,无状态,执行单一功能。计算器算数,数据库查询,天气API返回温度,这些都是工具。而Agent是自主系统,能推理,能规划,能用多个工具,维护长时间交互状态,在多轮对话中协商、澄清、精炼。把Agent包装成Tool,就像把一个高级顾问降格成一个计算器。你可能得到答案,但失去了顾问的判断力。
1.2 N²集成困局
没有标准协议时,每对Agent协作都需要点对点自定义开发。3个Agent需要3对集成,10个Agent需要45对,100个Agent需要4950对。经典的N²问题,集成代码随Agent数量平方级增长。
拆开来看,这个N²困局带出六个连锁痛点:
Agent降维(Agent Exposure)。把Agent包装成工具塞进Function Calling,前面已经说过,这会丢掉Agent的核心能力。这是最本质的痛点,其他五个都是它的连锁反应。
点对点自定义开发(Custom Integrations)。每对Agent协作都要单独写适配代码,没有标准接口。3个Agent需要3对集成,10个Agent需要45对,100个Agent需要4950对。
创新减速。每新增一个Agent,都要为所有需要协作的已有Agent写集成代码。新Agent上线周期从天级拖到周级。
互操作性缺失。不同框架(LangGraph、CrewAI、ADK)构建的Agent,不同公司运营的Agent,各自说各自的"方言",形成Agent孤岛。
安全缺口。临时通信方案各搞各的,有的用自定义HTTP,有的用WebSocket,有的甚至用文件交换。没有统一的安全审计路径,企业不敢在关键业务中部署。
可扩展性陷阱。Agent数量和交互复杂度增长后,系统变成一团乱麻。哪条链路出了问题?哪个集成挂了?几乎无法追踪。
1.3 五大设计原则
A2A的设计哲学可以浓缩为一句话:用最小的协议复杂度,实现最大范围的Agent安全协作。五条原则支撑这个目标。
简单性。 不重新发明传输协议,复用HTTP、JSON-RPC 2.0、SSE。不自定义序列化格式,不发明新的安全模型。开发者用现有的Web工程经验就能上手,不需要学一套全新的技术栈。
企业就绪。 对齐标准Web实践,OAuth2/OIDC认证、TLS传输安全、OpenTelemetry分布式追踪。企业可以直接用现有的API网关、负载均衡、日志审计基础设施来部署A2A Agent。A2A不发明新的安全和运营标准,集成已有的企业基础设施。
异步优先。 真实Agent任务可能需要几分钟(生成长文档)、几小时(批量数据分析)甚至几天(人工审批流程)。A2A从第一天就为这个现实设计,原生支持长运行任务、SSE流式更新和Push Notification异步通知。
模态无关。 Agent之间的交流不限于纯文本。Part数据结构支持四种内容类型:text(纯文本)、raw(内联二进制,base64编码)、url(外部文件引用)、data(结构化JSON)。Agent可以交换图片、PDF、音频、结构化表单数据,多模态混发。
不透明执行。 Agent协作时只依赖声明的能力(Agent Card)和交换的信息,不暴露内部逻辑、记忆、工具列表。三重价值:保护知识产权(Agent的核心逻辑不泄露)、安全边界清晰(企业可以安全地让外部Agent访问)、解耦(Agent内部重构不影响协作方)。
1.4 Agent技术栈四层定位
理解A2A在AI技术栈中的位置,有助于区分它和其他协议/框架的职责边界:

从上到下:A2A层负责Agent之间的横向协作,MCP层负责Agent连接工具和数据源,框架层(ADK/LangGraph/CrewAI等)是开发Agent用的工具链,模型层是底层LLM推理引擎。四层各司其职,不交叉、不替代。
1.5 与MCP的定位区分
很多人第一次接触A2A时都会问:已经有了MCP(Model Context Protocol),为什么还需要A2A?
答案取决于交互对象的性质。MCP是纵向的,Agent连接工具和数据源,结构化I/O,无状态,主从关系。A2A是横向的,Agent与Agent对等协作,多轮对话,有状态,任务可能演变。
一个简单的判断标准:如果交互对象能被包装成一个函数签名(明确的输入参数类型和输出结果类型),用MCP;如果交互对象是一个有自主推理能力的Agent(需要协商、澄清、精炼、委派),用A2A。
二者不是竞争关系是互补关系。在未来的AI系统架构中,上层A2A实现Agent间协作,下层MCP实现Agent内工具集成,两个协议层不交叉。
1.6 A2A请求生命周期
把前面的发现、认证、交互串起来,一个完整的A2A请求生命周期是四步:
步骤1:发现(Discovery)。客户端GET https://{agent-domain}/.well-known/agent-card.json,获取Agent Card。Card里包含端点URL、能力声明(是否支持流式、推送)、认证方案(securitySchemes)。
步骤2:认证(Authentication)。客户端解析Agent Card的securitySchemes。如果是OAuth2/OIDC,客户端先向授权服务器请求token(走标准OAuth流程获取JWT)。如果是API Key,客户端从安全配置中取密钥。
步骤3:发送消息(SendMessage)。客户端用获取的凭证(放在HTTP Header中,不在JSON-RPC payload里),POST到Agent Card声明的端点URL,创建Task。服务端返回Task Response(状态SUBMITTED或WORKING)。
步骤4:流式更新(SendMessageStream)。如果Agent支持流式,客户端POST到stream端点,SSE长连接打开。服务端推送事件序列:Task(SUBMITTED) → StatusUpdate(WORKING) → ArtifactUpdate(产出内容) → StatusUpdate(COMPLETED) → 流关闭。
四步串起来的完整时序:

发现与认证分离是重要设计。Agent Card是公开的(可以被搜索引擎索引、被Registry缓存),但实际交互需要认证。Agent可以被发现但不可以被随意调用。认证方案由Agent Card动态声明,客户端不需要预知服务端的认证方式。
2. 三角色模型与核心元素
2.1 三角色模型

A2A定义了三个核心角色。
User(用户):人或自动化服务,发起请求,定义目标。
A2A Client / Client Agent(客户端代理):代表用户发起A2A通信的应用、服务或另一个Agent。负责发现远程Agent、获取认证凭证、发送消息、接收响应。
A2A Server / Remote Agent(远程代理):暴露HTTP端点实现A2A协议的AI Agent。对客户端来说是不透明的黑盒,内部运作机制、记忆、工具都不暴露。
这个模型的关键设计在于不透明性。远程Agent对客户端来说就是一个标准HTTP服务,所有交互通过Agent Card声明的能力和A2A协议消息完成。客户端不知道也不需要知道Agent内部用了什么LLM、什么记忆系统、什么工具。
2.2 Agent Card:数字名片
Agent Card是一个JSON元数据文档,是A2A发现和交互的基础。包含五类关键信息:
- 身份:name、description、version、provider
- 端点:supportedInterfaces[],每个含url、protocolBinding、protocolVersion
- 能力:capabilities(streaming/pushNotifications/extendedAgentCard)
- 认证要求:securitySchemes[],对齐OpenAPI的认证定义
- 技能列表:skills[],每个skill有id、name、description、tags、examples、inputModes(接受的输入模态,如text/image/audio)、outputModes(产出的输出模态)
Agent Card是声明式的。Agent声明自己能做什么,Client据此决定是否调用。不需要事先约定、硬编码或自定义文档。N个Agent只需要N个Agent Card,而不是N²个点对点集成文档。
Agent Card的security字段对齐OpenAPI Specification的认证定义,例如:
{
"securitySchemes": {
"oauth2": {
"type": "oauth2",
"flows": {
"authorizationCode": {
"authorizationUrl": "https://auth.example.com/authorize",
"tokenUrl": "https://auth.example.com/token",
"scopes": {
"read:tasks": "Read tasks",
"write:tasks": "Create and update tasks"
}
}
}
}
},
"security": [{"oauth2": ["read:tasks", "write:tasks"]}]
}
2.3 Task:有状态的工作单元
Task是A2A协议的核心工作单元,有唯一ID和完整的生命周期。不是一次性请求-响应,是一个可以被追踪、中断、恢复、取消的状态机。
Task状态机枚举:
SUBMITTED → WORKING → 终态: COMPLETED / FAILED / CANCELED / REJECTED
→ 中断态: INPUT_REQUIRED / AUTH_REQUIRED

- SUBMITTED:Task已提交,等待Agent处理
- WORKING:Agent正在处理
- COMPLETED:成功完成(终态)
- FAILED:执行失败(终态)
- CANCELED:被取消(终态)
- REJECTED:Agent拒绝执行(终态)
- INPUT_REQUIRED:需要用户提供更多信息(中断态)
- AUTH_REQUIRED:需要额外认证凭证(中断态)
终态意味着Task永久结束,不可重启。中断态意味着Task暂停,等待外部输入后继续。这个设计带来可靠引用、清晰工作单元和实现简单三大好处。终态Task永远不变,每个Task等于一次完整执行,开发者不需要处理"新建vs重启"的歧义。
2.4 Message:单轮通信
Message是Agent之间的单轮通信,结构简洁:
Message {
role: "ROLE_USER" | "ROLE_AGENT" // 谁发的
messageId: string // 唯一标识
parts: Part[] // 内容列表
}
一个Message可以包含多个Part,比如同时发文本+图片+结构化数据。role区分发送方,user(客户端)或agent(服务端)。Message是过程性通信,用于传达指令、上下文、问题、状态更新。
2.5 Part:统一内容容器
Part是A2A v1.0的一个重要设计决策。v0.3有三种独立类型(TextPart/FilePart/DataPart)加一个kind判别字段。v1.0统一为:
Part {
oneof content {
text: string // 纯文本
raw: bytes // 内联二进制(base64 in JSON)
url: string // 外部文件引用
data: Value // 结构化JSON
}
mediaType: string // MIME类型
filename: string // 可选文件名
metadata: map // 额外上下文
}
四种内容类型各有适用场景:
| 字段 | 类型 | 场景 | JSON示例 |
|---|---|---|---|
text | string | 对话文本、指令 | {"text": "Hello", "mediaType": "text/plain"} |
raw | bytes(base64) | 小文件内联传输 | {"raw": "aGVsbG8=", "mediaType": "image/png", "filename": "logo.png"} |
url | string(URI) | 大文件外部引用 | {"url": "https://...", "mediaType": "application/pdf"} |
data | JSON Value | 结构化数据 | {"data": {"price": 100, "currency": "USD"}, "mediaType": "application/json"} |
这个统一设计精妙在几个地方。Message和Artifact共用同一Part结构,一个Message可以多模态混发,metadata map提供无协议变更的扩展点。
还有几个容易被忽略的细节。mediaType对所有内容类型都可用,不仅标识文件的MIME类型,也标识text的字符编码(如text/plain; charset=utf-8)和data的结构格式(如application/json或application/x-protobuf)。filename也不只是文件专属,text内容可以有filename(比如导出的.txt文件),data内容也可以有filename(比如序列化的.json配置文件)。
raw vs url的选择是工程权衡。小文件用raw内联(base64编码后体积增加约33%,但减少一次HTTP请求),大文件用url引用(避免payload膨胀,但客户端需要额外下载)。经验值:64KB以下用raw,1MB以上用url,中间看场景。如果数据敏感不适合外部存储,即使大文件也可能选择raw内联。
2.6 Artifact:任务产出
Artifact {
artifactId: string // 唯一标识
name: string // 人类可读名称
parts: Part[] // 产出内容(可分块)
}
Artifact是Task的具体交付物。不是过程通信,是最终产出。一个Task可以产生多个Artifact(比如生成多张图片)。Artifact可以分块流式传输,通过append和lastChunk标记组装。
Message vs Artifact的边界:
- Agent回复"正在处理中,请稍候" → Message(过程性状态更新)
- Agent输出一份完整报告 → Artifact(结果性交付物)
- Agent请求用户澄清 → Message(交互性通信)
- Agent生成一张图片 → Artifact(具体产出)
这种分离让客户端可以区分"Agent在告诉我什么"和"Agent给了我什么"。
3. Agent发现机制
3.1 Well-Known URI:标准化公开发现
A2A推荐的第一种发现策略遵循RFC 8615的Well-Known URI约定。标准路径是:
https://{agent-server-domain}/.well-known/agent-card.json
客户端只需知道Agent的域名,就能通过这个标准化路径获取Agent Card。与OpenID Connect Discovery(/.well-known/openid-configuration)、WebFinger(/.well-known/host-meta)等现有Web标准同属一个机制家族。A2A的发现机制与现有Web生态完全融合,不需要新协议、新端口、新DNS记录。
实现上就是一个静态JSON文件。服务端在对应路径放好Agent Card JSON就行。适合开放或域内可控的发现场景。
3.2 Curated Registry:策展注册表
第二种策略是中央注册表服务。Agent发布自己的Card到Registry,客户端查询Registry按skills、tags、provider、capabilities过滤,发现合适的Agent。
比Well-Known URI更适合企业内部场景。大企业有数十个Agent,Registry提供目录服务+访问控制+治理。类似企业内的服务注册中心(Consul/etcd),但面向Agent。
Registry支持基于能力的发现。不按域名查,按"哪个Agent能做航班搜索"查。比Well-Known URI的"知道域名才能查"灵活。
但这里有当前最大的互操作缺口:A2A规范未定义注册表的标准API。每个Registry实现自己的查询接口,客户端需要适配多个Registry,跨Registry搜索不可能。社区已将此列入未来标准化方向。
用一个类比理解三种发现策略的关系:Well-Known URI类似DNS的A记录(域名→IP直接映射),你知域名就能查到Agent Card;Registry类似DNS的SRV记录(按服务类型查询),你按能力搜索而不是按域名;Direct Config类似/etc/hosts(本地硬编码),简单但不动态。
Registry的典型查询流程是四步:Agent发布自己的Card到Registry→客户端按skills/tags/provider/capabilities发起过滤查询→Registry返回匹配的Agent Card列表→客户端选择目标Agent直接连接。当前各Registry实现的查询接口互不兼容,是最大互操作缺口。
3.3 Direct Config:直接配置
第三种策略最简单也最封闭。通过硬编码、配置文件、环境变量或私有API获取Agent Card信息。适合已知、静态的Agent关系,比如开发环境、内部系统、安全要求极高的场景。缺点是不灵活,Agent Card变化需要客户端重新配置。
3.4 两层Agent Card:渐进式信息暴露
A2A设计了Public Agent Card和Extended Agent Card两层。
Public Agent Card:基本信息+能力+公开技能,通过GET /.well-known/agent-card.json无需认证获取。让Agent可以被发现,但不暴露敏感信息。
Extended Agent Card:敏感技能+详细参数+内部端点,通过GetExtendedAgentCard RPC方法获取,需要认证。让已认证的客户端获取完整能力信息。
这种渐进式信息暴露平衡了可发现性和安全性。公开卡让Agent可被发现,扩展卡保护敏感信息。
3.5 安全保护与HTTP缓存
Agent Card包含敏感信息(内部URL、技能描述可能暴露业务逻辑),A2A提出三大保护机制。
第一,不嵌入静态密钥。Agent Card可能被缓存或转发,静态密钥一旦嵌入就会随Card传播扩散。强烈推荐使用out-of-band动态凭证(OAuth token等运行时获取的凭证)。
第二,mTLS/网络限制/HTTP认证。对Agent Card端点实施访问控制。
第三,Registry选择性披露。根据客户端身份和权限返回不同的Agent Card。普通用户看到公开Agent,内部员工看到内部Agent。
Agent Card通常变化不频繁,适合HTTP缓存。服务端推荐返回Cache-Control: max-age=3600和ETag。客户端在缓存过期后用If-None-Match做条件请求,服务端未变化则返回304 Not Modified,不传body,节省带宽。如果服务端未返回缓存头,客户端应应用合理的默认缓存时长(如1小时),避免频繁请求Agent Card。Extended Agent Card的缓存绑定到认证会话(session-scoped)。
4. Task生命周期与多轮交互
4.1 contextId + taskId两级设计
这是理解A2A多轮交互的关键。A2A用两个标识符解耦了两个正交关注点。
contextId(服务端生成):对话级上下文,逻辑分组多个相关Task和Message。Agent内部用它管理LLM对话历史,知道"我们在聊什么"。
taskId:任务级边界,单次工作单元的生命周期。知道"这次具体做什么"。
工作流程是这样的:
第一次消息:
Client → Server: message(无contextId, 无taskId)
Server → Client: contextId="ctx-abc" + taskId="task-001"
第二次消息(续同一上下文,新建Task):
Client → Server: message + contextId="ctx-abc"(无taskId)
Server → Client: 新 taskId="task-002"(同上下文)
第三次消息(续特定Task):
Client → Server: message + contextId="ctx-abc" + taskId="task-002"
Server → Client: 更新 task-002 状态
三次消息的时序对照:

这个设计解决了一个关键问题:对话连续性和任务边界可以解耦。用户可以在同一对话中发起多个独立任务,每个任务有自己的生命周期,但共享对话上下文。同一contextId下可以有多个并行Task,通过referenceTaskIds表达依赖关系。
为什么用contextId而不是session ID?四维度对比:
| 维度 | session ID | contextId |
|---|---|---|
| 粒度 | 会话级(所有交互共享一个session) | 上下文级(逻辑分组多个Task) |
| 并行性 | 难以支持同会话多并行Task | 天然支持同contextId多并行Task |
| LLM管理 | 需额外机制关联session和LLM context | contextId直接对应LLM context |
| 任务边界 | 模糊(session内所有操作混在一起) | 清晰(每个Task是独立工作单元) |
session ID把所有交互混在一个桶里,contextId让对话上下文和任务边界正交分离。
4.2 三种Agent类型
A2A定义了三种Agent响应策略,从简单到复杂。
Message-only Agent:总是返回Message,直接回复,无状态管理。适合问答型Agent、简单翻译、文本摘要。用contextId关联消息序列。
Task-generating Agent:总是返回Task,即使简单交互也建模为"立即完成的Task"。适合需要统一审计跟踪的企业Agent、所有交互都需要Task ID引用的编排系统。限制是一旦Task创建后只返回Task对象,Task完成后不能再发消息,必须创建新Task。不够灵活,但对审计场景足够。
Hybrid Agent(推荐):先用Message协商(确认能力、澄清范围),再用Task执行。Task创建后只返回Task对象,Task完成后可以重新开始Message→Task流程。这是生产级Agent的推荐模式。需要能力协商、错误处理、状态管理。
4.3 Task不可变性与精炼
Task到达终态后不可重启。后续交互必须创建新Task,在同一contextId下。
用户经常需要基于已有Task结果进行后续操作。"生成一张帆船图片"完成后,“把帆船改成红色”。这时客户端发新消息,带相同的contextId,在referenceTaskIds中引用原始Task ID。Agent响应新Task。
referenceTaskIds是"提示"而非强制引用。Agent作为领域专家,有权决定如何处理引用。
Agent推断Artifact引用的优先级是:先从referenceTaskIds推断相关Artifact;如果没带referenceTaskIds或引用不明确,从contextId推断(整个对话上下文中的Artifact);如果仍有歧义,返回INPUT_REQUIRED请求澄清。
客户端也可以更精确地引用。在Part的metadata中填充artifactId和taskId,这是最精确的引用方式,消除了推断的模糊性。
这个设计哲学很重要:Agent的核心价值在于推理和判断,不是盲目执行。不确定时问而非猜。
4.4 并行后续与依赖链
同一contextId下可以创建多个并行Task:

每个Task有独立ID和生命周期,依赖关系通过referenceTaskIds表达,Agent可以并行处理。
4.5 完整JSON-RPC示例:帆船图片生成→精炼
Step 1: 客户端发送生成请求
{
"method": "SendMessage",
"params": {
"message": {
"role": "user",
"parts": [{"text": "Generate an image of a sailboat on the ocean."}],
"messageId": "msg-user-001"
}
}
}
无contextId(首次请求),无taskId(新建Task)。
Step 2: Agent返回完成的Task
{
"task": {
"id": "task-boat-gen-123",
"contextId": "ctx-conversation-abc",
"status": {"state": "TASK_STATE_COMPLETED"},
"artifacts": [{
"artifactId": "artifact-boat-v1-xyz",
"name": "sailboat_image.png",
"parts": [{
"filename": "sailboat_image.png",
"mediaType": "image/png",
"raw": "base64_encoded_png_data..."
}]
}]
}
}
生成contextId和taskId,Task直接到达COMPLETED终态,Artifact用raw内联base64传输图片。
Step 3: 客户端发送精炼请求
{
"method": "SendMessage",
"params": {
"message": {
"role": "user",
"messageId": "msg-user-002",
"contextId": "ctx-conversation-abc",
"referenceTaskIds": ["task-boat-gen-123"],
"parts": [{"text": "Please modify the sailboat to be red."}]
}
}
}
带相同contextId续上下文,referenceTaskIds提示Agent引用原Task,不带taskId意味着创建新Task(原Task不可重启)。
Step 4: Agent返回新Task(精炼结果)
{
"task": {
"id": "task-boat-color-456",
"contextId": "ctx-conversation-abc",
"status": {"state": "TASK_STATE_COMPLETED"},
"artifacts": [{
"artifactId": "artifact-boat-v2-red-pqr",
"name": "sailboat_image.png",
"parts": [{
"filename": "sailboat_image.png",
"mediaType": "image/png",
"raw": "base64_encoded_png_data_of_a_RED_sailboat"
}]
}]
}
}
新taskId(不可变原则),同一contextId(上下文连续),同一artifact-name(版本追踪约定),新artifactId(新产出)。客户端通过artifact-name一致性知道这是同一产物的精炼版本,维护自己的版本历史。
5. 消息与产物的区分
5.1 过程性通信 vs 结果性交付
A2A将Agent间的信息交换分为两类。这个区分看似简单但工程意义深远。
Message是过程性通信。发起请求、澄清歧义、报告状态、追加输入。它是对话过程中的一次往返,不一定每次都有产出。Agent回复"正在处理中,请稍候"是Message,请求用户澄清"你说的红色是哪种红"也是Message。
Artifact是结果性交付。Agent完成工作后给出的具体产出。一份报告、一张图片、一组结构化数据。Artifact有明确的artifactId和name,可以被引用、被精炼、被版本追踪。
5.2 Message不保证持久化
一个容易踩的坑:Message不保证持久化存储。在流式交互中,如果SSE连接意外断开,断连期间通过SSE推送的Message可能丢失。Task状态是持久的(有独立的生命周期,可以重新订阅),但Message的传输层可靠性依赖于SSE连接的连续性。
如果客户端需要确保不丢失Message,应该使用SubscribeToTask重新订阅获取断连期间的状态更新,或者通过GetTask主动拉取Task的当前完整状态。
5.3 Artifact版本追踪由客户端负责
A2A明确了一个设计偏好:服务端不负责Artifact版本链接,由客户端管理。
理由是客户端最清楚什么构成"可接受的结果"。客户端可以接受或拒绝新版本,版本链是客户端的编排逻辑,不是协议规范。服务端的协作约定是使用一致的artifact-name标记同一产物的新版本。
这体现了A2A的重要设计偏好:尽量让服务端无状态,把编排逻辑推到客户端。好处是服务端实现简单、可水平扩展、无状态依赖。
服务端管理 vs 客户端管理的对比:
| 维度 | 服务端管理版本 | 客户端管理版本(A2A选择) |
|---|---|---|
| 服务端复杂度 | 需维护版本链 | 只需保持artifact-name一致 |
| 灵活性 | 固定版本策略 | 客户端自定义接受/拒绝逻辑 |
| 服务端状态 | 有状态(存储版本链) | 无状态(每次新artifactId) |
| 协议开销 | 需额外版本链接字段 | 不需额外字段 |
6. 流式更新与异步通知
6.1 三种机制覆盖完整时长谱系
A2A为长运行任务设计了三种通信机制,覆盖从秒级到天级的完整谱系:

Request/Response是最基本的同步模式。客户端发请求,等服务端返回结果。适合秒级短任务。长任务可以用客户端轮询(定期调GetTask查状态)。
SSE Streaming覆盖分钟级场景。HTTP长连接保持打开,服务端实时推送事件。
Push Notification覆盖小时/天级场景。客户端提供webhook URL,服务端在Task状态变化时主动POST通知。
6.2 SSE Streaming详解
前提条件:Agent Card中capabilities.streaming: true。
流程:客户端调用SendStreamingMessage RPC方法,一个调用同时完成两件事,发送初始消息+订阅Task更新。服务端返回200 OK+Content-Type: text/event-stream,HTTP长连接保持打开,通过此连接推送事件。
三类SSE事件:
| 事件类型 | 用途 | 关键字段 |
|---|---|---|
Task | 当前工作状态 | id, contextId, status.state, artifacts[] |
TaskStatusUpdateEvent | 生命周期状态变化 | state, 中间消息 |
TaskArtifactUpdateEvent | 新增/更新Artifact | artifact, append, lastChunk |
整条SSE事件流的全貌:

TaskArtifactUpdateEvent的分块机制值得注意。append: true表示内容追加到已有Artifact(不替换),lastChunk: true表示这是最后一块,客户端可以组装完整Artifact。用于流式输出大文件。
流终止规则:终态(COMPLETED/FAILED/CANCELED/REJECTED)或中断态(INPUT_REQUIRED/AUTH_REQUIRED)触发流关闭。服务端发送终态/中断态事件后不再发送更多更新。
重新订阅:如果SSE连接意外断开(网络问题),Task仍在运行。客户端可以用SubscribeToTask RPC方法重新订阅,获取断连期间错过的状态更新。这个设计让Task的状态连续性不依赖于传输层的连续性。SSE连接是"脆弱"的,但Task是"持久"的。
一个完整的SSE事件序列示例:
Client → Server: POST /message:stream (SendStreamingMessage)
Server → Client: event: message
data: {"result": {"task": {"id": "task-001", "status": {"state": "SUBMITTED"}}}}
Server → Client: event: message
data: {"result": {"statusUpdate": {"state": "WORKING"}}}
Server → Client: event: message
data: {"result": {"artifactUpdate": {"artifact": {"parts": [{"text": "第1段..."}]}, "append": false, "lastChunk": false}}}
Server → Client: event: message
data: {"result": {"artifactUpdate": {"artifact": {"parts": [{"text": "第2段..."}]}, "append": true, "lastChunk": true}}}
Server → Client: event: message
data: {"result": {"statusUpdate": {"state": "COMPLETED"}}}
[流关闭]
注意append: false表示第一块(不追加,是起始),append: true, lastChunk: true表示追加最后一块。客户端把两块Part内容拼接得到完整Artifact。
6.3 Push Notification详解
前提条件:Agent Card中capabilities.pushNotifications: true。
两步流程:
Step 1: 客户端在SendMessage请求中携带PushNotificationConfig
(含webhook URL + token + 认证方案)
→ 服务端返回Task Response(SUBMITTED/WORKING)
... 客户端断连 ...
... Task继续运行 ...
... 小时/天后 ...
Step 2: 服务端HTTP POST到webhook URL(StreamResponse payload)
→ 客户端验证真实性
→ 客户端调用GetTask(taskId)拉取完整Task+Artifacts
两步流程的完整时序:

为什么不是一步到位?Push Notification只传状态变化,不传完整Artifact。因为Artifact可能很大(文件/图片),webhook payload不宜过大;客户端可能需要决定是否拉取(比如CANCELED状态不需要拉取Artifact);解耦通知和内容获取让架构更灵活。
PushNotificationConfig的内容:
PushNotificationConfig {
url: string // HTTPS webhook URL
token: string // 客户端验证token(可选)
authentication: { // Server→webhook的认证方案(可选)
scheme: "Bearer" | "apikey" | "hmac" | "mtls"
}
}
通知触发时机由A2A Server决定,典型场景是终态或中断态。不用于中间状态更新,那是SSE的职责。可以为已存在的Task单独配置push notification(CreateTaskPushNotificationConfig RPC),支持一个Task配多个webhook。
6.4 SSE vs Push Notification对比
| 维度 | SSE Streaming | Push Notification |
|---|---|---|
| 连接模型 | 客户端保持长连接 | 服务端主动POST到webhook |
| 方向 | 服务端→客户端(单向推送) | 服务端→客户端webhook(主动外发) |
| 实时性 | 毫秒级延迟 | 秒级延迟 |
| 适用时长 | 秒级到分钟级 | 分钟级到天级 |
| 客户端状态 | 在线(保持连接) | 离线(断连后通知) |
| 事件粒度 | 所有状态变化(含中间态) | 显著状态变化(终态/中断态) |
| 内容 | 完整事件流 | 轻量通知,需GetTask获取完整数据 |
| 可靠性 | 断连后SubscribeToTask重新订阅 | webhook重试机制 |
| Agent Card声明 | capabilities.streaming: true | capabilities.pushNotifications: true |
| RPC方法 | SendStreamingMessage | CreateTaskPushNotificationConfig |
| 典型场景 | 实时查看生成进度 | 离线等待长任务完成通知 |
| 安全模型 | 连接级(TLS+认证) | 消息级(JWT/HMAC签名+防重放) |
二者可以组合使用。正常用SSE实时收更新,断连时用Push Notification兜底。
6.5 Push Notification的客户端架构
有个容易忽略的工程问题:Push Notification的接收端不一定是客户端应用本身。客户端App可能离线(手机休眠)、可能被系统回收(Serverless函数已销毁)、可能在NAT后面无法直接接收。所以生产环境中通常需要一个常驻的Push Notification Service来接收webhook回调。
这个服务的职责是:接收A2A Server的HTTP POST通知、验证真实性(JWT签名/HMAC/防重放)、验证相关性(确认这个通知属于某个活跃Task)、转发给真正的客户端应用。
Push Notification发送的payload是StreamResponse格式,和SSE事件用同一套数据结构。包含四种事件类型:task(完整Task对象)、message(中间消息)、statusUpdate(状态变化)、artifactUpdate(Artifact更新)。统一格式的好处是客户端只需要一套事件处理逻辑,不管数据来自SSE还是Push。
6.6 Push Notification安全
Push Notification的安全比SSE复杂得多。因为是服务端主动向外发起HTTP请求,引入了SSRF、重放攻击、身份伪造等风险。
SSRF防护:恶意客户端提供内网URL作为webhook,让A2A Server去访问内部服务。缓解策略包括域名白名单(allowlisting trusted domains)、所有权验证(challenge-response)、网络控制(egress firewall阻止内网访问)。
身份认证:A2A Server必须认证自己到webhook。根据PushNotificationConfig.authentication指定的方案,Bearer Token、API Key、HMAC签名或mTLS。
JWT+JWKS完整认证流程:
-
A2A Server用私钥签名生成JWT,claims包含iss(发行者)、aud(受众)、iat(签发时间)、exp(过期)、jti(唯一ID)、taskId
-
A2A Server通过JWKS endpoint暴露公钥
-
Client Webhook从Authorization header提取JWT,从JWKS endpoint获取对应公钥(缓存推荐),验证JWT签名
-
验证所有claims:iss匹配、aud匹配、exp未过期、jti未重复(防重放)
密钥轮换:生成新密钥对→JWKS同时暴露新旧公钥→新JWT用新密钥签名→等待旧JWT过期→移除旧公钥→销毁旧私钥。
防重放攻击是另一条关键防线。JWT中必须包含iat(签发时间)和jti(唯一ID),webhook端检查iat是否在合理时间窗口内(比如5分钟),拒绝过期通知;维护已见jti列表(或用Redis等缓存),拒绝重复通知。这两步确保即使攻击者截获了合法通知,也无法重放。
7. 多租户与多Agent路由
7.1 问题背景
企业内部有多个Agent,但只暴露一个域名:
agents.example.com ← 单一端点
├── Billing Agent (账单)
├── Support Agent (客服)
├── HR Agent (人事)
└── Finance Agent (财务)
A2A不规定具体路由实现,但提供了三种互补机制。
7.2 URL-Based Routing(子路径路由)
每个Agent有独立URL前缀,Agent Card中声明各自URL。网关/反向代理根据URL路径转发。
- Billing Agent:
https://agents.example.com/billing - Support Agent:
https://agents.example.com/support
最简单,标准HTTP路由,客户端零额外逻辑。缺点是每个Agent需要独立URL,大规模部署时URL空间膨胀,Agent新增/移除需要网关配置变更。
7.3 Auth Header-Based Routing(认证头路由)
多个Agent共享同一URL,网关通过认证凭证中的claims路由。Bearer Token的aud(audience)claim或scope claim可以做路由信号,API Key通过key→agent映射表路由。
关键特征是无协议侵入。A2A消息本身不变,路由完全在网关层通过HTTP Header完成。复用已有认证基础设施,支持动态路由(改token claims即可)。缺点是依赖JWT/API Key,不适用于无认证场景。
7.4 Body-Based Routing(tenant字段路由)
这是v1.0新增的协议级路由支持。A2A请求消息中的可选tenant字段,opaque string,协议不规定格式或语义,由Server运营商定义。
Agent Card声明:
{
"supportedInterfaces": [
{
"url": "https://agents.example.com/a2a",
"protocolBinding": "HTTP+JSON",
"protocolVersion": "1.0",
"tenant": "billing"
}
]
}
客户端规则是严格的MUST:AgentInterface声明了tenant → 客户端必须在每个请求中回显该值;AgentInterface未设置tenant → 客户端必须省略该字段。
tenant字段的opaque设计让它适用于任何路由场景。可以是billing(Agent标识)、org-abc123(组织ID)、workspace-marketing(工作空间slug)、region-eu-west-1(区域标识)。
7.5 组合使用
三种方式不互斥。典型组合:

URL做粗粒度路由(产品线/部门),Auth Header做安全相关路由(基于身份的访问控制),tenant字段做细粒度路由(客户/组织/工作空间)。这种分层路由与企业的组织架构天然对应。
四种组合模式:
| 组合 | 场景 |
|---|---|
| URL + Body | 按产品线分URL,同产品线内按客户分tenant |
| URL + Auth | 按产品线分URL,同产品线内按用户角色分权限 |
| Auth + Body | 共享URL,用Auth做身份路由,tenant做组织级路由 |
| 三者组合 | 大型企业:URL分产品线+Auth分权限+tenant分客户/组织 |
实际部署中,最常见的是URL+Auth组合(API网关标准能力),tenant字段作为补充细粒度路由。
Agent Card是路由信息的单一来源。无论哪种路由方式,路由信息都来自Agent Card的supportedInterfaces。客户端不需要从多个来源拼凑路由信息。
8. 企业级安全与可观测性
8.1 传输安全
三条硬性规则:所有生产环境通信必须HTTPS,TLS 1.2+加强密码套件,客户端验证服务端TLS证书。SSE长连接和Push Notification webhook都依赖TLS保护。A2A选择TLS 1.2+而非1.3+作为最低要求,因为TLS 1.2仍被企业旧基础设施广泛支持。"1.2+"允许1.3但不强制,渐进兼容。
8.2 认证与协议载荷分离
这是A2A最重要的安全设计决策之一。
A2A协议层(JSON-RPC payload)→ 不含身份信息
HTTP传输层(Header) → 承载认证凭证
身份信息不在JSON-RPC payload中,而在HTTP Header中。这让安全方案可替换(OAuth→API Key→mTLS,不改协议),API网关可直接处理认证(不解析JSON-RPC),业务日志不暴露凭证(Header不进入payload日志)。
401 vs 403的精确语义:401表示"你是谁?“(凭证缺失或无效,应含WWW-Authenticate header告知支持的认证方式),403表示"我知道你是谁,但你不能做这个”(认证通过但授权失败)。
8.3 In-Task二次认证
最精妙的安全机制。任务执行中需要额外凭证时,通过AUTH_REQUIRED中断态暂停Task。
场景:用户让Agent代为操作CRM系统,Agent需要用户的CRM token。Agent不预先获取所有可能的凭证,而是在需要时通过AUTH_REQUIRED中断Task,客户端通过OAuth流程获取CRM token(A2A协议之外),再继续Task。
这比预先传递所有凭证安全得多。减少凭证暴露面,按需授权,用户知情同意,体现最小权限原则。
8.4 四层授权模型

Agent Card声明的skills[]不仅用于发现,也用于授权边界定义。OAuth scope直接映射Agent Card的skills。举个例子,Agent Card声明了三个技能:
{
"skills": [
{"id": "flight-search", "name": "Flight Search"},
{"id": "hotel-book", "name": "Hotel Booking"},
{"id": "payment", "name": "Process Payment"}
]
}
对应的OAuth scope就是skill:flight(可用航班搜索)、skill:hotel(可用酒店预订)、skill:payment(高敏感,需额外审批)。客户端请求时只携带自己有权限的scope,Agent在收到请求后先验证scope是否覆盖请求的skill。即使客户端有skill级权限,Agent还在数据层验证具体操作的合法性。Agent不是简单的代理,它有授权逻辑。
8.5 数据隐私
四大要求:敏感性感知(Message和Artifact的Part可能含PII/财务/医疗数据)、合规(GDPR/CCPA/HIPAA)、数据最小化(不在A2A交换中包括不必要的敏感信息)、安全处理(传输用TLS,持久化按企业安全策略)。
Data Minimization与Part设计的关联:能用url引用就不内联raw(减少传输的敏感数据量),data结构化数据中只包括必要字段,text中不包括不必要的PII。
8.6 OpenTelemetry分布式追踪
A2A的HTTP基础让可观测性可以直接复用现有企业工具。使用OpenTelemetry,通过W3C Trace Context headers传播trace context。trace ID+span ID贯穿整个调用链。Client → A2A Server A → A2A Server B → MCP Tool,端到端可见。
在多Agent协作场景中(比如客户→店长Agent→技工Agent→零件供应商Agent),分布式追踪让整个链路可见。哪个环节慢、哪个环节出错。
日志关联通过taskId和contextId实现结构化日志查询。审计重大事件:Task创建、关键状态变化(COMPLETED/FAILED)、Agent执行涉及敏感数据或高影响的操作。Task不可变性让审计更可靠。审计日志中的Task状态是终态的,不会变化。
8.7 API网关治理
对于跨组织边界的A2A Server,API网关是标配:

API网关让A2A Server专注业务逻辑,安全/治理/路由交给网关处理。这是企业级A2A部署的标准架构。
9. 协议绑定与版本协商
9.1 三层架构
A2A协议采用三层架构组织。
Layer 1 — Data Model:Task、Message、AgentCard、Part、Artifact、Extension。定义核心数据结构。
Layer 2 — Abstract Operations:9个绑定无关的抽象操作。Send、Streaming、Get、List、Cancel、Subscribe、Push Config等。上层依赖下层。
Layer 3 — Protocol Bindings:JSON-RPC、gRPC、HTTP+REST以及自定义绑定。提供具体的线路级实现。
9.2 a2a.proto:唯一权威源
spec/a2a.proto是协议的唯一权威(normative)定义。a2a.json(JSON Schema 2020-12)是从proto自动生成的非权威构建产物。SDK和schema必须从proto生成,手工编辑是被禁止的。重命名的旧字段名标记为deprecated,只会在下一个大版本中移除。
这个设计确保了三种协议绑定的数据模型一致性。都从同一个proto定义生成。
9.3 三种协议绑定
| 绑定 | 传输 | 编码 | 流式 |
|---|---|---|---|
| JSON-RPC | HTTP POST | JSON | SSE |
| gRPC | HTTP/2 | Protobuf | server streaming |
| HTTP+REST | HTTP methods | JSON | SSE |
三种绑定功能等价,必须产生相同的行为。JSON-RPC是主要交互方式,gRPC适合高性能场景,HTTP+REST在v1.0中移除了/v1前缀简化URL。
9.4 版本协商
版本协商通过A2A-Version HTTP header(或query parameter)进行。只考虑Major.Minor,patch版本不影响协商。空值/缺失默认为0.3(向后兼容)。服务端不支持请求的版本时返回VersionNotSupportedError。
同一Agent可以从相同或不同URL服务多个版本。每个AgentInterface独立声明protocolVersion,SDK支持多版本兼容。新增协议绑定不改变数据模型。绑定是传输层的变化,数据结构由proto定义保持不变。
10. 扩展机制与治理
10.1 扩展机制

A2A通过URI识别扩展,在Agent Card的AgentCapabilities字段中声明,通过A2A-Extensions HTTP header激活。默认不激活,客户端必须显式opt-in。上图的流程:客户端先从Agent Card读取声明的扩展(步骤1),在请求头中声明所需扩展(步骤2),服务端检查并激活对应插件处理(步骤3),返回扩展数据(步骤4)。
四种扩展类型覆盖不同的扩展需求:
-
数据型扩展:向协议添加新的数据字段/类型
-
Profile型扩展:定义profile(约束子集或专用配置)
-
方法型扩展:扩展skills/RPC方法,添加核心9个操作之外的新操作
-
状态机型扩展:修改或扩展Task状态机(比如新状态或转换)
核心约束:扩展不能修改核心数据结构定义或枚举值。自定义数据必须放入metadata字段。这保证了核心协议的稳定性。扩展是"附加"而非"修改"。
10.2 两级治理
实验级:仓库前缀experimental-ext-{name},URI前缀experimental-。创建需要A2A Maintainer赞助。README必须声明非官方状态。TSC保留归档/移除的权利。
官方级:仓库前缀ext-{name},URI前缀https://a2a-protocol.org/extensions/。规范必须使用RFC 2119语言(MUST/SHOULD/MAY)。Apache 2.0许可证。至少一个参考实现。
URI是标识符而非可访问URL。HTTP访问不被期望。这避免了扩展URI成为单点故障。
10.3 从实验到毕业
实验扩展升级为官方扩展需要:生产级实现、完整文档、采用证据、维护承诺、TSC投票(50%法定人数,简单多数通过)。
成为官方后,破坏性变更需要新URI标识符+TSC审查。某些扩展可能通过标准规范变更流程被提升为核心协议,但不是所有扩展都适合这个路径。
已有扩展示例:Secure Passport(安全身份传递)、Timestamp(时间戳)、Traceability(追踪)、AGP(Agent Gateway Protocol)。
11. A2A与MCP的关系
11.1 Agent ≠ Tool:哲学基础
A2A与MCP并存的论证链很清晰。
前提一:Agent有自主推理、多轮协商、有状态交互的能力。前提二:工具是无状态、结构化I/O的原始件。前提三:把Agent包装成工具会限缩其核心能力。结论:需要两个协议,MCP管工具,A2A管Agent协作。
这不是技术细节的差异,是交互对象本质的差异。
11.2 纵向 vs 横向

MCP是纵向的。Agent连接工具和数据,主从关系,结构化I/O,无状态。A2A是横向的。Agent与Agent对等协作,多轮对话,有状态,任务可能演变。
11.3 不透明性是分界线
MCP:工具的schema(输入/输出结构)对调用方完全公开。工具需要schema公开才能被正确调用。
A2A:Agent的内部逻辑/记忆/工具完全不可见。Agent需要不透明才能保护IP、维持安全边界、允许独立演进。
这个差异不是偶然的,是设计意图。不透明性是A2A和MCP的分界线。
11.4 同一Agent的多重身份

一个Agent可以同时是A2A Server/Client + MCP Client。中间的虚线是组织或技术边界,左侧用Google ADK+Vertex AI构建,右侧用任意框架+LLM,两侧通过A2A协议对等通信,各自内部通过MCP连接API和企业应用。以汽车修理店场景为例。
参与方:Customer的助手Agent(A2A Client)、Shop Manager Agent(A2A Server+Client)、Mechanic Agent(A2A Server+Client+MCP Client)、Parts Supplier Agent(A2A Server),以及Vehicle Diagnostic Scanner、Repair Manual Database、Platform Lift三个MCP Tool。
交互流程:
-
Customer → Shop Manager (A2A):多轮对话诊断——“能发个噪音的视频吗?”“我看到有液体泄漏,这情况多久了?”
-
Shop Manager → Mechanic (A2A):委派诊断维修任务
-
Mechanic → 诊断工具们 (MCP):
scan_vehicle_for_error_codes(vehicle_id='XYZ123')、get_repair_procedure(error_code='P0300')、raise_platform(height_meters=2) -
Mechanic → Parts Supplier (A2A):跨Agent订货——“有Toyota Camry 2018的零件#12345吗?”
这个场景揭示了几个关键点。同一个Agent可以同时是A2A Server和Client(Mechanic既接收又发起A2A请求)。同一个Agent可以同时用A2A和MCP(Mechanic用A2A与其他Agent通信,用MCP调用自己的工具)。MCP调用对A2A通信方不可见(Customer不知道Mechanic用了什么工具)。
11.5 判断标准
什么时候用A2A,什么时候用MCP?
- 能包装成函数签名(明确的输入参数类型和输出结果类型)→ MCP
- 不能包装成函数签名(需要多轮协商、状态管理、Artifact交付)→ A2A
- 灰色地带(简单skill可无状态调用)→ 可以暴露为MCP工具,但复杂交互必须用A2A
A2A = partnering on tasks(任务协作),MCP = using capabilities(使用能力)。
12. 生态现状与路线图
12.1 SDK与语言支持
A2A提供6种语言的官方SDK:
- Python:
https://github.com/a2aproject/a2a-python - Go: https://github.com/a2aproject/a2a-go
- JavaScript:
https://github.com/a2aproject/a2a-js - Java: https://github.com/a2aproject/a2a-java
- .NET: https://github.com/a2aproject/a2a-dotnet
- Rust: https://github.com/a2aproject/a2a-rs
12.2 合作伙伴
A2A由Google贡献给Linux Foundation托管。技术指导委员会(TSC)包含AWS、Cisco、Google、IBM Research、Microsoft、Salesforce、SAP、ServiceNow 8家公司代表。已有150+合作伙伴参与生态。
这种治理结构确保了A2A不被单一公司控制。8家TSC成员代表不同厂商利益,扩展需要TSC投票通过,采用Apache 2.0许可证。
A2A的价值在多Agent跨组织协作时才真正体现。对于单Agent开发、个人项目、小团队场景,直接函数调用或MCP可能更务实。但当你的Agent需要与外部Agent协作,不同框架、不同公司、不同基础设施,A2A提供的标准化发现、认证、状态管理、流式通信和扩展机制,是当前最完整的Agent间通信协议方案。
理解A2A的设计思想,Task生命周期、Agent Card发现机制、不透明执行、扩展协商、多绑定架构,对构建任何形式的分布式Agent系统都有参考价值。即使你不打算全面采用A2A协议,这些设计模式和工程决策也值得内化为自己的架构思考。
最后
2026 年一晃已经过半,AI 大模型的热潮不仅没有降温,反而持续升温!
金融行业用大模型做风控、医疗依靠 AI 解析影像,电商、制造、教育各行各业,都在把 AI 融入日常业务。曾经热闹的 “百模大战”,早就告别单纯比拼模型参数,正式进入落地应用时代。
现在企业疯狂紧缺一类人才:懂业务、懂 AI、能做出可上线项目的大模型开发工程师,岗位缺口大,薪资待遇十分可观。

风口再好,不如手握高薪 offer 实在。行情火热,普通人、程序员该怎样从零入门大模型,抓住这波机会?
今天整理好【2026 最新版】AI 大模型全套免费学习资源,覆盖零基础入门、项目实战、理论知识、大厂面试,从基础一路进阶。所有资料分类归档,没有多余杂料,无套路免费分享给想要入局 AI 赛道的程序员与零基础小白!
👇👇扫码免费领取全部内容👇👇

1、大模型系统化完整学习路线

2、大模型经典书籍&文档

3、AI 大模型最新行业研究报告

4、企业级实战项目 + 完整配套源码

5、大厂大模型面试真题汇总

6、这些资料真的有用吗?
这份资料由我和鲁为民博士(北京清华大学学士和美国加州理工学院博士)共同整理,现任上海殷泊信息科技CEO,其创立的MoPaaS云平台获Forrester全球’强劲表现者’认证,服务航天科工、国家电网等1000+企业,以第一作者在IEEE Transactions发表论文50+篇,获NASA JPL火星探测系统强化学习专利等35项中美专利。本套AI大模型课程由清华大学-加州理工双料博士、吴文俊人工智能奖得主鲁为民教授领衔研发。
资料内容涵盖了从入门到进阶的各类视频教程和实战项目,无论你是小白还是有些技术基础的技术人员,这份资料都绝对能帮助你提升薪资待遇,转行大模型岗位。


这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】

更多推荐



所有评论(0)