news 2026/9/24 15:17:27

MAF快速入门(7)工作流的状态共享

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
MAF快速入门(7)工作流的状态共享

大家好,我是Edison。

最近我一直在跟着圣杰的《.NET+AI智能体开发进阶》课程学习MAF的开发技巧,我强烈推荐你也上车跟我一起出发!

上一篇,我们学习了MAF中进行了Agent和Executor的混合编排,相信你一定有了更多地理解。本篇,我们来通过一个经典的例子了解下MAF中工作流如何实现状态的共享。

状态共享的应用场景

在实际业务场景中,一个AI工作流的多个步骤之间往往需要共享上下文数据,例如用户原始输入、模型的输出缓存等。在MAF中,提供了一个 WorkflowContext 的模型,它原生提供了工作流上下文的状态共享能力。

举个例子,在下面这个聚合统计流程中,创建了一个FileContentState的共享内容通过WorkflowContext进行传递共享给后续的两个统计Executor使用,进而进行聚合输出结果。

WorkflowContext API一览

在WorkflowContext API中,它提供了以下一些状态读取和设置的接口,作为字典我们可以了解一下:

这里我们用的比较多的是:QueueStateUpdateAsync 和 ReadStateAsync 两个方法。

实验案例

今天来实践一个文档统计的工作流案例,和上面的例子相似:

这是一个典型的扇入扇出(Fan-In & Fan-Out)工作流:

首先,用户输入一个文件名,FileReadExecutor会读取该文件的内容并将文件内容传入工作流上下文中共享,同时将文件ID号传递给后续流程。

然后,单词统计Executor 和 段落统计Executor 会分别统计该文件内容的 单词数 和 段落数。

最后,聚合统计Executor会将收集到的 单词数 和 段落数 交由LLM进行友好信息的输出,最初反馈给用户。

这个案例展示了一次写入,多处读取的经典场景,它也是并发协作的基础。

准备工作

在今天的这个案例中,我们仍然创建了一个.NET控制台应用程序,安装了以下NuGet包:
  • Microsoft.Agents.AI.OpenAI

  • Microsoft.Agents.AI.Workflows

  • Microsoft.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 模型,之前的 Qwen2.5 模型在这个案例中不适用。你可以通过这个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").Get<OpenAIProvider>();

定义数据传输模型

首先,我们定义一下在这个工作流中需要生成传递的数据模型:

(1)FileStats :统计结果DTO

internal sealed class FileStats{ public int WordCount { get; init; } public int ParagraphCount { get; init; }}

(2)FileContentStateConstants :常量,类似于Cache Key的作用

internal static class FileContentStateConstants{ public const string ScopeName = "FileContentState";}

定义示例数据集

这里我们Mock了一个文档数据集,用于模拟读取的文件内容。

internal static class SharedStateSampleData{ private static readonly IReadOnlyDictionary<string, string> Documents = new Dictionary<string, string> { ["ProductBrief"] = "MAF Workflow 让 .NET 团队可以像积木一样组合 Agent、Executor 与工具, 支持流式事件、并发节点和可观测性。\n\n它强调企业级能力, 包括状态管理、依赖注入、权限控制, 适合搭建端到端 AI 业务流程。", ["WeeklyReport"] = "本周平台完成了 Shared State 功能的代码走查, 已经覆盖 Fan-out/Fan-in, Loop, Human-in-the-Loop 三种场景。\n\n下周计划: 1) 集成多模型投票; 2) 增加异常回滚; 3) 落地监控指标。" }; public static string GetDocument(string name) => Documents.TryGetValue(name, out var content) ? content : throw new ArgumentException($"未找到文档: {name}");}

文件读取Executor

MAF中定义了一个Executor的基类,所有自定义Exectuor都需要继承于它。

internal sealed class FileReadExecutor() : Executor<string, string>("FileReadExecutor"){ public override async ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default) { var content = SharedStateSampleData.GetDocument(message); var fileId = Guid.NewGuid().ToString("N"); await context.QueueStateUpdateAsync(fileId, content, FileContentStateConstants.ScopeName, cancellationToken); Console.WriteLine($"📦 FileReadExecutor 已成功将 {message} 写入 Scope:{FileContentStateConstants.ScopeName}"); return fileId; }}

