使用 Microsoft Agent Framework 构建代理工作流程

· 4 分钟阅读

简介

如果您阅读过我之前关于 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 管道,其中每个代理都是专家。从顺序工作流程开始,在步骤独立的情况下添加并行执行,并使用质量门的条件分支。这些模式是可组合的——一旦你掌握了它,你就可以构建一些相当复杂的自动化。

快乐编码!

资源