首页
/ AutoGen.NET 群聊机制详解:RoundRobinGroupChat 与 GroupChat 动态群聊的原理、Graph 工作流与实战示例

AutoGen.NET 群聊机制详解:RoundRobinGroupChat 与 GroupChat 动态群聊的原理、Graph 工作流与实战示例

2026-09-04 16:10:31作者:昌雅子Ethen

AutoGen.NET 中的群聊(Group Chat)是组织多个 Agent 在同一上下文下协作完成给定任务的基础能力,核心抽象为 IGroupChat 接口及其两个主要实现:按轮询顺序发言的 RoundRobinGroupChat,以及支持“LLM 管理员 + 图工作流”动态决策下一位发言者的 GroupChat。本文基于仓库文档与源码,完整讲解群聊的两种形态、下一位发言者的决策链路、Graph/Transition 工作流机制,并复现示例中“计算第 39 个斐波那契数”的四 Agent 动态群聊实战方案,帮助你掌握如何在 AutoGen.NET 中搭建可控的多 Agent 协作流程。

AutoGen 动态群聊运行效果示意

什么是群聊:IGroupChat 的核心约定

在 AutoGen 中,群聊的本质是:多个 Agent 共享同一段对话历史,每轮由“调度逻辑”选出下一位发言者,其回复被追加进历史,直到任务完成或达到轮数上限。这一约定集中在 IGroupChat 接口中:

public interface IGroupChat
{
    /// <summary>
    /// Send an introduction message to the group chat.
    /// </summary>
    void SendIntroduction(IMessage message);

    Task<IEnumerable<IMessage>> CallAsync(IEnumerable<IMessage>? conversation = null, int maxRound = 10, CancellationToken ct = default);
}

从接口定义可以看出三个关键契约:

  • SendIntroduction:向群聊注入初始化消息(即“自我介绍”),这些消息会作为对话历史的起点参与后续所有轮次;
  • CallAsync:驱动群聊运行,maxRound 默认值为 10,用于限制最大发言轮数,防止群聊无限进行;
  • 返回值是 IEnumerable<IMessage>,即包含初始化消息与新增消息在内的完整对话历史。

GroupChat 基类CallAsync 实现了主循环:每一轮构造 OrchestrationContext(候选成员 + 对话历史),调用编排器选出下一位发言者,执行 GenerateReplyAsync 并把结果追加进历史;若某条消息命中终止信号则立即返回(详见后文“群聊终止控制”一节)。

两种群聊形态:RoundRobinGroupChat 与 GroupChat

官方文档 Group-chat-overview 指出,AutoGen 提供两种群聊:

RoundRobinGroupChat:轮询式群聊

RoundRobinGroupChat 按固定的轮询顺序依次调用各个 Agent,聊天历史加上上一位 Agent 的最新回复会传给下一位 Agent。从 RoundRobinGroupChat 的源码看,它只是 GroupChat 的一个薄封装,没有传入 admin,也没有 workflow,因此按构造函数逻辑(下文详述)会落到轮询编排器:

/// <summary>
/// A group chat that allows agents to talk in a round-robin manner.
/// </summary>
public class RoundRobinGroupChat : GroupChat
{
    public RoundRobinGroupChat(
        IEnumerable<IAgent> agents,
        List<IMessage>? initializeMessages = null)
        : base(agents, initializeMessages: initializeMessages)
    {
    }
}

轮询式群聊适合流程固定、每个 Agent 职责单一且无需根据对话内容调整发言顺序的场景(例如“策划 → 写作 → 校对”的流水线)。值得注意的是,RoundRobinGroupChat 的父类中还保留了一个已标记 [Obsolete]SequentialGroupChat,它同样继承自 RoundRobinGroupChat,源码注释明确要求使用 RoundRobinGroupChat,迁移旧代码时应统一替换。

GroupChat:动态群聊

GroupChat 提供了“更动态但同样可控”的下一位发言者决策方式,可以:

  • 仅使用一个 LLM Agent 作为 group admin,由它根据对话上下文智能决定下一位发言者;
  • 仅使用一张 Graph 工作流,以规则驱动的方式确定流转路径;
  • 两者结合:先用工作流圈定候选集合,再由 admin 在候选中做最终裁决。

