多智能体微服务实战(2/4):三大开放协议让智能体互联互通

三大协议让智能体"说同一种语言" - MCP、A2A与Agent Framework深度解析

引言

在上一篇文章中,我们介绍了为什么企业需要多智能体协作系统。但一个关键问题还没有解答:

这么多智能体,它们之间怎么通信?如何确保不同团队开发的智能体能够互操作?

想象一下,如果没有HTTP协议,Web就不可能存在;如果没有TCP/IP协议,互联网就无从谈起。同样,要构建企业级的多智能体系统,我们需要标准化的通信协议

在AgentFrameworkAspire项目中,我们使用了三大开放协议:

  1. MCP (Model Context Protocol) - 工具暴露标准
  2. A2A (Agent-to-Agent Protocol) - 智能体通信协议
  3. Agent Framework - 工作流编排框架

这三个协议各司其职,又相互配合,共同构建了一个强大而灵活的多智能体生态系统。

本文将深入这三大协议的技术细节,带你理解它们的设计理念和实际应用。


一、MCP协议:让AI模型发现和调用工具

1.1 为什么需要MCP?

在MCP出现之前,如果你想让AI模型调用你的业务系统功能,通常有两种做法:

方式1:Function Calling(函数调用)

{
  "name": "validate_budget",
  "description": "验证预算是否在可用范围内",
  "parameters": {
    "type": "object",
    "properties": {
      "amount": {"type": "number"},
      "category": {"type": "string"}
    }
  }
}

问题:

  • 每个AI提供商的格式略有不同
  • 需要手工编写JSON描述
  • 工具和实现代码分离,容易不一致

方式2:自然语言提示

你有以下工具可用:
- validate_budget: 验证预算,参数amount和category
请根据用户问题调用工具...

问题:

  • 格式不标准,AI容易理解错误
  • 参数类型不明确
  • 无法自动发现工具

MCP的解决方案

MCP (Model Context Protocol) 是一个开放标准,定义了:

  • 工具如何描述自己(Tool Metadata)
  • 工具如何被调用(Tool Invocation)
  • 资源如何被访问(Resources)
  • 提示词如何被复用(Prompts)

更重要的是,MCP提供了自动发现机制。AI模型可以:

  1. 查询服务有哪些工具(List Tools)
  2. 获取工具的详细描述
  3. 调用工具并获得结果

1.3 MCP调用流程图解

下面用序列图展示完整的MCP工具调用流程:

sequenceDiagram participant AI as AI Model (GPT-4) participant PM as ProjectManager participant MCP as MCP Client participant Finance as Finance MCP Server participant Tool as ValidateBudget Tool Note over AI,PM: 用户询问:200万预算是否合理? AI->>PM: 需要验证预算 PM->>MCP: 初始化MCP客户端 MCP->>Finance: GET /finance/mcp (Handshake) Finance-->>MCP: Server Info + Protocol Version MCP->>Finance: ListTools() Finance-->>MCP: [ValidateBudget, GetHistoricalCosts, CalculateROI] MCP->>Finance: CallTool("ValidateBudget", {<br/> amount: 200,<br/> projectType: "ERP"<br/>}) Finance->>Tool: ValidateBudget(200, "ERP") Tool->>Tool: 业务逻辑处理 Tool-->>Finance: {IsValid: false, Message: "预算偏低,建议至少300万"} Finance-->>MCP: CallToolResult MCP-->>PM: 工具调用结果 PM->>AI: 提供验证结果给AI AI-->>AI: 生成最终响应

关键步骤解析

  1. Handshake(握手):客户端连接到MCP服务器,协商协议版本
  2. ListTools(列出工具):客户端查询服务器有哪些可用工具
  3. CallTool(调用工具):客户端调用具体工具,传递参数
  4. 工具执行:服务器执行业务逻辑
  5. 返回结果:服务器返回标准化的CallToolResult

1.4 MCP在AgentFrameworkAspire中的实现

让我们看一个实际的例子:Finance服务的MCP工具。

工具定义

// Finance/Program.cs
using ModelContextProtocol.Server;
using ModelContextProtocol.Protocol;

[McpServerToolType]
public class FinanceTools
{
    /// <summary>
    /// 验证预算是否在可用范围内
    /// </summary>
    [McpServerTool]
    public Task<CallToolResult> ValidateBudget(
        decimal amount, 
        string category, 
        string costCenter)
    {
        // 业务逻辑:检查预算
        var isValid = amount > 0 && amount < 1000000;
        var availableBudget = 500000m;
        
        var result = new
        {
            IsValid = isValid && amount <= availableBudget,
            RequestedAmount = amount,
            AvailableBudget = availableBudget,
            Category = category,
            CostCenter = costCenter,
            Message = isValid && amount <= availableBudget 
                ? "Budget validation passed" 
                : $"Budget validation failed: requested {amount} " +
                  $"exceeds available {availableBudget}"
        };

        return Task.FromResult(new CallToolResult
        {
            Content = [new TextContentBlock { 
                Text = System.Text.Json.JsonSerializer.Serialize(result) 
            }]
        });
    }

    /// <summary>
    /// 查询历史项目成本数据
    /// </summary>
    [McpServerTool]
    public Task<CallToolResult> GetHistoricalCosts(
        string projectType, 
        string department)
    {
        // 模拟从数据库查询历史数据
        var historicalData = new
        {
            ProjectType = projectType,
            Department = department,
            AverageCost = 250000m,
            MinCost = 150000m,
            MaxCost = 450000m,
            ProjectCount = 15,
            TimeframeMonths = 12
        };

        return Task.FromResult(new CallToolResult
        {
            Content = [new TextContentBlock { 
                Text = System.Text.Json.JsonSerializer.Serialize(historicalData) 
            }]
        });
    }

