# 第20篇-Agent-To-Agent协议

在开始之前我们需要了解一些基本概念

### 什么是 A2A？

A2A（Agent-to-Agent）是 Google 在 2024 年将 A2A 明确提出为“Agent 间通信的协议级抽象”。用于实现 AI 智能体之间的无缝通信与协作。它为由不同厂商、使用不同框架构建的智能体提供了一种通用语言，从而促进互操作性并打破孤岛。

智能体是能够在其环境中自主行动、独立解决问题的系统。A2A 允许来自不同开发者、基于不同框架、由不同组织拥有的智能体联合起来协同工作。

需要注意的是，A2A 本身并不是一个智能体开发框架，而是一种用于智能体之间通信与协作的协议标准。它关注的是智能体如何被发现、如何进行交互，以及如何在保持自治性的前提下协同工作。

### 为什么要使用 A2A 协议？

A2A 解决了 AI 智能体协作中的关键挑战，提供了一种标准化的交互方式。对于需要构建多智能体系统、跨团队或跨组织协作的 AI 应用而言，A2A 提供了一条可复用、可扩展的技术路径。

#### A2A 解决的问题

设想一个用户向 AI 助手提出请求："规划一次国际旅行"

这个任务通常需要协调多个专业化智能体，例如：

* 航班预订智能体
* 酒店预订智能体
* 本地旅游推荐智能体
* 货币换算智能体

在没有 A2A 的情况下，整合这些不同的智能体会面临多种挑战：

* 智能体暴露（Agent Exposure）\
  开发者通常会将智能体封装成“工具”，以便让其他智能体调用，类似于在多智能体控制平台（如 MCP，模型上下文协议）中暴露工具。但这种方式效率低下，因为智能体本身被设计为可以进行协商、规划和多轮对话。将智能体包装为工具会限制其能力，而 A2A 允许智能体以其原本形态对外暴露，无需进行这种简化封装。
* 定制集成（Custom Integrations）\
  每一次智能体之间的交互都需要定制的点对点集成方案，带来巨大的工程成本。
* 创新缓慢（Slow Innovation）\
  为每一种新集成进行定制开发，会显著拖慢系统演进和功能扩展的速度。
* 可扩展性问题（Scalability Issues）\
  随着智能体数量和交互复杂度的增长，系统会变得难以扩展和维护。
* 互操作性不足（Interoperability）\
  缺乏统一标准会限制不同生态中的智能体协作，阻碍复杂 AI 生态系统的自然形成。
* 安全缺口（Security Gaps）\
  临时拼凑的通信方式通常缺乏一致的认证、授权和安全控制机制。

A2A 协议通过建立一种可靠、安全且标准化的智能体互操作机制，将原本分散、定制化的协作问题，收敛为协议层面的统一能力，从而解决了上述问题。

### Function Calling、MCP 与 A2A 的层级区分

#### Function Calling 是模型的一种能力

**Function Calling 是模型的一种能力（Model Capability）**。

更严谨地说，Function Calling：

* 是 **LLM 的输出约束与推理能力**
* 决定模型是否、以及何时 **“决定要不要调用函数”**
* **不是协议**
* **不定义通信方式**
* **不负责工具发现、鉴权或生命周期管理**

因此，Function Calling 属于**模型层（Model Layer）能力**。

***

#### MCP / A2A 的区分

以下区分是**标准且重要的认知**：

* **MCP**：模型和工具之间的一种协议
* **A2A**：智能体和智能体之间的一种协议

为避免混淆，可按层级进行对齐：

| 层级     | 名称               | 本质        |
| ------ | ---------------- | --------- |
| 模型层    | Function Calling | 模型的一种能力   |
| 工具协议层  | MCP              | 模型 ↔ 工具   |
| 智能体协议层 | A2A              | 智能体 ↔ 智能体 |

***

#### 一句话总结（速记版）

* **Function Calling** 解决「模型会不会用工具」
* **MCP** 解决「模型怎么用工具」
* **A2A** 解决「智能体怎么和智能体协作」

### agent framework中使用A2A协议

#### 创建 Agent2Agent.Server（Server 端）

在开始之前我们需要两个 Agent，分别是 `Agent2Agent.Server` 和 `Agent2Agent.Client`，我们先定义我们的 Server\
这里需要注意一下：在 agent framework 的 `Microsoft.Agents.AI.OpenAI` 包 1.0.0-preview\.260205.1 及之后的版本中，使用 `AsAIAgent` 来定义 Agent，官方有变化，之前使用的是 `CreateAIAgent` 方法。这里稍微注意一下。

```csharp
Console.InputEncoding = Encoding.UTF8;
Console.OutputEncoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false);

var endpoint =
        Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")
        ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");

var deploymentName =
        Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME")
        ?? "gpt-4o-mini";

// ================================
// 1.创建本地 AI Agent
// ================================
AIAgent agent =
        new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
                .GetChatClient(deploymentName)
                .AsAIAgent(
                        name: "Assistant",
                        instructions: """
                                你是一个通过 A2A 协议对外提供服务的 AI Agent。
                                你需要清晰、简洁地回答问题。
                                """
                );
```

创建完之后，我们需要创建一个 ASP.NET Core Host 来做监听请求。

```csharp
// ================================
// 2️.创建 ASP.NET Core Host
// ================================
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
```

接下来我们定义一个AgentCard

**什么是 AgentCard？**

AgentCard 是 A2A 协议中用于描述一个智能体对外能力的自描述文档