文档中的建议:当 GroupChat 仅使用 group admin 决定下一位发言者时,建议使用更强的模型(如 gpt-4 级别)作为 admin,以保证决策质量。这一建议在源码层面有对应体现——admin 的发言者决策调用固定使用 Temperature = 0 以降低随机性(见下文编排器分析)。

下一位发言者如何被决定:三个编排器

GroupChat 的构造函数是理解整个机制的关键。GroupChat.cs 中有两个构造重载,第一个展示了 admin 与 workflow 如何组合成编排器:

public GroupChat(
    IEnumerable<IAgent> members,
    IAgent? admin = null,
    IEnumerable<IMessage>? initializeMessages = null,
    Graph? workflow = null)
{
    this.admin = admin;
    this.agents = members.ToList();
    this.initializeMessages = initializeMessages ?? new List<IMessage>();
    this.workflow = workflow;

    if (admin is not null)
    {
        this.orchestrator = new RolePlayOrchestrator(admin, workflow);
    }
    else if (workflow is not null)
    {
        this.orchestrator = new WorkflowOrchestrator(workflow);
    }
    else
    {
        this.orchestrator = new RoundRobinOrchestrator();
    }

    this.Validation();
}

从这段构造逻辑看,编排器的选择遵循明确的优先级:

传入参数 选用的编排器 决策方式
admin(可选传 workflow RolePlayOrchestrator 工作流先筛候选,LLM admin 在候选中做角色扮演式裁决
workflow WorkflowOrchestrator 完全由工作流转场规则决定
都不传 RoundRobinOrchestrator 轮询顺序

也就是说,RoundRobinGroupChat 实际上是“不传 admin 与 workflow 的 GroupChat”。构造函数还会执行 Validation:所有成员必须有名称且名称唯一;若提供了 workflow,则 workflow 中出现的所有 Agent 都必须是群聊成员,否则抛出 ArgumentException

第二个构造重载则允许完全自定义编排器(传入 IOrchestrator 实例),为需要更复杂调度策略的场景留出了扩展点。

RolePlayOrchestrator:admin 决策的底层机制

RolePlayOrchestratorGetNextSpeakerAsync 揭示了 admin 决策的完整链路:

  1. 候选收敛:若配置了 workflow,先根据上一位发言者调用 TransitToNextAvailableAgentsAsync 得到工作流允许的候选集,并与群聊成员求交集;
  2. 短路返回:候选集为空则本轮结束(返回 null,群聊主循环随之终止);候选集只剩一个则直接返回,不再消耗一次 LLM 调用
  3. 角色扮演裁决:候选多于一个时,向 admin 发送如下系统提示:
You are in a role play game. Carefully read the conversation history and carry on the conversation.
The available roles are:
{候选者名称列表,逗号分隔}

Each message will start with 'From name:', e.g:
From {第一个候选}:
//your message//.
  1. 响应解析:admin 被要求以 From 名称: 的格式回答,解析时执行 name!.Substring(5) 去掉 "From " 前缀,再与候选名称做大小写不敏感的精确匹配;若 admin 的回答不在候选列表或格式不符,直接抛出 ArgumentException

调用参数在 源码 中固定为:

var response = await this.admin.GenerateReplyAsync(
    messages: messages,
    options: new GenerateReplyOptions
    {
        Temperature = 0,
        MaxToken = 128,
        StopSequence = [":"],
        Functions = null,
    },
    cancellationToken: cancellationToken);
  • Temperature = 0:让裁决尽可能确定,减少同一上下文的漂移;
  • MaxToken = 128:发言者名称很短,限制输出长度防止冗余;
  • StopSequence = [":"]:模型输出到冒号即停止,恰好截取 From 名称 这一行。

对话历史在送入 admin 前会经过 GroupChatExtension.ProcessConversationsForRolePlay 处理:每条消息被重写为 From {来源}:\n{内容}\n<eof_msg>\nround # {序号} 的形式,保证 admin 能清晰分辨每条消息的归属与轮次。

WorkflowOrchestrator:纯规则驱动的流转

当不提供 admin 时,WorkflowOrchestrator 完全依赖工作流:它取最后一条消息的来源作为“当前发言者”,调用 TransitToNextAvailableAgentsAsync 计算可流转的下一跳 Agent,并与群聊成员求交集。候选为空则结束群聊;候选恰好一个则返回该 Agent;候选多于一个时抛出异常(因为纯工作流模式下无法在多个合法后继间做选择)。这一约束提示:仅用 workflow 时,每个节点在给定消息状态下的合法后继应当是唯一的,否则应配合 admin 使用。

Graph 与 Transition:用图描述 Agent 之间的流转

Graph 是工作流的数据结构,由一组 Transition(转场)组成:

public class Graph
{
    public Graph(IEnumerable<Transition>? transitions = null) { ... }

    public void AddTransition(Transition transition) { ... }

    public IEnumerable<Transition> Transitions => transitions;

    /// <summary>
    /// Get the next available agents that the messages can be transit to.
    /// </summary>
    public async Task<IEnumerable<IAgent>> TransitToNextAvailableAgentsAsync(IAgent fromAgent, IEnumerable<IMessage> messages, CancellationToken ct = default);
}

TransitToNextAvailableAgentsAsync 的语义是:找出所有“从 fromAgent 出发”的转场,逐一执行其 CanTransitionAsync 谓词,把通过校验的 To Agent 收集为候选集合。

Transition 提供三个静态工厂方法(源码):

// 无条件的转场:只要从 from 发言过,就允许流转到 to
public static Transition Create<TFromAgent, TToAgent>(TFromAgent from, TToAgent to)
    where TFromAgent : IAgent
    where TToAgent : IAgent
{
    return new Transition(from, to, (fromAgent, toAgent, messages, _) => Task.FromResult(true));
}

// 带条件谓词的转场:根据当前对话历史决定该转场是否生效
public static Transition Create<TFromAgent, TToAgent>(
    TFromAgent from, TToAgent to,
    Func<TFromAgent, TToAgent, IEnumerable<IMessage>, Task<bool>> canTransitionAsync)
    where TFromAgent : IAgent
    where TToAgent : IAgent;

// 带 CancellationToken 的条件转场重载(签名同上,附加 ct 参数)

canTransitionAsync 默认恒为 true,因此 Transition.Create(from, to) 等价于“无条件允许流转”;传入谓词后,可以在流转时检查最新消息内容、轮次等状态,实现“有错误才回退给 coder”这类分支逻辑。注意谓词返回 false 时该转场会被静默跳过,如果某节点的所有出边都不满足条件,工作流返回空候选,群聊随之终止——这一点在设计分支时要留意。

实战示例:计算第 39 个斐波那契数的动态群聊

仓库示例 Example07_Dynamic_GroupChat_Calculate_Fibonacci.cs 展示了完整的动态群聊:由 admincoderreviewerrunner 四个 Agent 协作计算第 39 个斐波那契数(期望结果 63245986)。官方文章 Group-chat 对同一示例有逐段拆解,可作为延伸阅读。

四个 Agent 的角色与职责

  • adminOpenAIChatAgenttemperature: 0,负责任务下达,并在任务完成时终止对话。仅凭 admin 驱动的动态群聊中,它就是唯一的调度者;
  • coder:一个 dotnet 编码 Agent,其系统提示词约束了代码风格(单个 csharp 代码块、top-level statements、打印结果到控制台等);
  • reviewercode_reviewer):代码审查 Agent,通过一个类型安全的 Function 检查代码块是否满足四条规则;
  • runner:代码执行 Agent,基于 Dotnet Interactive 内核执行 coder 产出的代码并回传结果。

