首页
/ AutoGen .NET Core 入门实战:用 Modifier 与 Checker 两个 Agent 搭建消息驱动倒计时工作流

AutoGen .NET Core 入门实战:用 Modifier 与 Checker 两个 Agent 搭建消息驱动倒计时工作流

2026-09-05 17:57:46作者:姚月梅Lane

本篇基于 AutoGen .NET SDK(Microsoft.AutoGen)官方教程 docs/dotnet/core/tutorial.md 展开,演示如何用 ModifierChecker 两个 Agent 协作完成"从 10 倒数到 1"的倒计时工作流。读完后你将掌握 .NET 版 AutoGen 的三个核心编程要素:强类型消息定义(CountMessage/CountUpdate)、基于 BaseAgent + IHandle<T> + [TypeSubscription] 的 Agent 定义方式,以及 AgentsAppBuilder 组装 in-process 运行时并驱动应用的完整流程。

示例总览:一个消息驱动的倒数工作流

教程的完整示例代码位于 dotnet/samples/GettingStarted 目录(可直接在仓库中查看和运行)。示例的目标很明确:

  • 定义两个 Agent:Modifier 负责修改计数值,Checker 负责校验计数值,并在计数到达 1 时停止应用;
  • 应用启动后先向 default 主题发布一条 CountMessage(初始值 10),之后 ModifierChecker 通过消息互相传递,驱动倒数循环,直到 Checker 判定结束并调用宿主生命周期服务停止应用。

消息流向为:CountMessage(10)Modifier 减一后发布 CountUpdate(9)Checker 判定 9 > 1 通过,回发 CountMessage(9) → ……如此往复,直到 CountUpdate(1) 触发 Checker 停止应用。

开始前需要安装 SDK 的两个核心包(详见 docs/dotnet/core/index.mddocs/dotnet/core/installation.md):

dotnet add package Microsoft.AutoGen.Contracts
dotnet add package Microsoft.AutoGen.Core

第一步:定义强类型消息

教程指出,Agent 之间传递的消息应当先定义为强类型类。示例中使用了两个消息类型:用 CountMessage 传递当前计数值,用 CountUpdate 传递修改后的计数值(CountMessage.cs):

public class CountMessage
{
    public int Content { get; set; }
}
public class CountUpdate
{
    public int NewCount { get; set; }
}

将消息类型拆分为独立的强类型类,好处在于可以构建"Agent 响应某类型消息、产出某类型消息"的确定性工作流:运行时按消息类型精确分发,Agent 只处理自己声明要处理的消息。

第二步:创建 Modifier Agent

继承 BaseAgent

在 AutoGen .NET 中,Agent 是一个可以接收和发送消息的类,它自己定义如何处理收到的消息。定义 Agent 的方式是创建一个继承自 BaseAgentBaseAgent.cs)的类:

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

public class Modifier(
    AgentId id,
    IAgentRuntime runtime,
    ) :
        BaseAgent(id, runtime, "MyAgent", null),
{
}

构造参数说明:AgentIdIAgentRuntime 会由框架在构造时始终传入,并应原样转发给基类构造函数;另外两个参数是 Agent 的描述和一个可选的 ILogger<BaseAgent>AgentIdType(Agent 类型名)与 Key(实例键)组成,是 Agent 在运行时中的身份标识。

实现消息处理器 IHandle<T>

我们希望 Modifier 收到 CountMessage 后修改计数并发布 CountUpdate。这通过实现 IHandle<CountMessage> 接口完成:

public class Modifier(
    // ...
    ) :
        BaseAgent(...),
        IHandle<CountMessage>
{
    public async ValueTask HandleAsync(CountMessage item, MessageContext messageContext)
    {
        // ...
    }
}

MessageContext 携带消息投递时的上下文信息(如发送方、消息 ID 等),来自 MessageContext.cs

添加主题订阅 [TypeSubscription]

仅仅定义了处理器还不够,在消息真正投递给 Agent 之前,还必须让 Agent 订阅消息所在的主题。这通过类上的 TypeSubscription 特性完成:

[TypeSubscription("default")]
public class Modifier(
    // ...

从源码看,TypeSubscriptionAttributeTypeSubscriptionAttribute.cs)实现了 IUnboundSubscriptionDefinition 接口,构造参数就是主题名;框架注册 Agent 时会调用其 Bind(AgentType) 方法,将该 Agent 的具体类型与主题绑定成一个 TypeSubscription。也就是说,[TypeSubscription("default")] 的含义是:该 Agent 的这个类型订阅 default 主题上的消息。同目录下的 TypePrefixSubscriptionAttribute.cs 还提供了按主题前缀订阅的变体。

发布消息

