首页
/ AutoGen .NET:使用 Protocol Buffers 定义跨进程 Agent 消息类型

AutoGen .NET:使用 Protocol Buffers 定义跨进程 Agent 消息类型

2026-09-06 20:12:04作者:秋阔奎Evelyn

AutoGen .NET 的 Agent 可以在进程内(InProcessRuntime)直接用普通 C# 类通信,但要接入 gRPC 等分布式运行时,所有消息类型就必须以 Protocol Buffers(proto3)定义。本文基于仓库文档 protobuf-message-types.md 讲解这一硬性要求的来源,给出完整的 .proto 集成步骤与 C# 代码写法,并结合 GettingStartedGrpc 示例源码剖析消息在跨进程运行时的完整流转过程。

为什么跨运行时消息必须是 Protocol Buffers 消息

文档 protobuf-message-types.md 开篇即给出约束:

For a message to be sent using a runtime other than the InProcessRuntime, it must be defined as a Protocol Buffers message. This is because the message is serialized and deserialized using Protocol Buffers.

也就是说,只要消息要离开当前进程(例如通过 Microsoft.AutoGen.Core.Grpc 的 gRPC 运行时发送到远程 Worker),消息在传输前会被 Protocol Buffers 序列化、在接收端被反序列化,因此消息类型本身必须由 .proto 文件定义并在构建期生成 C# 类。文档同时说明:这一限制未来可能通过允许 converter、自定义序列化等机制放宽,但在当前版本中它是刚性要求。

可以对比仓库中两个 GettingStarted 示例来验证这一点:

  • 进程内示例 GettingStarted/CountMessage.cs 直接用普通 C# POCO 定义消息:

    namespace GettingStartedSample;
    
    public class CountMessage
    {
        public int Content { get; set; }
    }
    

    该示例通过 appBuilder.UseInProcessRuntime()(见 Program.cs)运行,全程不发生网络序列化,所以不需要 protobuf。

  • 而 gRPC 示例 GettingStartedGrpc/message.proto 将同样的消息改为 proto 定义:

    syntax = "proto3";
    
    option csharp_namespace = "GettingStartedGrpcSample.Events";
    
    message CountMessage {
        int32 content = 1;
    }
    
    message CountUpdate {
        int32 new_count = 1;
    }
    

    其宿主 Program.cs 使用 appBuilder.AddGrpcAgentWorker("http://localhost:50051") 接入远程 Worker,此时消息类型必须来自 proto 生成代码。

在 .NET 项目中集成 Protocol Buffers 的四个步骤

以下是文档给出的完整操作路径,四步缺一不可。

第 1 步:在 .csproj 中引入 Grpc.Tools 包

<PackageReference Include="Grpc.Tools" PrivateAssets="All" />

Grpc.Tools 负责在构建期执行 protoc,把 .proto 文件编译为 C# 消息类(以及可选的 gRPC 客户端/服务端桩代码)。PrivateAssets="All" 表示这是一个纯构建期依赖:它只在当前项目编译时生效,不会传递给引用方,也不会出现在 NuGet 包的依赖列表中。仓库示例 GettingStartedGrpc.csproj 使用了完全相同的写法:

<ItemGroup>
  <PackageReference Include="Grpc.Tools" PrivateAssets="All" />
</ItemGroup>

第 2 步:在项目中声明 .proto 文件

<ItemGroup>
  <Protobuf Include="messages.proto" GrpcServices="Client;Server" Link="messages.proto" />
</ItemGroup>
  • Include 指定要编译的 proto 文件;Link 仅影响解决方案资源管理器中的显示路径,不影响实际位置。
  • GrpcServices 控制是否生成 gRPC 服务代码(Client / Server)。如果你只定义消息类型、不需要服务桩,可以像 GettingStartedGrpc.csproj 那样只写 GrpcServices="Client",甚至可省略该属性,仅生成消息类。

第 3 步:编写 proto3 消息定义

文档给出的最小示例:

syntax = "proto3";

package HelloAgents;

option csharp_namespace = "MyAgentsProtocol";

message TextMessage {
    string Source = 1;
    string Content = 2;
}

按文档指引,字段定义需遵循 Protocol Buffers Language Guide 中引用的 proto3 规范,几个关键点:

  • syntax = "proto3":必须声明 proto3 语法,这是当前 .NET gRPC 工具链支持的版本。
  • option csharp_namespace:指定生成 C# 类的命名空间。文档示例中使用 MyAgentsProtocol;真实示例 message.proto 使用 option csharp_namespace = "GettingStartedGrpcSample.Events";,因此后续 C# 代码中可以直接写 Events.CountMessage 这样的限定名,与示例代码 Checker.cs 中的 IHandle<Events.CountUpdate> 一致。
  • 字段编号从 1 开始且不能重复Source = 1Content = 2 是 wire format 的标签,一经发布即不可更改,新增字段只能追加新编号。
  • proto 类型映射stringint32 等基础类型会映射为 C# 的 stringint(例如 proto 中 int32 content = 1; 生成 C# 属性 int Content { get; set; }),这保证了生成的类可以直接作为业务消息使用。

第 4 步:针对生成的类编写 Agent 代码

文档示例:

using Microsoft.AutoGen.Contracts;
using Microsoft.AutoGen.Core;
using MyAgentsProtocol;

[TypeSubscription("default")]
public class Checker(
    AgentId id,
    IAgentRuntime runtime,
    ) :
        BaseAgent(id, runtime, "MyAgent", null),
        IHandle<TextMessage>
{
    public async ValueTask HandleAsync(TextMessage item, MessageContext messageContext)
    {
        Console.WriteLine($"Received message from {item.Source}: {item.Content}");
    }
}