coder 的系统提示词摘录(示例文件 L58-L73)展示了如何用提示词约束 Agent 输出格式:

var coder = new OpenAIChatAgent(
    chatClient: client,
    name: "coder",
    systemMessage: @"You act as dotnet coder, you write dotnet code to resolve task. Once you finish writing code, ask runner to run the code for you.

    Here're some rules to follow on writing dotnet code:
    - put code between ```csharp and ```
    - Avoid adding `using` keyword when creating disposable object. e.g `var httpClient = new HttpClient()`
    - Try to use `var` instead of explicit type.
    - Try avoid using external library, use .NET Core library instead.
    - Use top level statement to write code.
    - Always print out the result to console. Don't write code that doesn't print out anything.

    If you need to install nuget packages, put nuget packages in the following format:
    ```nuget
    nuget_package_name
    ```

    If your code is incorrect, runner will tell you the error message. Fix the error and send the code again.",
    temperature: 0.4f)
    .RegisterMessageConnector()
    .RegisterPrintMessage();

runner 并非 LLM Agent,而是 DefaultReplyAgent 加一段中间件:从 coder 的最新消息中提取代码块,交给内核执行,并把结果包裹在 [RUNNER_RESULT] 标记中返回(示例文件 L83-L121):

var runner = new DefaultReplyAgent(
    name: "runner",
    defaultReply: "No code available.")
    .RegisterMiddleware(async (msgs, option, agent, _) =>
    {
        if (msgs.Any() || msgs.All(msg => msg.From != "coder"))
        {
            return new TextMessage(Role.Assistant, "No code available. Coder please write code");
        }
        else
        {
            var coderMsg = msgs.Last(msg => msg.From == "coder");
            if (coderMsg.ExtractCodeBlock("```csharp", "```") is string code)
            {
                var codeResult = await kernel.RunSubmitCodeCommandAsync(code, "csharp");
                codeResult = $"""
                [RUNNER_RESULT]
                {codeResult}
                """;
                return new TextMessage(Role.Assistant, codeResult) { From = "runner" };
            }
            else
            {
                return new TextMessage(Role.Assistant, "No code available. Coder please write code");
            }
        }
    })
    .RegisterPrintMessage();

