从零搭建 CAD 智能体集群,A2A 协议全流程解析

从单点智能到集群协作:CAD 智能体的进化之路

在传统的 CAD 二次开发中,我们习惯了"点击按钮 - 执行命令"的线性交互。用户想要倒角,就点击倒角工具;想要统计面积,就运行面积统计插件。这种模式在单一任务场景下行之有效,但当面对复杂的工程设计------比如"找出所有厚度小于 0.1mm 的薄壁零件并生成修复建议报告"时,传统的脚本往往显得力不从心。它需要串联几何分析、规则判断、文档生成等多个环节,代码耦合度高且难以维护。

随着大语言模型(LLM)的爆发,AI Agent(智能体)为 CAD 软件带来了新的交互范式。但单个 Agent 的能力终究有限,真正的生产力飞跃来自于多个专业 Agent 的协同工作。这就引出了我们今天的主角:A2A(Agent-to-Agent)。本文将带你从零开始,构建一个基于 A2A 协议的 CAD 智能体集群,让不同的智能体像工程师团队一样分工协作,共同解决复杂的工程问题。

基础设施搭建:用 Elastic Agent Builder 打造数据大脑

构建智能体集群的第一步,是让 Agent 拥有"记忆"和"查询"能力。在 CAD 场景中,这通常意味着能够检索设计规范、历史案例或特定的几何参数。我们将利用 Elastic Agent Builder 快速创建一个具备数据查询能力的 Hello World Agent,作为集群中的"知识库节点"。

首先,你需要一个 Elastic Serverless 项目。登录 Elastic Cloud 创建新项目后,进入 Developer Tools 控制台。我们需要创建一个索引来存储文档数据,这里我们定义一个包含语义搜索能力的索引 my-docs

json 复制代码
PUT /my-docs
{
  "mappings": {
    "properties": {
      "title": { "type": "text" },
      "content": { "type": "semantic_text" },
      "filename": { "type": "keyword" },
      "last_modified": { "type": "date" }
    }
  }
}

索引创建完成后,注入一条测试数据。这条数据模拟了一份关于"问候语规范"的设计文档,后续我们的 Agent 将依据此文档进行回复:

json 复制代码
PUT /my-docs/_doc/greetings-md
{
  "title": "Greetings",
  "content": "# Greetings\n## Basic Greeting\nHello!\n## Helloworld\nGreeting Hello World! 🌎\n## Not Greeting\nI'm only a greeting agent. 🤷",
  "filename": "greetings.md",
  "last_modified": "2025-11-04T12:00:00Z"
}

数据就位后,核心步骤是创建 Tool (工具)。在 Agent Builder 中选择 New Tool,类型设为 ES|QL。我们将定义一个名为 example.get_greetings 的工具,其作用是从索引中提取特定的问候语文档。配置如下 ES|QL 查询语句:

sql 复制代码
FROM my-docs | WHERE filename == "greetings.md"

保存工具后,接着创建 Agent 。在 New Agent 表单中,设定 Agent ID 为 helloworld_agent,并在 Custom Instructions 中写入业务逻辑:如果用户输入包含"Hi"或"Hello",则返回文档中的基础问候语;若包含"Hello World",则返回对应的特定文本;否则返回默认提示。最关键的一步是在 Tools 标签页中,仅勾选刚才创建的 example.get_greetings 工具。

至此,一个能够理解自然语言并查询 Elastic 数据的独立 Agent 已经诞生。你可以在 Agent Builder 的聊天窗口中输入"hello world"进行测试,它会精准地返回预设的响应。但这只是单机智能,接下来我们要让它融入集群。

协议核心解析:读懂 Agent Card 与 Task 生命周期

要让上述 Elastic Agent 与其他 CAD 专用 Agent(如几何计算 Agent、渲染 Agent)协作,必须遵循统一的通信标准,即 A2A 协议 。A2A 的核心在于两个概念:Agent Card (能力名片)和 Task(任务对象)。

Agent Card:智能体的身份证

每个接入 A2A 网络的 Agent 都必须发布一个 JSON 格式的 Agent Card。这不仅是身份标识,更是能力说明书。Client 端通过读取这张"名片",决定何时调用该 Agent。一个标准的 CAD 几何分析 Agent 的 Card 可能长这样:

json 复制代码
{
  "name": "CAD Geometry Analyzer",
  "description": "Analyzes STL models for thin walls and intersections",
  "url": "https://cad-cluster.local/geometry-agent",
  "version": "1.0.0",
  "capabilities": {
    "streaming": true,
    "pushNotifications": false
  },
  "skills": [
    {
      "id": "thin-wall-detection",
      "name": "Thin Wall Detection",
      "description": "Identifies triangles with thickness below threshold",
      "inputModes": ["application/json"],
      "outputModes": ["application/json"]
    },
    {
      "id": "ray-intersection",
      "name": "Ray Intersection",
      "description": "Calculates intersection points for a given ray",
      "inputModes": ["application/json"],
      "outputModes": ["application/json"]
    }
  ]
}

注意 skills 字段,它详细列出了该 Agent 能做什么。在集群中, orchestrator(编排者)会根据这些技能描述,自动将"检查薄壁"的任务分发给这个 Agent,而不是发给只负责查文档的 Elastic Agent。

Task 生命周期:协作的节拍器

A2A 中的协作不是简单的请求 - 响应,而是基于 Task 的状态机管理。一个 Task 拥有明确的生命周期状态:submitted(已提交)、working(处理中)、input-required(需要人工介入)、completed(完成)、failed(失败)。

当 Client 发起任务时,Remote Agent 不会立即返回最终结果,而是先返回一个 Task 对象,状态标记为 working。随后,Agent 可以通过 SSE(Server-Sent Events)流式推送中间状态或日志。这种机制对于耗时的 CAD 几何计算尤为重要------用户不必盯着旋转的光标等待,而是能实时看到"正在加载模型"、"正在构建 BVH"、"检测到 12 个薄壁面"等进度信息。

深度集成:将 C# 几何内核注册为网络服务

在 CAD 领域,核心的几何算法(如布尔运算、网格简化、射线检测)通常由高性能的 C++ 或 C# 代码实现。如何将这些本地能力暴露给 A2A 集群?我们需要构建一个 Geometry API Service,将其封装为符合 A2A 标准的 Remote Agent。

假设我们已经有一个经过优化的 C# 几何内核,支持零拷贝加载 STL 文件和基于 BVH 的射线相交检测。现在,我们要为其包裹一层 HTTP 服务,使其成为集群中的"几何专家"。

1. 定义数据结构与接口

首先,确保几何接口清晰且与渲染逻辑解耦。定义基础结构如 PointRayIntersection,并提供统一的 API 类:

csharp 复制代码
public class GeometryAPI 
{
    private ObjectPool<Triangle> _trianglePool;
    private BVH _bvh;

    public bool LoadModel(string filename) 
    {
        // 零拷贝加载逻辑,直接填充内存池
        if (!ParseSTLToPool(filename, _trianglePool)) return false;
        _bvh = new BVH(_trianglePool.GetAll());
        _bvh.Build();
        return true;
    }

    public List<Intersection> GetIntersectingPoints(Ray ray) 
    {
        if (_bvh == null) return new List<Intersection>();
        var hits = _bvh.Intersect(ray);
        // 按距离排序并返回
        return hits.OrderBy(h => h.Distance).Select(h => h.ToIntersection()).ToList();
    }
    
    public List<Triangle> GetThinParts(float maxThickness) 
    {
        // 遍历内存池,计算厚度并筛选
        return _trianglePool.GetAll()
            .Where(t => ComputeThickness(t) <= maxThickness)
            .ToList();
    }
}

2. 实现 A2A 服务端

接下来,使用 ASP.NET Core 创建一个 Web API,实现 A2A 协议要求的端点。你需要处理 /agent/card 请求以返回前述的 JSON 名片,并实现 /tasks/send 来处理任务提交。

在处理 tasks/send 时,服务端需解析传入的 JSON-RPC 请求。例如,当收到 thin-wall-detection 技能调用时,实例化 GeometryAPI,执行 GetThinParts 方法,并将结果封装成 A2A 标准的 Artifact 返回。

csharp 复制代码
[HttpPost("/tasks/send")]
public async Task<IActionResult> SendTask([FromBody] A2ATaskRequest request) 
{
    var taskId = Guid.NewGuid().ToString();
    var skillId = request.Params.Message.Parts.First().SkillId;

    // 异步启动任务,立即返回 Task 对象(状态:working)
    _ = Task.Run(() => ProcessGeometryTask(taskId, skillId, request.Params));

    return Ok(new A2ATaskResponse 
    {
        Id = taskId,
        Status = new TaskStatus { State = "working" },
        SessionId = request.Params.SessionId
    });
}

