AutoGen.NET GroupChat 动态群聊:从 Admin 智能选角到 Graph 工作流编排
在 AutoGen.NET 中,AutoGen.Core.GroupChat 提供了让多个智能体协作解决复杂任务的群聊容器:它既可以完全依赖 admin 智能体基于对话上下文“角色扮演”式地决定下一个发言者,也可以结合 AutoGen.Core.Graph 工作流来约束和控制对话走向,是“动态”与“可控”兼顾的下一步发言者选择方案。读完本文,你能掌握 GroupChat 的三种编排模式及其源码级实现原理,并能完整复现官方“代码解释器”示例——由 admin、coder、reviewer、runner 四个智能体协作计算第 39 个 Fibonacci 数。
一、GroupChat 的三种编排模式
GroupChat 的构造函数(见 GroupChat.cs)决定了群聊由哪个编排器(IOrchestrator)负责挑选下一个发言者。从源码结构看,选择逻辑非常明确:
| 传入参数组合 | 使用的编排器 | 下一步发言者如何确定 |
|---|---|---|
传了 admin(可选同时传 workflow) |
RolePlayOrchestrator |
admin 通过角色扮演提示词从候选中选择 |
只传 workflow |
WorkflowOrchestrator |
完全由 Graph 中的转移条件决定 |
| 两者都不传 | RoundRobinOrchestrator |
按成员列表顺序轮询 |
对应的核心构造逻辑如下:
// dotnet/src/AutoGen.Core/GroupChat/GroupChat.cs(节选)
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();
}
构造函数同时做了三项校验(GroupChat.cs):所有智能体必须有名字、名字必须唯一、且工作流中出现的智能体必须都在群聊成员之中。
Admin 的“角色扮演”选角机制
RolePlayOrchestrator(RolePlayOrchestrator.cs)的选择流程分两步:
- 候选收窄:如果配置了
workflow,先调用workflow.TransitToNextAvailableAgentsAsync(currentSpeaker, chatHistory)从当前发言者出发得到可达智能体列表。若只剩 1 个候选,直接返回,不调用 admin;若超过 1 个,才进入第 2 步。 - admin 角色扮演:把候选名字拼进如下系统提示词,并把整段对话历史改写为
From {名字}: ... <eof_msg> round # {i}的形式发给 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//.
请求参数被刻意收紧为确定性输出:Temperature = 0、MaxToken = 128、StopSequence = [":"](RolePlayOrchestrator.cs),并要求 admin 的回复形如 From coder:,编排器随后截取冒号后的名字(name.Substring(5))在候选中做大小写不敏感匹配;匹配失败会抛出带详细信息的 ArgumentException。
官方文档特别提示:当群聊仅由 admin 决定下一个发言者(没有 Graph 约束)时,建议使用能力更强的模型(如
gpt-4)以获得最佳体验,因为此时选角质量完全依赖 admin 的上下文理解能力。
Graph 工作流:Transition 与转移条件
Graph 与 Transition 定义在 Graph.cs 中。Graph 本质是一个转移边集合,TransitToNextAvailableAgentsAsync(fromAgent, messages) 会筛出所有 From == fromAgent 的边,并逐条调用其 CanTransitionAsync(messages) 谓词,把满足条件的目标智能体作为“下一可达候选”返回(Graph.cs)。
Transition.Create 提供三个重载(Graph.cs):
// 1. 无条件转移(恒为 true)
Transition.Create(admin, coder);
// 2. 带转移条件:根据消息历史返回 true/false
Transition.Create(
from: reviewer,
to: runner,
canTransitionAsync: async (from, to, messages) =>
{
var lastMessage = messages.Last();
return lastMessage is TextMessage t && t.Content.ToLower().Contains("the code looks good");
});
// 3. 额外支持 CancellationToken 的重载
Transition.Create(from, to, (from, to, messages, ct) => ...);
这意味着工作流是“条件化”的:同一个 from 智能体可以连出多条边(例如 reviewer 既能转 runner 也能转 coder),由边上的谓词依据最近消息内容决定实际走哪条路。
WorkflowOrchestrator(WorkflowOrchestrator.cs)则是纯工作流模式:它取最后一条消息的发送者为当前发言者,查 Graph 得到下一可达集合;若集合为空则返回 null(群聊终止),若恰好 1 个则直接返回,若出现多于 1 个候选会抛出异常——即纯工作流模式下,每个状态点必须能唯一确定下一跳。
二、群聊的运行循环与终止信号
群聊主循环在 GroupChat.CallAsync(GroupChat.cs)中:每一轮先由编排器选出下一发言者,调用其 GenerateReplyAsync(conversationHistory),把回复追加进历史;若回复是群聊终止消息则立即返回完整历史,否则消耗 maxRound(默认 10)配额。
终止与清理信号由 GroupChatExtension.cs 定义:消息内容包含 TERMINATE 即判定为终止消息(IsGroupChatTerminateMessage),包含 CLEAR_MESSAGES 时由 MessageToKeep 截断历史。对外使用的流式扩展方法 SendAsync 会循环调用 CallAsync(..., maxRound: 1),逐条 yield return 最新消息,直到终止消息或轮次耗尽,这让调用方可以在消费消息流的过程中随时检测任务完成并提前 break(下面示例正是这么做的)。
此外还有介绍消息扩展(GroupChatExtension.cs):
// 为智能体发送自我介绍,作为群聊的初始上下文(initializeMessages)
public static void SendIntroduction(this IAgent agent, string message, IGroupChat groupChat);
官方文档提示:用 SendIntroduction 为群聊设置初始上下文,能帮助 admin 更好地组织对话流程。
三、实战示例:四智能体“代码解释器”群聊
官方示例 Example07_Dynamic_GroupChat_Calculate_Fibonacci.cs 构建了一个计算“第 39 个 Fibonacci 数”(正确答案 63245986)的动态群聊,成员及职责如下:
- admin:为群组创建任务,并在任务完成时终止对话;
- coder:会写 dotnet 代码的智能体;
- reviewer:代码审查者,检查 coder 的代码是否满足:只有一个 C# 代码块、使用顶层语句、是 dotnet 代码、把结果打印到控制台;
- runner:代码执行者,运行 coder 写出的代码并打印结果。
对话结构可概括为:
flowchart LR
subgraph Group Chat
B[Admin]
C[Coder]
D[Reviewer]
E[Runner]
end
3.1 创建动态群聊(纯 admin 驱动)
示例的 RunAsync 中创建群聊的核心代码(源码 create_group_chat 区域,Example07...cs):
var reviewer = await CreateReviewerAgentAsync(gpt4o);
var coder = await CreateCoderAgentAsync(gpt4o);
var runner = await CreateRunnerAgentAsync(kernel);
var admin = await CreateAdminAsync(gpt4o);
var groupChat = new GroupChat(
admin: admin,
members:
[
coder,
runner,
reviewer,
]);
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);
await foreach (var message in groupChat.SendAsync([taskMessage], maxRound: 10))
{
// 当 runner 的消息中包含答案时终止群聊
if (message.From == "runner" && message.GetContent().Contains(the39thFibonacciNumber.ToString()))
{
Console.WriteLine($"The 39th of fibonacci number is {the39thFibonacciNumber}");
break;
}
}
注意这里没有传 workflow,因此按前文的构造逻辑,群聊完全由 admin 通过角色扮演提示词驱动——这也正是官方建议使用强模型的典型场景。
3.2 四个智能体的实现拆解
admin(create_admin 区域,L123-L135):就是一个 temperature: 0 的 OpenAIChatAgent,叠加 RegisterMessageConnector()(把 AutoGen 消息转换为 Chat Completions 格式)和 RegisterPrintMessage()(打印消息)两个扩展。
coder(create_coder 区域,L52-L80):temperature: 0.4f 的 OpenAIChatAgent,其系统提示词把“代码规范”写成硬约束——代码放在 ```csharp 围栏中、使用顶层语句、避免 using 可释放对象、结果必须打印到控制台、需要 NuGet 包时用 ```nuget 块声明、出错后根据 runner 的报错修复代码重发:
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 your code is incorrect, runner will tell you the error message. Fix the error and send the code again.",
temperature: 0.4f)
.RegisterMessageConnector()
.RegisterPrintMessage();
reviewer(create_reviewer 区域,L137-L227):这是示例中最有技术含量的部分,展示了 AutoGen.NET 的类型安全函数调用机制。先用 [Function] 特性标注一个普通方法(配套的 reviewer_function 区域,L17-L50):
public struct CodeReviewResult
{
public bool HasMultipleCodeBlocks { get; set; }
public bool IsTopLevelStatement { get; set; }
public bool IsDotnetCodeBlock { get; set; }
public bool IsPrintResultToConsole { get; set; }
}
[Function]
public async Task<string> ReviewCodeBlock(
bool hasMultipleCodeBlocks,
bool isTopLevelStatement,
bool isDotnetCodeBlock,
bool isPrintResultToConsole)
{
var obj = new CodeReviewResult { ... };
return JsonSerializer.Serialize(obj);
}
[Function] 特性由 AutoGen.SourceGenerator 在编译期生成 ReviewCodeBlockFunctionContract(函数契约,供模型侧使用)与 ReviewCodeBlockWrapper(参数序列化/反序列化的调用包装),详见 Create-type-safe-function-call.md。reviewer 用 FunctionCallMiddleware 把契约挂给模型:
var functionCallMiddleware = new FunctionCallMiddleware(
functions: [functions.ReviewCodeBlockFunctionContract],
functionMap: new Dictionary<string, Func<string, Task<string>>>()
{
{ nameof(functions.ReviewCodeBlock), functions.ReviewCodeBlockWrapper },
});
var reviewer = new OpenAIChatAgent(chatClient: chatClient, name: "code_reviewer",
systemMessage: @"You review code block from coder")
.RegisterMessageConnector()
.RegisterStreamingMiddleware(functionCallMiddleware)
.RegisterMiddleware(async (msgs, option, innerAgent, ct) =>
{
// 最多重试 3 次,直到模型真正发起 ReviewCodeBlock 工具调用
var maxRetry = 3;
var reply = await innerAgent.GenerateReplyAsync(msgs, option, ct);
while (maxRetry-- > 0)
{
if (reply.GetToolCalls() is var toolCalls && toolCalls.Count == 1
&& toolCalls[0].FunctionName == nameof(ReviewCodeBlock))
{
var reviewResultObj = JsonSerializer.Deserialize<CodeReviewResult>(reply.GetContent());
// 逐项检查:多代码块 / 非 dotnet / 非顶层语句 / 未打印结果
// 有问题则汇总成 "There're some comments from code reviewer, please fix these comments"
// 无问题则返回 "The code looks good, please ask runner to run the code for you."
}
else
{
// 模型没走函数调用:提示词引导它把内容转成函数参数,再试一次
reply = await innerAgent.SendAsync(prompt, msgs, ct);
}
}
throw new Exception("Failed to review code block");
})
.RegisterPrintMessage();
可以看到 reviewer 通过**中间件(middleware)**在 LLM 调用外层实现了“校验—重试—给出结构化审查意见”的确定性控制:审查结论是布尔结构体而非自由文本,四条检查项分别对应不同的修复提示,这使 coder 的修复方向明确可预期。
runner(create_runner 区域,L82-L121):不是 LLM 智能体,而是 DefaultReplyAgent + 自定义中间件构成的“工具型智能体”。它只关心来自 coder 的最后一条消息,用 ExtractCodeBlock("```csharp", "```") 抽出代码,交给 AutoGen.DotnetInteractive 的 dotnet interactive kernel 执行(内置执行能力说明见 Run-dotnet-code.md):
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");
return new TextMessage(Role.Assistant, $"""
[RUNNER_RESULT]
{codeResult}
""") { From = "runner" };
}
...
}
})
.RegisterPrintMessage();
执行结果被包上 [RUNNER_RESULT] 标记返回,失败时错误信息会直接成为 coder 修复代码的输入,形成 coder ↔ reviewer ↔ runner 的闭环。
3.3 进阶:用 Graph 工作流控制对话流程
同一个示例还提供了 RunWorkflowAsync(L229-L330),展示如何用 Graph 把隐式流程变成显式状态机。转移边定义(create_workflow 区域)如下:
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();
// reviewer 说“代码没问题”才允许进入 runner
return lastMessage is TextMessage textMessage
&& textMessage.Content.ToLower().Contains("the code looks good, please ask runner to run the code for you.");
});
var reviewer2CoderTransition = Transition.Create(
from: reviewer, to: coder,
canTransitionAsync: async (from, to, messages) =>
{
var lastMessage = messages.Last();
// reviewer 有审查意见时回到 coder 修复
return lastMessage is TextMessage textMessage
&& textMessage.Content.ToLower().Contains("there're some comments from code reviewer, please fix these comments");
});
var runner2CoderTransition = Transition.Create(
from: runner, to: coder,
canTransitionAsync: async (from, to, messages) =>
{
var lastMessage = messages.Last();
// 运行报错时回到 coder
return lastMessage is TextMessage textMessage && textMessage.Content.ToLower().Contains("error");
});
var runner2AdminTransition = Transition.Create(runner, admin);
var workflow = new Graph(
[
admin2CoderTransition,
coder2ReviewerTransition,
reviewer2RunnerTransition,
reviewer2CoderTransition,
runner2CoderTransition,
runner2AdminTransition,
]);
然后把 workflow 与 admin 同时传给 GroupChat(create_group_chat_with_workflow 区域):
var groupChat = new GroupChat(
admin: admin,
workflow: workflow,
members: [admin, coder, runner, reviewer]);
这种“admin + workflow”组合对应 RolePlayOrchestrator 的完整行为:优先按 Graph 收窄候选(本例中每条转移条件互斥,几乎总能唯一确定下一跳,因此很少真正调用 admin);只有当 Graph 给出多个候选时才交给 admin 仲裁。相比纯 admin 模式,这种写法把“reviewer 通过 → runner”“reviewer 打回 → coder”“运行报错 → coder”等关键流转固化成了代码,对话走向更可预测、可测试。
四、关键要点回顾
GroupChat构造时按admin/workflow的组合自动选择编排器:admin →RolePlayOrchestrator(角色扮演提示词,Temperature=0、MaxToken=128、以:停止、要求输出From 名字:格式);仅 workflow →WorkflowOrchestrator(唯一下一跳,多候选直接报错);都不传 →RoundRobinOrchestrator(轮询)。相关实现见 GroupChat.cs、Orchestrator 目录。- 纯 admin 驱动时建议用强模型(官方 NOTE 建议如
gpt-4);示例当前使用LLMConfiguration.GetOpenAIGPT4o_mini()。 SendIntroduction提供群聊初始上下文;SendAsync流式返回消息,配合TERMINATE终止信号可提前结束群聊。Transition.Create的canTransitionAsync谓词让工作流“条件化”,Graph.TransitToNextAvailableAgentsAsync负责按当前发言者求下一可达集合。- 示例展示了完整的工程组合拳:类型安全函数调用(
[Function]+ Source Generator)、中间件实现的审查重试逻辑、dotnet interactive 内核执行代码片段。 - 群聊/编排器的单元测试可参考 Orchestrator 测试 与 GroupChat 测试。
相关文档:Group-chat-overview.md、Roundrobin-chat.md、Use-graph-in-group-chat.md、Create-type-safe-function-call.md、Run-dotnet-code.md。
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 StartedRust0622
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