有了处理器和订阅后,就可以在处理函数内部发布结果:

public async ValueTask HandleAsync(CountMessage item, MessageContext messageContext)
{
    int newValue = item.Content - 1;
    Console.WriteLine($"\nModifier:\nModified {item.Content} to {newValue}");

    CountUpdate updateMessage = new CountUpdate { NewCount = newValue };
    await this.PublishMessageAsync(updateMessage, topic: new TopicId("default"));
}

发布消息时需要显式指定目标主题。这里发布到 default 主题,与订阅的是同一个主题——当然也可以发布到别的话题,本教程为保持简单使用了同一主题。

通过构造函数注入配置逻辑

教程进一步把"如何修改计数"做成可配置的:向 Agent 传入一个 Func<int, int> 委托,由外部决定计数逻辑:

using ModifyF = System.Func<int, int>;

[TypeSubscription("default")]
public class Modifier(
    AgentId id,
    IAgentRuntime runtime,
    ModifyF modifyFunc // <-- 新增
    ) :
        BaseAgent(...),
        IHandle<CountMessage>
{
    public async ValueTask HandleAsync(CountMessage item, MessageContext messageContext)
    {
        int newValue = modifyFunc(item.Content); // <-- 在这里使用

        // ...
    }
}

这里的依赖注入能生效,是因为示例程序把 modifyFunc 注册成了单例服务(见下文 Program 部分),AgentsAppBuilder 通过标准 DI 容器解析 Agent 构造参数。

Modifier 最终实现

示例中的最终代码(Modifier.cs):

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

using ModifyF = System.Func<int, int>;

namespace GettingStartedSample;

[TypeSubscription("default")]
public class Modifier(
    AgentId id,
    IAgentRuntime runtime,
    ModifyF modifyFunc
    ) :
        BaseAgent(id, runtime, "Modifier", null),
        IHandle<CountMessage>
{
    public async ValueTask HandleAsync(CountMessage item, MessageContext messageContext)
    {
        int newValue = modifyFunc(item.Content);
        Console.WriteLine($"\nModifier:\nModified {item.Content} to {newValue}");

        CountUpdate updateMessage = new CountUpdate { NewCount = newValue };
        await this.PublishMessageAsync(updateMessage, topic: new TopicId("default"));
    }
}

源码级原理:消息如何被分发到 Handler

结合 BaseAgent.cs 可以理解上述写法背后的机制:

  1. 构造期反射建索引BaseAgent 构造函数调用 ReflectInvokers(),扫描当前类型实现的所有 IHandle<T> / IHandle<T, T2> 泛型接口,提取每个接口的 HandleAsync 方法并包装为 HandlerInvoker,以消息类型 T 为键存入 Dictionary<Type, HandlerInvoker>(见 ReflectInvokers)。
  2. 运行期按类型精确分发:当运行时把消息交给 Agent 时,OnMessageAsyncmessage.GetType() 查表,命中则调用对应的 HandlerInvoker.InvokeAsync,未命中则返回 null 不做处理(见 OnMessageAsync)。
  3. 收发消息统一走 RuntimeBaseAgent.PublishMessageAsync 只是把 sender: this.Id 补上后委托给 IAgentRuntime.PublishMessageAsyncBaseAgent.cs);SendMessageAsync 点对点发送同理。这意味着替换 IAgentRuntime 的实现(例如换成 gRPC 分布式运行时)即可让同一套 Agent 代码运行在分布式环境中。

这也解释了为什么"实现 IHandle<T>"与"加 [TypeSubscription]"缺一不可:前者决定 Agent 能处理什么消息,后者决定 Agent 能收到什么消息。

第三步:创建 Checker Agent

Checker 负责校验计数,并在计数到达 1 时停止应用。这里额外演示了用依赖注入获取 IHostApplicationLifetime 服务来关停应用。完整代码(Checker.cs):

using Microsoft.AutoGen.Contracts;
using Microsoft.AutoGen.Core;
using Microsoft.Extensions.Hosting;
using TerminationF = System.Func<int, bool>;

namespace GettingStartedSample;

[TypeSubscription("default")]
public class Checker(
    AgentId id,
    IAgentRuntime runtime,
    IHostApplicationLifetime hostApplicationLifetime,
    TerminationF runUntilFunc
    ) :
        BaseAgent(id, runtime, "Modifier", null),
        IHandle<CountUpdate>
{
    public async ValueTask HandleAsync(CountUpdate item, MessageContext messageContext)
    {
        if (!runUntilFunc(item.NewCount))
        {
            Console.WriteLine($"\nChecker:\n{item.NewCount} passed the check, continue.");
            await this.PublishMessageAsync(new CountMessage { Content = item.NewCount }, new TopicId("default"));
        }
        else
        {
            Console.WriteLine($"\nChecker:\n{item.NewCount} failed the check, stopping.");
            hostApplicationLifetime.StopApplication();
        }
    }
}