要点解析:

  • TextMessage 不是手写的,而是构建期由 messages.proto 自动生成的 C# 类,位于 option csharp_namespace 指定的 MyAgentsProtocol 命名空间。

  • IHandle<T> 是 AutoGen 的通用消息处理契约,定义在 IHandle.cs

    public interface IHandle<in T>
    {
        ValueTask HandleAsync(T item, MessageContext messageContext);
    }
    

    Agent 只要实现该接口(T 为 proto 生成的消息类型),运行时就会把匹配的消息分发进来。由于 T 位于逆变位置(in T),handler 也可以接受更宽泛的基类型。

  • HandleAsync 的第二个参数 MessageContext 携带消息元数据,定义见 MessageContext.cs,核心属性包括:MessageId(消息唯一标识)、CancellationToken(取消令牌)、Sender(发送方 AgentId,未指定时为 null)、Topic(消息所属话题)、IsRpc(是否为 RPC 调用)。这些属性在调试跨进程消息流向时非常有用。

  • [TypeSubscription("default")] 声明该 Agent 订阅 default 话题下 TextMessage 类型的所有消息。

实战示例:GettingStartedGrpc 的完整消息流

仓库中的 GettingStartedGrpc 是这套机制的最小可运行示例,完整复现了“修改—校验”循环,消息全程经过 gRPC 序列化。

消息定义message.proto):CountMessage(携带当前计数)与 CountUpdate(携带更新后的计数),均生成到 GettingStartedGrpcSample.Events 命名空间。

Modifier AgentModifier.cs):实现 IHandle<Events.CountMessage>,收到消息后对计数执行注入的 modifyFunc,再发布 CountUpdatedefault 话题:

[TypeSubscription("default")]
public class Modifier(
    AgentId id,
    IAgentRuntime runtime,
    ModifyF modifyFunc
    ) :
        BaseAgent(id, runtime, "Modifier", null),
        IHandle<Events.CountMessage>
{
    public async ValueTask HandleAsync(Events.CountMessage item, MessageContext messageContext)
    {
        int newValue = modifyFunc(item.Content);
        var updateMessage = new Events.CountUpdate { NewCount = newValue };
        await this.PublishMessageAsync(updateMessage, topic: new TopicId("default"));
    }
}

Checker AgentChecker.cs):实现 IHandle<Events.CountUpdate>,若 new_count > 1 则把计数重新包装为 CountMessage 发布回去形成循环;否则调用 hostApplicationLifetime.StopApplication() 终止应用:

public async ValueTask HandleAsync(Events.CountUpdate item, MessageContext messageContext)
{
    if (!runUntilFunc(item.NewCount))
    {
        await this.PublishMessageAsync(new Events.CountMessage { Content = item.NewCount }, new TopicId("default"));
    }
    else
    {
        hostApplicationLifetime.StopApplication();
    }
}

宿主程序Program.cs):

AgentsAppBuilder appBuilder = new AgentsAppBuilder();
appBuilder.AddGrpcAgentWorker("http://localhost:50051");  // 连接远程 gRPC Worker

appBuilder.AddAgent<Checker>("Checker");
appBuilder.AddAgent<Modifier>("Modifier");

var app = await appBuilder.BuildAsync();
await app.StartAsync();

await app.PublishMessageAsync(new GettingStartedGrpcSample.Events.CountMessage
{
    Content = 10
}, new TopicId("default"));

await app.WaitForShutdownAsync();

与进程内版本 GettingStarted/Program.cs 相比,唯一本质差异是 UseInProcessRuntime() 换成了 AddGrpcAgentWorker(...)——Agent 代码(Checker/Modifier)几乎逐行相同,只是消息类型从 POCO 换成了 proto 生成类。这说明:只要消息类型按本文方式定义,同一套 Agent 逻辑可以在进程内与 gRPC 运行时之间平移,代价仅仅是把消息类改为 proto 定义。

项目依赖上,GettingStartedGrpc.csproj 额外引用了三个项目:Microsoft.AutoGen.ContractsIHandle<T>TypeSubscriptionMessageContext 等契约)、Microsoft.AutoGen.Core(Agent 基础类型与进程内能力)、Microsoft.AutoGen.Core.Grpc(gRPC 运行时与 Worker 接入)。

关键限制与注意事项

  1. 适用范围:protobuf 消息要求仅针对 InProcessRuntime 之外的运行时。纯进程内应用可继续使用普通 C# 类(参见 GettingStarted/CountUpdate.cs 等 POCO),不必引入 proto 工具链。
  2. 未来可能放宽:文档明确提示该要求“may be relaxed in future by allowing for converters, custom serialization, or other mechanisms”,即官方预留了 converter / 自定义序列化机制的扩展方向。当前版本请以 proto 定义为唯一合规做法。
  3. csharp_namespace 决定代码组织:所有 C# 端引用(usingIHandle<...> 泛型参数)都依赖该选项;示例将其设为 GettingStartedGrpcSample.Events 后,Agent 代码得以用 Events.CountMessage 这类短限定名引用生成类型。
  4. 字段编号是 wire 契约:proto 字段的数字标签参与二进制编码,修改或复用编号会导致跨版本反序列化错乱,扩展消息时只应追加新字段。
  5. GrpcServices 按需选择:仅定义消息类型时可只生成客户端代码或完全不生成服务桩(示例使用 GrpcServices="Client");文档示例中的 GrpcServices="Client;Server" 适用于 proto 文件中同时定义 service 的场景。

按以上方式,你就可以在 AutoGen .NET 项目中定义一套跨进程可传输的强类型消息:构建期由 Grpc.Tools 生成 C# 类,运行期由 gRPC 运行时完成序列化与分发,Agent 通过 IHandle<T> + [TypeSubscription] 以类型安全的方式订阅并处理消息。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
898
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
921
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
391