在这个Executor中,接收了用户的输入文件名,然后模拟从Mock文档内容中读取文件内容,并将文件ID 和 文件内容 通过 QueueStateUpdateAsync 方法传入共享状态存储区中,以便后续节点在需要的时候可以从共享状态中读取传递的内容。

最后,将文件ID直接传递给下一个节点。

文字统计 和 段落统计Executor

这里我们弄了两个用于统计文件内容的Executor,演示一下如何通过 ReadStateAsync 方法读取共享状态存储区中的内容。

(1)文字统计

internal sealed class WordCountingExecutor() : Executor<string, FileStats>("WordCountingExecutor"){ public override async ValueTask<FileStats> HandleAsync(string fileId, IWorkflowContext context, CancellationToken cancellationToken = default) { string? content = await context.ReadStateAsync<string>(fileId, FileContentStateConstants.ScopeName, cancellationToken); if (content is null) { throw new InvalidOperationException($"无法在 Scope:{FileContentStateConstants.ScopeName} 中找到 fileId={fileId}"); } int wordCount = content.Split([' ', '\n', '\r'], StringSplitOptions.RemoveEmptyEntries).Length; return new FileStats { WordCount = wordCount }; }}

(2)段落统计

internal sealed class ParagraphCountingExecutor() : Executor<string, FileStats>("ParagraphCountingExecutor"){ public override async ValueTask<FileStats> HandleAsync(string fileId, IWorkflowContext context, CancellationToken cancellationToken = default) { string? content = await context.ReadStateAsync<string>(fileId, FileContentStateConstants.ScopeName, cancellationToken); if (content is null) { throw new InvalidOperationException($"无法在 Scope:{FileContentStateConstants.ScopeName} 中找到 fileId={fileId}"); } int paragraphCount = content.Split(['\n', '\r'], StringSplitOptions.RemoveEmptyEntries).Length; return new FileStats { ParagraphCount = paragraphCount }; }}

聚合输出Executor

在工作流最后,我们定义给一个聚合输出的节点来汇总和输出用户友好的统计结果,这里我们首先获取到结构化的统计结果,然后通过LLM让其根据结果输出最终的反馈信息:

internal sealed class AggregationExecutor : Executor<FileStats>{ private readonly AIAgent _aggregationAgent; private readonly AgentThread _thread; private readonly List<FileStats> _buffer = []; public AggregationExecutor(IChatClient chatClient) : base("AggregationExecutor") { // 创建 Agent 和对话线程 this._aggregationAgent = chatClient.CreateAIAgent( "你是一个专业的文档统计结果输出大师,你可以将收到的JSON格式统计结果(如总词数、总段落数 以及 统计时间等)进行友好的信息输出给用户。", "output_agent", "Output user friendly message based on input document stats result"); ; this._thread = this._aggregationAgent.GetNewThread(); } public override async ValueTask HandleAsync(FileStats message, IWorkflowContext context, CancellationToken cancellationToken = default) { this._buffer.Add(message); if (this._buffer.Count < 2) { return; } int totalWords = this._buffer.Sum(x => x.WordCount); int totalParagraphs = this._buffer.Sum(x => x.ParagraphCount); var output = new { 总词数 = totalWords, 总段落数 = totalParagraphs, 统计时间 = DateTimeOffset.UtcNow }; var response = await this._aggregationAgent.RunAsync(JsonSerializer.Serialize(output), this._thread, cancellationToken: cancellationToken); await context.YieldOutputAsync(response.Text, cancellationToken); }}

构建工作流

现在万事俱备,只欠一个Workflow,现在Let's 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 fileRead = new FileReadExecutor();var wordCounting = new WordCountingExecutor();var paragraphCounting = new ParagraphCountingExecutor();var aggregate = new AggregationExecutor(chatClient);

Step3: 创建Fan-out/Fan-in工作流

var sharedStateWorkflow = new WorkflowBuilder(fileRead) .AddFanOutEdge(fileRead, [wordCounting, paragraphCounting]) .AddFanInEdge([wordCounting, paragraphCounting], aggregate) .WithOutputFrom(aggregate) .Build(); Console.OutputEncoding = Encoding.UTF8;Console.WriteLine("✅ Shared State Workflow 构建完成");

Step4: 测试工作流

var documentKey = "ProductBrief";Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");Console.WriteLine($"📂 演示文档: {documentKey}");Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");await using (var run = await InProcessExecution.StreamAsync(sharedStateWorkflow, documentKey)){ await foreach (WorkflowEvent evt in run.WatchStreamAsync()) { switch (evt) { case WorkflowStartedEvent started: Console.WriteLine($"🚀 Workflow Started"); break; case ExecutorCompletedEvent executorCompleted: Console.WriteLine($"✅ {executorCompleted.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; } } await run.DisposeAsync();}

测试结果如下图所示:

可以看见,经过多个节点的统计和聚合,由LLM总结输出了用户友好的统计结果内容。

小结

本文介绍了工作流的状态共享的应用场景 和 WorkflowContext相关的API,最后给出了一个文档内容统计的Fan-out/Fan-In工作流案例供参考。

下一篇,我们将继续学习MAF中工作流的上下文相关内容。

示例源码

Github: https://github.com/EdisonTalk/MAFD

参考资料

圣杰,《.NET + AI 智能体开发进阶》

年终总结:Edison的2024年终总结

数字化转型:我在传统企业做数字化转型

C#刷算法题:C#刷剑指Offer算法题系列文章目录

C#刷设计模式:C#刷23种设计模式系列文章目录

.NET面试:.NET开发面试知识体系

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/24 23:34:45

Langchain-Chatchat构建API文档智能查询

Langchain-Chatchat构建API文档智能查询 在现代软件开发中&#xff0c;API文档的数量和复杂性正以前所未有的速度增长。一个典型的微服务架构可能涉及数十个服务&#xff0c;每个服务都有自己的接口规范、参数说明和调用示例。开发者每天要面对的不仅是编写代码&#xff0c;更是…

作者头像 李华
网站建设 2026/9/24 12:10:32

FaceFusion镜像兼容性测试:支持Windows/Linux/CUDA版本

FaceFusion镜像兼容性测试&#xff1a;支持Windows/Linux/CUDA版本在AI内容创作日益普及的今天&#xff0c;人脸替换技术已从实验室走向大众应用。无论是短视频平台上的趣味换脸滤镜&#xff0c;还是影视制作中的数字替身&#xff0c;背后都离不开像FaceFusion这样高效、开源的…

作者头像 李华
网站建设 2026/9/25 1:11:17

FaceFusion在虚拟健身教练中的动态演示能力

FaceFusion在虚拟健身教练中的动态演示能力 在智能健身设备日益普及的今天&#xff0c;用户早已不再满足于“播放-暂停”的视频教学模式。他们希望获得更个性化的指导、更具沉浸感的体验&#xff0c;甚至期待一位能读懂情绪、懂得鼓励的“数字私教”。然而&#xff0c;大多数AI…

作者头像 李华
网站建设 2026/9/25 4:14:47

Langchain-Chatchat在职业教育中的技能培训

Langchain-Chatchat在职业教育中的技能培训 在职业院校的实训车间里&#xff0c;一名学生正站在数控机床前&#xff0c;眉头紧锁。他刚刚完成粗加工&#xff0c;却突然记不清下一步换刀程序该怎么操作。翻手册&#xff1f;太慢&#xff1b;问老师&#xff1f;可能正在指导其他同…

作者头像 李华
网站建设 2026/9/25 7:11:07

FaceFusion如何实现唇形与音频同步?技术路线剖析

FaceFusion如何实现唇形与音频同步&#xff1f;技术路线剖析 在虚拟主播直播、AI配音短片甚至电影后期制作中&#xff0c;一个看似微小却极为关键的细节正在决定内容的真实感——人物说话时的嘴型是否与声音完全匹配。哪怕只是几帧的错位&#xff0c;都会让观众立刻察觉“这不是…

作者头像 李华
网站建设 2026/9/25 2:23:56

小程序毕设项目推荐-基于springboot+微信小程序的汽车后市场二手车出售系统基于springboot+uniapp二手车汽车销售系统 【附源码+文档,调试定制服务】

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华