大家好,我是Edison。
上一篇,我们学习了MAF中进行多Agent智能体的顺序和移交编排。但是,很多时候我们想要嵌入一些业务逻辑和结构化输出,亦或者是需要保持历史对话,这时我们就可以开发一些自定义Executor来组成工作流。
什么是Executor?
Executor又称为执行器,它是MAF中处理工作流消息的基本构建模块,是接受结构化消息、执行并生成输出结果消息或事件的独立单元。
很多时候,我们想要封装一些复杂的业务流程到Agent的工作流中,又或者想要完全控制Agent的生命周期和对话历史,这时候我们就需要开发一些自定义的Executor。
例如,我们想要做一些严格的评分判断、循环控制、条件终止的业务逻辑,就需要自定义Executor了。
这里以一个智能营销文案的场景为例,假设一个企业有两个专家,一个专门负责编写文案,另一个则负责对文案进行审核评分。只有当撰写的文案通过审核负责人的审核(假设量化指标:评分>=8)才能进行发布,否则文案编写者需要根据审核人提供的反馈改进建议进行反复修改。

案例来自圣杰《.NET + AI 智能体开发进阶》
由上图可知,这里不仅需要文案专家 和 审核专家 嵌入一些评分和反馈的逻辑,还要设置循环和终止的条件,才能让这个工作流能够比较准确的满足企业的需求。因此,我们就需要开发两个自定义的Executor来封装文案撰写 和 文案审核的Agent。
那么,哪些场景不需要自定义Executor呢?
比如,就只需要一次性Agent的调用输出回答,又或者不需要嵌入严格的业务逻辑的场景。
下面,就让我们来一一实现这个案例。
准备工作
在今天的这个案例中,我们创建了一个.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)SloganResult :文案生成结果
/// <summary>
/// 文案生成结果
/// </summary>
public sealed class SloganResult
{
/// <summary>
/// 产品任务描述
/// </summary>
[JsonPropertyName("task")]
public required string Task { get; set; }
/// <summary>
/// 生成的标语
/// </summary>
[JsonPropertyName("slogan")]
public required string Slogan { get; set; }
}
(2)FeedbackResult:审核反馈结果
/// <summary>
/// 审核反馈结果
/// </summary>
public sealed class FeedbackResult
{
/// <summary>
/// 审核评论
/// </summary>
[JsonPropertyName("comments")]
public string Comments { get; set; } = string.Empty;
/// <summary>
/// 质量评分(1-10分)
/// </summary>
[JsonPropertyName("rating")]
public int Rating { get; set; }
/// <summary>
/// 改进建议
/// </summary>
[JsonPropertyName("actions")]
public string Actions { get; set; } = string.Empty;
}
定义自定义事件
MAF中定义了一个WorkflowEvent的基类,所有自定义Event都需要继承于它。
(1)SloganGeneratedEvent :文案已生成事件
public sealed class SloganGeneratedEvent : WorkflowEvent
{
private readonly SloganResult _sloganResult;
public SloganGeneratedEvent(SloganResult sloganResult)
: base(sloganResult)
{
this._sloganResult = sloganResult;
}
public override string ToString() =>
$"📝 [标语生成] {_sloganResult.Slogan}";
}
(2)FeedbackFinishedEvent : 反馈已完成事件
/// <summary>
/// 自定义事件:审核反馈完成
/// </summary>
public sealed class FeedbackFinishedEvent : WorkflowEvent
{
private readonly FeedbackResult _feedbackResult;
public FeedbackFinishedEvent(FeedbackResult feedbackResult)
: base(feedbackResult)
{
this._feedbackResult = feedbackResult;
}
public override string ToString() =>
$"""
📊 [审核反馈]
评分: {_feedbackResult.Rating}/10
评论: {_feedbackResult.Comments}
建议: {_feedbackResult.Actions}
""";
}
开发文案生成Executor
MAF中定义了一个Executor的基类,所有自定义Exectuor都需要继承于它。
/// <summary>
/// 文案生成 Executor - 根据任务或反馈生成标语
/// </summary>
public class SloganWriterExecutor : Executor
{
private readonly AIAgent _agent;
private readonly AgentThread _thread;
/// <summary>
/// 初始化文案生成 Executor
/// </summary>
/// <param name="id">Executor 唯一标识</param>
/// <param name="chatClient">AI 聊天客户端</param>
public SloganWriterExecutor(string id, IChatClient chatClient) : base(id)
{
// 配置 Agent 选项
ChatClientAgentOptions agentOptions = new(
instructions: "你是一名专业的文案撰写专家。你将根据产品特性创作简洁有力的宣传标语。"
)
{
ChatOptions = new()
{
// 配置结构化输出:要求返回 SloganResult JSON 格式
ResponseFormat = ChatResponseFormat.ForJsonSchema<SloganResult>()
}
};
// 创建 Agent 和对话线程
this._agent = new ChatClientAgent(chatClient, agentOptions);
this._thread = this._agent.GetNewThread();
}
/// <summary>
/// 配置消息路由:支持两种输入类型
/// </summary>
protected override RouteBuilder ConfigureRoutes(RouteBuilder routeBuilder) =>
routeBuilder
.AddHandler<string, SloganResult>(this.HandleInitialTaskAsync) // 处理初始任务
.AddHandler<FeedbackResult, SloganResult>(this.HandleFeedbackAsync); // 处理反馈
/// <summary>
/// 处理初始任务(首次生成)
/// </summary>
private async ValueTask<SloganResult> HandleInitialTaskAsync(
string message,
IWorkflowContext context,
CancellationToken cancellationToken = default)
{
Console.WriteLine($"✍️ [文案生成] 接收到任务: {message}");
// 调用 Agent 生成标语
var result = await this._agent.RunAsync(message, this._thread, cancellationToken: cancellationToken);
// 反序列化结构化输出
var sloganResult = JsonSerializer.Deserialize<SloganResult>(result.Text)
?? throw new InvalidOperationException("❌ 反序列化标语结果失败");
Console.WriteLine($"📝 [文案生成] 生成标语: {sloganResult.Slogan}");
// 发布自定义事件(将在后续定义)
await context.AddEventAsync(new SloganGeneratedEvent(sloganResult), cancellationToken);
return sloganResult;
}
/// <summary>
/// 处理审核反馈(改进优化)
/// </summary>
private async ValueTask<SloganResult> HandleFeedbackAsync(
FeedbackResult feedback,
IWorkflowContext context,
CancellationToken cancellationToken = default)
{
// 构造反馈消息
var feedbackMessage = $"""
以下是对你之前标语的审核反馈:
评论: {feedback.Comments}
评分: {feedback.Rating} / 10
改进建议: {feedback.Actions}
请根据反馈改进你的标语,使其更加精准有力。
""";
Console.WriteLine($"🔄 [文案生成] 接收到反馈,评分: {feedback.Rating}/10");
// 调用 Agent 改进标语(保持对话上下文)
var result = await this._agent.RunAsync(feedbackMessage, this._thread, cancellationToken: cancellationToken);
var sloganResult = JsonSerializer.Deserialize<SloganResult>(result.Text)
?? throw new InvalidOperationException("❌ 反序列化标语结果失败");
Console.WriteLine($"📝 [文案生成] 改进后标语: {sloganResult.Slogan}");
// 发布事件
await context.AddEventAsync(new SloganGeneratedEvent(sloganResult), cancellationToken);
return sloganResult;
}
}
在这个Executor中,需要注意以下几点:
(1)在实例化Agent时配置结构化输出,严格输出强类型的JSON格式数据 ;
(2)需要重写ConfigureRoutes方法配置消息路由 ,即从处理初始任务开始,并设置处理反馈闭环;
其工作机制如下:
-
当接收到string消息(即用户的任务信息)时,调用 HandleInitialTaskAsync 方法进行首次生成;
-
当接收到FeedbackResult类型消息(即质量审核反馈的消息)时,调用 HandleFeedbackAsync 方法进行改进生成。
(3)在调用完Agent获取响应之后,需要将其进行强类型的反序列化输出 ;
(4)最后通过发布自定义事件进行工作流传递 ,这里是 SloganGeneratedEvent;
(5)通过AgentThread实现对话历史保持 ,而不是每次从头开始。
开发质量审核Executor
在质量审核中,假设我们有如下的审核逻辑:评分>=8代表通过审核可以发布,评分<8则发送反馈继续循环,如果编辑次数>=3次则需要终止循环输出当前版本文案。
/// <summary>
/// 审核反馈 Executor - 评估标语质量并提供反馈
/// </summary>
public sealed class FeedbackExecutor : Executor<SloganResult>
{
private readonly AIAgent _agent;
private readonly AgentThread _thread;
private int _attempts = 0;
/// <summary>
/// 最低评分要求(1-10分)
/// </summary>
public int MinimumRating { get; init; } = 8;
/// <summary>
/// 最大尝试次数
/// </summary>
public int MaxAttempts { get; init; } = 3;
/// <summary>
/// 初始化审核反馈 Executor
/// </summary>
/// <param name="id">Executor 唯一标识</param>
/// <param name="chatClient">AI 聊天客户端</param>
public FeedbackExecutor(string id, IChatClient chatClient)
: base(id)
{
// 配置 Agent 选项
ChatClientAgentOptions agentOptions = new(
instructions: "你是一名专业的文案审核专家。你将评估标语的质量,并提供改进建议。"
)
{
ChatOptions = new()
{
// 配置结构化输出:要求返回 FeedbackResult JSON 格式
ResponseFormat = ChatResponseFormat.ForJsonSchema<FeedbackResult>()
}
};
this._agent = new ChatClientAgent(chatClient, agentOptions);
this._thread = this._agent.GetNewThread();
}
/// <summary>
/// 处理标语审核
/// </summary>
public override async ValueTask HandleAsync(
SloganResult slogan,
IWorkflowContext context,
CancellationToken cancellationToken = default)
{
// 构造审核消息
var reviewMessage = $"""
请审核以下标语:
任务: {slogan.Task}
标语: {slogan.Slogan}
请提供:
1. 详细的评论(comments)
2. 质量评分(rating,1-10分)
3. 改进建议(actions)
""";
Console.WriteLine($"🔍 [质量审核] 开始审核标语: {slogan.Slogan}");
// 调用 Agent 进行审核
var response = await this._agent.RunAsync(reviewMessage, this._thread, cancellationToken: cancellationToken);
// 反序列化反馈结果
var feedback = JsonSerializer.Deserialize<FeedbackResult>(response.Text)
?? throw new InvalidOperationException("❌ 反序列化反馈结果失败");
Console.WriteLine($"📊 [质量审核] 评分: {feedback.Rating}/10");
// 发布自定义事件(将在后续定义)
await context.AddEventAsync(new FeedbackFinishedEvent(feedback), cancellationToken);
// 业务逻辑:判断是否通过审核
if (feedback.Rating >= this.MinimumRating)
{
// ✅ 通过审核
await context.YieldOutputAsync(
$"""
✅ 标语已通过审核!
任务: {slogan.Task}
标语: {slogan.Slogan}
评分: {feedback.Rating}/10
评论: {feedback.Comments}
""",
cancellationToken
);
Console.WriteLine($"✅ [质量审核] 标语通过审核");
return;
}
// ❌ 未通过审核,检查尝试次数
if (this._attempts >= this.MaxAttempts)
{
// 达到最大尝试次数,输出最终版本
await context.YieldOutputAsync(
$"""
⚠️ 标语在 {this.MaxAttempts} 次尝试后未达到最低评分要求。
最终标语: {slogan.Slogan}
最终评分: {feedback.Rating}/10
评论: {feedback.Comments}
""",
cancellationToken
);
Console.WriteLine($"⚠️ [质量审核] 达到最大尝试次数,终止流程");
return;
}
// 🔄 继续循环:发送反馈消息回到 SloganWriterExecutor
await context.SendMessageAsync(feedback, cancellationToken: cancellationToken);
this._attempts++;
Console.WriteLine($"🔄 [质量审核] 发送反馈,第 {this._attempts} 次尝试");
}
}
在这个Executor中,除了之前提到的几点之外,我们还需要注意:
-
在反序列化反馈结果及发布自定义事件之后,需要嵌入评分逻辑,即判断是否通过审核;如果通过审核,就及时结束循环;如果不通过,则发送反馈消息继续循环;
-
记录审核次数,如果达到设定的最大值,也及时结束循环不恋战!
-
通过 IWorkflowContext 的 SendMessageAsync 方法将反馈消息传递给其他参与者,这里是 SloganWriterExecutor。
构建工作流
现在万事俱备,只欠一个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: 实例化自定义Executors
var solganWriter = new SloganWriterExecutor(id: "SloganWriter", chatClient);
var feebackHandler = new FeedbackExecutor(id: "FeedbackHandler", chatClient);
Console.WriteLine("✅ Executor 实例创建完成");
Step3: 创建工作流
var workflow = new WorkflowBuilder(solganWriter)
.AddEdge(source: solganWriter, target: feebackHandler) // 生成 → 审核
.AddEdge(source: feebackHandler, target: solganWriter) // 审核不通过 → 重新生成
.WithOutputFrom(feebackHandler) // 指定输出来源
.Build();
Console.WriteLine("✅ 工作流构建完成");
Step4: 测试工作流
// 定义产品任务
var productTask = "请为马自达一款经济实惠且驾驶乐趣十足的电动SUV创作标语,要求结合马自达电车的特性来创作";
Console.WriteLine($"📋 产品需求: {productTask}\n");
Console.WriteLine($"📊 审核标准: 评分 >= 8分");
Console.WriteLine($"🔄 最大尝试: 3次\n");
Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
Console.WriteLine("⏱️ 开始执行工作流...");
Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
// 执行工作流
await using (var run = await InProcessExecution.StreamAsync(workflow, input: productTask))
{
// 监听工作流事件
await foreach (WorkflowEvent evt in run.WatchStreamAsync())
{
// 使用模式匹配识别不同类型的事件
switch (evt)
{
case SloganGeneratedEvent sloganEvent:
// 处理标语生成事件
Console.WriteLine($"✨ {sloganEvent}");
Console.WriteLine();
break;
case FeedbackFinishedEvent feedbackEvent:
// 处理审核反馈事件
Console.WriteLine($"{feedbackEvent}");
Console.WriteLine();
break;
case WorkflowOutputEvent outputEvent:
// 处理最终输出事件
Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
Console.WriteLine("🎉 工作流执行完成");
Console.WriteLine("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
Console.WriteLine($"{outputEvent.Data}");
break;
}
}
Console.WriteLine("\n✅ 所有流程已完成");
}
测试结果如下图所示:
首先,获得了首次的文案撰写内容:

然后,第一轮审核评分6分并给出反馈:

然后,文案撰写开始修改形成第二版文案:

然后,再次评分为6分又给出反馈:

然后,终于获得审核通过(本次评分9分>=8分),可以发布:

至此,工作流已经结束,可以看见,第三次生成的文案内容比前两次要好一些。
小结
本文介绍了Executor的基本概念 以及 如何开发自定义Executor,然后给出了一个营销文案生成审核的工作流案例详细介绍了自定义Executor的应用。
下一篇,我们将继续学习MAF中如何进行混合编排 Agent 和 Executor,覆盖实际场景中 确定性的业务逻辑 和 AI智能决策 的结合应用。
示例源码
GitHub: https://github.com/EdisonTalk/MAFD
参考资料
Microsoft Learn,《Agent Framework Tutorials》
推荐学习
圣杰,《.NET + AI 智能体开发进阶》

作者:爱迪生
出处:https://edisontalk.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。