news 2026/6/25 23:29:12

[MAF Workflow编排模式-01]Sequential:打造环环相扣的标准化智能流水线

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
[MAF Workflow编排模式-01]Sequential:打造环环相扣的标准化智能流水线

MAF提供了几种内置的多Agent编排模式,包括Sequential、Concurrent、Handoff和Group Chat等。本文将详细介绍Sequential模式的工作原理和应用场景。在Sequential编排中,Agent按管道顺序组织。每个Agent依次处理任务,并将其输出传递给序列中的下一个Agent。这非常适合每个步骤都建立在前一个步骤基础上的工作流,例如文档审查、数据处理管道或多阶段推理。

1. 采用Sequential模式创建多体裁作品创作Agent

MAF针对Sequential模式编排的Workflow通过调用静态类型AgentWorkflowBuilderBuildSequential方法来构建。在正是介绍该方法针对Workflow的构建逻辑之前,我们先通过一个简单的示例来演示Sequential模式的应用场景。假设我们要创建一个多体裁作品创作Agent,我们给定一个主题和素材,Agent将依次生成一首唐诗、一首宋词和一篇短篇小说。

如下面的代码片段所示,我们创建了三个不同的Agent,分别负责创作唐诗、宋词和短篇小说。每个Agent都被赋予了特定的指令,以确保它们专注于各自的创作任务。然后,我们使用AgentWorkflowBuilderBuildSequential方法将这些Agent按顺序编排成一个工作流。

usingAzure;usingdotenv.net;usingMicrosoft.Agents.AI.Workflows;usingMicrosoft.Extensions.AI;usingOpenAI;DotEnv.Load();vartangPoetryComposer=CreateChatClient().AsAIAgent(name:"TangPoetryComposer",instructions:""" 你是一个精通唐诗创作的智能体,负责根据提供的主题和意境创作一首符合唐诗风格的诗歌。 如果用户的任务了提及了基于其他非唐诗(比如宋词、短篇小说)的创作,请忽略。""");varsongLyricsComposer=CreateChatClient().AsAIAgent(name:"SongLyricsComposer",instructions:""" 你是一个精通宋词创作的智能体,负责根据提供的主题和意境创作一首宋词,你可以自选词牌名 如果用户的任务了提及了基于其他非宋词(比如唐诗、短篇小说)的创作,请忽略。""");varnovelComposer=CreateChatClient().AsAIAgent(name:"NovelComposer",instructions:""" 你是一个精通小说创作的智能体,负责根据提供的主题和意境创作一篇1000字以内的短篇小说。 如果用户的任务了提及了基于其他非小说(比如唐诗、宋词)的创作,请忽略。""");varworkflow=AgentWorkflowBuilder.BuildSequential(tangPoetryComposer,songLyricsComposer,novelComposer);IChatClientCreateChatClient(){varmodel=Environment.GetEnvironmentVariable("MODEL")!;varapiKey=Environment.GetEnvironmentVariable("API_KEY")!;varendpoint=Environment.GetEnvironmentVariable("OPENAI_URL")!;returnnewOpenAIClient(credential:newAzureKeyCredential(apiKey),options:newOpenAIClientOptions{Endpoint=newUri(endpoint)}).GetResponsesClient().AsIChatClient(defaultModelId:model);}

在如下的演示程序中,我们以流的方式运行这个Worflow,要求Agent基于《诗经·卫风·氓》的背景和情感基调,分别创作一首唐诗、一首宋词和一篇短篇小说。我们遍历Workflow的事件流,通过监听AgentResponseUpdateEvent事件来获取每个Agent的输出,并将其打印到控制台上。

varoriginalPoem=""" 氓之蚩蚩,抱布贸丝。匪来贸丝,来即我谋。 送子涉淇,至于顿丘。匪我愆期,子无良媒。 将子无怒,秋以为期。 乘彼垝垣,以望复关。不见复关,泣涕涟涟。 既见复关,载笑载言。尔卜尔筮,体无咎言。 以尔车来,以我贿迁。 桑之未落,其叶沃若。于嗟鸠兮,无食桑葚! 于嗟女兮,无与士耽!士之耽兮,犹可说也; 女之耽兮,不可说也。 桑之落矣,其黄而陨。自我徂尔,三岁食贫。 淇水汤汤,渐车帷裳。女也不爽,士贰其行。 士也罔极,二三其德。 三岁为妇,靡室劳矣;夙兴夜寐,靡有朝矣。 言既遂矣,至于暴怒。兄弟不知,咥其笑矣。 静言思之,躬自悼矣。 及尔偕老,老使我怨。淇则有岸,隰则有泮。 总角之宴,言笑晏晏。信誓旦旦,不思其反。 反是不思,亦已焉哉!""";varprompt=$""" 基于如下这首《卫风·氓》的背景和情感基调,分别创作一首唐诗、一首宋词和一篇短篇小说。 原文如下:{originalPoem}""";awaitusing(varrun=awaitInProcessExecution.Default.RunStreamingAsync(workflow,prompt)){awaitrun.TrySendMessageAsync(newTurnToken(emitEvents:true));string?lastExecutorId=null;awaitforeach(WorkflowEventevtinrun.WatchStreamAsync()){if(evtisAgentResponseUpdateEvente){if(e.ExecutorId!=lastExecutorId){lastExecutorId=e.ExecutorId;Console.WriteLine($"\n{newstring('-',20)}{e.ExecutorId}{newstring('-',20)}");}Console.Write(e.Update.Text);}}}

