MAF快速入门(12)主工作流+子工作流
目录简介1 子工作流模式介绍2 主工作流子工作流实验案例2.1 关键依赖包引入2.2 定义数据传输模型2.3 定义产品质量处理子工作流2.4 定义物流问题处理子工作流2.5 构建主工作流2.6 测试工作流3 小结4 示例源码简介大家好我是Edison。上一篇我们学习了MAF中如何进行并行工作流。本篇我们来了解下在MAF中如何开启 主工作流 自工作流 的模式相信这会是大家可能会碰到的场景。1 子工作流模式介绍在实际业务场景中往往需要在主工作流中根据工单类型分发或移交到不同的标准化子流程中进行后续处理。在MAF中我们可以使用 Sub-Worklfow 模式来实现这个目的如下代码片段所示。首先将子工作流转换为一个Executorvar logisticsSubExecutor logisticsSubWorkflow.BindAsExecutor(LogisticsSubWorkflow); var productQualitySubExecutor productQualitySubWorkflow.BindAsExecutor(ProductQualitySubWorkflow);然后构建主工作流并根据条件路由到子工作流Executorvar mainWorkflow new WorkflowBuilder(classifierExecutor) .AddEdgeComplaintProcessingRecord(classifierExecutor, productQualitySubExecutor, condition: record record.Category 产品质量) .AddEdgeComplaintProcessingRecord(classifierExecutor, logisticsSubExecutor, condition: record record.Category 物流问题) .AddEdge(productQualitySubExecutor, complianceExecutor) .AddEdge(logisticsSubExecutor, complianceExecutor) .AddEdge(complianceExecutor, sentimentExecutor) .WithOutputFrom(sentimentExecutor) .Build();这样就实现了主工作流进行分类转发自工作流进行详细处理。2 主工作流子工作流实验案例假设我们是一个企业的客服中心每天都会收到大量的工单需要根据工单类型比如是 产品质量话题 还是 物流话题分发到不同的标准化子流程去做后续处理最后生成一个用户友好 且 满足合规要求 的回复给用户。因此我们的目标是配置 1个主工作流 2个子工作流 来覆盖这个需求。2.1 关键依赖包引入在今天的这个案例中我们仍然创建了一个.NET控制台应用程序安装了以下NuGet包Microsoft.Agents.AI.OpenAIMicrosoft.Agents.AI.WorkflowsMicrosoft.Extensions.AI.OpenAI2.2 定义数据传输模型首先我们定义一下在这个工作流中需要生成传递的数据模型投诉数据模型 及 共享状态namespace SubWorkflow.Models; // 共享状态投诉处理记录各执行器会更新此对象 internal class ComplaintProcessingRecord { public CustomerComplaint Original { get; set; } public string Category { get; set; } 未分类; public string Handler { get; set; } 待分配; public Liststring ProcessingSteps { get; set; } new(); public string AIGeneratedResponse { get; set; } ; public string ComplianceStatus { get; set; } 待审核; public string SentimentScore { get; set; } 未评估; } // 投诉数据模型 internal record CustomerComplaint( string OrderId, string CustomerName, string ComplaintText, DateTime SubmittedAt );2.3 定义产品质量处理子工作流1问题评估封装产品质量评估的逻辑这里仅仅做演示用无实际逻辑internal sealed class ProductEvaluationExecutor() : ExecutorComplaintProcessingRecord, ComplaintProcessingRecord(nameof(ProductEvaluationExecutor)) { public override ValueTaskComplaintProcessingRecord HandleAsync(ComplaintProcessingRecord record, IWorkflowContext context, CancellationToken cancellationToken default) { Console.WriteLine( 正在评估产品质量问题...); record.ProcessingSteps.Add([产品评估] 检测到屏幕外观缺陷符合质量问题定义); record.Handler 产品质量团队; return ValueTask.FromResult(record); } }2退/换货判定负责判定是退货 还是 换货 处理。​​​​​​​internal sealed class ReturnPolicyExecutor() : ExecutorComplaintProcessingRecord, ComplaintProcessingRecord(nameof(ReturnPolicyExecutor)) { public override ValueTaskComplaintProcessingRecord HandleAsync(ComplaintProcessingRecord record, IWorkflowContext context, CancellationToken cancellationToken default) { Console.WriteLine( 判定退换货政策...); var daysFromOrder (DateTime.Now - record.Original.SubmittedAt).Days; if (daysFromOrder 7) { record.ProcessingSteps.Add([退换货判定] 符合7天无理由退货政策批准全额退款); } else { record.ProcessingSteps.Add([退换货判定] 超过退货期限建议换货或部分补偿); } return ValueTask.FromResult(record); } }3AI回复在投诉内容和判定结果的基础上生成友好的回复返回给用户。​​​​​​​internal sealed class AIResponseGeneratorExecutor(IChatClient chatClient) : ExecutorComplaintProcessingRecord, ComplaintProcessingRecord(nameof(AIResponseGeneratorExecutor)) { public override async ValueTaskComplaintProcessingRecord HandleAsync(ComplaintProcessingRecord record, IWorkflowContext context, CancellationToken cancellationToken default) { Console.WriteLine( AI 正在生成客户回复...); var prompt $你是专业的客服主管。根据以下投诉处理信息生成一封正式、有同理心的客户回复邮件150字内 客户{record.Original.CustomerName} 订单号{record.Original.OrderId} 投诉内容{record.Original.ComplaintText} 处理步骤 {string.Join(\n, record.ProcessingSteps)} 要求 1. 表达歉意和理解 2. 说明处理方案 3. 提供后续联系方式 4. 语气真诚、专业; var response await chatClient.GetResponseAsync(prompt, cancellationToken: cancellationToken); record.AIGeneratedResponse response.Text ?? AI 生成失败; record.ProcessingSteps.Add($[AI 回复] 已生成客户回复模板{record.AIGeneratedResponse.Length}字); return record; } }最后构建子工作流并将其转换为一个Executor以便后续集成到主工作流中​​​​​​​var productEvalExecutor new ProductEvaluationExecutor(); var returnPolicyExecutor new ReturnPolicyExecutor(); var aiResponseExecutor new AIResponseGeneratorExecutor(chatClient); var productQualitySubWorkflow new WorkflowBuilder(productEvalExecutor) .AddEdge(productEvalExecutor, returnPolicyExecutor) .AddEdge(returnPolicyExecutor, aiResponseExecutor) .WithOutputFrom(aiResponseExecutor) .Build(); var productQualitySubExecutor productQualitySubWorkflow.BindAsExecutor(ProductQualitySubWorkflow);2.4 定义物流问题处理子工作流1物流追踪封装物流查询逻辑这里仅仅做演示用无实际逻辑​​​​​​​internal sealed class LogisticsTrackingExecutor() : ExecutorComplaintProcessingRecord, ComplaintProcessingRecord(nameof(LogisticsTrackingExecutor)) { public override ValueTaskComplaintProcessingRecord HandleAsync(ComplaintProcessingRecord record, IWorkflowContext context, CancellationToken cancellationToken default) { Console.WriteLine( 正在查询物流信息...); record.ProcessingSteps.Add([物流追踪] 包裹在中转站滞留3天当前状态运输中); record.Handler 物流运营团队; return ValueTask.FromResult(record); } }2延迟分析封装延迟分析逻辑这里仅仅做演示用无实际逻辑​​​​​​​internal sealed class DelayAnalysisExecutor() : ExecutorComplaintProcessingRecord, ComplaintProcessingRecord(nameof(DelayAnalysisExecutor)) { public override ValueTaskComplaintProcessingRecord HandleAsync(ComplaintProcessingRecord record, IWorkflowContext context, CancellationToken cancellationToken default) { Console.WriteLine( 分析延迟原因...); record.ProcessingSteps.Add([延迟分析] 延迟原因暴雨导致道路封闭预计2天内恢复配送); record.ProcessingSteps.Add([补偿方案] 提供50元优惠券 免运费); return ValueTask.FromResult(record); } }最后构建子工作流并将其转换为一个Executor以便后续集成到主工作流中​​​​​​​var logisticsTrackExecutor new LogisticsTrackingExecutor(); var delayAnalysisExecutor new DelayAnalysisExecutor(); var logisticsAIResponseExecutor new AIResponseGeneratorExecutor(chatClient); var logisticsSubWorkflow new WorkflowBuilder(logisticsTrackExecutor) .AddEdge(logisticsTrackExecutor, delayAnalysisExecutor) .AddEdge(delayAnalysisExecutor, logisticsAIResponseExecutor) .WithOutputFrom(logisticsAIResponseExecutor) .Build(); var logisticsSubExecutor logisticsSubWorkflow.BindAsExecutor(LogisticsSubWorkflow);2.5 构建主工作流现在开始构建主工作流Step1: 获取ChatClient​​​​​​​var chatClient new OpenAIClient( new ApiKeyCredential(openAIProvider.ApiKey), new OpenAIClientOptions { Endpoint new Uri(openAIProvider.Endpoint) }) .GetChatClient(openAIProvider.ModelId) .AsIChatClient();Step2: 创建工单分类Executor,合规审核Executor 以及 情绪评估ExecutorNOTE前面的子工作流均生成了AI回复这里需要对其进行合规审核以防止不合规的回复到用户那里。​​​​​​​// 1. 工单分类执行器 internal sealed class ComplaintClassifierExecutor() : ExecutorComplaintProcessingRecord, ComplaintProcessingRecord(nameof(ComplaintClassifierExecutor)) { public override ValueTaskComplaintProcessingRecord HandleAsync(ComplaintProcessingRecord record, IWorkflowContext context, CancellationToken cancellationToken default) { Console.WriteLine(️ 正在分类投诉类型...); // 简单规则分类实际可用 AI 分类 if (record.Original.ComplaintText.Contains(划痕) || record.Original.ComplaintText.Contains(质量) || record.Original.ComplaintText.Contains(退货)) { record.Category 产品质量; } else if (record.Original.ComplaintText.Contains(延迟) || record.Original.ComplaintText.Contains(物流) || record.Original.ComplaintText.Contains(配送)) { record.Category 物流问题; } else { record.Category 其他; } record.ProcessingSteps.Add($[分类器] 投诉类型识别为{record.Category}); return ValueTask.FromResult(record); } } // 2. 合规审核执行器 internal sealed class ComplianceCheckExecutor() : ExecutorComplaintProcessingRecord, ComplaintProcessingRecord(nameof(ComplianceCheckExecutor)) { public override ValueTaskComplaintProcessingRecord HandleAsync(ComplaintProcessingRecord record, IWorkflowContext context, CancellationToken cancellationToken default) { Console.WriteLine(️ 合规审核中...); var checks new Liststring(); if (record.AIGeneratedResponse.Contains(歉意)) checks.Add(✅ 包含道歉); if (record.AIGeneratedResponse.Length 300) checks.Add(✅ 字数合规); if (!record.AIGeneratedResponse.Contains(法律) !record.AIGeneratedResponse.Contains(诉讼)) checks.Add(✅ 无敏感法律词汇); record.ComplianceStatus checks.Count 2 ? ✅ 审核通过 : ⚠️ 需人工复审; record.ProcessingSteps.Add($[合规审核] {record.ComplianceStatus} - {string.Join(, , checks)}); return ValueTask.FromResult(record); } } // 3. 情绪评估执行器使用 AI internal sealed class SentimentAnalysisExecutor(IChatClient chatClient) : ExecutorComplaintProcessingRecord, ComplaintProcessingRecord(nameof(SentimentAnalysisExecutor)) { public override async ValueTaskComplaintProcessingRecord HandleAsync(ComplaintProcessingRecord record, IWorkflowContext context, CancellationToken cancellationToken default) { Console.WriteLine( AI 情绪评估中...); var prompt $分析以下客服回复的情绪基调用一个词概括如友好、专业、冷淡、热情 {record.AIGeneratedResponse} 只返回一个词不要解释。; var response await chatClient.GetResponseAsync(prompt, cancellationToken: cancellationToken); record.SentimentScore response.Text?.Trim() ?? 中性; record.ProcessingSteps.Add($[情绪评估] AI 评估语气为{record.SentimentScore}); return record; } }Step3: 创建主工作流​​​​​​​var classifierExecutor new ComplaintClassifierExecutor(); var complianceExecutor new ComplianceCheckExecutor(); var sentimentExecutor new SentimentAnalysisExecutor(chatClient); // 主工作流分类 → 条件路由到子流程 → 合规 → 情绪 var mainWorkflow new WorkflowBuilder(classifierExecutor) .AddEdgeComplaintProcessingRecord(classifierExecutor, productQualitySubExecutor, condition: record record.Category 产品质量) .AddEdgeComplaintProcessingRecord(classifierExecutor, logisticsSubExecutor, condition: record record.Category 物流问题) .AddEdge(productQualitySubExecutor, complianceExecutor) .AddEdge(logisticsSubExecutor, complianceExecutor) .AddEdge(complianceExecutor, sentimentExecutor) .WithOutputFrom(sentimentExecutor) .Build(); Console.OutputEncoding Encoding.UTF8; Console.WriteLine(✅ Main Sub Workflow 构建完成);2.6 测试工作流定义投诉的工单内容如下这是一个产品质量相关的投诉。​​​​​​​var processingRecord new ComplaintProcessingRecord { Original new CustomerComplaint( OrderId: ORD-2025-8821, CustomerName: 张先生, ComplaintText: 收到的手机屏幕有明显划痕要求退货退款, SubmittedAt: DateTime.Now) };通过Streaming流式执行​​​​​​​Console.WriteLine( 开始执行投诉处理流水线...); Console.WriteLine(new string(━, 60)); await using (var streaming await InProcessExecution.StreamAsync(mainWorkflow, processingRecord)) { await foreach (WorkflowEvent evt in streaming.WatchStreamAsync()) { switch (evt) { case ExecutorInvokedEvent started: Console.WriteLine($\n {started.ExecutorId} 开始执行); break; case ExecutorCompletedEvent completed when completed.Data is ComplaintProcessingRecord rec: Console.WriteLine($ 共享状态更新 → 处理步骤数{rec.ProcessingSteps.Count}); break; case WorkflowOutputEvent outputEvt when outputEvt.Data is ComplaintProcessingRecord finalRecord: Console.WriteLine(\n new string(━, 60)); Console.WriteLine( 投诉处理完成最终处理记录:\n); Console.WriteLine(JsonSerializer.Serialize(finalRecord)); Console.WriteLine(\n 处理步骤详情); foreach (var step in finalRecord.ProcessingSteps) { Console.WriteLine($ {step}); } Console.WriteLine(\n AI 生成的客户回复); Console.WriteLine(new string(─, 60)); Console.WriteLine(finalRecord.AIGeneratedResponse); Console.WriteLine(new string(─, 60)); break; } } }测试结果如下图所示首先可以通过监控日志发现准确地进入到了产品质量子流程其次最后由AI生成的回复内容也很友好同时也满足合规审核的要求3 小结本文介绍了MAF中主工作流 子工作流的工作模式最后通过一个企业客服中心处理投诉工单的案例介绍了这种模式的代码实现。需要注意的是建议每个子工作流都是用标准统一的状态模型以便在主工作流中复用 和 传递。建议每个子工作流只保持3~4个步骤单一职责在工作流中同样适用下一篇​​​​​​​我们将继续学习MAF中智能体的几种常见编排模式 - 顺序并发 和 移交。4 示例源码Github: https://github.com/EdisonTalk/MAFDhttps://github.com/mzhongl524/MAFD2026-8月更新示例引入地址

