AutoGen .NET HelloAgent 样例实战:事件驱动 Agent、IHandle 处理链与本地/gRPC 两种 App Runtime 启动方式
本文基于 AutoGen 仓库中的 HelloAgent 样例(dotnet/samples/Hello/HelloAgent/README.md),完整讲解 AutoGen 0.4 .NET SDK 的"Hello World":如何用 TypeSubscription 订阅主题、用 IHandle<T> 编写事件处理器、用 AgentsAppBuilder 在本地进程内或 gRPC 网关两种模式下启动 App Runtime,并通过发布 NewMessageReceived → ConversationClosed → Shutdown 消息链驱动一次完整的会话。读完后,你将能够独立编写一个可监听事件、发布事件、自我关闭的最小 AutoGen .NET Agent 应用,并理解其底层反射式处理器注册与消息分发机制。
样例定位与前置条件
HelloAgent 是 AutoGen .NET(仓库 dotnet/ 目录)最小的完整示例,它演示了 AutoGen 0.4 分布式 Agent 模型的两个核心概念:
- 事件驱动的 Agent:Agent 通过订阅某个 Topic(主题)来"等待"事件;收到事件后,运行时调用对应的处理器;
- 可编程的 App Runtime:SDK 提供
Microsoft.AutoGen.Agents.App(源码中为AgentsApp/AgentsAppBuilder)运行时,既可以在本地进程内启动(InProcess Runtime),也可以作为 gRPC Worker 连接到分布式网关。
运行该样例需要 .NET 8.0 或更高版本。从工程文件 HelloAgent.csproj 可以看到,项目目标框架为 net8.0,并引用了 Microsoft.Extensions.Hosting(宿主生命周期管理)、Google.Protobuf 与 Grpc.Tools(gRPC 代码生成),同时通过 ProjectReference 依赖 SDK 的三个核心工程:
<ProjectReference Include="..\..\..\src\Microsoft.AutoGen\Contracts\Microsoft.AutoGen.Contracts.csproj" />
<ProjectReference Include="..\..\..\src\Microsoft.AutoGen\Core.Grpc\Microsoft.AutoGen.Core.Grpc.csproj" />
<ProjectReference Include="..\..\..\src\Microsoft.AutoGen\Core\Microsoft.AutoGen.Core.csproj" />
并直接以 <Protobuf> 项引入了 SDK 内置的事件 proto 文件(注意该 proto 位于 SDK 源码目录,而非样例目录):
<Protobuf Include="..\..\..\src\Microsoft.AutoGen\Agents\protos\agent_events.proto" Link="protos\agent_events.proto" />
配置文件 appsettings.json 仅用于调整日志级别:
{
"Logging": {
"LogLevel": {
"Default": "Warning",
"Microsoft": "Information",
"Microsoft.Orleans": "Warning"
}
}
}
其中 Microsoft.Orleans 一项印证了从源码结构可推断的信息:gRPC 网关一侧的消息注册与订阅状态托管在 Orleans 之上(见 RuntimeGateway.Grpc/Services/Orleans)。
运行样例
按 dotnet/samples/Hello/HelloAgent/README.md 给出的步骤:
# Clone the repository
gh repo clone microsoft/autogen
cd dotnet/samples/Hello
dotnet run
即克隆仓库后进入 dotnet/samples/Hello/HelloAgent 执行 dotnet run(目录级说明见 dotnet/samples/Hello/README.md)。该样例还支持命令行参数,完整参数说明可从 Program.cs 的 PrintHelp 方法得到:
HelloAgent [--host <hostAddress>] [--nosend]
--host 使用 <hostAddress> 处的 gRPC 网关;也可以改用 AGENT_HOST 环境变量
--nosend 不发送初始消息。意味着 HelloAgent 会一直等待其他 Agent
发送该消息。注意:搭配 InProcessRuntime 时此选项不可用。
- 不传
--host(且未设置AGENT_HOST环境变量)时,使用进程内运行时,消息在本地直接投递; - 传入
--host <address>或设置AGENT_HOST时,使用 gRPC 运行时,样例以 gRPC Worker 身份连接到分布式网关; --nosend用于分布式演示场景(本进程只等别人发消息),但在进程内运行时没有外部消息源,因此 Program.cs 会直接打印警告并以非零码退出,避免程序挂起。
除单进程直接运行外,仓库还提供了基于 .NET Aspire 的 App Host(dotnet/samples/Hello/Hello.AppHost),可一并启动 Agent 与后端服务并在 Aspire Dashboard 中查看遥测与日志;此外还有 dotnet/samples/GettingStartedGrpc/ 演示了如何把 Agent 部署为 gRPC Worker 并与 Host 协作。
完整事件流:一条消息链驱动"问候—告别—关闭"
HelloAgent 的整体行为可以概括为一条三段式消息链,对应 HelloAgent.cs 中的三个处理器:
%%{init: {'theme':'forest'}}%%
graph LR;
A["Program.cs<br/>PublishMessageAsync(NewMessageReceived('Hello World!'))"] --> B{"HandleAsync(NewMessageReceived, MessageContext)"}
B --> |"PublishMessageAsync(ConversationClosed('Goodbye'))"| C{"HandleAsync(ConversationClosed, MessageContext)"}
C --> |"PublishMessageAsync(Shutdown())<br/>(未设置 STAY_ALIVE_ON_GOODBYE 时)"| D{"HandleAsync(Shutdown, MessageContext)"}
D --> E{"IHostApplicationLifetime.StopApplication()"}
各环节的具体行为:
- 入口:
Program.cs构建并启动 App 后,向主题HelloTopic发布NewMessageReceived { Message = "Hello World!" }(Program.cs L58-L62); - 收到消息:
HelloAgent的HandleAsync(NewMessageReceived, ...)将消息打印到控制台,随后向同一主题发布ConversationClosed,其中UserId取自this.Id.Type、UserMessage = "Goodbye"(HelloAgent.cs L23-L33); - 收到告别:第二个处理器打印
"{userId} said Goodbye";除非设置了环境变量STAY_ALIVE_ON_GOODBYE=true(用于让分布式演示中进程保持存活),否则继续发布Shutdown消息(HelloAgent.cs L34-L43); - 收到关闭:第三个处理器调用注入的
IHostApplicationLifetime.StopApplication()停止宿主,Program.cs中的WaitForShutdownAsync()随即返回,进程退出(HelloAgent.cs L45-L49、Program.cs L69)。
值得注意的是,第 2、3 步发布的消息最终都由 HelloAgent 自己接收——这正是 Program.cs 中进程内运行时配置 deliverToSelf: true 的作用:允许 Agent 投递消息给自身(Program.cs L52)。
编写事件处理器:TypeSubscription 与 IHandle 注册机制
README 指出:"AutoGen 应用的核心是事件处理器。Agent 选择一个 TopicSubscription 来监听某个主题上的事件;当事件到达时,Agent 的处理器被调用;处理器内部还可以 emit(发布)新事件,交给事件总线供其他 Agent 处理"。消息类型(EventTypes)是以 gRPC ProtoBuf 消息定义的,默认消息集位于 Microsoft.AutoGen.Contracts 命名空间,proto 定义见 dotnet/src/Microsoft.AutoGen/Agents/protos/agent_events.proto 与根目录 protos/(agent_worker.proto 与 cloudevents 协议)。
当前样例中,订阅通过类上的 [TypeSubscription] 特性声明,处理器通过实现 IHandle<T> 接口注册(注意:README 早期版本写作 TopicSubscription,当前代码为 TypeSubscription):
[TypeSubscription("HelloTopic")]
public class HelloAgent(
IHostApplicationLifetime hostApplicationLifetime,
AgentId id,
IAgentRuntime runtime,
Logger<BaseAgent>? logger = null) : BaseAgent(id, runtime, "Hello Agent", logger),
IHandle<NewMessageReceived>,
IHandle<ConversationClosed>,
IHandle<Shutdown>
{
// 捕获 Program.cs 中发布的消息
public async ValueTask HandleAsync(NewMessageReceived item, MessageContext messageContext)
{
Console.Out.WriteLine(item.Message); // 打印到控制台
ConversationClosed goodbye = new ConversationClosed
{
UserId = this.Id.Type,
UserMessage = "Goodbye"
};
// 发布 ConversationClosed,将由自身的 ConversationClosed 处理器处理
await this.PublishMessageAsync(goodbye, new TopicId("HelloTopic"));
}
// ... ConversationClosed 与 Shutdown 处理器同理
}
处理器是如何被自动发现和分发的
IHandle<T> 接口定义在 dotnet/src/Microsoft.AutoGen/Contracts/IHandle.cs,存在两种重载:
public interface IHandle<in T>
{
ValueTask HandleAsync(T item, MessageContext messageContext);
}
public interface IHandle<in InT, OutT>
{
ValueTask<OutT> HandleAsync(InT item, MessageContext messageContext);
}
所谓"在构造函数/类型声明中用 IHandle 注册事件类型",其底层实现在基类 BaseAgent 中:构造函数调用 ReflectInvokers(),通过反射扫描当前 Agent 类型实现的所有 IHandle<> / IHandle<,> 泛型接口,取出每个接口的 HandleAsync 方法并包装为 HandlerInvoker,建立起 "消息类型 → 处理器" 的字典(BaseAgent.cs L60-L81)。当运行时把消息投递给 Agent 时,OnMessageAsync 按消息的运行时类型查表并调用对应处理器,查不到则静默返回 null(BaseAgent.cs L83-L93)。这意味着:
- 你只需声明接口 + 实现方法,无需任何显式注册代码;
- 一个 Agent 可以同时处理任意多种消息类型,互不干扰;
- 未处理的消息类型不会报错,而是被忽略——这对事件总线场景是有意为之的设计。
发布消息同样由基类统一提供:PublishMessageAsync(object message, TopicId topic, ...) 转发给 IAgentRuntime.PublishMessageAsync,SendMessageAsync 则面向指定收件 Agent 做点对点投递(BaseAgent.cs L95-L104)。
继承与组合:复用基类能力
README 在"Inheritance and Composition"一节强调,该样例同时展示了 AutoGen 的继承机制(早期版本中 HelloAgent 继承自提供 WriteConsole 方法的 ConsoleAgent;当前仓库代码中则直接继承 BaseAgent,控制台打印逻辑内联在处理器中)。无论继承链如何演变,设计意图一致:业务 Agent 通过继承基类获得"发布消息、点对点发送、元数据、日志"等横切能力,自身只专注事件语义。
从源码结构看,BaseAgent 还暴露了 Metadata(由 AgentId.Type、AgentId.Key 与 Description 组成)和 ActivitySource(用于 OpenTelemetry 链路追踪),这些都会随 Agent 注册到运行时(参见 Contracts/AgentMetadata.cs)。HelloAgent 通过主构造函数把 AgentId、IAgentRuntime、IHostApplicationLifetime、Logger 全部以依赖注入方式传入——由 AddAgent<HelloAgent>("HelloAgent") 时由宿主解析构造。SDK 的 Agents 包 还预置了 InferenceAgent(AI 推理)、IHandleConsole / IHandleFileIO(IO 处理)等可组合构件,体现了同一套"基类 + 接口组合"思路。
启动 App Runtime:进程内与 gRPC 两种模式
Program.cs 展示了如何用 AgentsAppBuilder 一条链完成"选运行时 → 注册 Agent → 构建 → 启动 → 发消息 → 等关闭":
var appBuilder = new AgentsAppBuilder(); // 创建 app builder
bool usingGrpc = false;
if (hostAddress is string agentHost)
{
// 分布式模式:连接 AGENT_HOST / --host 指定的 gRPC 网关
usingGrpc = true;
appBuilder.AddGrpcAgentWorker(agentHost)
.AddAgent<HelloAgent>("HelloAgent");
}
else
{
// 进程内运行时:允许 Agent 给自己投递消息,并注册 HelloAgent
appBuilder.UseInProcessRuntime(deliverToSelf: true).AddAgent<HelloAgent>("HelloAgent");
}
var app = await appBuilder.BuildAsync(); // 构建 app
await app.StartAsync();
if (sendHello)
{
var message = new NewMessageReceived { Message = "Hello World!" };
await app.PublishMessageAsync(message, new TopicId("HelloTopic")).ConfigureAwait(false);
}
await app.WaitForShutdownAsync().ConfigureAwait(false); // 等待 Agent 触发关闭
两条分支的关键差异:
| 维度 | UseInProcessRuntime(deliverToSelf: true) |
AddGrpcAgentWorker(address) |
|---|---|---|
| 部署形态 | 单机单进程,消息在内存总线中直接投递 | 以 gRPC Worker 身份连接分布式网关 |
| 消息投递 | 可投递给自己(deliverToSelf),支撑"自我事件链" |
经由网关的事件总线跨进程投递 |
| 适用场景 | 本地开发、单元测试 | 多 Agent 分布式编排(配合 RuntimeGateway) |
gRPC 分支的实现位于 AgentsAppBuilderExtensions.cs:它注册 AgentRpc.AgentRpcClient,地址解析顺序为显式参数 > AGENT_HOST 配置项 > 默认 http://localhost:53071,并把 GrpcAgentRuntime 作为 IAgentRuntime 与 IHostedService 注入(即 Worker 连接随宿主生命周期自动连接/断开),同时配置了带退避重试(最多 5 次、初始 1s、最大 5s、1.5 倍递增)的 gRPC 通道,应对网关瞬时不可用。
关于消息的封装,README 说明"消息遵循 CloudEvents 规范包装后发送到事件总线":SDK 在网关侧将 Protobuf 消息包装为 CloudEvent(见 Core.Grpc/CloudEventExtensions.cs 及根目录 protos/cloudevent.proto),这也是消息能够跨进程、跨语言(Python/.NET 互操作)传递的基础。
定义自定义消息:proto + csproj 配置
AutoGen 中可发布的消息集合由 proto 文件定义,gRPC 工具将其编译为 C# 类。样例内置了 NewMessageReceived、ConversationClosed、Shutdown 等消息(agent_events.proto),例如:
message NewMessageReceived {
string message = 1;
}
message ConversationClosed {
string user_id = 1;
string user_message = 2;
}
message Shutdown {
string message = 1;
}
若要定义自己的消息类型,只需新建 .proto 文件并在工程中引入 gRPC 工具。README 给出的示例(字段取自 dev-team 样例的消息集,真实定义见 dotnet/samples/dev-team/Protos/messages.proto):
syntax = "proto3";
package devteam;
option csharp_namespace = "DevTeam.Shared";
message NewAsk {
string org = 1;
string repo = 2;
string ask = 3;
int64 issue_number = 4;
}
message ReadmeRequested {
string org = 1;
string repo = 2;
int64 issue_number = 3;
string ask = 4;
}
对应 .csproj 配置(HelloAgent 自身的 HelloAgent.csproj L17-L31 正是这种写法的实例):
<ItemGroup>
<PackageReference Include="Google.Protobuf" />
<PackageReference Include="Grpc.Tools" PrivateAssets="All" />
<Protobuf Include="..\Protos\messages.proto" Link="Protos\messages.proto" />
</ItemGroup>
其中 PrivateAssets="All" 表示 Grpc.Tools 仅参与本工程的代码生成、不随包传递;<Protobuf Include> 的相对路径可指向任意位置,Link 只影响解决方案资源管理器中的展示路径。定义完成后,新的消息类型即可像 NewMessageReceived 一样被 PublishMessageAsync 发布、被 IHandle<T> 处理。
小结
HelloAgent 样例用不到百行代码完整覆盖了 AutoGen .NET SDK 的最小闭环:[TypeSubscription] 声明主题订阅、IHandle<T> 声明事件处理器(由 BaseAgent 的反射机制自动注册)、BaseAgent.PublishMessageAsync 发布事件、AgentsAppBuilder 在进程内/gRPC 两种模式下启动运行时,并以消息驱动的方式优雅关闭宿主。理解这个"Hello World"之后,替换掉 Console.WriteLine、接入 InferenceAgent 或 IO Agent,就可以按同一套模式扩展出真正的分布式多 Agent 应用;仓库中的 dotnet/samples/GettingStartedGrpc/ 与 dotnet/samples/dev-team/ 分别演示了 gRPC Worker 部署和完整的团队级 Agent 后端,可作为下一步的进阶材料。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00