Microsoft Agent Framework を使用したエージェント ワークフローの構築

· 4分で読める

はじめに

Microsoft のエージェント フレームワークに関する私の以前の投稿を読んだことがあれば、個々のエージェント、さらには複数エージェントのグループ チャットを作成する方法をご存知でしょう。しかし、現実のシナリオでは、多くの場合、より構造化されたものが必要になります。これは、エージェントが特定の順序で実行し、結果を相互に渡し、結果に基づいて分岐ロジックを処理するワークフローです。

これはまさに、Agent Framework のワークフロー機能が提供するものです。エージェントをグループ チャットに放り込んで、彼らがそれを理解してくれることを期待するのではなく、明示的なステップ、依存関係、およびエージェント間のデータ フローを定義します。

エージェントのワークフローとは何ですか?

エージェントのワークフローはパイプラインのようなものだと考えてください。各ステップは専門のエージェントによって処理され、あるステップの出力は次のステップにフィードされます。ステップは、結果に基づいて、順次、並列、または条件付きで実行できます。

いくつかの例:

  • コンテンツ パイプライン: 調査 → 草案 → レビュー → 公開
  • データ処理: 抽出 → 変換 → 検証 → ロード
  • カスタマー サポート: 分類 → ルート → 対応 → フォローアップ

前提条件

最新のエージェント フレームワーク パッケージがあることを確認してください。

dotnet add package Microsoft.SemanticKernel
dotnet add package Microsoft.SemanticKernel.Agents.Core

Azure OpenAI または OpenAI エンドポイントが構成されています。

順次ワークフローの構築

研究者、ライター、編集者の 3 人のエージェントによるコンテンツ作成ワークフローを構築してみましょう。

エージェントの定義

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);

その後、すべての研究概要を 1 つのライター エージェントにフィードして、1 つのまとまりのある投稿を作成できます。

条件分岐

実際のワークフローには意思決定が必要です。投稿が十分でない場合にライターに戻す品質チェックのステップが必要な場合があります。

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;
    }
}

ワークフローでのエラー処理

複数のエージェントをチェーンしている場合、エラー処理が重要になります。 1 つのステップが失敗しただけで、ワークフロー全体が静かにクラッシュすることは望ましくありません。

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; }
}

ベストプラクティス

いくつかのエージェント ワークフローを構築した後、私が学んだことは次のとおりです。- エージェントの指示に重点を置く — 各エージェントは 1 つのことをうまく実行する必要があります。スイスアーミーナイフのエージェントになろうとしないでください。

  • コンテキストを制限する — 会話履歴全体をすべてのエージェントに渡さないでください。各ステップに必要なものだけを与えます。
  • リビジョン制限を設定 — 品質ループは優れていますが、注意しないと永久に実行される可能性があります。常に最大反復回数の上限を設定します。
  • すべてをログに記録 — エージェントの出力は予測できない場合があります。デバッグのためにすべてのステップの入力と出力をログに記録します。
  • 並列実行を賢く使用してください - 速度は向上しますが、API レート制限とトークン コストに注意してください。
  • 最初に小規模なモデルでテストします — 本番環境で GPT-4o に切り替える前に、GPT-3.5 でワークフロー ロジックを開発およびテストします。

結論

エージェント ワークフローを使用すると、各エージェントが専門家である複雑な複数ステップの AI パイプラインを構築できます。順次ワークフローから開始し、ステップが独立した並列実行を追加し、品質ゲートのために条件付き分岐を使用します。パターンは構成可能です。一度コツを掴めば、かなり洗練された自動化を構築できます。

コーディングを楽しんでください!

リソース