Creación de flujos de trabajo de agentes con Microsoft Agent Framework

· 7 min de lectura

Introducción

Si ha leído mis publicaciones anteriores sobre Agent Framework de Microsoft, sabrá cómo crear agentes individuales e incluso chats grupales con múltiples agentes. Pero en escenarios del mundo real, a menudo se necesita algo más estructurado: un flujo de trabajo en el que los agentes se ejecutan en un orden específico, se pasan resultados entre sí y manejan la lógica de ramificación basada en los resultados.

Eso es exactamente lo que le brindan las capacidades de flujo de trabajo en Agent Framework. En lugar de incluir a los agentes en un chat grupal y esperar que lo resuelvan, usted define pasos explícitos, dependencias y flujo de datos entre agentes.

¿Qué son los flujos de trabajo de los agentes?

Piense en los flujos de trabajo de los agentes como si fueran un canal. Cada paso es manejado por un agente especializado, y el resultado de un paso alimenta el siguiente. Puede ejecutar pasos de forma secuencial, en paralelo o condicionalmente en función de los resultados.

Algunos ejemplos:

  • Contenido en proceso: Investigación → Borrador → Revisar → Publicar
  • Procesamiento de datos: Extraer → Transformar → Validar → Cargar
  • Atención al cliente: Clasificar → Ruta → Responder → Seguimiento

Requisitos previos

Asegúrese de tener los últimos paquetes de Agent Framework:

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

Y un punto final de Azure OpenAI u OpenAI configurado.

Construyendo un flujo de trabajo secuencial

Construyamos un flujo de trabajo de creación de contenido con tres agentes: un investigador, un escritor y un editor.

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

Ejecutando secuencialmente

El flujo de trabajo más simple es secuencial: cada agente procesa el resultado del 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 obtiene el contexto acumulado, lo procesa y el resultado pasa al siguiente paso.

Ejecución paralela

A veces los agentes pueden trabajar de forma independiente. Por ejemplo, es posible que desees investigar varios subtemas al mismo tiempo:

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

Luego, puede introducir todos los resúmenes de investigación en un único agente de redacción para producir una publicación coherente.

Ramificación condicional

Los flujos de trabajo reales necesitan decisiones. Tal vez quieras un paso de control de calidad que te lleve al autor si la publicación no es lo suficientemente buena:

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

Este patrón es súper útil. El verificador de calidad actúa como una puerta y el flujo de trabajo se repite hasta que el resultado es lo suficientemente bueno o alcanza el límite máximo de revisión.

Agregar complementos a los agentes de flujo de trabajo

Los agentes en flujos de trabajo pueden usar complementos como agentes independientes. Aquí es donde las cosas se vuelven realmente poderosas: los agentes pueden llamar a API, consultar bases de datos o realizar operaciones con archivos como parte de su paso de flujo de trabajo:

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

Manejo de errores en flujos de trabajo

Cuando encadena varios agentes, el manejo de errores se vuelve crítico. No querrás que un paso fallido bloquee todo el flujo de trabajo de forma silenciosa:

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

Mejores prácticas

Después de crear varios flujos de trabajo de agentes, esto es lo que aprendí:- Mantenga enfocadas las instrucciones de los agentes: cada agente debe hacer una cosa bien. No intentes convertirte en un agente de la navaja suiza.

  • Limitar contexto: no pase todo el historial de conversaciones a cada agente. Dale a cada paso solo lo que necesita.
  • Establecer límites de revisión: los bucles de calidad son excelentes, pero pueden ejecutarse para siempre si no tienes cuidado. Tenga siempre un límite máximo de iteraciones.
  • Registrar todo: los resultados de los agentes pueden ser impredecibles. Registre la entrada y salida de cada paso para depurar.
  • Utilice la ejecución paralela con prudencia: acelera las cosas, pero tenga cuidado con los límites de tasa de API y los costos de los tokens.
  • Pruebe primero con modelos más pequeños: desarrolle y pruebe su lógica de flujo de trabajo con GPT-3.5 antes de cambiar a GPT-4o para producción.

Conclusión

Los flujos de trabajo de los agentes le permiten crear canales de IA complejos y de varios pasos en los que cada agente es un especialista. Comience con flujos de trabajo secuenciales, agregue ejecución paralela donde los pasos sean independientes y use ramificaciones condicionales para puertas de calidad. Los patrones se pueden componer: una vez que lo domines, puedes crear una automatización bastante sofisticada.

¡Feliz codificación!

Recursos