    /// <summary>
    /// 计算投资回报率(ROI)
    /// </summary>
    [McpServerTool]
    public Task<CallToolResult> CalculateROI(
        decimal investment, 
        string expectedReturnsJson)
    {
        var expectedReturns = System.Text.Json.JsonSerializer
            .Deserialize<decimal[]>(expectedReturnsJson) ?? [];
        var totalReturn = expectedReturns.Sum();
        var roi = investment > 0 ? ((totalReturn - investment) / investment) * 100 : 0;

        var result = new
        {
            Investment = investment,
            TotalReturn = totalReturn,
            NetProfit = totalReturn - investment,
            ROI_Percentage = Math.Round(roi, 2),
            PaybackPeriod = expectedReturns.Length > 0 ? "Estimated 18 months" : "N/A"
        };

        return Task.FromResult(new CallToolResult
        {
            Content = [new TextContentBlock { 
                Text = System.Text.Json.JsonSerializer.Serialize(result) 
            }]
        });
    }
}

关键点解析

  1. [McpServerToolType] 特性:标记这个类包含MCP工具
  2. [McpServerTool] 特性:标记每个公开的工具方法
  3. 强类型参数decimal amount, string category - MCP会自动生成类型描述
  4. 返回 CallToolResult:标准化的返回格式,包含文本内容

注册MCP服务

// Finance/Program.cs
var builder = WebApplication.CreateBuilder(args);

// 注册MCP Server,支持HTTP传输
builder.Services.AddMcpServer()
    .WithHttpTransport()
    .WithTools<FinanceTools>()           // 注册工具
    .WithResources<BudgetResources>();   // 注册资源(可选)

var app = builder.Build();

// 暴露MCP端点
app.MapMcp("/finance/mcp");

app.Run();

工具发现流程

当AI模型或其他智能体想要使用Finance的工具时:

步骤1:发现工具

POST /finance/mcp
Content-Type: application/json

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/list"
}

MCP服务器响应

{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "tools": [
      {
        "name": "ValidateBudget",
        "description": "验证预算是否在可用范围内",
        "inputSchema": {
          "type": "object",
          "properties": {
            "amount": {"type": "number"},
            "category": {"type": "string"},
            "costCenter": {"type": "string"}
          },
          "required": ["amount", "category", "costCenter"]
        }
      },
      {
        "name": "GetHistoricalCosts",
        "description": "查询历史项目成本数据",
        "inputSchema": {
          "type": "object",
          "properties": {
            "projectType": {"type": "string"},
            "department": {"type": "string"}
          }
        }
      },
      {
        "name": "CalculateROI",
        "description": "计算投资回报率(ROI)",
        "inputSchema": {
          "type": "object",
          "properties": {
            "investment": {"type": "number"},
            "expectedReturnsJson": {"type": "string"}
          }
        }
      }
    ]
  }
}

步骤2:调用工具

POST /finance/mcp
Content-Type: application/json

{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "ValidateBudget",
    "arguments": {
      "amount": 300000,
      "category": "Development",
      "costCenter": "IT-001"
    }
  }
}

MCP服务器响应

{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "content": [
      {
        "type": "text",
        "text": "{\"IsValid\":true,\"RequestedAmount\":300000,\"AvailableBudget\":500000,...}"
      }
    ]
  }
}

1.3 MCP Resources:不止于工具

除了工具,MCP还支持Resources(资源),用于暴露静态或动态的数据源。

// Finance/Program.cs
[McpServerResourceType]
public class BudgetResources
{
    /// <summary>
    /// 获取标准预算分配模板
    /// </summary>
    [McpServerResource]
    public Task<ReadResourceResult> GetBudgetTemplate()
    {
        var template = new
        {
            Category = "General",
            StandardAllocations = new
            {
                Personnel = "60%",
                Infrastructure = "20%",
                Operations = "15%",
                Contingency = "5%"
            }
        };

        return Task.FromResult(new ReadResourceResult
        {
            Contents = [new TextResourceContents 
            { 
                Uri = "budget://templates/general",
                MimeType = "application/json",
                Text = System.Text.Json.JsonSerializer.Serialize(template) 
            }]
        });
    }
}

使用场景

  • 暴露配置文件、模板、规范文档
  • 提供数据集、历史记录
  • 分享知识库、FAQ

1.4 MCP的价值

对比传统的REST API:

特性 REST API MCP
自动发现 ❌ 需要文档 ✅ 内置List API
类型安全 ⚠️ OpenAPI可选 ✅ 强制类型定义
AI友好 ❌ 需要额外转换 ✅ 原生设计
统一标准 ❌ 各家不同 ✅ 开放标准
工具和代码一致性 ⚠️ 容易不同步 ✅ 代码即文档

二、A2A协议:智能体之间的"标准对讲机"

2.1 智能体通信的挑战

假设Tech团队和Finance团队各自开发了自己的智能体,现在PMO团队想要调用它们。没有标准协议的情况下:

Tech团队的接口

{
  "query": "评估技术可行性",
  "response_format": "json"
}

Finance团队的接口

{
  "question": "评估技术可行性",
  "output_type": "structured"
}