reviewer 则演示了“LLM 判断 + 函数校验 + 重试”的组合模式:它注册了 FunctionCallMiddleware 携带 ReviewCodeBlockFunctionContract,当 LLM 未按约定发起工具调用时,中间件会以 maxRetry = 3 的上限反复提示其转换输出为函数参数,最终将四项检查结果(是否多个代码块、是否 top-level、是否 dotnet 代码、是否打印结果)序列化为 JSON 并生成审查意见(示例文件 L137-L227)。

用 Graph 定义流转规则

示例的“带工作流”版本(RunWorkflowAsync)显式定义了四条边,其中三条带条件谓词,把“审查通过/驳回”“运行成功/报错”的分支逻辑编码进图里(示例文件 L244-L299):

var admin2CoderTransition = Transition.Create(admin, coder);
var coder2ReviewerTransition = Transition.Create(coder, reviewer);
var reviewer2RunnerTransition = Transition.Create(
    from: reviewer,
    to: runner,
    canTransitionAsync: async (from, to, messages) =>
{
    var lastMessage = messages.Last();
    if (lastMessage is TextMessage textMessage && textMessage.Content.ToLower().Contains("the code looks good, please ask runner to run the code for you.") is true)
    {
        // ask runner to run the code
        return true;
    }
    return false;
});
var reviewer2CoderTransition = Transition.Create(
    from: reviewer,
    to: coder,
    canTransitionAsync: async (from, to, messages) =>
{
    var lastMessage = messages.Last();
    if (lastMessage is TextMessage textMessage && textMessage.Content.ToLower().Contains("there're some comments from code reviewer, please fix these comments") is true)
    {
        // ask coder to fix the code based on reviewer's comments
        return true;
    }
    return false;
});
var runner2CoderTransition = Transition.Create(
    from: runner,
    to: coder,
    canTransitionAsync: async (from, to, messages) =>
{
    var lastMessage = messages.Last();
    if (lastMessage is TextMessage textMessage && textMessage.Content.ToLower().Contains("error") is true)
    {
        // ask coder to fix the error
        return true;
    }
    return false;
});
var runner2AdminTransition = Transition.Create(runner, admin);

var workflow = new Graph(
[
    admin2CoderTransition,
    coder2ReviewerTransition,
    reviewer2RunnerTransition,
    reviewer2CoderTransition,
    runner2CoderTransition,
    runner2AdminTransition,
]);

这条链路的流转语义是:admin → coder → reviewer,之后 reviewer 依据自己的结论文案在“回给 coder 修改”和“放行给 runner 执行”之间分支;runner 再依据输出中是否含 error 在“回给 coder 修复”和“交还 admin”之间分支。注意谓词依赖 Agent 输出中的固定短语(如 the code looks good...),这与 reviewer 中间件生成的文案严格对应——条件谓词与 Agent 的提示词/固定输出必须保持一致,否则分支永远不会命中。

创建群聊、注入介绍并驱动对话

最终组装群聊并运行(示例文件 L302-L329):

var groupChat = new GroupChat(
    admin: admin,
    workflow: workflow,
    members:
    [
        admin,
        coder,
        runner,
        reviewer,
    ]);
