ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

MAF快速入门(9)多路分支路由工作流

MAF快速入门(9)多路分支路由工作流 目录简介Why switch-case?实验案例准备工作定义数据传输模型入口节点垃圾邮件检测Executor下游节点A正常邮件处理发送下游节点B垃圾邮件处理下游节点C不确定邮件处理执行器兜底处理构建工作流测试工作流小结示例源码简介大家好我是Edison。上一篇我们学习了MAF中如何进行if-else类型的条件路由但是实际工作中可能会存在多个分支路由的场景。本篇我们来了解下MAF中的switch-case路由实现多分支路由工作流。Why switch-case?在实际业务场景中很多的业务逻辑涉及到不止两个判断条件而是多个。例如在上一篇的企业内部邮件检测案例中我们的检测结果只有两个垃圾邮件 或 正常邮件但如果我们想增加一个结果不确定就无法适用了。在MAF中我们可以使用 Switch-Case 来实现这种工作流内部多类决策条件的 工作流需求。实验案例今天来晚上上一篇这个企业内部邮件检测的工作流案例上一篇的流程是这样的今天假设我们需要有更为精细的分类✅ **正常邮件**NotSpam客户咨询、业务往来❌ **垃圾邮件**Spam明显的诈骗、广告⚠️ **不确定邮件**Uncertain可能是钓鱼邮件需要人工审核那么这就是一个三元分类的在业务开发中我们通常会用到switch-case语法而在MAF工作流中也为我们定义了这种switch-case接口。在下面的代码示例中比对了两种接口的使用方式// Conditional Edge builder .AddEdge(source, target1, condition: c c.IsSpam false) .AddEdge(source, target2, condition: c c.IsSpam true); // Switch-Case builder.AddSwitch(source, sb sb .AddCase(c c.Decision NotSpam, target1) .AddCase(c c.Decision Spam, target2) .WithDefault(target3) );可以看到switch-case模式其价值主要在于增强代码可维护性对于后续如果有新增分类只需要添加一个AddCase接口方法实现新增分类的处理同时基于WithDefault接口方法实现兜底确保所有情况都有处理。最后下面是我们需要重构后的分支路由图准备工作在今天的这个案例中我们仍然创建了一个.NET控制台应用程序安装了以下NuGet包Microsoft.Agents.AI.OpenAIMicrosoft.Agents.AI.WorkflowsMicrosoft.Extensions.AI.OpenAI我们的配置文件中定义了LLM API的信息{ OpenAI: { EndPoint: https://api.siliconflow.cn, ApiKey: ******************************, ModelId: Qwen/Qwen3-30B-A3B-Instruct-2507 } }这里我们使用 SiliconCloud 提供的 Qwen/Qwen3-30B-A3B-Instruct-2507 模型你可以通过这个URL注册账号https://cloud.siliconflow.cn/i/DomqCefW 获取大量免费的Token来进行本次实验。然后我们将配置文件中的API信息读取出来var config new ConfigurationBuilder() .AddJsonFile($appsettings.json, optional: false, reloadOnChange: true) .Build(); var openAIProvider config.GetSection(OpenAI).GetOpenAIProvider();定义数据传输模型首先我们定义一下在这个工作流中需要生成传递的数据模型1DetectionResult 拉件邮件检测结果public sealed class DetectionResult { /// summary /// 检测决策NotSpam / Spam / Uncertain /// /summary [JsonPropertyName(spam_decision)] [JsonConverter(typeof(JsonStringEnumConverter))] // JSON 序列化为字符串 public SpamDecision spamDecision { get; set; } /// summary /// 判定理由用于审计和调试 /// /summary [JsonPropertyName(reason)] public string Reason { get; set; } string.Empty; /// summary /// 邮件ID用于关联 Shared State 中的原始内容 /// /summary [JsonIgnore] public string EmailId { get; set; } string.Empty; } public enum SpamDecision { Spam, // 垃圾邮件 NotSpam, // 正常邮件 UnCertain // 无法确定需要人工审核 }2EmailStateConstants 常量类似于Cache Key的作用​​​​​​​internal static class EmailStateConstants { public const string EmailStateScope EmailState; }3EmailMessage EmailResponse DTO作用​​​​​​​internal sealed class EmailMessage { [JsonPropertyName(email_id)] public string EmailId { get; set; } string.Empty; [JsonPropertyName(email_content)] public string EmailContent { get; set; } string.Empty; } public sealed class EmailResponse { [JsonPropertyName(response)] public string Response { get; set; } string.Empty; }入口节点垃圾邮件检测Executor这个垃圾邮件检测是本流程的核心节点这次我们将其重构为支持三分类​​​​​​​internal sealed class SpamDetectionExecutor : ExecutorChatMessage, DetectionResult { private readonly AIAgent _agent; private readonly AgentThread _thread; public SpamDetectionExecutor(AIAgent agent) : base(SpamDetectionExecutor) { // 创建 Agent 和对话线程 this._agent agent; this._thread this._agent.GetNewThread(); } public override async ValueTaskDetectionResult HandleAsync(ChatMessage message, IWorkflowContext context, CancellationToken cancellationToken default) { // 1️⃣ 生成唯一邮件ID并保存内容到 Shared State var trackedEmail new EmailMessage { EmailId Guid.NewGuid().ToString(N), EmailContent message.Text }; await context.QueueStateUpdateAsync( trackedEmail.EmailId, trackedEmail, scopeName: EmailStateConstants.EmailStateScope, cancellationToken ); // 2️⃣ 调用 AI Agent 进行三分类检测 var agentResponse await _agent.RunAsync( message, _thread, cancellationToken: cancellationToken ); // 3️⃣ 解析结构化输出 var detection JsonSerializer.DeserializeDetectionResult(agentResponse.Text) ?? throw new InvalidOperationException(无法解析 Spam Detection 响应); // 4️⃣ 关联 EmailId供下游 Executor 查找原始内容 detection.EmailId trackedEmail.EmailId; return detection; } }在这个Executor中它接收我们如下所示定义好的Agent来实现​​​​​​​var spamDetectionAgent new ChatClientAgent( chatClient, new ChatClientAgentOptions( instructions: 你是一个垃圾邮件检测助手。判定规则 - NotSpam: 明显的正常业务邮件订单查询、售后咨询等 - Spam: 明显的垃圾邮件诈骗、广告、钓鱼 - Uncertain: 无法明确判断包含可疑元素但不确定如含可疑链接但内容模糊 对于模棱两可的情况倾向于标记为 Uncertain 以保证安全。 ) { ChatOptions new ChatOptions { ResponseFormat ChatResponseFormat.ForJsonSchemaDetectionResult() } } );在ChatOptions中指定了该Agent返回的消息需要进行序列化到强类型便于后续通过强类型数据进行决策路由。下游节点A正常邮件处理发送这里我们针对识别到的正常邮件开发两个执行器假设其用于邮件处和转发1邮件处理读取共享状态区的原文然后调用Agent输出JSON回复。​​​​​​​internal sealed class EmailAssistantExecutor : ExecutorDetectionResult, EmailResponse { private readonly AIAgent _agent; private readonly AgentThread _thread; public EmailAssistantExecutor(AIAgent agent) : base(EmailAssistantExecutor) { // 创建 Agent 和对话线程 this._agent agent; this._thread this._agent.GetNewThread(); } public override async ValueTaskEmailResponse HandleAsync(DetectionResult message, IWorkflowContext context, CancellationToken cancellationToken default) { // ️ 防御性检查确保只处理正常邮件 if (message.spamDecision SpamDecision.Spam) throw new InvalidOperationException( EmailAssistantExecutor 不应处理垃圾邮件请检查路由配置。 ); // 1️⃣ 从 Shared State 读取原始邮件内容 var email await context.ReadStateAsyncEmailMessage( message.EmailId, scopeName: EmailStateConstants.EmailStateScope, cancellationToken ) ?? throw new InvalidOperationException($找不到 EmailId{message.EmailId} 的邮件内容); // 2️⃣ 调用 AI Agent 生成回复 var agentResponse await _agent.RunAsync( email.EmailContent, _thread, cancellationToken: cancellationToken ); // 3️⃣ 解析结构化输出 var emailResponse JsonSerializer.DeserializeEmailResponse(agentResponse.Text) ?? throw new InvalidOperationException(无法解析 Email Assistant 响应); return emailResponse; } }这里的Agent定义如下​​​​​​​var emailAssistantAgent new ChatClientAgent( chatClient, new ChatClientAgentOptions( instructions: 你是一个企业邮件助手为客户邮件生成专业、友好的中文回复。 ) { ChatOptions new ChatOptions { ResponseFormat ChatResponseFormat.ForJsonSchemaEmailResponse() } } );2邮件转发模拟邮件转发到具体的客服这里仅仅使用YieldOutputAsync完成工作流输出消息内容。​​​​​​​internal sealed class EmailSendingExecutor() : ExecutorEmailResponse(EmailSendingExecutor) { public override async ValueTask HandleAsync(EmailResponse message, IWorkflowContext context, CancellationToken cancellationToken default) { // 模拟邮件发送实际项目中可调用 SMTP、SendGrid 等服务 await context.YieldOutputAsync( $ 邮件已发送: {message.Response}, cancellationToken ); } }下游节点B垃圾邮件处理当判断到是垃圾邮件时转交给该执行器处理这里模拟输出了一段风险提示实际中可能是上报人工跟进等等操作​​​​​​​internal sealed class SpamHandlingExecutor() : ExecutorDetectionResult(SpamHandlingExecutor) { public override async ValueTask HandleAsync(DetectionResult message, IWorkflowContext context, CancellationToken cancellationToken default) { // ️ 防御性检查确保只处理垃圾邮件 if (message.spamDecision ! SpamDecision.Spam) throw new InvalidOperationException( SpamHandlingExecutor 只应处理 Spam 类型的邮件请检查路由配置。 ); // 记录垃圾邮件实际项目中可写入数据库或日志系统 await context.YieldOutputAsync( $ 垃圾邮件已拦截: {message.Reason}, cancellationToken ); } }下游节点C不确定邮件处理执行器兜底处理当判断到属于不确定的邮件分类时转交给该执行器做兜底处理 或 默认处理​​​​​​​internal class UncertainHandlingExecutor() : ExecutorDetectionResult(UncertainHandlingExecutor) { public override async ValueTask HandleAsync( DetectionResult message, IWorkflowContext context, CancellationToken cancellationToken default) { // ️ 防御性检查确保只处理不确定邮件 if (message.spamDecision ! SpamDecision.UnCertain) throw new InvalidOperationException( UncertainHandlingExecutor 只应处理 Uncertain 类型的邮件或作为 Default Case。 ); // 1️⃣ 从 Shared State 读取原始邮件内容用于人工审核 var email await context.ReadStateAsyncEmailMessage( message.EmailId, scopeName: EmailStateConstants.EmailStateScope, cancellationToken ); // 2️⃣ 输出待审核信息 await context.YieldOutputAsync( $⚠️ 不确定邮件需人工审核:\n $原因: {message.Reason}\n $内容预览: {email?.EmailContent?.Substring(0, Math.Min(100, email.EmailContent.Length))}..., cancellationToken ); } }构建工作流现在万事俱备只欠一个Workflow现在Lets do it!Step1: 获取ChatClient​​​​​​​var chatClient new OpenAIClient( new ApiKeyCredential(openAIProvider.ApiKey), new OpenAIClientOptions { Endpoint new Uri(openAIProvider.Endpoint) }) .GetChatClient(openAIProvider.ModelId) .AsIChatClient();Step2: 实例化自定义Agent Executors​​​​​​​var spamDetectionExecutor new SpamDetectionExecutor(spamDetectionAgent); var emailAssistantExecutor new EmailAssistantExecutor(emailAssistantAgent); var sendEmailExecutor new EmailSendingExecutor(); var handleSpamExecutor new SpamHandlingExecutor(); var handleUncertainExecutor new UncertainHandlingExecutor();Step3: 创建switch-case多路由决策工作流​​​​​​​// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // 条件函数工厂方法 // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Funcobject?, bool GetCondition(SpamDecision expectedDecision) detectionResult detectionResult is DetectionResult result result.spamDecision expectedDecision; // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // 使用 AddSwitch 构建 Switch-Case 工作流 // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ var builder new WorkflowBuilder(spamDetectionExecutor); builder.AddSwitch(spamDetectionExecutor, sb sb // Case 1: NotSpam → EmailAssistant .AddCase(GetCondition(expectedDecision: SpamDecision.NotSpam), new[] { (ExecutorBinding)emailAssistantExecutor }) // Case 2: Spam → HandleSpam .AddCase(GetCondition(expectedDecision: SpamDecision.Spam), new[] { (ExecutorBinding)handleSpamExecutor }) // Default: Uncertain (或任何未匹配的情况) → HandleUncertain .WithDefault(new[] { (ExecutorBinding)handleUncertainExecutor }) ) // EmailAssistant 之后自动发送邮件 .AddEdge(emailAssistantExecutor, sendEmailExecutor) // 配置输出节点三个终点执行器都会产生输出 .WithOutputFrom(handleSpamExecutor, sendEmailExecutor, handleUncertainExecutor); var workflow builder.Build(); Console.OutputEncoding Encoding.UTF8; Console.WriteLine(✅ Conditional Workflow 构建完成);测试工作流首先为了便于后续测试我们将执行工作流封装为一个静态方法​​​​​​​static async Task RunWorkflowAsync( Workflow workflow, string scenarioName, string emailContent) { Console.WriteLine(━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━); Console.WriteLine($ 测试场景{scenarioName}); Console.WriteLine(━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━); Console.WriteLine($ 邮件内容{emailContent.Substring(0, Math.Min(80, emailContent.Length))}...\n); await using var run await InProcessExecution.StreamAsync( workflow, new ChatMessage(ChatRole.User, emailContent) ); // 发送 Turn Token启用事件推送 await run.TrySendMessageAsync(new TurnToken(emitEvents: true)); // 订阅事件流 await foreach (WorkflowEvent evt in run.WatchStreamAsync()) { switch (evt) { case ExecutorCompletedEvent completedEvent: Console.WriteLine($✅ {completedEvent.ExecutorId} 完成); break; case WorkflowOutputEvent outputEvent: Console.WriteLine(━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━); Console.WriteLine( 工作流执行完成); Console.WriteLine(━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n); Console.WriteLine(${outputEvent.Data}); break; case WorkflowErrorEvent errorEvent: Console.WriteLine(✨ 收到 Workflow Error Event); Console.WriteLine(${errorEvent.Data}); break; default: break; } } Console.WriteLine(); }测试用例1正常咨询邮件输入​​​​​​​var scenarioName1 正常邮件 → EmailAssistant → SendEmail; var emailContent1 尊敬的客服团队 您好我是贵公司的长期客户订单号为 #2025 -001。 我想确认一下上周提交的采购订单是否已经安排发货。 如果需要补充任何信息请随时告知。 期待您的回复谢谢 客户张先生 ; await RunWorkflowAsync(workflow, scenarioName1, emailContent1); Console.WriteLine(✅ 正常邮件路径验证完成);测试结果如下图所示可以看见对于正常咨询邮件进行正常的邮件处理和转发。测试用例2垃圾邮件输入​​​​​​​var scenarioName2 垃圾邮件 → HandleSpam; var emailContent2 恭喜您中奖啦 您已被选中获得 100 万现金大奖 立即点击以下链接领取 http://suspicious-site.com/claim-prize 仅限今日有效过期作废 不需要任何手续费完全免费 快速行动机不可失 ; await RunWorkflowAsync(workflow, scenarioName2, emailContent2); Console.WriteLine(✅ 垃圾邮件路径验证完成);测试结果如下图所示可以看见对于垃圾邮件进行有效的拦截后续还可以进行上报人工跟踪等等。测试用例3无法确认类型的邮件输入​​​​​​​var uncertainEmail 主题需要验证您的账户 尊敬的客户 我们检测到您的账户存在异常活动需要验证您的身份以确保账户安全。 请登录您的账户并完成验证流程以继续使用服务。 账户详情 - 用户名johndoecontoso.com - 最后登录08/15/2025 - 登录地点西雅图华盛顿州 - 登录设备移动设备 这是一项自动安全措施。如果您认为此邮件是错误发送的请立即联系我们的支持团队。 此致 安全团队 客户服务部门 ; await RunWorkflowAsync( workflow, 不确定邮件 → HandleUncertain (Default), uncertainEmail ); Console.WriteLine(✅ 不确定邮件路径验证完成);测试结果如下图所示可以看到对于LLM无法确认的类型进入了该执行器这时可能需要人工介入审核。同时这也是实际中常见的一种兜底机制的展现话句话说即使AI无法明确判断也应该有对应的处理流程。小结本文介绍了MAF中的switch-case路由以及如何实现多条件路由最后优化了上一篇的企业内部邮件检测工作流案例特别适合于大于3个分支的复杂路由场景。下一篇​​​​​​​我们将继续学习MAF中工作流的循环工作流。示例源码Github: https://github.com/EdisonTalk/MAFD​​​​​​​GitHub - mzhongl524/MAFD: Microsoft Agent Framwork Demos · GitHub2026-8月更新示例引入地址
返回列表