使用 Microsoft Agent Framework 构建代理工作流程
简介
如果您阅读过我之前关于 Microsoft 代理框架的文章,您就会知道如何创建单个代理甚至多代理群组聊天。但在现实场景中,您通常需要更结构化的东西 - 代理按特定顺序执行、相互传递结果并根据结果处理分支逻辑的工作流程。
这正是 Agent Framework 中的工作流功能为您提供的功能。您无需将代理放入群聊中并希望他们能够解决问题,而是可以定义代理之间的显式步骤、依赖关系和数据流。
什么是代理工作流程?
将代理工作流程视为管道。每个步骤都由专门的代理处理,一个步骤的输出将输入到下一步。您可以按顺序、并行或根据结果有条件地运行步骤。
一些例子:
- 内容管道:研究→草稿→审查→发布
- 数据处理:提取→转换→验证→加载
- 客户支持:分类→路线→响应→跟进
先决条件
确保您拥有最新的代理框架包:
dotnet add package Microsoft.SemanticKernel
dotnet add package Microsoft.SemanticKernel.Agents.Core
并配置了 Azure OpenAI 或 OpenAI 端点。
构建顺序工作流程
让我们用三个代理构建一个内容创建工作流程:研究员、作家和编辑。
定义代理
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
var kernel = Kernel.CreateBuilder()
.AddAzureOpenAIChatCompletion(
deploymentName: "gpt-4o",
endpoint: config["AzureOpenAI:Endpoint"],
apiKey: config["AzureOpenAI:ApiKey"])
.Build();
var researcher = new ChatCompletionAgent
{
Name = "Researcher",
Instructions = """
You are a research specialist. Given a topic, find key facts, statistics,
and talking points. Return a structured research brief with bullet points.
Be thorough but concise.
""",
Kernel = kernel
};
var writer = new ChatCompletionAgent
{
Name = "Writer",
Instructions = """
You are a technical blog writer. Given a research brief, write a clear
and engaging blog post. Use a conversational tone, include code examples
where relevant, and structure the post with clear headings.
""",
Kernel = kernel
};
var editor = new ChatCompletionAgent
{
Name = "Editor",
Instructions = """
You are a senior editor. Review the blog post for clarity, accuracy,
grammar, and flow. Return the corrected version with a summary of
changes made at the end.
""",
Kernel = kernel
};
顺序执行
最简单的工作流程是顺序的——每个代理处理前一个代理的输出:
using Microsoft.SemanticKernel.Agents.Chat;
using Microsoft.SemanticKernel.ChatCompletion;
async Task<string> RunSequentialWorkflow(string topic)
{
var history = new ChatHistory();
string currentInput = $"Research the following topic: {topic}";
var agents = new[] { researcher, writer, editor };
foreach (var agent in agents)
{
history.AddUserMessage(currentInput);
var response = new System.Text.StringBuilder();
await foreach (var message in agent.InvokeAsync(history))
{
response.Append(message.Content);
}
currentInput = response.ToString();
Console.WriteLine($"✅ {agent.Name} completed");
}
return currentInput;
}
var result = await RunSequentialWorkflow("Blazor render modes in .NET 9");
Console.WriteLine(result);
每个代理获取累积的上下文,对其进行处理,然后将结果转移到下一步。
并行执行
有时代理可以独立工作。例如,您可能想同时研究多个子主题:
async Task<List<string>> RunParallelResearch(string[] topics)
{
var tasks = topics.Select(async topic =>
{
var history = new ChatHistory();
history.AddUserMessage($"Research: {topic}");
var response = new System.Text.StringBuilder();
await foreach (var message in researcher.InvokeAsync(history))
{
response.Append(message.Content);
}
Console.WriteLine($"✅ Research completed: {topic}");
return response.ToString();
});
var results = await Task.WhenAll(tasks);
return results.ToList();
}
var topics = new[]
{
"Blazor SSR streaming",
"Enhanced navigation in .NET 9",
"Render mode boundaries"
};
var briefs = await RunParallelResearch(topics);
然后,您可以将所有研究摘要提供给单个作家代理,以生成一篇有凝聚力的帖子。
条件分支
真正的工作流程需要决策。也许您需要一个质量检查步骤,如果帖子不够好,可以返回给作者:
var qualityChecker = new ChatCompletionAgent
{
Name = "QualityChecker",
Instructions = """
You are a quality assurance reviewer. Evaluate the blog post on:
1. Technical accuracy
2. Clarity and readability
3. Completeness
Respond with either:
- "APPROVED" if the post meets all criteria
- "REVISION NEEDED: [specific feedback]" if changes are required
Be strict. Only approve posts that are truly ready to publish.
""",
Kernel = kernel
};
async Task<string> RunWithQualityLoop(string topic, int maxRevisions = 3)
{
var history = new ChatHistory();
// Step 1: Research
history.AddUserMessage($"Research: {topic}");
string research = await InvokeAgent(researcher, history);
// Step 2: Write
history.AddUserMessage(research);
string draft = await InvokeAgent(writer, history);
// Step 3: Quality loop
for (int i = 0; i < maxRevisions; i++)
{
var qaHistory = new ChatHistory();
qaHistory.AddUserMessage($"Review this post:\n\n{draft}");
string qaResult = await InvokeAgent(qualityChecker, qaHistory);
if (qaResult.Contains("APPROVED", StringComparison.OrdinalIgnoreCase))
{
Console.WriteLine($"✅ Approved after {i + 1} review(s)");
return draft;
}
Console.WriteLine($"🔄 Revision {i + 1}: {qaResult[..Math.Min(100, qaResult.Length)]}...");
// Send back to writer with feedback
history.AddUserMessage($"Please revise based on this feedback:\n{qaResult}");
draft = await InvokeAgent(writer, history);
}
Console.WriteLine("⚠️ Max revisions reached, returning latest draft");
return draft;
}
async Task<string> InvokeAgent(ChatCompletionAgent agent, ChatHistory history)
{
var response = new System.Text.StringBuilder();
await foreach (var message in agent.InvokeAsync(history))
{
response.Append(message.Content);
}
return response.ToString();
}
这个模式超级有用。质量检查器充当大门,工作流程循环直至输出足够好或达到最大修订限制。
添加插件到工作流代理
工作流中的代理可以像独立代理一样使用插件。这就是事情变得真正强大的地方 - 代理可以调用 API、查询数据库或执行文件操作,作为其工作流程步骤的一部分:
var researcherWithTools = new ChatCompletionAgent
{
Name = "Researcher",
Instructions = "Research the topic using available tools. Summarize findings.",
Kernel = kernel
};
// Add a web search plugin
kernel.Plugins.AddFromType<WebSearchPlugin>();
// Add a database plugin
kernel.Plugins.AddFromObject(new DatabasePlugin(connectionString), "Database");
public class WebSearchPlugin
{
[KernelFunction, Description("Search the web for information")]
public async Task<string> SearchAsync(
[Description("The search query")] string query)
{
// Your search implementation
using var httpClient = new HttpClient();
var response = await httpClient.GetStringAsync(
$"https://api.search.example.com?q={Uri.EscapeDataString(query)}");
return response;
}
}
工作流程中的错误处理
当您链接多个代理时,错误处理变得至关重要。您不希望某个失败的步骤导致整个工作流程悄无声息地崩溃:
async Task<WorkflowResult> RunResilientWorkflow(string input)
{
var result = new WorkflowResult();
try
{
result.Research = await InvokeAgent(researcher, new ChatHistory(input));
}
catch (Exception ex)
{
result.Errors.Add($"Research failed: {ex.Message}");
return result;
}
try
{
var writeHistory = new ChatHistory(result.Research);
result.Draft = await InvokeAgent(writer, writeHistory);
}
catch (Exception ex)
{
result.Errors.Add($"Writing failed: {ex.Message}");
result.Draft = result.Research; // Fallback to research output
}
result.Success = result.Errors.Count == 0;
return result;
}
class WorkflowResult
{
public string Research { get; set; } = "";
public string Draft { get; set; } = "";
public List<string> Errors { get; set; } = new();
public bool Success { get; set; }
}
最佳实践
在构建了几个代理工作流程之后,我学到了以下内容:- 保持代理指示的重点 - 每个代理都应该做好一件事。不要试图做瑞士军刀特工。
- 限制上下文 — 不要将整个对话历史记录传递给每个代理。只提供每一步所需的内容。
- 设置修订限制 - 质量循环很棒,但如果您不小心,可能会永远运行。始终有最大迭代次数上限。
- 记录一切 - 代理输出可能是不可预测的。记录每个步骤的输入和输出以进行调试。
- 明智地使用并行执行 - 它可以加快速度,但请注意您的 API 速率限制和令牌成本。
- 首先使用较小的模型进行测试 — 使用 GPT-3.5 开发和测试您的工作流程逻辑,然后再切换到 GPT-4o 进行生产。
结论
代理工作流程可让您构建复杂的多步骤 AI 管道,其中每个代理都是专家。从顺序工作流程开始,在步骤独立的情况下添加并行执行,并使用质量门的条件分支。这些模式是可组合的——一旦你掌握了它,你就可以构建一些相当复杂的自动化。
快乐编码!