admin.SendIntroduction("Welcome to my group, work together to resolve my task", groupChat);
coder.SendIntroduction("I will write dotnet code to resolve task", groupChat);
reviewer.SendIntroduction("I will review dotnet code", groupChat);
runner.SendIntroduction("I will run dotnet code once the review is done", groupChat);
var task = "What's the 39th of fibonacci number?";

var taskMessage = new TextMessage(Role.User, task, from: admin.Name);
await foreach (var message in groupChat.SendAsync([taskMessage], maxRound: 10))
{
    // teminate chat if message is from runner and run successfully
    if (message.From == "runner" && message.GetContent().Contains(the39thFibonacciNumber.ToString()))
    {
        Console.WriteLine($"The 39th of fibonacci number is {the39thFibonacciNumber}");
        break;
    }
}

要点说明:

  • SendIntroductionGroupChatExtension 提供的扩展方法,它把消息以 Role.User + From = agent.Name 的形式追加到群聊的初始化消息列表,作为对话历史的前缀——这些介绍会被 ProcessConversationsForRolePlay 一并格式化后送给 admin,帮助其理解每个成员的角色;
  • SendAsyncIAsyncEnumerable<IMessage> 异步流(源码),每产生一条新消息就 yield 一次,因此宿主代码可以边接收边判断是否提前结束(示例中以 runner 输出包含期望结果 63245986 作为终止条件,break 退出循环);
  • maxRound: 10 限制了群聊最多运行 10 轮,与 CallAsync 的默认值一致,是防止 Agent 互相“空转”的兜底。

示例还提供了“纯 admin 驱动”的版本(RunAsyncL346-L375):不传 workflow,仅由 admin 依据角色扮演提示选择下一位发言者。这正是文档建议采用更强模型的场景。

群聊终止控制与消息管理

群聊如何优雅结束由两类“控制消息”机制支撑,定义在 GroupChatExtension

public const string TERMINATE = "[GROUPCHAT_TERMINATE]";
public const string CLEAR_MESSAGES = "[GROUPCHAT_CLEAR_MESSAGES]";
  • 终止IsGroupChatTerminateMessage 检测消息内容是否包含 [GROUPCHAT_TERMINATE]GroupChat.CallAsync 每轮发言后检查该标记,命中即返回完整历史;SendAsync 同样在 yield 前检查并 yield break。因此任何 Agent(如示例中的 admin)只要在自己的回复中输出该标记,就能主动结束群聊;
  • 清理历史MessageToKeep 依据 [GROUPCHAT_CLEAR_MESSAGES] 标记从对话历史中截断旧消息——若存在多个清理标记,仅保留倒数第二个标记之后的内容。这一机制让群聊在长对话中可以“阶段性遗忘”,控制上下文长度(注意 ProcessConversationForAgent 已标记为 [Obsolete],但 MessageToKeepProcessConversationsForRolePlay 仍在 RolePlay 链路中实际使用)。

选型建议与使用限制

结合文档建议与源码实现,可以归纳如下选型原则:

  1. 流程固定 → 用 RoundRobinGroupChat,零 LLM 调度开销,顺序可预期;
  2. 分支明确、可用规则表达 → 用 GroupChat + Graph(无 admin,走 WorkflowOrchestrator)。此时要保证同一节点在给定消息状态下至多只有一个合法后继,否则运行时抛出“multiple available agents”异常;
  3. 需要语义级调度(如“根据对话进展自由决定谁该说话”)→ 传 admin,并遵循文档建议使用能力较强的模型;Temperature = 0MaxToken = 128StopSequence = [":"] 的参数组合意味着 admin 只需输出 From 名称 一行,提示词设计应确保 Agent 名称简短、无歧义;
  4. 既要可控又要灵活admin + workflow 组合:工作流先收敛候选(省调用、控边界),admin 只在存在多个候选时介入裁决。

使用限制方面:所有成员 Agent 必须有非空且唯一的名称(Validation 强制校验);workflow 中出现的所有 Agent 必须包含在 members 中;SendAsyncmaxRoundCallAsyncmaxRound 共同构成轮数上限;admin 的裁决若返回非候选名称会直接抛异常,生产环境中可考虑对 admin 模型或提示词做加固。完整的动态群聊拆解可进一步参考 Group-chat 文档示例源码,类型安全函数调用可查阅 Create type safe function call,代码执行能力可查阅 Run dotnet code

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

项目优选

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