详解 Microsoft Agent Framework 图形化工作流系统,通过客服邮件分类案例展示多 agent 协调、业务规则集成与确定性流程。
最初发布于 Medium,2025 年 12 月 28 日。
在之前的文章中,我们探讨了如何使用 Microsoft Agent Framework 构建单个 AI 智能体——这些智能体可以调用函数、提取结构化数据并利用 RAG。这些是强大的构建块,但现实世界的场景通常需要更多:将多个智能体和确定性逻辑编排成复杂的、多步骤的流程。
这就是 Agent Framework 的工作流系统派上用场的地方。
想象一个客户支持电子邮件系统。单个智能体无法处理所有事情。你需要预处理电子邮件、对其进行分类、应用业务规则、适当地路由、起草响应,有时需要升级到人工。每个步骤都有不同的要求——有些需要 AI 推理,有些需要确定性逻辑,所有这些都需要无缝协作。
在本指南中,我们将构建完全相同的东西:一个客户支持电子邮件分类工作流,它将 LLM 智能体与业务逻辑相结合,以自动处理、分类和响应客户电子邮件。

Agent Framework 中的工作流是基于图的编排系统。与其编写试图处理每个场景的单片代码,不如构建一个有向图,其中:
执行器是单个处理单元(智能体或自定义逻辑)
边连接执行器并定义数据流
边上的条件根据上下文启用动态路由
这种架构为您提供:
模块化:每个执行器专注于一项任务
清晰性:图的结构使流程流显式化
灵活性:条件边适应不同场景
可维护性:一个步骤的更改不会级联到整个系统
有关工作流的更多信息,请参阅 Agent Framework 工作流指南。
让我们定义我们的业务问题。我们每天收到数百封客户支持电子邮件。我们想要:
自动处理常规请求
一致地应用业务规则
在需要人工判断时适当升级
保持与数据保护和政策的合规性
以下是我们将构建的工作流:

工作流处理四个路由场景:
高优先级升级:负面情绪 + 高紧迫性 → 人工转接
需要澄清:信息缺失 → 智能体起草问题
退款请求:自动退款创建 → 人工审查
正常回复:标准响应 → 智能体起草回复
在深入代码之前,让我们理解三个核心概念。
执行器是一个处理单元,它接收输入、执行某些操作并返回输出。每个执行器都继承自 Executor<TInput, TOutput>:
internal sealed class PreprocessEmailExecutor : Executor<string, EmailDocument>
{
public override async ValueTask<EmailDocument> HandleAsync(
string message,
IWorkflowContext context,
CancellationToken cancellationToken = default)
{
// Process the raw email...
return processedEmail;
}
}
执行器类型:
确定性:纯逻辑,没有 AI(预处理、路由、验证)
智能体化:使用 LLM 进行推理(分类、响应起草)
边和条件连接执行器,可以包括动态路由的条件:
var workflow = new WorkflowBuilder(startExecutor)
.AddEdge(preprocess, intake) // Simple edge
.AddEdge<PolicyContext>( // Conditional edge
source: policyGate,
target: responder,
condition: ctx => ctx.Policy.Mode == ResponseMode.DraftReply)
.Build();
条件让您构建分支逻辑:"如果情绪是负面的 AND 紧迫性很高,则升级到人工。"
共享状态:执行器可以通过共享状态进行通信。当多个执行器需要访问相同数据时,这至关重要:
// Write to shared state
await context.QueueStateUpdateAsync(
SupportRunState.KeyEmail,
email,
scopeName: SupportRunState.ScopeName
);
// Read from shared state
var email = await context.ReadStateAsync<EmailDocument>(
SupportRunState.KeyEmail,
scopeName: SupportRunState.ScopeName
);
让我们比较两个执行器,看看方法的区别。
这个执行器使用纯 C# 逻辑来清理电子邮件和检测 PII:
internal sealed partial class PreprocessEmailExecutor : Executor<string, EmailDocument>
{
public override async ValueTask<EmailDocument> HandleAsync(
string message,
IWorkflowContext context,
CancellationToken cancellationToken = default)
{
var lines = SplitLines(message);
// Extract headers
string? from = TryExtractHeaderValue(lines, "From:");
string? subject = TryExtractHeaderValue(lines, "Subject:");
// Clean the body
var body = RemoveHeaders(lines);
body = StripQuotedReplies(body);
body = NormalizeWhitespace(body);
// Detect PII using regex
var detectedEmails = EmailRegex().Matches(body)...;
var detectedPhones = PhoneRegex().Matches(body)...;
var detectedOrderIds = OrderIdRegex().Matches(body)...;
// Mask PII for model safety
var modelSafe = MaskPii(body);
var email = new EmailDocument
{
OriginalText = message,
CleanText = body,
ModelSafeText = modelSafe,
ContainsPii = detectedEmails.Count > 0 || detectedPhones.Count > 0,
DetectedEmails = detectedEmails,
DetectedPhones = detectedPhones,
DetectedOrderIds = detectedOrderIds
};
// Store in shared state and emit event
await context.QueueStateUpdateAsync(SupportRunState.KeyEmail, email, ...);
await context.AddEventAsync(new EmailPreprocessedEvent(email), ...);
return email;
}
}
是什么使其具有确定性?
使用正则表达式模式来检测电子邮件、电话和订单 ID
应用一致的文本清理规则
没有 LLM 调用——可预测、快速和无成本
非常适合需要保证行为的操作
这个执行器使用 LLM 来分类电子邮件:
internal sealed class EmailIntakeExecutor : Executor<EmailDocument, IntakeContext>
{
private readonly AIAgent _agent;
private readonly AgentThread _thread;
public EmailIntakeExecutor(string id, IChatClient chatClient) : base(id)
{
ChatClientAgentOptions agentOptions = new()
{
ChatOptions = new()
{
Instructions = """
You are a customer support intake assistant.
Return JSON that matches the schema exactly.
Be concise and do not invent missing facts.
""",
ResponseFormat = ChatResponseFormat.ForJsonSchema<IntakeResult>()
}
};
_agent = new ChatClientAgent(chatClient, agentOptions);
_thread = _agent.GetNewThread();
}
public override async ValueTask<IntakeContext> HandleAsync(
EmailDocument message,
IWorkflowContext context,
CancellationToken cancellationToken = default)
{
var prompt = $"""
Classify this inbound customer support email.
Subject: {message.Subject ?? "(none)"}
From: {message.From ?? "(unknown)"}
Email:
{message.ModelSafeText}
""";
var result = await _agent.RunAsync(prompt, _thread, cancellationToken: cancellationToken);
var intake = JsonSerializer.Deserialize<IntakeResult>(result.Text);
var intakeContext = new IntakeContext { Email = message, Intake = intake };
await context.QueueStateUpdateAsync(SupportRunState.KeyIntake, intakeContext, ...);
await context.AddEventAsync(new IntakeCompletedEvent(intakeContext), ...);
return intakeContext;
}
}
是什么使其具有智能体化?
使用 LLM 来理解电子邮件的意图、紧迫性和情绪
通过 ForJsonSchema<IntakeResult>() 提取结构化数据
处理正则表达式无法捕捉的细微差别和上下文
非常适合分类、推理和自然语言理解
**结合的优势:**通过组合两种类型的执行器,您可以获得两者最好的部分:
确定性步骤提供速度、一致性和成本控制
智能体化步骤处理复杂性、细微差别和推理
它们一起创建了一个既聪明又可靠的系统
PolicyGateExecutor 演示了如何实现业务逻辑路由:
internal sealed class PolicyGateExecutor : Executor<IntakeContext, PolicyContext>
{
public override async ValueTask<PolicyContext> HandleAsync(
IntakeContext message,
IWorkflowContext context,
CancellationToken cancellationToken = default)
{
// 应用业务规则
var missingInfo = message.Intake.MissingInformation ?? [];
var mode = missingInfo.Count > 0
? ResponseMode.AskClarifyingQuestions
: ResponseMode.DraftReply;
var sla = message.Intake.Urgency switch
{
UrgencyLevel.High => "4h",
UrgencyLevel.Normal => "24h",
_ => "72h"
};
var complianceNotes = new List<string>();
if (message.Email.ContainsPii)
{
complianceNotes.Add("检测到PII。在回复中仅使用脱敏内容。");
}
if (message.Intake.Intent is UserIntent.Refund or UserIntent.CancelOrder)
{
complianceNotes.Add("不要承诺退款。先确认政策。");
}
// 构建策略决策
var policy = new PolicyDecision
{
Mode = mode,
RedactedEmailText = message.Email.ModelSafeText,
Sla = sla,
ComplianceNotes = complianceNotes
};
var policyContext = new PolicyContext
{
Email = message.Email,
Intake = message.Intake,
Policy = policy
};
// 在共享状态中存储路由决策
var isEscalation = policyContext.Intake.Sentiment == Sentiment.Negative
&& policyContext.Intake.Urgency == UrgencyLevel.High;
var isRefund = policyContext.Policy.Mode == ResponseMode.DraftReply
&& policyContext.Intake.Intent == UserIntent.Refund
&& !isEscalation;
var route = isEscalation ? "人工升级"
: isRefund ? "退款请求(人工审核)"
: "正常回复";
await context.QueueStateUpdateAsync(SupportRunState.KeySelectedRoute, route, ...);
return policyContext;
}
}
评估 Intake 结果以确定响应模式
根据紧急程度应用 SLA 规则
通过标记 PII 和敏感请求来强制合规
通过条件逻辑确定路由
工作流构建器随后使用这些决策进行适当的路由:
return new WorkflowBuilder(preprocess)
.AddEdge(preprocess, intake)
.AddEdge(intake, policyGate)
// 升级:负面情绪 + 高紧急
.AddEdge<PolicyContext>(
source: policyGate,
target: humanPrep,
condition: ctx => ctx.Intake.Sentiment == Sentiment.Negative
&& ctx.Intake.Urgency == UrgencyLevel.High)
// 退款:无缺失信息 + 退款意图
.AddEdge<PolicyContext>(
source: policyGate,
target: refundRequest,
condition: ctx => ctx.Policy.Mode == ResponseMode.DraftReply
&& ctx.Intake.Intent == UserIntent.Refund
&& !(ctx.Intake.Sentiment == Sentiment.Negative
&& ctx.Intake.Urgency == UrgencyLevel.High))
// 默认:正常回复
.AddEdge<PolicyContext>(
source: policyGate,
target: responder,
condition: ctx => ctx.Policy.Mode == ResponseMode.DraftReply
&& ctx.Intake.Intent != UserIntent.Refund
&& !(ctx.Intake.Sentiment == Sentiment.Negative
&& ctx.Intake.Urgency == UrgencyLevel.High))
.Build();
Agent Framework 工作流最强大的功能之一是自定义事件系统。事件提供对工作流内部发生情况的实时洞察,使调试和监控变得非常容易。
自定义事件继承自 WorkflowEvent,可以承载任何你需要的数据:
internal sealed class EmailPreprocessedEvent(EmailDocument email) : WorkflowEvent(email)
{
public EmailDocument Email { get; } = email;
}
internal sealed class IntakeCompletedEvent(IntakeContext context) : WorkflowEvent(context)
{
public IntakeContext Context { get; } = context;
}
internal sealed class PolicyAppliedEvent(PolicyContext context) : WorkflowEvent(context)
{
public PolicyContext Context { get; } = context;
}
这些事件只是简单的数据承载者,但它们改变了你观察工作流执行的方式。
在任何执行器内,你通过工作流上下文发出事件:
public override async ValueTask<EmailDocument> HandleAsync(
string message,
IWorkflowContext context,
CancellationToken cancellationToken = default)
{
// ... 处理电子邮件 ...
var email = new EmailDocument
{
OriginalText = message,
CleanText = body,
ModelSafeText = modelSafe,
ContainsPii = containsPii,
// ... 其他属性
};
// 发出事件
await context.AddEventAsync(new EmailPreprocessedEvent(email), cancellationToken);
return email;
}
事件在工作流执行时实时发出,允许你在其发生时观察进度。
现在事情变得有趣了。当你运行工作流时,你可以监视事件流:
await using StreamingRun run = await InProcessExecution.StreamAsync(workflow, input: email);
await foreach (var evt in run.WatchStreamAsync())
{
switch (evt)
{
case EmailPreprocessedEvent e:
Console.WriteLine(
$"[预处理|确定性] 主题='{e.Email.Subject}' | " +
$"PII={e.Email.ContainsPii} | 订单ID数={e.Email.DetectedOrderIds.Count}");
break;
case IntakeCompletedEvent e:
Console.WriteLine(
$"[Intake|AI智能体] {e.Context.Intake.Category} | " +
$"{e.Context.Intake.Urgency} | {e.Context.Intake.Sentiment}");
Console.WriteLine($" 总结:{e.Context.Intake.Summary}");
break;
case PolicyAppliedEvent e:
Console.WriteLine(
$"[策略|确定性] 模式={e.Context.Policy.Mode} | " +
$"SLA={e.Context.Policy.Sla}");
break;
case ResponseDraftedEvent e:
Console.WriteLine(
$"[响应器|AI智能体] 生成 {e.Info.Mode} 响应");
break;
case HumanHandoffPreparedEvent e:
Console.WriteLine(
$"[人工准备|混合] 队列='{e.Package.Queue}' | " +
$"SLA={e.Package.Sla}");
Console.WriteLine($" 总结:{e.Package.Summary}");
break;
case RefundRequestCreatedEvent e:
Console.WriteLine(
$"[退款|确定性] Id={e.Request.RefundRequestId}");
break;
case WorkflowOutputEvent outputEvent:
Console.WriteLine("=== 工作流输出 ===");
Console.WriteLine(outputEvent.Data);
break;
case WorkflowErrorEvent errorEvent:
Console.WriteLine($"错误:{errorEvent}");
break;
}
}
这为什么强大:
实时可见性:你看到工作流执行时到底发生了什么
类型安全的模式匹配:每种事件类型可以被不同处理
丰富的上下文:事件承载来自每一步的完整数据
内置事件:WorkflowOutputEvent 和 WorkflowErrorEvent 自动提供
当出错时,事件会讲述故事:

除了自定义事件,Agent Framework 与 OpenTelemetry 集成以提供生产级可观测性。
var tracerProvider = Sdk.CreateTracerProviderBuilder()
.SetResourceBuilder(
ResourceBuilder.CreateDefault()
.AddService("AgentFrameworkWorkflows"))
.AddSource("Microsoft.Agents.AI.*")
.SetSampler(new AlwaysOnSampler())
.AddOtlpExporter(options =>
{
options.Endpoint = new Uri("http://localhost:4319");
options.Protocol = OtlpExportProtocol.Grpc;
})
.Build();
标识你的服务(AgentFrameworkWorkflows)
捕获 Agent Framework 追踪(Microsoft.Agents.AI.*)
采样所有追踪(在生产环境使用选择性采样)
通过 OTLP 导出到你的可观测性后端(Jaeger、Zipkin、Azure Monitor 等)
OpenTelemetry 自动捕获:
执行器执行时间:每一步耗时多长
AI智能体 LLM 调用:Token 计数、延迟、模型调用
状态操作:读取和写入共享状态
边界转换:采取了哪条条件路径
错误上下文:带有工作流上下文的堆栈跟踪
这就是真正强大的地方。Azure AI Foundry VS Code 扩展可以使用 OpenTelemetry 追踪来可视化工作流执行。

