Construindo fluxos de trabalho de agentes com o Microsoft Agent Framework

· 7 min de leitura

Introdução

Se você leu minhas postagens anteriores sobre o Agent Framework da Microsoft, sabe como criar agentes individuais e até mesmo bate-papos em grupo com vários agentes. Mas em cenários do mundo real, muitas vezes você precisa de algo mais estruturado: um fluxo de trabalho em que os agentes executam em uma ordem específica, passam os resultados entre si e lidam com a lógica de ramificação com base nos resultados.

É exatamente isso que os recursos de fluxo de trabalho do Agent Framework oferecem. Em vez de colocar os agentes em um bate-papo em grupo e esperar que eles descubram, você define etapas, dependências e fluxo de dados explícitos entre os agentes.

O que são fluxos de trabalho de agentes?

Pense nos fluxos de trabalho dos agentes como um pipeline. Cada etapa é realizada por um agente especializado e o resultado de uma etapa alimenta a próxima. Você pode executar etapas sequencialmente, em paralelo ou condicionalmente com base nos resultados.

Alguns exemplos:

  • Pipeline de conteúdo: Pesquisa → Rascunho → Revisão → Publicar
  • Processamento de dados: Extrair → Transformar → Validar → Carregar
  • Suporte ao cliente: Classificar → Encaminhar → Responder → Acompanhamento

Pré-requisitos

Certifique-se de ter os pacotes mais recentes do Agent Framework:

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

E um ponto final Azure OpenAI ou OpenAI configurado.

Construindo um fluxo de trabalho sequencial

Vamos construir um fluxo de trabalho de criação de conteúdo com três agentes: um pesquisador, um redator e um editor.

Definindo os agentes

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

Executando sequencialmente

O fluxo de trabalho mais simples é sequencial — cada agente processa a saída do anterior:

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

Cada agente obtém o contexto acumulado, processa-o e o resultado passa para a próxima etapa.

Execução paralela

Às vezes, os agentes podem trabalhar de forma independente. Por exemplo, você pode querer pesquisar vários subtópicos ao mesmo tempo:

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

Em seguida, você pode alimentar todos os resumos de pesquisa em um único agente redator para produzir uma postagem coesa.

Ramificação condicional

Fluxos de trabalho reais precisam de decisões. Talvez você queira uma etapa de verificação de qualidade que retorne ao redator se a postagem não for boa o suficiente:

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

Esse padrão é super útil. O verificador de qualidade atua como uma porta e o fluxo de trabalho faz um loop até que a saída seja boa o suficiente ou atinja o limite máximo de revisão.

Adicionando plug-ins a agentes de fluxo de trabalho

Os agentes em fluxos de trabalho podem usar plug-ins da mesma forma que os agentes autônomos. É aqui que as coisas ficam realmente poderosas – os agentes podem chamar APIs, consultar bancos de dados ou realizar operações de arquivo como parte de sua etapa de fluxo de trabalho:

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

Tratamento de erros em fluxos de trabalho

Ao encadear vários agentes, o tratamento de erros se torna crítico. Você não quer que uma etapa com falha trave todo o fluxo de trabalho silenciosamente:

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

Melhores práticas

Depois de criar vários fluxos de trabalho de agentes, eis o que aprendi:- Mantenha o foco nas instruções do agente — cada agente deve fazer uma coisa bem feita. Não tente ser um agente canivete suíço.

  • Limite o contexto — não passe todo o histórico da conversa para todos os agentes. Dê a cada passo apenas o que ele precisa.
  • Defina limites de revisão — loops de qualidade são ótimos, mas podem funcionar para sempre se você não tomar cuidado. Sempre tenha um limite máximo de iterações.
  • Registre tudo — as saídas do agente podem ser imprevisíveis. Registre a entrada e a saída de cada etapa para depuração.
  • Use a execução paralela com sabedoria — isso acelera as coisas, mas observe os limites de taxa da API e os custos de token.
  • Teste primeiro com modelos menores — desenvolva e teste sua lógica de fluxo de trabalho com GPT-3.5 antes de mudar para GPT-4o para produção.

Conclusão

Os fluxos de trabalho dos agentes permitem criar pipelines de IA complexos e de várias etapas, nos quais cada agente é um especialista. Comece com fluxos de trabalho sequenciais, adicione execução paralela onde as etapas são independentes e use ramificação condicional para portas de qualidade. Os padrões são combináveis ​​– depois de pegar o jeito, você pode construir uma automação bastante sofisticada.

Boa codificação!

Recursos