相关新闻

【信息科学与工程学】【物理/化学和工程技术】第七十九篇 物理化学 系列一 基础知识-1

【信息科学与工程学】【物理/化学和工程技术】第七十九篇 物理化学 系列一 基础知识-1

表格 编号 类型 领域 问题 问题的现象 问题的数学分析 逐step推理思考的数学方程式 参数列表及参数的数值范围及数值分析设计 关联知识 1 推导+计算 化学热力学 理想气体绝热可逆过程的 p-V-T 全关系与功 单原子理想气体 n=2 mol,从 (p₁=10 atm, V₁=5 L, T₁=3…

2026/10/15 18:11:11 阅读更多 →
Python游戏化学习指南:从零到一,在玩中掌握编程核心

Python游戏化学习指南:从零到一,在玩中掌握编程核心

很多Python初学者都经历过这样的阶段:对着枯燥的语法书和练习题,感觉编程既抽象又无趣,学习热情很快就被消磨殆尽。直到有一天,你发现原来Python可以如此“好玩”——通过游戏化的方式,在闯关、解谜、甚至编写小游戏的…

2026/10/13 17:31:50 阅读更多 →
AI Agent上下文压缩:Headroom原理、实战与长对话优化指南

AI Agent上下文压缩:Headroom原理、实战与长对话优化指南

1. 项目概述:为什么我们需要“上下文压缩”?如果你最近在折腾AI Agent或者大语言模型应用,大概率被“上下文长度”这个问题折磨过。无论是OpenAI的GPT-4 Turbo那128K的“豪华”窗口,还是Claude那令人咋舌的200K上下文,…

2026/10/14 3:43:46 阅读更多 →

最新新闻

MFC Tab Control 标签切换实战:子页面管理与消息响应全解析

MFC Tab Control 标签切换实战:子页面管理与消息响应全解析

简介:一套基于MFC对话框的TabControl标签切换示例工程,面向需要实现多页面动态切换的Windows桌面应用开发者。压缩包共39个文件,包含7个C头文件、5个源程序文件,以及Visual Studio工程文件、资源脚本、可执行程序与调试符号等&…

2026/10/15 18:10:22 阅读更多 →
Git定时自动备份:从脚本设计到定时任务的全攻略

Git定时自动备份:从脚本设计到定时任务的全攻略

“git定时自动备份”这件事,说大不大,说小也不小。我见过太多项目栽在“忘了备份”这四个字上——本地硬盘突然坏掉,或者误删了分支还傻乎乎地接着写,等回过神来才发现唯一一份代码已经躺在了回收站里。如果你也经历过这种“两眼一…

2026/10/15 18:10:22 阅读更多 →
测试开发面试题全解析:从接口测试到算法题的标准答题框架

测试开发面试题全解析:从接口测试到算法题的标准答题框架

简介:阿里巴巴数字马力测试开发工程师面试题详解是一份面向软件测试、测试开发岗位求职者的面试题库,聚焦数字马力及阿里系技术面试常见场景。题目按基础、自动化、性能、测试开发、阿里场景、支付专项等模块展开,覆盖Python编程、排序算法、…

2026/10/15 18:10:22 阅读更多 →
异步FIFO设计详解:跨时钟域、格雷码与空满判断实践

异步FIFO设计详解:跨时钟域、格雷码与空满判断实践

1. 从跨时钟域说起:为什么绕不开异步FIFO做过数字IC或FPGA开发的工程师,迟早都会撞上跨时钟域处理这堵墙。两个时钟域之间要传数据,最直接的办法是用握手协议——请求、应答、再请求,简单可靠但吞吐量上不去,每次传输都…

2026/10/15 18:10:22 阅读更多 →
PyTorch计算图深度解析:从显存泄漏到梯度优化的实战指南

PyTorch计算图深度解析:从显存泄漏到梯度优化的实战指南

1. 从一次显存泄漏说起:为什么必须搞懂计算图刚接触PyTorch那会儿,我写过一个看起来毫无问题的训练循环:每个batch前向传播、算loss、反向传播、更新参数,然后进入下一轮。代码跑起来loss确实在降,但显存占用却像爬楼梯…

2026/10/15 18:10:22 阅读更多 →
PyQt5平移动画实战:从QPropertyAnimation到性能优化

PyQt5平移动画实战:从QPropertyAnimation到性能优化

直接开始正文。1. 先搞清楚一件事:PyQt5里实现平移动画,你到底有几种选择我在群里经常看到有人问“PyQt5怎么做平移动画”,结果下面回复五花八门,有说用QTimer循环改坐标的,有说直接上QPropertyAnimation的&#xff0c…

2026/10/15 18:09:21 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/15 16:18:54 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/15 13:48:15 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 9:49:14 阅读更多 →