输出:

--------------------TangPoetryComposer_8a507c5d31ad4a20995d93f3af2ef24a-------------------- 基于《卫风·氓》的背景与情感,我为你创作以下三首作品: ### 唐诗:五言古诗《淇水叹》 淇水送君行,悠悠赴顿丘。 叶沃桑林密,鸠鸣误春柔。 三岁为君妇,靡室劳未休。 信誓如朝露,日晞不可留。 汤汤淇水畔,独坐叹浮舟。 ### 简析 此诗以五言古体浓缩《氓》的核心情节。从“送君淇水”的温柔开篇,到“叶沃桑林”暗喻女子青春,再到“信誓如朝露”的决绝,最后以“叹浮舟”收尾,紧扣原诗“不可说也”的哀婉,唐诗风骨分明。 --------------------SongLyricsComposer_fd502ab8411a4000931a78f6cec0bdaa-------------------- 好的,我将专注于你所要求的宋词创作,忽略唐诗与小说的部分。根据《卫风·氓》的背景与情感基调,我为你填这首《钗头凤》,以词体承继此弃妇之悲: --- **《钗头凤·淇水寒》** 淇水寒,复关远, 抱布嗤嗤成旧怨。 誓如磐,夜如年。 一怀桑落,三岁贫煎。 瞒、瞒、瞒。 车帷卷,言笑短, 夙兴夜寐谁人见? 鸠无端,叶黄陨。 信誓虽在,情分已断。 难、难、难。 --- **注:** - 词牌取《钗头凤》之体,取其断肠之调,与原诗决绝之情相合。 - “淇水”“复关”“桑落”“鸠鸣”等意象皆从原诗化出,延续“不可说也”的哀婉。 - 上下片末叠字“瞒瞒瞒”“难难难”,直指女子隐忍与绝望,词尽而悲不尽。 --------------------NovelComposer_4020aedd7c834642978be2a573c11b64-------------------- 好的,根据你的要求,我为你创作一篇基于《卫风·氓》背景与情感基底的短篇小说。 --- ## 桑落 她又一次站在淇水边。 河水汤汤,依旧如三年前那般浑浊而匆忙。水花溅上车帷,打湿了她的裙角,冰凉刺骨。她想起那年秋天,他驾着车来迎娶她,她坐在车上,笑声清脆得像桑林间的斑鸠。 那时桑叶正绿,沃若如洗。 “将子无怒,秋以为期。”她说这话时,不过十六岁。他站在顿丘的土坡上,抱着那匹布,笑得憨厚又狡黠——哪里是来换丝的?分明是来谋她这个人。 她竟信了。 信他卜筮的吉兆,信他信誓旦旦的承诺,信那一句“及尔偕老”。她带着嫁妆渡水而来,以为渡过去便是暖屋热饭、举案齐眉。 可三年了。 三年里,她夙兴夜寐,靡室劳矣。鸡鸣便起,星出未歇,灶台的柴灰染白了鬓角,手里的老茧磨粗了指尖。她从没抱怨过贫穷,却等来了他的暴怒——那张曾经嗤嗤憨笑的脸,不知何时变得陌生而狰狞。 淇水边的桑树黄了叶子,一片一片,落在泥里。 “于嗟女兮,无与士耽。” 她终于懂了。士之耽兮,犹可说也——他腻了、厌了、倦了,大可以甩袖就走,还能在复关的酒肆里笑谈风流。可她呢?女之耽兮,不可说也。她无处可逃,无处可说,连兄弟都不知道她的苦楚,只在年节聚会上对她咥笑不止,笑她憔悴,笑她失了当年的光彩。 她独自坐在灶前,火光映着脸,眼泪一颗一颗掉进灰烬里,无声无息。 “静言思之,躬自悼矣。” 今日她又来到淇水边。河水东流,有岸有畔,可她的苦楚无边无际。她站在岸边想了很久,想那年总角之宴的言笑晏晏,想他当初信誓旦旦的模样——那些誓言像是昨天的事,又像是上辈子的事。 “不思其反。” 算了。 她转过身,背对淇水。风从桑林里吹过来,吹落了最后一片枯黄的桑叶,落在她肩头,又滑下来,被水冲走。 反是不思,亦已焉哉。 这一次,她没有哭。 她的眼泪早在三年的灶火里烧干了。

