从 DLL 到 A2A:C# CAD 插件的智能化转身
对于深耕 CAD 二次开发的 .NET 工程师而言,过去十年的工作流往往围绕着 DLL 加载、COM 互操作以及复杂的内存管理打转。我们习惯了在 C# 中直接调用几何内核的 API,通过指针操作三角形网格,或者在渲染循环中插入自定义的逻辑。然而,随着 AI Agent 生态的爆发式增长,传统的"本地库调用"模式正面临挑战:当你的 CAD 插件需要被一个运行在 Python 环境中的大模型调度器调用,或者需要与另一个负责供应链管理的 Java 智能体协作时,单纯的 DLL 暴露显得力不从心。
这就引出了本文的核心议题:如何利用微软的 Semantic Kernel (SK) 框架,将原本封闭在 C# 进程内的 CAD 几何分析能力,封装为符合 A2A (Agent-to-Agent) 协议的标准化智能体服务。这不仅仅是换个调用方式,更是让传统工业软件融入分布式 AI 生态的关键一步。我们将以一个具体的"薄壁检测"功能为例,展示如何让它从一个本地函数,变身成为网络上可被发现、可被调用的远程 Agent。
为什么 CAD 插件需要 A2A 协议?
在传统的 CAD 二次开发场景中,假设你编写了一个高性能的 C# 插件,能够利用 BVH(层次包围盒)加速算法,在毫秒级内找出 STL 模型中所有厚度小于 0.1mm 的薄壁区域。在单机环境下,这完美无缺:宿主程序加载你的 DLL,调用 GetThinParts() 方法,拿到结果列表。
但在多智能体协作的场景下,这种耦合度极高的方式就成了瓶颈。想象一个跨语言的自动化流程:一个基于 Python 的项目管理 Agent 接收到用户指令"检查这个零件的可制造性",它需要调用你的几何分析能力。如果依赖传统方式,Python 端必须通过复杂的 IPC(进程间通信)、REST API 包装甚至 C++/CLI 混合编译来桥接你的 C# 代码。这不仅增加了部署复杂度,还破坏了智能体之间的松耦合原则。
A2A 协议的出现正是为了解决这个问题。作为谷歌主导的开放标准,A2A 定义了智能体之间如何发现彼此(通过 Agent Card)、如何协商任务(Task)、以及如何交换结果(Artifact)。它不关心你的内部是用 C# 写的还是 Python 写的,只关心你是否遵循了标准的通信契约。
而 Semantic Kernel 在这里扮演了"翻译官"和"适配器"的角色。SK 原生支持将 C# 方法注册为 Plugin,并能轻松对接各种通信协议。通过 SK,我们可以将 CAD 内核的底层几何 API 包装成标准的 SK Plugin,再进一步通过 A2A Server 暴露出去。这样,外部的 Python 调度器只需发送一个标准的 JSON-RPC 请求,就能驱动本地的 C# 几何引擎工作,实现了真正的跨语言、跨平台协作。
核心架构:用 Semantic Kernel 桥接几何内核
要实现这一目标,我们需要构建一个分层架构。底层是现有的 CAD 几何内核(可能是自研的 C++ 库或通过 P/Invoke 调用的原生代码),中间层是 Semantic Kernel 定义的 Plugin,最上层则是符合 A2A 规范的 HTTP 服务端点。
1. 定义几何能力的 C# 接口
首先,我们需要确保底层的几何分析逻辑是清晰且无状态的。假设我们已经有一个经过优化的 GeometryEngine 类,它负责加载模型并执行薄壁检测。为了适配 SK,我们需要将其方法封装为 KernelFunction。
csharp
using Microsoft.SemanticKernel;
using System.ComponentModel;
public class CadGeometryPlugin
{
private readonly GeometryEngine _engine;
public CadGeometryPlugin()
{
// 初始化几何引擎,可能涉及内存池或 BVH 结构的预分配
_engine = new GeometryEngine();
}
[KernelFunction("detect_thin_walls")]
[Description("分析加载的 STL 模型,识别厚度小于指定阈值的薄壁区域。")]
[return: Description("返回包含薄壁三角形 ID 列表及平均厚度的 JSON 字符串。")]
public string DetectThinWalls(
[Description("STL 文件的本地路径或已加载模型的 ID")] string modelSource,
[Description("厚度阈值,单位毫米,例如 0.1")] float maxThicknessMm)
{
// 调用底层高性能几何算法
// 这里假设 LoadModel 和 GetThinParts 是内部优化过的零拷贝实现
var modelId = _engine.LoadModel(modelSource);
var thinParts = _engine.GetThinParts(modelId, maxThicknessMm);
// 序列化为标准 JSON 格式,便于 A2A 协议传输
return System.Text.Json.JsonSerializer.Serialize(new
{
ModelId = modelId,
Threshold = maxThicknessMm,
Count = thinParts.Count,
TriangleIds = thinParts.Select(t => t.Id).ToList(),
AvgThickness = thinParts.Average(t => t.Thickness)
});
}
[KernelFunction("get_model_bounds")]
[Description("获取模型的包围盒信息,用于快速预判尺寸。")]
public string GetModelBounds([Description("模型源")] string modelSource)
{
var bounds = _engine.GetBounds(modelSource);
return System.Text.Json.JsonSerializer.Serialize(bounds);
}
}
这段代码的关键在于 [KernelFunction] 和 [Description] 属性。Semantic Kernel 会读取这些元数据,自动生成函数的描述信息。这些信息至关重要,因为它们将被提取并填入 A2A 协议中的 Agent Card,让远程的 Python Agent 知道这个 C# 服务能做什么,需要什么参数。
2. 构建 A2A 兼容的服务端
有了 Plugin,接下来需要创建一个 HTTP 服务来监听 A2A 请求。虽然 A2A 协议底层基于 JSON-RPC 和 SSE(Server-Sent Events),但利用 SK 的扩展性,我们可以手动构建一个轻量级的 ASP.NET Core 控制器来处理协议握手和任务分发。
A2A 协议的核心交互流程包括:能力发现 (GET /agent/card)、任务提交 (POST /tasks/send)和状态轮询/推送。
csharp
using Microsoft.AspNetCore.Mvc;
using Microsoft.SemanticKernel;
using System.Text.Json;
[ApiController]
[Route("api/a2a")]
public class A2AAgentController : ControllerBase
{
private readonly Kernel _kernel;
private readonly ILogger<A2AAgentController> _logger;
// 模拟任务存储,实际生产环境应使用 Redis 或数据库
private static readonly Dictionary<string, TaskState> _taskStore = new();
public A2AAgentController(Kernel kernel, ILogger<A2AAgentController> logger)
{
_kernel = kernel;
_logger = logger;
}
// 1. 能力发现:返回 Agent Card
[HttpGet("card")]
public IActionResult GetAgentCard()
{
var card = new
{
name = "CAD Geometry Analyst",
description = "专业的 CAD 几何分析智能体,提供薄壁检测、干涉检查和包围盒计算服务。",
url = "https://localhost:5001/api/a2a",
version = "1.0.0",
capabilities = new { streaming = true, pushNotifications = false },
skills = new[]
{
new {
id = "detect_thin_walls",
name = "薄壁检测",
description = "识别模型中厚度低于阈值的区域,适用于 DFM 分析。",
inputModes = new[] { "application/json" },
outputModes = new[] { "application/json" }
},
new {
id = "get_model_bounds",
name = "包围盒计算",
description = "快速获取模型的三维空间范围。",
inputModes = new[] { "application/json" }
}
}
};
return Ok(card);
}
// 2. 任务提交:接收 Remote Agent 的请求
[HttpPost("tasks/send")]
public async Task<IActionResult> SendTask([FromBody] JsonElement request)
{
// 解析 A2A 标准请求结构
var taskId = Guid.NewGuid().ToString();
var method = request.GetProperty("method").GetString();
var parameters = request.GetProperty("params");
// 初始化任务状态
_taskStore[taskId] = new TaskState { Status = "working", Messages = new List<string>() };
try
{
// 映射 A2A 方法名到 SK Plugin 函数名
// 这里需要根据实际协议字段做更严谨的映射逻辑
var functionName = "detect_thin_walls";
var args = parameters.GetProperty("message").GetProperty("parts")[0].GetProperty("text").GetString();
// 反序列化参数以便 SK 调用
var inputArgs = JsonSerializer.Deserialize<Dictionary<string, object>>(args);
// 调用 Semantic Kernel 执行 C# 方法
var result = await _kernel.InvokeAsync<CadGeometryPlugin>(
functionName,
new KernelArguments(inputArgs)
);
// 更新任务状态为完成
_taskStore[taskId].Status = "completed";
_taskStore[taskId].Result = result.ToString();
// 构造 A2A 标准响应
var response = new
{
jsonrpc = "2.0",
id = request.GetProperty("id"),
result = new
{
id = taskId,
status = new { state = "completed" },
artifacts = new[]
{
new {
name = "analysis_result",
parts = new[] { new { type = "text", text = result.ToString() } }
}
}
}
};
return Ok(response);
}
catch (Exception ex)
{
_taskStore[taskId].Status = "failed";
_logger.LogError(ex, "Task execution failed");
return StatusCode(500, new { error = ex.Message });
}
}
// 3. 任务状态查询 (简化版)
[HttpGet("tasks/{taskId}/status")]
public IActionResult GetTaskStatus(string taskId)
{
if (_taskStore.TryGetValue(taskId, out var state))
{
return Ok(new { id = taskId, status = new { state = state.Status }, result = state.Result });
}
return NotFound();
}
}
public class TaskState
{
public string Status { get; set; }
public string Result { get; set; }
public List<string> Messages { get; set; }
}
上述代码展示了如何将 C# 的业务逻辑嵌入到 A2A 的协议框架中。GetAgentCard 方法生成的 JSON 就是智能体的"身份证",任何遵循 A2A 协议的客户端(无论是 Python 脚本还是其他语言的 Agent 框架)都可以通过读取这个 URL 了解到该服务具备"薄壁检测"和"包围盒计算"的能力。而 SendTask 方法则负责接收标准化的任务请求,利用 Semantic Kernel 的动态调用能力执行具体的 C# 代码,并将结果封装回 A2A 格式的 Artifact。
跨语言协作实战:Python 调度器调用 C# 几何服务
为了验证这套架构的有效性,我们可以模拟一个典型的跨语言场景:一个运行在 Python 环境中的主调度器(Client Agent),需要调用上述 C# 服务来分析一个零件。
在 Python 端,无需安装任何 .NET 运行时或复杂的桥接库,只需要一个标准的 HTTP 客户端(如 httpx 或 requests)即可。Python 脚本首先访问 C# 服务的 /card 接口,解析出可用的技能,然后构造符合 A2A 规范的 JSON-RPC 请求。
python
import httpx
import json
async def invoke_cad_agent():
agent_url = "http://localhost:5001/api/a2a"
async with httpx.AsyncClient() as client:
# 1. 发现能力
card_resp = await client.get(f"{agent_url}/card")
card = card_resp.json()
print(f"发现智能体:{card['name']} - {card['description']}")
# 2. 构造任务请求 (模拟 A2A 协议格式)
task_payload = {
"jsonrpc": "2.0",
"id": 1,
"method": "tasks/send",
"params": {
"id": "task-001",
"message": {
"role": "user",
"parts": [{
"type": "text",
"text": json.dumps({
"modelSource": "C:/models/engine_block.stl",
"maxThicknessMm": 0.15
})
}]
}
}
}
# 3. 发送请求并获取结果
print("正在发送薄壁检测任务...")
response = await client.post(f"{agent_url}/tasks/send", json=task_payload)
result = response.json()
if 'result' in result:
artifact = result['result']['artifacts'][0]['parts'][0]['text']
analysis_data = json.loads(artifact)
print(f"检测完成!发现 {analysis_data['Count']} 个薄壁区域。")
print(f"平均厚度:{analysis_data['AvgThickness']:.4f} mm")
else:
print("任务执行失败")
# 运行异步入口
# asyncio.run(invoke_cad_agent())
在这个流程中,Python 端完全不需要知道 C# 内部是如何利用 BVH 加速射线相交检测的,也不需要关心内存池是如何管理的。它只关心输入(文件路径、阈值)和输出(JSON 结果)。这种解耦极大地降低了系统集成的难度。如果未来需要将 C# 服务迁移到 Docker 容器中,或者替换为 C++ 重写的高性能版本,只要保持 A2A 接口不变,Python 调度器无需修改任何代码。
调试技巧与工程化落地建议
将传统的 CAD 插件改造为 A2A 智能体,除了代码层面的转换,还需要注意以下几个工程化细节,以确保服务的稳定性和可维护性。
首先是状态管理与超时处理 。CAD 几何计算(尤其是针对百万级三角面片的模型)可能耗时较长。在 A2A 协议中,推荐使用异步任务模式。上述示例为了简洁展示了同步返回,但在生产环境中,SendTask 应立即返回 submitted 或 working 状态,并通过 SSE(Server-Sent Events)或客户端轮询机制通知任务进度。Semantic Kernel 支持异步函数执行,结合 ASP.NET Core 的后台服务(BackgroundService),可以轻松实现长任务的队列化处理,避免 HTTP 请求超时。
其次是安全性与鉴权 。暴露几何分析能力意味着可能涉及敏感的设计图纸数据。A2A 协议支持多种认证机制,如 Bearer Token 或 OAuth2。在 ASP.NET Core 中间件中,应添加相应的认证策略,确保只有持有有效凭证的 Remote Agent 才能调用 detect_thin_walls 等敏感方法。此外,建议在 Plugin 层增加参数校验逻辑,防止恶意的大文件或非法路径输入导致服务崩溃。
最后是日志与可观察性。在分布式系统中,追踪一个请求从 Python 发起,经过网络传输,进入 C# 内核,再返回结果的全链路至关重要。利用 Semantic Kernel 内置的遥测支持(Telemetry),可以将函数调用的耗时、输入输出摘要等信息发送到 OpenTelemetry 兼容的后端。这对于排查"为什么这次薄壁检测比上次慢了 200ms"这类性能问题非常有帮助。
结语
通过 Semantic Kernel 和 A2A 协议,C# 开发者不再局限于单机版的 DLL 插件开发模式。我们将厚重的几何内核能力轻量化、服务化,使其成为多智能体生态中一个标准的、可被任意语言调用的节点。这不仅延长了传统 CAD 二次开发代码的生命周期,更为工业软件接入未来的自主 AI 系统打开了一扇大门。当下一次你需要让 CAD"听懂"自然语言指令,或者与其他系统的智能体协同工作时,这套架构将是你最坚实的后盾。