问题

  • 参数名不一样(query vs question
  • 格式不一样(response_format vs output_type
  • 如何知道智能体能做什么?
  • 如何处理流式响应?
  • 如何传递上下文?

A2A协议的解决方案

A2A (Agent-to-Agent Protocol) 是由 Google Cloud 主导的智能体间通信标准,它定义了:

  1. AgentCard:智能体的"名片",描述自己的能力
  2. 标准消息格式MessageSendParamsAgentMessage
  3. 发现机制.well-known/agent-card.json 约定
  4. 能力声明:支持的输入输出模式、是否支持流式等

2.2 A2A握手流程图解

下面展示ProjectManager如何通过A2A协议连接并调用Tech Agent:

sequenceDiagram participant PM as ProjectManager Agent participant HTTP as HTTP Client participant Tech as Tech Agent Server participant Card as AgentCard Endpoint participant Agent as RequirementAnalyst Note over PM,Tech: 阶段1: 服务发现 PM->>HTTP: 创建A2ACardResolver HTTP->>Card: GET https://localhost:7063/.well-known/agent-card.json Card-->>HTTP: AgentCard JSON<br/>{name, description, capabilities} HTTP-->>PM: AgentCard对象 Note over PM: 解析AgentCard<br/>- 验证协议版本<br/>- 检查capabilities<br/>- 获取endpoint URL Note over PM,Tech: 阶段2: Agent初始化 PM->>HTTP: GetAIAgentAsync(AgentCard) HTTP->>Tech: 建立连接到/tech/requirement-analyst/run Tech-->>HTTP: 连接确认 HTTP-->>PM: AIAgent客户端实例 Note over PM,Tech: 阶段3: 消息传递 PM->>HTTP: agent.InvokeAsync(messages) HTTP->>Tech: POST /tech/requirement-analyst/run<br/>{messages: [{role: "user", content: "..."}]} Tech->>Agent: 处理用户消息 Agent->>Agent: 执行业务逻辑 Agent-->>Tech: 生成响应 Tech-->>HTTP: AgentRunResponse<br/>{messages: [{role: "assistant", content: "..."}]} HTTP-->>PM: 解析响应 PM->>PM: 处理Tech Agent返回的结果

三阶段详解

  1. 服务发现阶段:通过标准化的.well-known/agent-card.json端点获取Agent元数据
  2. 初始化阶段:使用AgentCard中的信息创建可复用的AIAgent客户端
  3. 消息传递阶段:使用标准化的消息格式进行双向通信

2.3 AgentCard:智能体的自我介绍

每个A2A智能体都需要暴露一个AgentCard,描述自己:

// Tech/Program.cs
public class TechAnalystAgent
{
    // ... (其他代码)

    private Task<AgentCard> GetAgentCardAsync(
        string agentUrl, 
        CancellationToken cancellationToken)
    {
        return Task.FromResult(new AgentCard
        {
            Name = "Technical Requirements Analyst",
            Description = "Technical analyst agent specializing in " +
                         "requirements analysis, architecture design, " +
                         "and technology stack validation",
            Url = agentUrl,
            Version = "1.0.0",
            DefaultInputModes = ["text"],      // 支持文本输入
            DefaultOutputModes = ["text"],     // 输出文本
            Capabilities = new AgentCapabilities
            {
                Streaming = false,             // 不支持流式
                PushNotifications = false      // 不支持推送
            },
            Skills = []                        // 技能列表(可选)
        });
    }
}

AgentCard会自动暴露在标准端点

https://localhost:7063/.well-known/agent-card.json

任何智能体或客户端都可以访问这个端点,了解Tech Agent的能力。

2.3 消息发送和接收

发送方(Web前端的ProjectManagerAgent)

// AgentFrameworkAspire.Web/Services/ProjectManagerAgent.cs
public class ProjectManagerAgent
{
    private AIAgent? _techAgent;
    
    /// <summary>
    /// 使用A2ACardResolver初始化单个agent
    /// </summary>
    private async Task<AIAgent> InitializeAgentAsync(
        string agentName, 
        string agentUrl, 
        CancellationToken ct)
    {
        _logger.LogInformation(
            "Initializing {AgentName} agent from {Url}", 
            agentName, agentUrl);
        
        // A2ACardResolver会自动在URL后追加 /.well-known/agent-card.json
        // 例如: https://localhost:7063/.well-known/agent-card.json
        var url = new Uri(agentUrl);
        var agentCardResolver = new A2ACardResolver(url, _httpClient);
        
        // 获取AgentCard并创建AIAgent客户端
        var agent = await agentCardResolver.GetAIAgentAsync(
            httpClient: _httpClient, 
            cancellationToken: ct);
        
        _logger.LogInformation("{AgentName} agent initialized successfully", agentName);
        return agent;
    }
    
    /// <summary>
    /// 调用A2A Agent
    /// </summary>
    private async Task<AgentRunResponse> CallA2AAgentAsync(
        string agentName,
        AIAgent agent,
        string userInput,
        CancellationToken ct)
    {
        _logger.LogInformation("Calling {AgentName} agent via A2A...", agentName);
        
        // 创建标准消息
        var messages = new List<ChatMessage>
        {
            new(ChatRole.User, userInput)
        };
        
        // 调用远程智能体
        var response = await agent.InvokeAsync(
            messages,
            cancellationToken: ct);
        
        _logger.LogInformation(
            "{AgentName} agent responded with {MessageCount} messages", 
            agentName, 
            response.Messages.Count);
        
        return response;
    }
}

关键步骤

  1. A2ACardResolver:从URL解析AgentCard
  2. GetAIAgentAsync:创建AIAgent客户端代理
  3. InvokeAsync:调用远程智能体,发送消息并获取响应

接收方(Tech服务的TechAnalystAgent)

// Tech/Program.cs
public class TechAnalystAgent
{
    private readonly IChatClient _chatClient;

    public void Attach(ITaskManager taskManager)
    {
        // 注册消息处理器
        taskManager.OnMessageReceived = ProcessMessageAsync;
        // 注册AgentCard查询处理器
        taskManager.OnAgentCardQuery = GetAgentCardAsync;
    }

    /// <summary>
    /// 处理收到的A2A消息
    /// </summary>
    private async Task<A2AResponse> ProcessMessageAsync(
        MessageSendParams messageSendParams, 
        CancellationToken cancellationToken)
    {
        // 提取消息文本
        var messageText = messageSendParams.Message.Parts
            .OfType<TextPart>()
            .FirstOrDefault()?.Text ?? "";

        // 构造提示词
        var messages = new List<ChatMessage>
        {
            new(ChatRole.System, 
                "You are a technical requirements analyst specializing in " +
                "software architecture and technical feasibility. You help " +
                "analyze technical requirements, assess complexity, and " +
                "recommend technology stacks."),
            new(ChatRole.User, messageText)
        };

        // 调用底层的IChatClient(连接到OpenAI等)
        var completion = await _chatClient.GetResponseAsync(
            messages, 
            cancellationToken: cancellationToken);

        // 返回标准的A2A响应
        return new AgentMessage
        {
            Role = MessageRole.Agent,
            MessageId = Guid.NewGuid().ToString(),
            ContextId = messageSendParams.Message.ContextId,
            Parts = [new TextPart { Text = completion.Text ?? "No response generated" }]
        };
    }
}

关键点

  • ITaskManager:A2A协议的任务管理器
  • MessageSendParams:标准消息参数(包含消息ID、上下文ID、消息内容)
  • AgentMessage:标准响应格式

暴露A2A端点

// Tech/Program.cs
var app = builder.Build();

// 创建TechAnalystAgent实例
var requirementAnalyst = new TechAnalystAgent(
    AgentHelpers.CreateChatClient(app.Configuration)
);

// 创建TaskManager并绑定Agent
var techTaskManager = new TaskManager();
requirementAnalyst.Attach(techTaskManager);

// 暴露A2A端点
app.MapA2A(techTaskManager, "/tech/requirement-analyst");

// 暴露AgentCard端点
app.MapWellKnownAgentCard(techTaskManager, "/tech/requirement-analyst");

app.Run();

2.4 完整的A2A通信流程

让我们追踪一次完整的A2A调用:

sequenceDiagram participant PM as ProjectManager Agent<br/>(Web) participant Tech as Tech Agent<br/>(Service) PM->>Tech: 1. GET /.well-known/agent-card.json Tech-->>PM: 2. AgentCard Response<br/>{name, description, capabilities} PM->>Tech: 3. POST /tech/requirement-analyst<br/>MessageSendParams Note over Tech: 处理消息<br/>执行业务逻辑 Tech-->>PM: 4. AgentMessage Response

2.5 A2A vs REST:为什么需要专门的协议?

你可能会问:A2A和普通的REST API有什么区别?

特性 REST API A2A
目标 数据交换 智能体协作
消息格式 自定义JSON 标准化MessageSendParams
能力描述 OpenAPI(可选) AgentCard(强制)
上下文传递 ❌ 需要自己设计 ✅ 内置ContextId
流式支持 ⚠️ SSE或WebSocket ✅ 标准流式协议
AI原生 ❌ 为人类设计 ✅ 为AI设计

核心差异:A2A协议是AI原生的,考虑了智能体协作的特殊需求。


三、Agent Framework:编排复杂的多阶段工作流

3.1 为什么需要工作流编排?

前面介绍的MCP和A2A解决了"单点调用"的问题。但在实际场景中,我们常常需要:

  • 串行执行:先做A,再做B,最后做C
  • 并行执行:同时做A、B、C、D
  • 条件分支:如果X成立,做A;否则做B
  • 状态传递:前一步的输出是后一步的输入

这就需要工作流编排(Workflow Orchestration)

3.2 两种Agent:Simple Agent vs Workflow Agent

在AgentFrameworkAspire中,我们使用了两种类型的智能体:

Simple Agent(简单智能体)

适用于单次调用、无状态的场景:

// Finance/Program.cs - Simple Agent
public class FinanceAgent
{
    private readonly IChatClient _chatClient;

    private async Task<A2AResponse> ProcessMessageAsync(
        MessageSendParams messageSendParams, 
        CancellationToken cancellationToken)
    {
        // 直接调用IChatClient,返回结果
        var messages = new List<ChatMessage>
        {
            new(ChatRole.System, "You are a financial analyst..."),
            new(ChatRole.User, messageText)
        };

        var completion = await _chatClient.GetResponseAsync(
            messages, 
            cancellationToken: cancellationToken);

        return new AgentMessage { ... };
    }
}

特点

  • 单次输入输出
  • 无内部状态
  • 快速响应

Workflow Agent(工作流智能体)

适用于多阶段、有状态的复杂场景。以QA团队的RiskManagementExpert为例,展示典型的4阶段串行工作流:

graph LR Start([用户输入:请评估项目风险]) --> RiskIdentifier subgraph Stage1 [阶段1: 风险识别] RiskIdentifier[Risk Identifier Agent<br/>识别项目主要风险] RiskIdentifier --> Output1[输出:风险列表<br/>例如:技术风险、成本风险、进度风险] end Output1 --> ImpactAnalyzer subgraph Stage2 [阶段2: 影响分析] ImpactAnalyzer[Impact Analyzer Agent<br/>分析每个风险的影响和概率] ImpactAnalyzer --> Output2[输出:优先级矩阵<br/>例如:技术风险 - 高概率高影响] end Output2 --> MitigationPlanner subgraph Stage3 [阶段3: 缓解策略] MitigationPlanner[Mitigation Planner Agent<br/>制定高优先级风险的缓解措施] MitigationPlanner --> Output3[输出:应对措施<br/>例如:技术预研、技术顾问、原型验证] end Output3 --> MonitoringPlanner subgraph Stage4 [阶段4: 监控计划] MonitoringPlanner[Monitoring Planner Agent<br/>制定风险监控和控制计划] MonitoringPlanner --> Output4[输出:监控计划<br/>例如:每周风险审查、KPI跟踪] end Output4 --> End([最终输出:完整风险管理报告]) style Start fill:#3273dc,stroke:#333,color:#fff style End fill:#3273dc,stroke:#333,color:#fff style RiskIdentifier fill:#9b59b6,stroke:#333,color:#fff style ImpactAnalyzer fill:#9b59b6,stroke:#333,color:#fff style MitigationPlanner fill:#9b59b6,stroke:#333,color:#fff style MonitoringPlanner fill:#9b59b6,stroke:#333,color:#fff style Output1 fill:#ffe082,stroke:#333 style Output2 fill:#ffe082,stroke:#333 style Output3 fill:#ffe082,stroke:#333 style Output4 fill:#ffe082,stroke:#333 style Stage1 fill:#f0f0f0,stroke:#999,stroke-width:2px style Stage2 fill:#f0f0f0,stroke:#999,stroke-width:2px style Stage3 fill:#f0f0f0,stroke:#999,stroke-width:2px style Stage4 fill:#f0f0f0,stroke:#999,stroke-width:2px

Workflow Agent实现代码

// QA/Program.cs - Workflow Agent
public class RiskManagementExpertAgent
{
    private readonly Workflow _riskAnalysisWorkflow;
    private readonly IChatClient _chatClient;

    public RiskManagementExpertAgent(IChatClient chatClient)
    {
        _chatClient = chatClient;
        
        // 定义4个阶段的Agent
        var riskIdentifier = new ChatClientAgent(chatClient, new ChatClientAgentOptions
        {
            Instructions = "你是项目风险识别专家。请用简洁的中文列出项目的主要风险...",
            Name = "Risk Identifier"
        });

        var impactAnalyzer = new ChatClientAgent(chatClient, new ChatClientAgentOptions
        {
            Instructions = "你是风险影响分析专家。请用中文分析每个风险的影响和概率...",
            Name = "Impact Analyzer"
        });

        var mitigationPlanner = new ChatClientAgent(chatClient, new ChatClientAgentOptions
        {
            Instructions = "你是风险缓解策略专家。请为每个高优先级风险制定具体的缓解措施...",
            Name = "Mitigation Planner"
        });

        var monitoringPlanner = new ChatClientAgent(chatClient, new ChatClientAgentOptions
        {
            Instructions = "你是风险监控计划专家。请制定风险监控和控制计划...",
            Name = "Monitoring Planner"
        });

        // 使用WorkflowBuilder构建工作流
        _riskAnalysisWorkflow = new WorkflowBuilder(riskIdentifier)
            .AddEdge(riskIdentifier, impactAnalyzer)
            .AddEdge(impactAnalyzer, mitigationPlanner)
            .AddEdge(mitigationPlanner, monitoringPlanner)
            .WithOutputFrom(monitoringPlanner)
            .Build();
    }
}

工作流关键特性

  1. 串行执行:每个阶段按顺序执行,前一阶段输出作为后一阶段输入
  2. 状态传递:通过AddEdge()定义数据流向
  3. 专业分工:每个Agent有明确的专业角色和职责
  4. 最终输出WithOutputFrom()指定最终输出来源

3.3 执行工作流并流式返回

graph LR Input([用户输入:评估项目风险]) --> RiskId[Risk Identifier<br/>风险识别专家] RiskId --> |识别的风险列表| Impact[Impact Analyzer<br/>影响分析专家] Impact --> |优先级矩阵| Mitigation[Mitigation Planner<br/>缓解策略专家] Mitigation --> |应对措施| Monitoring[Monitoring Planner<br/>监控计划专家] Monitoring --> Output([最终输出:完整风险管理计划]) style Input fill:#3273dc,stroke:#333,color:#fff style Output fill:#3273dc,stroke:#333,color:#fff style RiskId fill:#9b59b6,stroke:#333,color:#fff style Impact fill:#9b59b6,stroke:#333,color:#fff style Mitigation fill:#9b59b6,stroke:#333,color:#fff style Monitoring fill:#9b59b6,stroke:#333,color:#fff

执行工作流的代码实现

// QA/Program.cs
private async Task<A2AResponse> ProcessMessageAsync(
    MessageSendParams messageSendParams, 
    CancellationToken cancellationToken)
{
    var messageText = messageSendParams.Message.Parts
        .OfType<TextPart>()
        .FirstOrDefault()?.Text ?? "";
    
    _logger.LogInformation("=== QA Risk Management Expert - Message Received ===");
    _logger.LogInformation("Input Message: {MessageText}", messageText);

    // 使用workflow处理风险分析 - 使用StreamingRun
    var env = InProcessExecution.Default;
    var run = await env.StreamAsync(_riskAnalysisWorkflow, messageText);
    await run.TrySendMessageAsync(new TurnToken(emitEvents: true));
    
    var responseBuilder = new StringBuilder();
    
    // 监听工作流的流式事件
    await foreach (var evt in run.WatchStreamAsync().WithCancellation(cancellationToken))
    {
        if (evt is AgentRunUpdateEvent update)
        {
            var response = update.AsResponse();
            
            foreach (var message in response.Messages)
            {
                foreach (var content in message.Contents)
                {
                    if (content is TextContent textContent)
                    {
                        responseBuilder.Append(textContent.Text);
                    }
                }
            }
        }
    }
    
    return new AgentMessage
    {
        Role = MessageRole.Agent,
        MessageId = Guid.NewGuid().ToString(),
        ContextId = messageSendParams.Message.ContextId,
        Parts = [new TextPart { Text = responseBuilder.ToString() }]
    };
}

关键API

  • InProcessExecution.Default:本地执行环境
  • StreamAsync:启动流式工作流
  • WatchStreamAsync:监听工作流事件
  • AgentRunUpdateEvent:每个阶段的输出事件

3.4 Web前端的3阶段工作流编排

ProjectManagerAgent实现了一个更复杂的工作流:

// AgentFrameworkAspire.Web/Services/ProjectManagerAgent.cs
public async IAsyncEnumerable<ExecutorEvent> ExecuteWorkflowStreamAsync(
    string userInput,
    [EnumeratorCancellation] CancellationToken ct = default)
{
    // 确保agents已初始化
    await InitializeAgentsAsync(ct);
    
    // ==================== Stage 1: 并行分析 ====================
    yield return new WorkflowStageStartEvent("pm-workflow")
    {
        StageName = "并行分析阶段",
        InvolvedAgents = ["Tech", "HR", "Finance", "QA"]
    };
    
    // 并行调用4个specialist agents (使用A2A)
    var parallelTasks = new[]
    {
        CallA2AAgentAsync("Tech", _techAgent!, userInput, ct),
        CallA2AAgentAsync("HR", _hrAgent!, userInput, ct),
        CallA2AAgentAsync("Finance", _financeAgent!, userInput, ct),
        CallA2AAgentAsync("QA", _qaAgent!, userInput, ct)
    };
    
    var analysisResults = new List<AgentRunResponse>();
    
    // 使用 Task.WhenEach 实现真正的并行 + 流式返回
    await foreach (var task in Task.WhenEach(parallelTasks))
    {
        var response = await task;
        analysisResults.Add(response);
        
        // 流式输出每个specialist的响应
        foreach (var message in response.Messages)
        {
            var update = new AgentRunResponseUpdate
            {
                Role = message.Role,
                Contents = message.Contents,
                AgentId = response.AgentId,
                CreatedAt = DateTimeOffset.Now,
                MessageId = message.MessageId
            };
            
            yield return new AgentRunUpdateEvent("pm-workflow", update);
        }
    }
    
    yield return new WorkflowStageCompleteEvent("pm-workflow")
    {
        StageName = "并行分析阶段"
    };
    
    // ==================== Stage 2: PMO规划 ====================
    yield return new WorkflowStageStartEvent("pm-workflow")
    {
        StageName = "PMO规划阶段",
        InvolvedAgents = ["PMO"]
    };
    
    // 整合Stage 1的结果
    var consolidatedAnalysis = ConsolidateAnalysisResults(analysisResults);
    
    // 调用PMO Agent
    var pmoResponse = await CallA2AAgentAsync(
        "PMO", 
        _pmoAgent!, 
        $"基于以下分析结果生成详细项目计划:\n\n{consolidatedAnalysis}", 
        ct);
    
    foreach (var message in pmoResponse.Messages)
    {
        var update = new AgentRunResponseUpdate
        {
            Role = message.Role,
            Contents = message.Contents,
            AgentId = "PMO",
            CreatedAt = DateTimeOffset.Now,
            MessageId = message.MessageId
        };
        
        yield return new AgentRunUpdateEvent("pm-workflow", update);
    }
    
    yield return new WorkflowStageCompleteEvent("pm-workflow")
    {
        StageName = "PMO规划阶段"
    };
    
    // ==================== Stage 3: PM整合决策 ====================
    yield return new WorkflowStageStartEvent("pm-workflow")
    {
        StageName = "整合决策阶段",
        InvolvedAgents = ["ProjectManager"]
    };
    
    // PM使用IChatClient进行最终整合
    var chatClient = AgentHelpers.CreateChatClient(_configuration);
    var finalPrompt = BuildFinalIntegrationPrompt(analysisResults, pmoResponse);
    
    var finalResponse = await chatClient.GetResponseAsync(
        [new ChatMessage(ChatRole.User, finalPrompt)],
        null,
        ct);
    
    var finalUpdate = new AgentRunResponseUpdate
    {
        Role = ChatRole.Assistant,
        Contents = [new TextContent(finalResponse.Text ?? "")],
        AgentId = "ProjectManager",
        CreatedAt = DateTimeOffset.Now
    };
    
    yield return new AgentRunUpdateEvent("pm-workflow", finalUpdate);
    
    yield return new WorkflowStageCompleteEvent("pm-workflow")
    {
        StageName = "整合决策阶段"
    };
    
    // 工作流完成
    yield return new WorkflowOutputEvent("pm-workflow");
}

工作流特点

  • Stage 1并行:Tech、HR、Finance、QA同时执行,大幅缩短时间
  • Stage 2依赖Stage 1:PMO基于前4个团队的分析结果进行规划
  • Stage 3汇总:PM整合所有信息,生成最终报告
  • 流式返回:使用IAsyncEnumerable,每个Agent完成后立即返回,无需等待全部完成

3.5 WorkflowBuilder API详解

Agent Framework提供了流畅的API来构建工作流:

// 线性工作流(Sequential)
var workflow = new WorkflowBuilder(agent1)
    .AddEdge(agent1, agent2)
    .AddEdge(agent2, agent3)
    .WithOutputFrom(agent3)
    .Build();

// 并行工作流(Parallel)
var workflow = new WorkflowBuilder(agent1)
    .AddEdge(agent1, agent2)
    .AddEdge(agent1, agent3)  // agent2和agent3并行执行
    .AddEdge(agent1, agent4)
    .WithOutputFrom([agent2, agent3, agent4])  // 收集所有输出
    .Build();

// 条件分支(Conditional)
var workflow = new WorkflowBuilder(agent1)
    .AddEdge(agent1, agent2, condition: ctx => ctx.SomeFlag)
    .AddEdge(agent1, agent3, condition: ctx => !ctx.SomeFlag)
    .Build();

四、三大协议如何协同工作?

现在我们理解了每个协议的作用,让我们看看它们如何配合:

4.1 分层架构

graph TB subgraph AppLayer [应用层 - Agent Framework] Workflow[3阶段工作流编排] Scheduling[并行/串行调度] Workflow --> Scheduling end subgraph CommLayer [通信层 - A2A Protocol] Discovery[智能体发现 AgentCard] Messaging[标准化消息传递] Context[上下文管理] Discovery --> Messaging --> Context end subgraph ToolLayer [工具层 - MCP Protocol] ToolExpose[工具暴露和发现] ParamValidation[参数验证和调用] ResourceAccess[资源访问] ToolExpose --> ParamValidation --> ResourceAccess end AppLayer --> |使用| CommLayer CommLayer --> |调用| ToolLayer style AppLayer fill:#e8f5e9,stroke:#4caf50,stroke-width:2px style CommLayer fill:#e3f2fd,stroke:#2196f3,stroke-width:2px style ToolLayer fill:#fff3e0,stroke:#ff9800,stroke-width:2px style Workflow fill:#3273dc,stroke:#333,color:#fff style Scheduling fill:#3273dc,stroke:#333,color:#fff style Discovery fill:#48c774,stroke:#333,color:#fff style Messaging fill:#48c774,stroke:#333,color:#fff style Context fill:#48c774,stroke:#333,color:#fff style ToolExpose fill:#9b59b6,stroke:#333,color:#fff style ParamValidation fill:#9b59b6,stroke:#333,color:#fff style ResourceAccess fill:#9b59b6,stroke:#333,color:#fff

4.2 完整的调用链路

让我们追踪一次完整的用户提问:

graph TD User[用户:开发一个ERP系统<br/>预算300万] --> PM[PM Agent Web Frontend<br/>Agent Framework: 启动3阶段工作流] PM --> Stage1{Stage 1: 并行分析} Stage1 --> |A2A调用| Tech[Tech Agent<br/>技术评估] Stage1 --> |A2A调用| HR[HR Agent<br/>人力评估] Stage1 --> |A2A调用| Finance[Finance Agent<br/>财务评估] Stage1 --> |A2A调用| QA[QA Agent<br/>风险评估] Finance --> |MCP调用| ValidateBudget[ValidateBudget Tool<br/>验证预算] Finance --> |MCP调用| GetHistoricalCosts[GetHistoricalCosts Tool<br/>历史成本] Tech --> |返回分析| Gather1[Stage 1结果汇总] HR --> |返回分析| Gather1 Finance --> |返回分析| Gather1 QA --> |返回分析| Gather1 Gather1 --> Stage2[Stage 2: PMO规划<br/>PMO Agent Workflow内部] Stage2 --> |任务分解<br/>里程碑| Stage3[Stage 3: PM整合<br/>生成最终计划] Stage3 --> Output[综合项目计划<br/>返回给用户] style User fill:#3273dc,stroke:#333,color:#fff style PM fill:#9b59b6,stroke:#333,color:#fff style Stage1 fill:#ffe082,stroke:#333 style Stage2 fill:#ffe082,stroke:#333 style Stage3 fill:#ffe082,stroke:#333 style Tech fill:#48c774,stroke:#333,color:#fff style HR fill:#48c774,stroke:#333,color:#fff style Finance fill:#48c774,stroke:#333,color:#fff style QA fill:#48c774,stroke:#333,color:#fff style ValidateBudget fill:#e3f2fd,stroke:#2196f3 style GetHistoricalCosts fill:#e3f2fd,stroke:#2196f3 style Gather1 fill:#f0f0f0,stroke:#999 style Output fill:#3273dc,stroke:#333,color:#fff

调用链路详解

  1. 用户提问 → PM Agent接收并启动Agent Framework工作流
  2. Stage 1(并行):PM通过A2A协议同时调用4个专家Agent
  3. MCP工具调用:Finance Agent调用MCP工具验证预算和查询历史数据
  4. 结果汇总:4个Agent的分析结果收集到一起
  5. Stage 2(串行):PMO Agent基于前4个结果进行项目规划
  6. Stage 3(整合):PM Agent整合所有信息生成最终报告
  7. 返回用户:完整的项目计划呈现给用户

关键点

  1. Workflow编排:PM Agent决定调用哪些Agent、什么时候调用
  2. A2A通信:所有Agent间的调用使用标准A2A协议
  3. MCP工具:Agent在处理请求时,可以调用自己的MCP工具获取数据

五、代码实践:如何实现一个新的智能体?

理论讲完了,让我们动手实践。假设你要添加一个"Legal(法务)"智能体。

步骤1:创建ASP.NET Core项目

dotnet new webapi -n Legal
cd Legal
dotnet add package A2A.AspNetCore
dotnet add package ModelContextProtocol.Server
dotnet add package Microsoft.Extensions.AI.OpenAI

步骤2:定义MCP工具

// Legal/Program.cs
using ModelContextProtocol.Server;
using ModelContextProtocol.Protocol;

[McpServerToolType]
public class LegalTools
{
    [McpServerTool]
    public Task<CallToolResult> CheckCompliance(string projectType, string region)
    {
        // 模拟合规检查
        var result = new
        {
            ProjectType = projectType,
            Region = region,
            ComplianceStatus = "Compliant",
            RequiredLicenses = new[] { "Business License", "Data Processing Agreement" },
            Risks = new[] { "GDPR compliance required for EU customers" }
        };

        return Task.FromResult(new CallToolResult
        {
            Content = [new TextContentBlock { 
                Text = System.Text.Json.JsonSerializer.Serialize(result) 
            }]
        });
    }
}

步骤3:实现A2A Agent

// Legal/Program.cs
using A2A;
using A2A.AspNetCore;
using Microsoft.Extensions.AI;

public class LegalAgent
{
    private readonly IChatClient _chatClient;

    public LegalAgent(IChatClient chatClient)
    {
        _chatClient = chatClient;
    }

    public void Attach(ITaskManager taskManager)
    {
        taskManager.OnMessageReceived = ProcessMessageAsync;
        taskManager.OnAgentCardQuery = GetAgentCardAsync;
    }

    private async Task<A2AResponse> ProcessMessageAsync(
        MessageSendParams messageSendParams, 
        CancellationToken cancellationToken)
    {
        var messageText = messageSendParams.Message.Parts
            .OfType<TextPart>()
            .FirstOrDefault()?.Text ?? "";

        var messages = new List<ChatMessage>
        {
            new(ChatRole.System, 
                "You are a legal compliance expert. Analyze legal and compliance requirements."),
            new(ChatRole.User, messageText)
        };

        var completion = await _chatClient.GetResponseAsync(
            messages, 
            cancellationToken: cancellationToken);

        return new AgentMessage
        {
            Role = MessageRole.Agent,
            MessageId = Guid.NewGuid().ToString(),
            ContextId = messageSendParams.Message.ContextId,
            Parts = [new TextPart { Text = completion.Text ?? "" }]
        };
    }

    private Task<AgentCard> GetAgentCardAsync(
        string agentUrl, 
        CancellationToken cancellationToken)
    {
        return Task.FromResult(new AgentCard
        {
            Name = "Legal Compliance Expert",
            Description = "Legal expert for compliance analysis and risk assessment",
            Url = agentUrl,
            Version = "1.0.0",
            DefaultInputModes = ["text"],
            DefaultOutputModes = ["text"],
            Capabilities = new AgentCapabilities
            {
                Streaming = false,
                PushNotifications = false
            },
            Skills = []
        });
    }
}

步骤4:启动服务

// Legal/Program.cs
var builder = WebApplication.CreateBuilder(args);

builder.AddServiceDefaults();

// 注册MCP Server
builder.Services.AddMcpServer()
    .WithHttpTransport()
    .WithTools<LegalTools>();

var app = builder.Build();

app.MapDefaultEndpoints();

// 暴露MCP端点
app.MapMcp("/legal/mcp");

// 暴露A2A端点
var legalAgent = new LegalAgent(
    AgentHelpers.CreateChatClient(app.Configuration)
);
var legalTaskManager = new TaskManager();
legalAgent.Attach(legalTaskManager);
app.MapA2A(legalTaskManager, "/legal/compliance-expert");
app.MapWellKnownAgentCard(legalTaskManager, "/legal/compliance-expert");

app.Run();

步骤5:在Aspire中注册

// AgentFrameworkAspire.AppHost/Program.cs
var legalService = builder.AddProject<Projects.Legal>("legal")
    .WithEnvironment("OpenAI__ApiKey", openAiApiKey)
    .WithEnvironment("OpenAI__DeploymentName", openAiDeployment);

builder.AddProject<Projects.AgentFrameworkAspire_Web>("webfrontend")
    // ... 其他引用
    .WithReference(legalService)
    .WaitFor(legalService);

完成! 现在你有了一个完整的Legal智能体,支持MCP工具和A2A通信。


六、最佳实践与注意事项

6.1 MCP工具设计

✅ DO

  • 工具职责单一,每个工具只做一件事
  • 参数类型明确,避免使用object
  • 返回结构化数据,便于AI理解
  • 添加详细的XML注释,会自动生成工具描述

❌ DON'T

  • 工具不要有副作用(如修改数据库)
  • 避免长时间运行的操作(超过10秒)
  • 不要返回大量数据(超过10KB)

6.2 A2A Agent实现

✅ DO

  • AgentCard要准确描述能力
  • 正确处理ContextId,支持多轮对话
  • 使用结构化日志,便于调试
  • 返回有意义的错误消息

❌ DON'T

  • 不要在ProcessMessageAsync中做耗时操作
  • 不要忽略CancellationToken
  • 避免在Agent中保存状态(应该是无状态的)

6.3 Workflow设计

✅ DO

  • 合理划分阶段,每个阶段职责清晰
  • 并行任务要真正独立,没有依赖
  • 使用IAsyncEnumerable实现流式返回
  • 记录每个阶段的耗时,便于性能优化

❌ DON'T

  • 避免过深的嵌套(超过5层)
  • 不要在循环中调用Agent(可能导致成本爆炸)
  • 避免没有超时控制的等待

七、性能与成本优化

7.1 并行加速

// ❌ 串行执行(慢)
var techResult = await CallA2AAgentAsync("Tech", _techAgent, input, ct);
var hrResult = await CallA2AAgentAsync("HR", _hrAgent, input, ct);
var financeResult = await CallA2AAgentAsync("Finance", _financeAgent, input, ct);
// 总时间 = T_tech + T_hr + T_finance

// ✅ 并行执行(快)
var tasks = new[]
{
    CallA2AAgentAsync("Tech", _techAgent, input, ct),
    CallA2AAgentAsync("HR", _hrAgent, input, ct),
    CallA2AAgentAsync("Finance", _financeAgent, input, ct)
};
var results = await Task.WhenAll(tasks);
// 总时间 = max(T_tech, T_hr, T_finance)

7.2 缓存AgentCard

private static readonly ConcurrentDictionary<string, AIAgent> _agentCache = new();

private async Task<AIAgent> GetOrCreateAgentAsync(string url)
{
    if (_agentCache.TryGetValue(url, out var cachedAgent))
    {
        return cachedAgent;
    }

    var agent = await InitializeAgentAsync(url);
    _agentCache.TryAdd(url, agent);
    return agent;
}

7.3 控制AI调用成本

// 使用更便宜的模型进行初步分析
var cheapClient = AgentHelpers.CreateChatClient(config, "gpt-4o-mini");

// 只在关键决策时使用高级模型
var premiumClient = AgentHelpers.CreateChatClient(config, "gpt-4");

八、常见问题(FAQ)

Q1: 为什么不直接用HTTP/REST?

A: A2A和MCP是AI原生的协议,专门为智能体协作设计:

  • AgentCard:AI可以自动理解智能体的能力
  • 标准化消息格式:减少适配工作
  • 上下文管理:内置支持多轮对话
  • 社区生态:未来会有更多工具支持

Q2: 这些协议成熟吗?会不会频繁变化?

A:

  • MCP:由 Anthropic 主导,已有不少项目使用
  • A2A:由 Google Cloud 主导推出的开放标准,Microsoft 提供了 .NET SDK 实现
  • 都是开放标准,社区驱动
  • 建议:代码做好抽象层,协议变化时影响面小

Q3: 性能够吗?HTTP调用会不会太慢?

A:

  • 并行执行可以大幅减少总时间(3-4倍加速)
  • 对于AI应用,瓶颈通常是LLM响应(5-15秒),而非网络(100-300ms)
  • 可以使用gRPC替代HTTP(更快,但生态较小)

Q4: 如何调试分布式的Agent系统?

A:

  • 使用Aspire Dashboard查看所有服务日志
  • 分布式追踪(OpenTelemetry)追踪请求链路
  • 结构化日志记录每个Agent的输入输出
  • 单独启动服务进行单元测试

九、结语

三大协议——MCP、A2A、Agent Framework——共同构建了一个强大的多智能体协作生态:

  • MCP:让每个团队暴露专业工具,AI可以自动发现和调用
  • A2A:让智能体之间"说同一种语言",标准化通信
  • Agent Framework:编排复杂工作流,实现真正的智能协作

关键优势

  1. 互操作性:基于开放标准,不同团队开发的Agent可以无缝协作
  2. 灵活性:可以单独使用MCP/A2A,也可以组合使用
  3. 可扩展性:添加新Agent只需几行代码
  4. 面向未来:这些协议会成为企业AI的基础设施

下一步

  • 克隆项目,运行Demo
  • 尝试添加你自己的智能体
  • 探索更复杂的工作流场景

在下一篇文章中,我们将深入 .NET Aspire,看看如何用它简化多智能体系统的开发和调试。


参考资料

posted @ 2025-10-14 10:34  MadLongTom  阅读(165)  评论(0)    收藏  举报