设置可视化:
安装 VS Code 中的 Azure AI Foundry 扩展
配置 OTLP 端点(默认 http://localhost:4319
启用 OpenTelemetry 运行工作流
打开 Foundry 扩展面板查看实时追踪
该扩展在实时显示工作流图,在运行时突出显示活动执行器,并在完成时显示完整路径。
在生产环境中,同样的基础设施为标准可观测性工具提供数据:
Azure Monitor:与 Application Insights 的原生集成
Jaeger:开源分布式追踪
Zipkin:轻量级追踪可视化
Datadog/New Relic:商业 APM 平台
所有这些都支持 OTLP,因此你可以与基础设施的其他部分一起监控工作流。
在 appsettings.Development.json 中配置 Azure OpenAI:
{
"ModelName": "your-model-deployment",
"Endpoint": "https://your-resource.openai.azure.com/",
"ApiKey": "your-api-key"
}
cd AgentFrameworkWorkflows
dotnet run
工作流将处理电子邮件并向你展示:
预处理结果(检测到的 PII、订单 ID)
分类输出(类别、紧急程度、情感)
策略决定(SLA、合规说明)
路由决定和最终输出
工作流在以下情况下表现出色:
涉及 AI 和业务逻辑的多步流程
基于上下文的条件路由
人工参与循环模式
需要确定性实施的合规性要求
复杂智能体交互的可观测性
不要让一切都成为智能体。工作流的强大之处在于组合:
使用确定性执行器进行验证、路由、格式化、合规性检查
使用智能执行器进行分类、推理、内容生成
使用共享状态在执行器之间传递数据
使用事件进行可观测性和调试
每个执行器应该做好一件事:
PreprocessEmailExecutor:清理和检测 PII
EmailIntakeExecutor:分类电子邮件
PolicyGateExecutor:应用业务规则
SupportResponderExecutor:起草回复
这种模块化设计使测试、调试和维护变得显著更容易。
Agent Framework 工作流改变了我们构建复杂 AI 系统的方式。通过提供基于图的编排、条件路由和共享状态管理,它们实现了一种混合方法,将 LLM 的智能与确定性逻辑的可靠性相结合。
我们探索的客户支持电子邮件分类工作流在实践中演示了这些概念:使用规则进行预处理、使用 AI 进行分类、使用业务逻辑进行路由,以及由智能体起草的回复——所有这些都无缝协作。
当你构建自己的工作流时,请记住:强大的力量不在于让一切都变得智能——而在于在恰好需要的地方应用智能,并在其他所有地方使用确定性逻辑。
🔗 探索完整的工作流实现:GitHub 上的 AgentFrameworkWorkflows
🔍 从头开始:阅读《Microsoft Agent Framework 入门指南》了解基础知识
🤝 你的反馈非常宝贵!欢迎留言、提出问题,或分享你的见解和优化方案。每一项贡献都有助于增进我们的集体知识,建立一个资源丰富的开发者社区。
如需进一步行动,你可以考虑屏蔽此人和/或举报滥用