要点:

  • Checker 实现的是 IHandle<CountUpdate>,即它接收的是 Modifier 发布出来的 CountUpdate,与 Modifier 形成消息闭环;
  • 校验逻辑同样是注入的 TerminationF runUntilFuncFunc<int, bool>),返回 true 表示达到终止条件;
  • 校验未通过时,把新计数包装回 CountMessage 发布到 default 主题,交给 Modifier 继续下一轮;校验通过(计数到达 1)时调用 hostApplicationLifetime.StopApplication() 优雅停止宿主应用。

一个小细节值得注意:示例源码中 Checker 传给基类的描述字符串仍是 "Modifier"Checker.cs),这是示例代码里从 Modifier 复制而来的一处笔误,不影响消息分发逻辑(分发依赖 IHandle<T> 与订阅特性,而非描述字符串),但读者在自己实现时应改为 "Checker"

第四步:用 AgentsAppBuilder 组装并运行应用

现在把两个 Agent 组装成一个完整应用(Program.cs)。

定义业务委托

using 之后,先定义两个业务委托——修改函数和终止判定函数:

using ModifyF = System.Func<int, int>;
using TerminationF = System.Func<int, bool>;

ModifyF modifyFunc = (int x) => x - 1;
TerminationF runUntilFunc = (int x) =>
{
    return x <= 1;
};

构建应用

AgentsAppBuilder(定义于 AgentsApp.cs)是应用组装入口,示例中依次做了四件事:

AgentsAppBuilder appBuilder = new AgentsAppBuilder();
appBuilder.UseInProcessRuntime();

appBuilder.Services.TryAddSingleton(modifyFunc);
appBuilder.Services.TryAddSingleton(runUntilFunc);

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

var app = await appBuilder.BuildAsync();
await app.StartAsync();
  • 指定 in-process 运行时UseInProcessRuntime() 使用进程内单进程运行时;
  • 注册业务服务:把两个委托注册为单例,Agent 构造函数即可通过 DI 拿到;
  • 注册 Agent 类AddAgent<T>(key) 按类型注册,key 作为 AgentId 的实例键("Checker""Modifier");
  • 构建并启动BuildAsync() 生成应用,StartAsync() 启动宿主。

发布首条消息并等待退出

应用启动后需要一条消息来触发整个流程:向 Agent 订阅的 default 主题发布初始 CountMessage(值为 10),然后等待应用退出:

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

// Run until application shutdown
await app.WaitForShutdownAsync();

运行后控制台会看到计数从 10 一路倒数到 1,Checker 输出 1 failed the check, stopping. 后应用正常退出。

自己动手试试

教程最后给出了几个练习方向,都可以直接在 dotnet/samples/GettingStarted 上修改验证:

  • 修改初始计数值(Program.cs 中的 Content = 10);
  • 新增一个"递增"的 modifyFunc,让计数向上走(记得同步修改 runUntilFunc 的终止条件);
  • 新增一个只负责打印到控制台的 Agent,替代 Modifier/Checker 自己 Console.WriteLine(提示:定义一个新的消息类型并让对应 Agent 订阅 default 主题即可)。

做这些练习时,正好可以练习本教程的三件套:新消息类型类、IHandle<新类型> 处理器、[TypeSubscription("default")] 订阅。

参考文件

内容 仓库路径
教程原文 docs/dotnet/core/tutorial.md
.NET Core 概念总览 docs/dotnet/core/index.md
安装说明 docs/dotnet/core/installation.md
与 Python 版差异 docs/dotnet/core/differences-from-python.md
示例:消息类型 dotnet/samples/GettingStarted/CountMessage.csdotnet/samples/GettingStarted/CountUpdate.cs
示例:Agent 实现 dotnet/samples/GettingStarted/Modifier.csdotnet/samples/GettingStarted/Checker.cs
示例:程序入口 dotnet/samples/GettingStarted/Program.cs
源码:Agent 基类与消息分发 dotnet/src/Microsoft.AutoGen/Core/BaseAgent.cs
源码:类型订阅特性 dotnet/src/Microsoft.AutoGen/Core/TypeSubscriptionAttribute.cs
源码:应用构建器 dotnet/src/Microsoft.AutoGen/Core/AgentsApp.cs
合同接口 dotnet/src/Microsoft.AutoGen/ContractsIAgentRuntimeIHandleAgentIdTopicIdMessageContext 等)
登录后查看全文
热门项目推荐
相关项目推荐

项目优选

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