```csharp
// ================================
// 3️.定义 AgentCard
// ================================
AgentCard card = new AgentCard
{
        Name = "Assistant",
        Description = "一个通过 A2A 协议提供问答能力的 AI 助手",
        Version = "1.0.0",
        Url = "http://localhost:5000",

        DefaultInputModes = ["text"],
        DefaultOutputModes = ["text"],

        Capabilities = new AgentCapabilities
        {
                Streaming = false,
                PushNotifications = false
        },

        Skills =
        [
                new AgentSkill
                {
                        Id = "general_chat",
                        Name = "通用问答",
                        Description = "回答用户提出的自然语言问题",
                        Tags = ["chat", "qa", "general"],
                        Examples =
                        [
                                "什么是 A2A 协议？",
                                "请用一句话解释 Azure OpenAI",
                                "帮我写一个 C# Hello World"
                        ]
                }
        ]
};    
```

最后我们需要将 Agent 和 Host 进行绑定，并启动 Host，我们可用通过 `http://localhost:5000/.well-known/agent-card.json` 来查看 AgentCard 的元数据。

```csharp
// ================================
// 4️.绑定 Agent 并启动 Host
// ================================
app.MapA2A(
        agent,
        path: "/",                        // Agent 执行入口
        agentCard: card,
        taskManager =>
        {
                // .well-known/agent-card.json
                app.MapWellKnownAgentCard(taskManager, "/");
        }
);

// ================================
// 5️.启动
// ================================
await app.RunAsync();
```

### 创建 Agent2Agent.Client（Client 端）

这样整个 Server 端就完成了，接下来我们来看 **Agent2Agent.Client** 端的实现。

***

#### 1. 创建 AgentCardResolver（发现器）

首先需要创建 AgentCardResolver（发现器）。那这行代码具体在做什么？

#### 它的作用是：

> **创建一个“Agent 发现器”，并指向一个 A2A Server 的地址。**

你可以把 `A2ACardResolver` 理解为：

* A2A 世界里的 **Service Discovery Client**
* 或者：**“去某个地址要 Agent 名片的人”**

它在内部会完成的事情大致包括：

* 访问 A2A 协议约定的 *well-known endpoint*
* 获取该 Agent Server 对外暴露的 **AgentCard**

```csharp
A2ACardResolver agentCardResolver =
    new A2ACardResolver(new Uri("http://localhost:5000"));
```

### 2. 获取 AgentCard（能力发现）

```csharp
AgentCard agentCard = await agentCardResolver.GetAgentCardAsync();
```

这行代码在干什么？向远程 A2A Server 请求它的 AgentCard（智能体名片）。

这一步对应 A2A 协议中的：Agent Discovery（智能体发现）阶段。

通过这一步，Client 端可以了解：

* 这个 Agent 是谁
* 它对外声明了哪些能力（Skills）
* 它支持什么样的交互方式

此时，Client 仍然没有调用 Agent，只是完成了能力发现。

### 3. AsAIAgent（把远程 Agent 包装成“本地代理”）

```csharp
AIAgent a2aAgent = agentCard.AsAIAgent();
```

这行代码并没有创建一个真正运行在本地的 Agent 实例。\
它真正做的是：根据 AgentCard，生成一个“远程 Agent 的本地代理（Proxy）”。

也就是说：

* `a2aAgent` 看起来像一个本地 `AIAgent`，
* 但实际上它只是 A2A Client 侧的代理对象，
* 后续对 `a2aAgent` 的调用，都会通过 A2A 协议转发到远程 Server Agent 执行。

### 4.把“远程 Agent”包装成 Tool

把“调用远程 A2A Agent”这件事，包装成一个模型可理解、可选择的函数（Tool）。

```csharp
var callA2AAgent = AIFunctionFactory.Create(
    async (string input, CancellationToken ct) =>
    {
        Console.WriteLine("[Client Agent] 调用 Server A2A Agent...");
        Console.WriteLine($"📨 input: {input}");

        var response = await a2aAgent.RunAsync(input, cancellationToken: ct);

        Console.WriteLine("[ClientAgent] Server Agent 输出");
        Console.WriteLine($"📩 output: {response.Text}");
    },
    new AIFunctionFactoryOptions
    {
        Name = "call_remote_agent",
        Description = """
    调用远程 A2A Agent，用于通用问答与推理。
    """
    }
);
```

### 5. 创建 Client Agent 并注入 Tool

我们创建一个新的 Client Agent，并把上一步创建的 Tool 注入进去。

```csharp
AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential())
    .GetChatClient(deploymentName)
    .AsIChatClient()
    .AsAIAgent(
        name: "ClientAgent",
        instructions: "你是一个乐于助人的助手。",
        tools: [callA2AAgent]);
Console.WriteLine(await agent.RunAsync("请调用远程 agent 解释什么是 A2A 协议"));
```

在这里我们也可以不创建客户端的Agent，可以直接使用Server的Agent代理来直接执行，为了演示我们创建了一个Client Agent来调用Server Agent。

这样整个 Client 端就完成了，启动 Client 端后，你会看到 Client 端调用了 Server 端的 Agent，并得到了回答。

### 总结

我们主要介绍了 A2A 协议的基本概念、作用以及在 Microsoft Agent Framework 中的使用方法。通过创建 `Agent2Agent.Server` 和 `Agent2Agent.Client`，我们展示了如何实现智能体之间的通信与协作。A2A 协议为多智能体系统提供了一种标准化的交互方式，促进了智能体的互操作性和协作能力。更多的关于 A2A 协议的细节和高级用法，请参考<https://a2a-protocol.org/latest/。>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://bingbing-gui.gitbook.io/blog/ai-agent/agent-framework/di-19-pian-agenttoagent-xie-yi.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