在这个后台任务 ProcessGeometryTask 中,调用 C# 几何内核进行实际计算。计算完成后,通过 SSE 通道推送最终结果,或将状态更新为 completed 并附带包含检测结果的 Artifact。这样,无论底层是 C#、Python 还是 Go 实现的 Agent,只要遵循 A2A 协议,就能无缝对话。

集群编排:使用 Microsoft Agent Framework 串联工作流

有了数据查询 Agent(Elastic)和几何计算 Agent(C#),我们需要一个"指挥官"来协调它们。Microsoft Agent Framework 提供了强大的编排能力,允许我们在 Python 或 C# 中定义复杂的工作流。

以下是一个基于 Python 的编排示例,展示如何让两个 Agent 协作完成"查询规范并检测模型"的任务:

python 复制代码
import asyncio
from a2a.client import A2ACardResolver
from agent_framework.a2a import A2AAgent

async def main():
    # 1. 发现集群中的 Agent
    resolver = A2ACardResolver(base_url="http://cad-cluster.local")
    
    # 获取几何 Agent 的名片
    geo_card = await resolver.get_agent_card("/geometry-agent.json")
    geo_agent = A2AAgent(agent_card=geo_card)
    
    # 获取文档 Agent 的名片
    doc_card = await resolver.get_agent_card("/helloworld_agent.json")
    doc_agent = A2AAgent(agent_card=doc_card)

    # 2. 定义协作流程
    user_prompt = "检查 engine.stl 是否有薄壁,并参考问候语文档格式输出报告"
    
    # 第一步:调用几何 Agent 进行检测
    print(">> 调用几何分析 Agent...")
    geo_response = await geo_agent.run({
        "file": "engine.stl",
        "threshold": 0.1
    })
    thin_parts = geo_response.artifacts[0].data
    
    # 第二步:调用文档 Agent 获取报告模板
    print(">> 调用文档规范 Agent...")
    doc_response = await doc_agent.run("How to format a report?")
    template = doc_response.messages[0].text
    
    # 第三步:本地整合结果(或由第三个总结 Agent 完成)
    final_report = f"{template}\n\n检测结果:发现 {len(thin_parts)} 处薄壁风险。"
    print(f"\n最终报告:\n{final_report}")

if __name__ == "__main__":
    asyncio.run(main())

在这个流程中,Microsoft Agent Framework 充当了 Client 角色,它不需要知道几何计算的具体算法,也不需要关心文档存储在哪个 Elastic 索引中。它只需要根据 Agent Card 找到对应的服务,发送标准化的 Task,然后处理返回的 Artifact。这种解耦使得我们可以随时替换底层的几何引擎(比如从 C# 换成 Rust 实现),或者升级文档数据库,而无需修改上层业务逻辑。

异常处理与生产级考量

在实际落地中,网络波动、计算超时或参数错误不可避免。A2A 协议通过 Task 的状态机制提供了天然的容错基础。

当几何计算耗时过长导致 HTTP 超时时,服务端不应直接断开连接,而应保持 Task 状态为 working,并通过心跳包维持会话。若计算过程中发现模型文件损坏,Agent 应将 Task 状态更新为 failed,并在 Message 中附带详细的错误码(如 ERR_INVALID_STL),以便上游编排者决定是重试、跳过还是通知人工介入。

此外,安全性不容忽视。在生产环境中,Agent Card 应包含认证方案(如 OAuth2 或 API Key),并在 HTTP 头中校验权限。对于涉及核心设计数据的 CAD 集群,建议启用 HTTPS 并配置严格的 Roots 策略,限制 Agent 只能访问指定的文件目录或数据库表,防止越权操作。

从单个脚本到智能体集群,CAD 开发的范式正在发生深刻变化。通过 A2A 协议,我们将分散的工具、数据和算法连接成了一个有机的整体。无论是 Elastic 的数据检索,还是 C# 的高性能几何计算,都能在统一的协作框架下发挥最大价值。未来,随着更多专业 Agent 的加入,这个集群将不仅能执行命令,更能自主规划、协同创新,真正成为工程师的得力助手。