上面的输出分为三段,分别对应唐诗、宋词和短篇小说的创作结果。每个Agent根据原始《卫风·氓》的背景和情感基调,独立完成了各自的创作任务,并将结果输出到控制台上。可以看出,不论是诗词还是短篇小说,质量都还不错。

2. Workflow的结构

要了解AgentWorkflowBuilderBuildSequential方法构建Workflow的内部逻辑,我们最好先看看它构建出来的Worflow具有怎样的结构。为此我们在如下这个静态类Utilities中提供了一个GenerateAndShowPngImageAsync方法,它可以将Workflow的结构转换为Mermaid格式的图形,并通过mermaid.ink服务生成PNG图片并在本地打开。我们可以利用这个方法来可视化Workflow的结构。

publicstaticclassUtilities{publicstaticasyncTaskGenerateAndShowPngImageAsync(Workflowworkflow){stringmermaidCode=workflow.ToMermaidString();byte[]bytes=Encoding.UTF8.GetBytes(mermaidCode);stringbase64=Convert.ToBase64String(bytes);stringsafeBase64=base64.Replace("+","-").Replace("/","_").TrimEnd('=');stringurl=$"https://mermaid.ink/img/{safeBase64}";using(HttpClientclient=new()){byte[]imageBytes=awaitclient.GetByteArrayAsync(url);awaitFile.WriteAllBytesAsync("workflow.png",imageBytes);}Process.Start(newProcessStartInfo("workflow.png"){UseShellExecute=true});}}

如果我们将上面构建的Workflow对象作为参数传入GenerateAndShowPngImageAsync方法中,会呈现出具有如下结构的流程图。可以看出,整个Workflow是一个线性的结构,前三个Executor基于我们提供的AIAgent创建而成,后跟一个ID为OutputMessages的Executor。

3. OutputMessagesExecutor

流程最后的OutputMessages对应如下这个名为OutputMessagesExecutor的Executor类型。OutputMessagesExecutor派生于支持对话协议的基类ChatProtocolExecutor,所以它具有消息收集的能力。它唯一的使命就是将收集的消息列表原样输出来。所以它利用重写的ConfigureProtocol方法注册了针对List<ChatMessage>输出(YieldOutput)类型,并在重写TakeTurnAsync方法中调用IWorkflowContextYieldOutputAsync方法将收集到的消息列表输出。

internalsealedclassOutputMessagesExecutor:ChatProtocolExecutor,IResettableExecutor{publicconststringExecutorId="OutputMessages";publicOutputMessagesExecutor(ChatProtocolExecutorOptions?options=null):base("OutputMessages",options,declareCrossRunShareable:true){}protectedoverrideProtocolBuilderConfigureProtocol(ProtocolBuilderprotocolBuilder)=>base.ConfigureProtocol(protocolBuilder).YieldsOutput<List<ChatMessage>>();protectedoverrideValueTaskTakeTurnAsync(List<ChatMessage>messages,IWorkflowContextcontext,bool?emitEvents,CancellationTokencancellationToken=default)=>context.YieldOutputAsync(messages,cancellationToken);}

4. 顺利流程的编排

Sequential模式的Workflow的编排实现在AgentWorkflowBuilder的两个重载的BuildSequential方法中,它们最终会调用私有的BuildSequentialCore方法来构建Workflow。BuildSequentialCore方法构建Workflow的逻辑非常简单:它通过调用扩展方法BindAsExecutor将每个AIAgent对象转换成对应的AIAgentBinding对象(我的文章“Worflow功能节点的多种定义方式”中具有针对该类型的详细介绍)。构建的Workflow将第一个AIAgentBinding作为起点,然后使用DirectEdge将这些AIAgentBinding对象按顺序连接起来,最终同样使用DirectEdge将最后一个AIAgentBindingOutputMessagesExecutor连接起来。

