MAF 入门(5):多 Agent 编排全解------Sequential / Concurrent / Handoff / GroupChat
写在前面:一个 Agent 不够用了(非常重要)
前面几篇,我们学会了让一个 Agent 对话、调工具、记历史、挂中间件。现在咱们把多个agent放一块编排,这篇博客主要以一个实际业务中的例子切入,让你快速学习agent编排。现在有一个电商的工单需要处理,会有不通角色的AI来分析这个工单。
「这个工单既要查物流,又要看政策,还要评估退款比例------让一个 Agent 全包,要么回答又长又散,要么顾此失彼。」
这不是模型「不够聪明」,而是分工的问题。人类客服中心也不会让一个人同时扮演分拣员、法务、财务------大家各管一摊,再按流程协作。
Microsoft Agent Framework(MAF) 在 Microsoft.Agents.AI.Workflows 里提供了四种常见的多 Agent 编排模式:
| 模式 | 一句话 | 生活类比 |
|---|---|---|
| Concurrent | 多个 Agent 同时处理同一输入 | 三位专家同时看同一份病历 |
| Sequential | 多个 Agent 按顺序接力 | 写稿 → 改稿 → 终审 |
| Handoff | Agent 主动转交给更合适的同事 | 前台分拣 → 专科医生 |
| GroupChat | 多个 Agent 轮流发言协商 | 科室会诊圆桌 |
本文用同一笔电商客服工单 (订单 ORD-8842,签收第 8 天、礼盒角部压损、要求全额退款 ¥299)贯穿四种模式。你可以把它想象成:同一位委屈的客户,公司用四种不同的「内部流程」来应对------每种流程适合不同的业务场景。
一、场景设定:一笔「烫手」的工单
Demo 把背景故事集中在 SupportScenario.cs:
csharp
public const string OrderId = "ORD-8842";
public const string TicketSummary =
"""
工单 #{0}:客户签收第 8 天反馈课程礼盒角部压损,要求全额退款 ¥299。
会员银卡,物流显示已签收,商品为 P002《.NET AI 开发课程》实体礼盒。
争议点:已超过 7 天无理由退货窗口,但客户主张运输途中挤压损坏。
""";
争议点很典型:超期 vs 运输损坏 ,政策、物流、质检各有一套说法。这正是多 Agent 编排发挥价值的场景------不是让一个大模型「硬想」,而是让角色清晰的小专家分别贡献视角,再按流程合成结论。
二、Workflow 执行模型:四种模式共用的「发动机」
不管选哪种编排,跑起来的代码骨架是一样的。以 Concurrent 为例:
csharp
// ① 用 AgentWorkflowBuilder 组装 Workflow
Workflow workflow = AgentWorkflowBuilder.BuildConcurrent(/* ... */);
// ② 准备输入
var input = new List<ChatMessage> { new(ChatRole.User, prompt) };
// ③ 启动流式执行
await using StreamingRun run = await InProcessExecution.RunStreamingAsync(
workflow, input, cancellationToken: cancellationToken);
// ④ 发送 TurnToken ------ 告诉引擎「可以开始本轮了」
await run.TrySendMessageAsync(new TurnToken(emitEvents: true));
// ⑤ 订阅事件流
await foreach (WorkflowEvent evt in run.WatchStreamAsync(cancellationToken))
{
// 根据事件类型处理输出
}
2.1 三个关键类型
| 类型 | 作用 |
|---|---|
Workflow |
编排好的「流程图」,由 Builder 构建 |
InProcessExecution |
进程内执行器,适合 Demo 和单机场景 |
StreamingRun |
一次运行实例,可发消息、读事件流 |
2.2 常见事件
Demo 里主要处理三类:
AgentResponseUpdateEvent--- 某个 Agent 流式输出的文字片段(Sequential / Handoff / GroupChat 控制台逐字打印靠它)WorkflowOutputEvent--- Workflow 最终输出(Concurrent 汇总结果靠它,避免多 Agent 并行时控制台文字交错)WorkflowErrorEvent--- 执行出错
扩展知识 :生产环境还可以把 Workflow 部署到支持 MAF 的宿主(如 Azure、自定义 Worker),
InProcessExecution只是最轻量的本地跑法。事件驱动模型意味着你可以把输出接到 SignalR、SSE、日志系统,而不只是Console.Write。
2.3 创建 Agent 的统一方式
所有角色都通过 SupportAgentsFactory 从同一个 IChatClient 创建------和前面几篇一样,底层模型不变,变的只是 Instructions 和 Tools:
csharp
public static class SupportAgentsFactory
{
private static AIAgent Create(
IChatClient chatClient,
string id,
string name,
string description,
string instructions,
IList<AITool>? tools = null) =>
chatClient.AsAIAgent(new ChatClientAgentOptions
{
Id = id,
Name = name,
Description = description,
ChatOptions = new ChatOptions
{
Instructions = instructions,
Tools = tools,
},
});
// ── Concurrent:三专家并行 ──
public static AIAgent CreateLogisticsAnalyst(IChatClient client) =>
Create(client, "logistics", "物流分析师", "分析签收、运输损坏与物流责任",
"你是物流分析师。只分析物流与签收责任,简洁中文,3-5句。");
public static AIAgent CreatePolicyAnalyst(IChatClient client) =>
Create(client, "policy", "政策分析师", "分析退换货政策与合规边界",
"你是退换货政策分析师。只分析政策合规与可执行方案,简洁中文,3-5句。");
public static AIAgent CreateQualityAnalyst(IChatClient client) =>
Create(client, "quality", "质检分析师", "分析商品损坏程度与责任归属",
"你是商品质检分析师。只分析损坏性质与举证建议,简洁中文,3-5句。");
// ── Sequential:结构化 → 草稿 → 润色 ──
public static AIAgent CreateTicketStructurer(IChatClient client) =>
Create(client, "structurer", "工单结构化专员", "把多方意见整理为结构化纪要",
"""
你是工单结构化专员。将输入整理为:
1. 问题摘要 2. 各方观点 3. 待决事项
只输出结构化纪要,不要写给客户的话术。
""");
public static AIAgent CreateReplyDrafter(IChatClient client) =>
Create(client, "drafter", "回复草稿专员", "根据结构化纪要撰写客服回复草稿",
"你是客服文案专员。根据上游纪要,写一段发给客户的回复草稿,语气专业友善,150字内。");
public static AIAgent CreateReplyReviewer(IChatClient client) =>
Create(client, "reviewer", "回复质检专员", "润色草稿并输出最终可发送版本",
"你是质检专员。润色上游草稿,输出最终可发送给客户的回复,保留关键承诺与时限。");
// ── Handoff:分拣 → 专员(带工具)──
public static AIAgent CreateTriageAgent(IChatClient client) =>
Create(client, "triage", "分拣专员", "判断工单类型并转交订单/退货/退款专员",
"""
你是客服分拣专员。判断用户诉求后,必须 handoff 给合适专员处理,不要自己编造订单数据。
物流/订单状态 → 订单专员;退货流程 → 退货专员;退款审批 → 退款专员。
""");
public static AIAgent CreateOrderAgent(IChatClient client)
{
IList<AITool> tools = [AIFunctionFactory.Create(SupportTools.CheckOrderStatus)];
return Create(client, "order", "订单专员", "查询订单物流状态并解释签收情况",
"你是订单专员。先调用 CheckOrderStatus 查订单,再简洁回答用户。", tools: tools);
}
public static AIAgent CreateReturnAgent(IChatClient client) =>
Create(client, "return", "退货专员", "处理退货流程与举证要求",
"你是退货专员。说明退货条件、举证材料、寄回地址(可模拟),简洁中文。");
public static AIAgent CreateRefundAgent(IChatClient client)
{
IList<AITool> tools = [AIFunctionFactory.Create(SupportTools.CheckOrderStatus)];
return Create(client, "refund", "退款专员", "评估退款金额与审批路径",
"你是退款专员。需要订单信息时调用 CheckOrderStatus,再给出退款建议。", tools: tools);
}
// ── GroupChat:圆桌协商 ──
public static AIAgent CreateFacilitator(IChatClient client) =>
Create(client, "facilitator", "客服主持", "主持圆桌并推动形成一致方案",
"你是客服主管。主持讨论,总结各方观点,推动形成一致的退款/退货方案。");
public static AIAgent CreatePolicyExpert(IChatClient client) =>
Create(client, "policy-expert", "政策顾问", "从政策与合规角度发表意见",
"你是政策顾问。从 7 天无理由与运输损坏条款出发发表意见,简洁有力。");
public static AIAgent CreateRefundLead(IChatClient client) =>
Create(client, "refund-lead", "退款主管", "从成本与客户体验权衡退款比例",
"你是退款主管。提出可执行的退款比例与替代方案(换货/部分退款)。");
}
Name 和 Description 在 Handoff 模式里尤其重要------模型靠它们判断「该转交给谁」。
三、Concurrent:三专家并行,快但各说各话
3.1 什么时候用?
- 子任务彼此独立,没有严格先后依赖
- 希望缩短总耗时(三个专家同时看,而不是排队)
- 最后需要有人汇总多方意见
物流、政策、质检三个角度互不阻塞------Perfect fit。
3.2 代码
csharp
Workflow workflow = AgentWorkflowBuilder.BuildConcurrent(
"support-concurrent",
[
SupportAgentsFactory.CreateLogisticsAnalyst(chatClient),
SupportAgentsFactory.CreatePolicyAnalyst(chatClient),
SupportAgentsFactory.CreateQualityAnalyst(chatClient),
],
aggregator: agentOutputs =>
{
var merged = new List<ChatMessage>
{
new(ChatRole.User, "【并行分析汇总】以下是三位专家的结论:"),
};
string[] roles = ["物流", "政策", "质检"];
for (int i = 0; i < agentOutputs.Count; i++)
{
ChatMessage? last = agentOutputs[i].LastOrDefault(m => m.Role == ChatRole.Assistant);
if (last?.Text is { Length: > 0 } text)
{
string role = i < roles.Length ? roles[i] : $"专家{i + 1}";
merged.Add(new ChatMessage(ChatRole.Assistant, $"[{role}] {text}"));
}
}
return merged;
});
三个 Agent 的 Instructions 都强调「只从你负责的角度 分析,3-5 句话」------这是在并行场景下的重要技巧:收窄职责,避免每个 Agent 都写一份完整小作文,汇总时反而冗长重复。
3.3 Aggregator 是灵魂
aggregator 回调收到每个 Agent 的完整消息列表,由你决定怎么合并。Demo 取了每位专家最后一条 Assistant 消息,贴上 [物流]、[政策]、[质检] 标签。
扩展知识
- Aggregator 也可以再调一次 LLM 做「摘要仲裁」,Demo 用规则合并是为了让输出稳定、省 Token。
- 并行 = 三次模型调用同时发生,latency 接近单次最慢的那路 ,但 Token 费用是三份。
- 在 AutoGen lineage 里,这类似多 Agent 并行回复后由
GroupChatManager或自定义函数汇总;MAF 把它收敛成BuildConcurrent一个 API。
四、Sequential:流水线接力,适合「一步步精炼」
4.1 什么时候用?
- 后一步依赖前一步的结构化输出
- 流程固定:整理 → 起草 → 润色
- 类似内容生产、代码 Review 链、ETL 式信息加工
Concurrent 解决「同时看」,Sequential 解决「接着干」。
4.2 本 Demo 的三棒接力
csharp
Workflow workflow = AgentWorkflowBuilder.BuildSequential(
"support-sequential",
[
SupportAgentsFactory.CreateTicketStructurer(chatClient), // 结构化纪要
SupportAgentsFactory.CreateReplyDrafter(chatClient), // 回复草稿
SupportAgentsFactory.CreateReplyReviewer(chatClient), // 润色定稿
]);
| 顺序 | 角色 | 产出 |
|---|---|---|
| 1 | 工单结构化专员 | 问题摘要、各方观点、待决事项 |
| 2 | 回复草稿专员 | 发给客户的回复草稿(150 字内) |
| 3 | 回复质检专员 | 最终可发送版本 |
Sequential 模式下,上一个 Agent 的输出会自动成为下一个 Agent 的上下文 ,你不需要手动传参------这是 BuildSequential 帮你做的「管道胶合」。
扩展知识
Sequential 的总耗时 ≈ 各步之和,Token 也是累加的。若某一步失败,整条链中断------生产上可在 Agent 外包装 Middleware 做重试或降级(第 4 篇学过)。
对比 Prompt Chaining (手写多次
RunAsync并拼接),Workflow 的好处是编排可视化潜力 、统一事件模型、以及后续扩展 Handoff / 条件分支时不用推倒重来。
五、Handoff:前台分拣,专员接手
5.1 什么时候用?
- 用户意图一开始不确定,需要路由到不同专家
- 对话是多轮 的,处理过程中可能换负责人
- 各专员能力不同(有的带工具,有的只讲政策)
这最接近真实客服:分拣台 → 订单组 / 退货组 / 退款组。
5.2 构建 Handoff 图
csharp
var triage = SupportAgentsFactory.CreateTriageAgent(chatClient);
var order = SupportAgentsFactory.CreateOrderAgent(chatClient);
var returnAgent = SupportAgentsFactory.CreateReturnAgent(chatClient);
var refund = SupportAgentsFactory.CreateRefundAgent(chatClient);
Workflow workflow = AgentWorkflowBuilder
.CreateHandoffBuilderWith(triage) // 入口:分拣专员
.WithHandoffs(triage, [order, returnAgent, refund]) // 分拣可转给三人
.WithHandoffs(returnAgent, [refund]) // 退货后可转退款
.WithHandoffs([order, returnAgent, refund], triage, // 专员可转回分拣
"问题已处理完毕或需重新分拣")
.WithName("support-handoff")
.Build();
画成图更直观:
text
┌─────────┐
┌─────────│ 分拣专员 │─────────┐
│ └────┬────┘ │
│ handoff │ handoff │ handoff
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ 订单专员 │ │ 退货专员 │ │ 退款专员 │
│ (+工具) │ │ │ │ (+工具) │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
└─────────────┴──────┬──────┘
│ 处理完 / 需重新分拣
▼
┌─────────┐
│ 分拣专员 │
└─────────┘
Handoff 不是你在代码里写 if (退款) goto RefundAgent,而是模型通过 Handoff 工具决定「下一位是谁」。因此 Demo 启动时会提示:
Handoff 需模型支持 Tool Calling / Handoff 工具
若模型不支持工具调用,分拣 Agent 可能「自己编造订单信息」------Instructions 里虽然写了「不要编造」,但没有 Handoff 能力就转交不出去。
5.3 专员带工具
订单专员和退款专员挂载了 CheckOrderStatus:
csharp
[Description("查询订单状态、签收时间与商品信息")]
public static string CheckOrderStatus(
[Description("订单号,例如 ORD-8842")] string orderId)
{
return orderId.Trim().ToUpperInvariant() switch
{
"ORD-8842" =>
"ORD-8842 | 状态=已签收 | 签收=8天前 | 商品=P002 .NET AI课程礼盒 ¥299 | ...",
_ => $"未找到订单 {orderId}(模拟数据)"
};
}
Handoff + Tool Calling 是黄金搭档:路由 决定谁说话,工具让专员能查真实(模拟)数据,而不是瞎编。
用户消息用的是口语化版本,和内部工单摘要不同:
csharp
public static string HandoffUserMessage =>
$"你好,我的订单 {OrderId} 签收 8 天了,课程礼盒角被压坏了,我想全额退款,能帮我查一下并处理吗?";
5.4 运行
扩展知识
Handoff 图可以很复杂:多入口、条件边、循环回分拣。设计时要想清楚谁可以转给谁 ,避免模型在专员之间 ping-pong 踢皮球。
这和 OpenAI 的
handoff、AutoGen 的Swarm模式思路相近------MAF 用WithHandoffs显式声明图结构,可审计、可静态检查,比纯 Prompt 里写「必要时转交」靠谱得多。
六、GroupChat:圆桌会诊,要「商量出一致」
6.1 什么时候用?
- 需要多方观点碰撞,而不是单人决策
- 没有固定流水线顺序,但要控制轮次防止无限扯皮
- 典型:评审会、方案定稿、争议仲裁
Concurrent 是「各交作业」,GroupChat 是「开会讨论」。
6.2 代码
csharp
Workflow workflow = AgentWorkflowBuilder
.CreateGroupChatBuilderWith(agents =>
new RoundRobinGroupChatManager(agents) { MaximumIterationCount = 6 })
.AddParticipants([
SupportAgentsFactory.CreateFacilitator(chatClient), // 客服主持
SupportAgentsFactory.CreatePolicyExpert(chatClient), // 政策顾问
SupportAgentsFactory.CreateRefundLead(chatClient), // 退款主管
])
.WithName("support-groupchat")
.Build();
RoundRobinGroupChatManager 按固定顺序轮流发言:主持 → 政策 → 退款 → 主持 → ...
MaximumIterationCount = 6 表示最多 6 轮发言------给讨论设上限,避免 Token 账单爆炸,也逼 Agent 尽快收敛。
三位角色的 Instructions 分别强调:
- 主持:总结观点、推动一致方案
- 政策顾问:7 天无理由 vs 运输损坏条款
- 退款主管:退款比例、换货/部分退款等可执行方案
Prompt 要求「讨论并给出一致的处理方案」------这是在 GroupChat 里减少「各说各话、没有结论」的关键 Prompt 设计。
6.3 和 Concurrent 的区别
| Concurrent | GroupChat | |
|---|---|---|
| 发言方式 | 同时 | 轮流 |
| 是否看到彼此 | 否(各自只看原始输入) | 是(能看到前面发言) |
| 典型目标 | 收集并行意见 | 协商一致 |
| 耗时 | ~最慢那路 | 轮次 × 单次 |
6.4 运行
`
扩展知识
MAF 的 GroupChat 可换不同的
GroupChatManager(如基于 LLM 的「下一位谁发言」选择器),RoundRobin 最简单、行为最可预测,适合 Demo 和需要稳定复现的场景。若讨论质量不够,常见调优手段:换更强的模型当「主持」、减少参与人数、在 Prompt 里要求「最后一轮必须输出 JSON 方案」等。
七、四种模式怎么选?一张决策表
| 你的问题 | 推荐模式 |
|---|---|
| 多个独立分析维度,要快 | Concurrent |
| 固定加工步骤,后步吃前步 | Sequential |
| 用户意图不明,对话中要换人 | Handoff |
| 需要讨论、博弈、达成一致 | GroupChat |
还可以组合 ------本 Demo 的 all 模式就是:
text
Concurrent(收集意见)→ Sequential(生成客户回复)→ Handoff(模拟用户对话路径)→ GroupChat(内部仲裁方案)
真实项目不必四次全跑;选一种或两种组合即可。
八、踩坑与建议
8.1 Handoff 不转交
- 确认模型支持 Tool Calling
- 检查 Agent 的
Name/Description是否清晰 - 分拣 Agent 的 Instructions 是否明确写了「必须 handoff,不要自己处理」
8.2 Concurrent 控制台乱码式交错
并行 Agent 会同时流式输出。Demo 刻意只打印 WorkflowOutputEvent 的最终汇总;若你要看每路详情,可分别订阅并带 Agent 标识打印。
9.3 GroupChat 扯不完
- 降低
MaximumIterationCount - 主持 Agent 的 Instructions 里要求「第 N 轮必须表决」
- 参与 Agent 不宜过多(3-5 个为宜)
8.4 Token 与成本
多 Agent ≠ 免费午餐。粗略估算:
- Concurrent:调用次数 = Agent 数
- Sequential:调用次数 = 步骤数
- Handoff:取决于转交轮数,通常 2-4 次
- GroupChat:≈ 轮次 × 参与人数
生产环境建议配合第 4 篇的 RequestLogging Middleware 按 Workflow 维度做用量统计。
九、小结
| 模式 | MAF API | 本 Demo 角色 |
|---|---|---|
| Concurrent | AgentWorkflowBuilder.BuildConcurrent |
物流 / 政策 / 质检分析师 |
| Sequential | AgentWorkflowBuilder.BuildSequential |
结构化 → 草稿 → 润色 |
| Handoff | CreateHandoffBuilderWith + WithHandoffs |
分拣 → 订单 / 退货 / 退款专员 |
| GroupChat | CreateGroupChatBuilderWith + RoundRobinGroupChatManager |
主持 + 政策顾问 + 退款主管 |
同一笔 ORD-8842 工单,四种编排给出了四种「组织方式」:
- Concurrent 像同时拨通三个专家电话
- Sequential 像经三道工序写出回复
- Handoff 像客户打进 400 电话被转接
- GroupChat 像下午三点的跨部门协调会
MAF 用统一的 Workflow + StreamingRun + 事件流把四种模式收口在同一套运行时里。下一篇可以在此基础上继续探索:条件分支、人工介入(Human-in-the-loop)、持久化 Workflow------那是从 Demo 走向生产编排的下一层台阶。
附录:依赖与相关阅读
NuGet:
xml
<PackageReference Include="Microsoft.Agents.AI.Workflows" Version="1.7.0" />