1. MAF工作流架构设计理念
MAF(Microsoft Agents Framework)的工作流系统采用了一种模块化设计思想,将复杂业务流程拆分为可组合的单元。这种架构特别适合处理具有明确阶段划分和条件分支的业务场景。主工作流+子工作流的组合模式,本质上是一种责任链模式(Chain of Responsibility)与策略模式(Strategy)的混合实现。
在实际业务中,我们经常会遇到这样的场景:一个完整业务流程包含多个阶段,某些阶段又需要根据上下文选择不同的处理策略。以客服工单系统为例,主流程负责工单的接收和分类,而具体的处理逻辑则根据工单类型(如产品质量问题、物流问题等)分发到不同的子流程执行。
提示:MAF的工作流设计允许子工作流独立开发和测试,这种解耦使得系统更易于维护和扩展。当新增工单类型时,只需开发对应的子工作流并在主工作流中添加路由规则即可。
2. 子工作流的核心实现机制
2.1 子工作流的定义与绑定
在MAF中,子工作流通过WorkflowBuilder构建后,需要转换为可执行的Executor实例。这个过程通过BindAsExecutor方法完成:
csharp复制var productQualitySubWorkflow = new WorkflowBuilder(productEvalExecutor)
.AddEdge(productEvalExecutor, returnPolicyExecutor)
.AddEdge(returnPolicyExecutor, aiResponseExecutor)
.WithOutputFrom(aiResponseExecutor)
.Build();
var productQualitySubExecutor =
productQualitySubWorkflow.BindAsExecutor("ProductQualitySubWorkflow");
关键点在于:
- 每个子工作流有自己的输入输出类型,必须与主工作流的数据模型兼容
BindAsExecutor方法为子工作流分配唯一标识符,用于在主工作流中引用- 子工作流内部可以包含任意复杂的处理逻辑,但对主工作流而言只是一个黑盒
2.2 子工作流的执行上下文
子工作流执行时会继承主工作流的上下文信息,包括:
- 共享状态对象(如案例中的
ComplaintProcessingRecord) - 取消令牌(CancellationToken)
- 日志追踪上下文
这种上下文传递机制确保了:
- 子工作流可以访问和修改共享状态
- 主工作流可以监控子工作流的执行进度
- 整个流程可以统一取消
3. 主工作流的编排策略
3.1 条件路由的实现
主工作流通过AddEdge方法的重载实现条件路由:
csharp复制.AddEdge<ComplaintProcessingRecord>(
classifierExecutor,
productQualitySubExecutor,
condition: record => record.Category == "产品质量")
这种设计实现了:
- 类型安全的条件判断(泛型参数确保只能访问正确的数据类型)
- 延迟执行的条件计算(只有当数据到达该节点时才评估条件)
- 清晰的可读性(Lambda表达式直观展示路由逻辑)
3.2 工作流的合并与同步
在客服工单案例中,两个子工作流最终都汇合到合规审核环节:
csharp复制.AddEdge(productQualitySubExecutor, complianceExecutor)
.AddEdge(logisticsSubExecutor, complianceExecutor)
这种模式称为"扇入"(Fan-in),需要注意:
- 确保不同子工作流的输出类型兼容下游处理
- 考虑并发执行时的线程安全问题
- 设计合理的错误处理机制
4. 完整案例实现详解
4.1 环境准备与依赖配置
创建一个.NET控制台应用,添加以下NuGet包:
xml复制<PackageReference Include="Microsoft.Agents.AI.OpenAI" Version="1.0.0" />
<PackageReference Include="Microsoft.Agents.AI.Workflows" Version="1.0.0" />
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" Version="1.0.0" />
配置OpenAI服务(实际项目应使用配置注入):
csharp复制var openAIProvider = new {
ApiKey = "your-api-key",
Endpoint = "https://api.openai.com/v1",
ModelId = "gpt-3.5-turbo"
};
var chatClient = new OpenAIClient(
new ApiKeyCredential(openAIProvider.ApiKey),
new OpenAIClientOptions { Endpoint = new Uri(openAIProvider.Endpoint) })
.GetChatClient(openAIProvider.ModelId)
.AsIChatClient();
4.2 数据模型设计
定义共享状态类时需要考虑:
- 哪些字段会被多个执行器修改(需要线程安全)
- 哪些字段是只读的(可设为init-only)
- 如何设计才能最小化跨执行器的数据传递
csharp复制internal class ComplaintProcessingRecord
{
// 只读的原始数据
public CustomerComplaint Original { get; init; }
// 可变状态
public string Category { get; set; } = "未分类";
public List<string> ProcessingSteps { get; } = new();
// 使用线程安全的集合类型
private readonly ConcurrentDictionary<string, string> _metadata = new();
public string this[string key]
{
get => _metadata.TryGetValue(key, out var value) ? value : null;
set => _metadata[key] = value;
}
}
4.3 执行器的最佳实践
每个执行器应遵循单一职责原则。以产品质量评估执行器为例:
csharp复制internal sealed class ProductEvaluationExecutor()
: Executor<ComplaintProcessingRecord, ComplaintProcessingRecord>(nameof(ProductEvaluationExecutor))
{
public override ValueTask<ComplaintProcessingRecord> HandleAsync(
ComplaintProcessingRecord record,
IWorkflowContext context,
CancellationToken cancellationToken = default)
{
// 1. 验证输入
if (string.IsNullOrEmpty(record.Original.ComplaintText))
throw new WorkflowException("投诉内容不能为空");
// 2. 执行核心逻辑
var defectTypes = DetectDefectTypes(record.Original.ComplaintText);
// 3. 更新状态
record["DefectTypes"] = string.Join(",", defectTypes);
record.ProcessingSteps.Add($"[{DateTime.Now:HH:mm:ss}] 产品缺陷检测完成");
// 4. 返回结果
return ValueTask.FromResult(record);
}
private static IEnumerable<string> DetectDefectTypes(string complaintText)
{
// 实际项目中可能调用AI服务或规则引擎
if (complaintText.Contains("划痕")) yield return "外观损伤";
if (complaintText.Contains("死机")) yield return "功能故障";
}
}
4.4 工作流构建技巧
构建复杂工作流时,推荐采用分段构建模式:
csharp复制// 第一阶段:构建分类流程
var classificationStage = new WorkflowBuilder(classifierExecutor)
.AddEdge<ComplaintProcessingRecord>(
classifierExecutor,
productQualitySubExecutor,
condition: record => record.Category == "产品质量")
.AddEdge<ComplaintProcessingRecord>(
classifierExecutor,
logisticsSubExecutor,
condition: record => record.Category == "物流问题");
// 第二阶段:添加公共后处理
var fullWorkflow = classificationStage
.AddEdge(productQualitySubExecutor, complianceExecutor)
.AddEdge(logisticsSubExecutor, complianceExecutor)
.AddEdge(complianceExecutor, sentimentExecutor)
.WithOutputFrom(sentimentExecutor)
.Build();
这种模式的优势:
- 提高代码可读性
- 便于单元测试各阶段
- 方便后期扩展中间步骤
5. 高级应用场景
5.1 动态工作流加载
对于需要热更新的场景,可以实现动态加载子工作流:
csharp复制// 从配置文件或数据库加载工作流定义
var workflowDef = await LoadWorkflowDefinitionAsync("ProductQuality");
// 动态创建执行器实例
var executorTypes = workflowDef.Steps
.Select(step => Type.GetType(step.ExecutorType))
.Where(t => t != null)
.Select(t => Activator.CreateInstance(t));
// 动态构建工作流
var builder = new WorkflowBuilder();
foreach (var executor in executorTypes)
{
builder.AddStep(executor);
}
5.2 跨工作流事务管理
对于需要事务性保证的场景,可以实现补偿机制:
csharp复制public class TransactionalExecutor<TInput, TOutput> : Executor<TInput, TOutput>
{
private readonly List<Func<CancellationToken, Task>> _compensations = new();
public void RegisterCompensation(Func<CancellationToken, Task> compensation)
{
_compensations.Add(compensation);
}
protected override async ValueTask<TOutput> HandleAsync(
TInput input,
IWorkflowContext context,
CancellationToken cancellationToken)
{
try
{
// 执行实际逻辑
return await base.HandleAsync(input, context, cancellationToken);
}
catch
{
// 执行补偿操作(逆序)
foreach (var compensate in _compensations.AsEnumerable().Reverse())
{
await compensate(cancellationToken);
}
throw;
}
}
}
5.3 性能监控与优化
通过工作流事件实现细粒度监控:
csharp复制await using (var streaming = await InProcessExecution.StreamAsync(mainWorkflow, processingRecord))
{
var stopwatch = Stopwatch.StartNew();
var stats = new Dictionary<string, (int Count, long TotalMs)>();
await foreach (WorkflowEvent evt in streaming.WatchStreamAsync())
{
switch (evt)
{
case ExecutorInvokedEvent started:
stats[started.ExecutorId] = (
stats.TryGetValue(started.ExecutorId, out var s) ? s.Count + 1 : 1,
stopwatch.ElapsedMilliseconds
);
break;
case ExecutorCompletedEvent completed:
var elapsed = stopwatch.ElapsedMilliseconds - stats[completed.ExecutorId].TotalMs;
Console.WriteLine($"⏱️ {completed.ExecutorId} 耗时: {elapsed}ms");
break;
}
}
}
6. 调试与问题排查
6.1 常见问题及解决方案
问题1:子工作流未按预期执行
可能原因:
- 条件表达式逻辑错误
- 数据类型不匹配
- 子工作流未正确绑定
排查步骤:
- 检查主工作流的路由条件
- 验证子工作流的输入输出类型
- 使用工作流可视化工具检查拓扑结构
问题2:共享状态不同步
解决方案:
- 使用不可变数据模型
- 实现显式的状态拷贝方法
- 对于复杂对象,考虑使用代理模式
csharp复制public class ComplaintProcessingRecordProxy : IComplaintProcessingRecord
{
private readonly ComplaintProcessingRecord _inner;
public ComplaintProcessingRecordProxy(ComplaintProcessingRecord inner)
{
_inner = inner.DeepClone(); // 实现深拷贝
}
// 通过代理控制访问
public string Category
{
get => _inner.Category;
set
{
ValidateCategory(value);
_inner.Category = value;
}
}
}
6.2 日志记录策略
建议实现结构化日志记录:
csharp复制public override ValueTask<TOutput> HandleAsync(TInput input, IWorkflowContext context)
{
var logger = context.GetService<ILogger>();
using (logger.BeginScope(new Dictionary<string, object>
{
["WorkflowId"] = context.WorkflowId,
["Executor"] = this.GetType().Name
}))
{
logger.LogInformation("开始处理 {InputType}", typeof(TInput).Name);
try
{
// 业务逻辑
logger.LogDebug("中间状态: {State}", JsonSerializer.Serialize(input));
return /* 结果 */;
}
catch (Exception ex)
{
logger.LogError(ex, "处理失败");
throw;
}
}
}
7. 扩展与定制
7.1 自定义工作流行为
通过继承WorkflowBuilder实现扩展:
csharp复制public class RetryWorkflowBuilder : WorkflowBuilder
{
private readonly int _maxRetries;
public RetryWorkflowBuilder(int maxRetries = 3)
{
_maxRetries = maxRetries;
}
public override WorkflowBuilder AddEdge(
IExecutor from,
IExecutor to,
Func<object, bool> condition = null)
{
var retryExecutor = new RetryExecutor(from, _maxRetries);
return base.AddEdge(retryExecutor, to, condition);
}
}
// 使用示例
var workflow = new RetryWorkflowBuilder(3)
.AddEdge(executorA, executorB)
.Build();
7.2 集成外部系统
实现与Service Bus的集成示例:
csharp复制public class ServiceBusTriggerExecutor : Executor<Message, Message>
{
private readonly ServiceBusClient _client;
public ServiceBusTriggerExecutor(ServiceBusClient client)
{
_client = client;
}
public override async ValueTask<Message> HandleAsync(
Message input,
IWorkflowContext context)
{
var sender = _client.CreateSender("workflow-queue");
await sender.SendMessageAsync(new ServiceBusMessage
{
Body = BinaryData.FromObjectAsJson(input),
SessionId = context.WorkflowId
});
return input;
}
}
在实际项目中,MAF的主子工作流模式可以显著提高复杂业务流程的可维护性和扩展性。关键在于合理划分工作流边界、设计清晰的数据契约,以及实现完善的监控机制。