publicstaticclassAgentWorkflowBuilder{publicstaticWorkflowBuildSequential(paramsIEnumerable<AIAgent>agents)=>BuildSequentialCore(null,agents);publicstaticWorkflowBuildSequential(stringworkflowName,paramsIEnumerable<AIAgent>agents)=>BuildSequentialCore(workflowName,agents);privatestaticWorkflowBuildSequentialCore(string?workflowName,paramsIEnumerable<AIAgent>agents){AIAgentHostOptionsoptions=newAIAgentHostOptions{ReassignOtherAgentsAsUsers=true,ForwardIncomingMessages=true};List<ExecutorBinding>list=agents.Select((AIAgentagent)=>agent.BindAsExecutor(options)).ToList();ExecutorBindingexecutorBinding=list[0];WorkflowBuilderworkflowBuilder=newWorkflowBuilder(executorBinding);foreach(ExecutorBindingiteminlist.Skip(1)){workflowBuilder.AddEdge(executorBinding,item);executorBinding=item;}OutputMessagesExecutoroutputMessagesExecutor=newOutputMessagesExecutor();workflowBuilder=workflowBuilder.AddEdge(executorBinding,outputMessagesExecutor).WithOutputFrom(outputMessagesExecutor);if(workflowName!=null){workflowBuilder=workflowBuilder.WithName(workflowName);}returnworkflowBuilder.Build();}

由于创建AIAgentBinding时指定的AIAgentHostOptionsForwardIncomingMessages都设置true,所以每个Agent在接收到消息时都会将其转发给下一个Agent。这意味着后面执行的Agent会将前面创建的整个对话历史作为输入,这样才能保证原始的输入可以抵达每个Agent。由于最后一个OutputMessagesExecutor会作为Workflow的输出节点,所以最后一个Agent输出的响应消息列表会作为整个Workflow的输出。

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

哪些企业可以申报中关村小微企业研发费补贴项目?怎么申报?

一、哪些企业可以申报中关村小微企业研发费补贴项目&#xff1f;申报企业应满足政策规定的相关条件&#xff0c;具体包括&#xff1a;&#xff08;1&#xff09;企业属于新一代信息技术、医药健康、智能制造、新材料、绿色能源与节能环保等硬科技领域。&#xff08;2&#xff0…

作者头像 李华
网站建设 2026/6/25 23:27:28

PN7462时钟与电源管理:从寄存器配置到嵌入式系统稳定实战

1. 项目概述与核心价值在嵌入式开发&#xff0c;特别是涉及射频识别&#xff08;RFID&#xff09;或USB通信的项目中&#xff0c;我们常常会与NXP的PN7462这类高度集成的微控制器打交道。这类芯片的魅力在于其“麻雀虽小&#xff0c;五脏俱全”&#xff0c;但随之而来的挑战是如…

作者头像 李华
网站建设 2026/6/25 23:26:34

如何理解梯度下降

目录 一、梯度下降 1.1 什么是梯度下降&#xff1f; 1.2 梯度下降解决什么问题&#xff1f; 1.3 梯度下降的损失函数 1.4 梯度怎么走损失降的快 1.5 梯度下降线性回归的递推公式 1.6 代码如何实现梯度下降的更新规则 1.7 对数据还要进行特征标准化&#xff08;Z-score&…

作者头像 李华
网站建设 2026/6/25 23:26:10

打破中医 AI 效验认证难题,知医邦以循证体系筑牢数智诊疗临床底气

在人工智能深度赋能中医药现代化的当下&#xff0c;“算法能否对标名医辨证”“AI 诊疗准确率是否具备临床公信力” 两大难题&#xff0c;长期桎梏中医 AI 产品临床落地与医疗器械合规申报。相较于西医 AI 成熟的量化评价体系&#xff0c;中医望闻问切兼具整体性、主观性的辨证…

作者头像 李华
网站建设 2026/6/25 23:26:05

实训反馈总慢半拍?数字化即时反馈打通学习闭环最后一步

职业院校的实训课堂上&#xff0c;常有这样的尴尬&#xff1a;学生花了两节课做完一个设备调试或编程项目&#xff0c;提交给老师后&#xff0c;往往要等上两三天甚至一周才能拿到批改反馈。等结果回到学生手里&#xff0c;当初操作时卡在哪一步、为什么调整这个参数、试错时走…

作者头像 李华
网站建设 2026/6/25 23:25:24

【WMM 四队列参数说明】

WMM 四队列参数说明"wmm_enabled1\n" \"wmm_ac_bk_cwmin4\n" \"wmm_ac_bk_cwmax10\n" \"wmm_ac_bk_aifs7\n" \"wmm_ac_bk_txop_limit0\n" \"wmm_ac_bk_acm0\n" \"wmm_ac_be_aifs3\n" \"wmm_ac_be_…

作者头像 李华