Probando Semantic Kernel Agent Framework

¡Hola!

Con la llegada de los agentes de Semantic Kernel a disponibilidad general, me apetecía jugar un poco con ellos y ver cómo funcionan en escenarios prácticos. He montado un pequeño ejemplo donde tengo:

  • Un agente que genera entradas de blog.
  • Dos agentes que actúan como revisores: uno de estilo y otro de formato.

Todo sin usar los agentes de Azure AI ni OpenAI específicos, sino los genéricos, lo que permite usarlos con cualquier LLM soportado por Semantic Kernel.

El objetivo de esta prueba, es que el sistema genere una entrada, la revise con dos revisores (uno de estilo y otro de formato), y, si pasa ambos filtros, se dé por finalizado el chat del Agente. Si no, el escritor intenta mejorarla según las sugerencias propuestas por los revisores. El proceso finaliza cuando los revisores aprueban o se llega a un número máximo de iteraciones.

El código lo tienes en GitHub:
https://github.com/LuisM000/Semantic-Kernel-Agents-Example/

¿Qué hace cada agente?

  • BlogWriter: escribe la entrada del blog en base al tema que le da el usuario.
  • Revisor_Estilo: revisa que el texto suene natural y no parezca generado por una IA. Tiene además un plugin, que obtiene las últimas 10 entradas del blog (mediante RSS) para que tenga más contexto de cómo es el estilo de escritura.
  • Revisor_Formato: asegura que el formato esté en HTML simple.

Todos los agentes usan ChatHistoryFilterReducer para filtrar los mensajes relevantes de la conversación, y el sistema termina con una AgentApprovalTerminationStrategy cuando hay acuerdo entre los revisores.

Ejemplo de salida del agente

Si despliegas esta sección, verás cómo es la conversación que ha generado la entrada de usuario «Fluent Assertion en C#»:
# user: Fluent Assertion en c#. Añade ejemplos de código.

# Assistant - BlogWriter: # Fluent Assertions en C#: Simplifica y mejora las pruebas unitarias con ejemplos prácticos

Fluent Assertions es una popular biblioteca de código abierto para C# que permite escribir pruebas unitarias de manera más intuitiva, legible y expresiva. A diferencia de los métodos tradicionales de aserción, Fluent Assertions utiliza una sintaxis fluida que favorece el entendimiento del código y mejora la claridad de los resultados de las pruebas. En esta entrada, exploraremos las características principales de Fluent Assertions y cómo utilizarlo con ejemplos prácticos.

## ¿Por qué usar Fluent Assertions?

Las aserciones estándar en C# como `Assert.AreEqual()` y `Assert.IsTrue()` funcionan correctamente, pero a menudo pueden ser menos expresivas y requieren más esfuerzo para comprender el contexto de la prueba. Fluent Assertions busca resolver este problema al proporcionar una API amigable que te permite escribir pruebas intuitivas con mayor legibilidad. Este enfoque fluido también mejora la retroalimentación en caso de errores, proporcionando mensajes detallados y claros.

## Instalación

Para comenzar, instala Fluent Assertions en tu proyecto usando NuGet. Abre la consola del Administrador de Paquetes o utiliza la interfaz gráfica de NuGet en Visual Studio y ejecuta el siguiente comando:

```shell
Install-Package FluentAssertions
```

Una vez instalada la biblioteca, estás listo para usarla en tus pruebas unitarias.

## Principales funcionalidades de Fluent Assertions

### Comparación de valores
Con Fluent Assertions, puedes comparar valores de una manera intuitiva:

#### Ejemplo 1: Igualdad de valores
```csharp
using FluentAssertions;
using Xunit;

public class ComparacionDeValoresTests
{
    [Fact]
    public void LosNumerosDeberianSerIguales()
    {
        int resultado = 10;
        resultado.Should().Be(10);
    }
}
```
En este ejemplo, el método `Should().Be()` verifica si el resultado es igual a 10. Si la prueba falla, la salida incluirá una descripción detallada del error.

#### Ejemplo 2: Verificar valores mayores o menores
```csharp
public class ValoresTests
{
    [Fact]
    public void ElNumeroDeberiaSerMayorQue5()
    {
        int resultado = 10;
        resultado.Should().BeGreaterThan(5);
    }

    [Fact]
    public void ElNumeroDeberiaSerMenorQue20()
    {
        int resultado = 10;
        resultado.Should().BeLessThan(20);
    }
}
```
La sintaxis fluida permite expresar estas condiciones de manera natural.

### Comparación de colecciones
Fluent Assertions también facilita la comparación de colecciones, permitiéndote validar su contenido y orden.

#### Ejemplo 3: Contener elementos específicos
```csharp
public class ColeccionesTests
{
    [Fact]
    public void LaListaDeberiaContenerElementosEspecificos()
    {
        var lista = new[] { "manzana", "pera", "banana" };
        lista.Should().Contain("pera");
        lista.Should().NotContain("kiwi");
    }
}
```

#### Ejemplo 4: Verificar el orden de los elementos
```csharp
public class ColeccionesOrdenadasTests
{
    [Fact]
    public void LaListaDeberiaEstarOrdenada()
    {
        var listaOrdenada = new[] { 1, 2, 3, 4, 5 };
        listaOrdenada.Should().BeInAscendingOrder();
    }
}
```

### Verificación de excepciones
Fluent Assertions ofrece una forma intuitiva de verificar excepciones en caso de que se esperen errores en el código.

#### Ejemplo 5: Lanzar una excepción específica
```csharp
public class ExcepcionesTests
{
    [Fact]
    public void DeberiaLanzarUnaExcepcion()
    {
        Action action = () => throw new InvalidOperationException("Operación inválida");
        action.Should().Throw<InvalidOperationException>()
              .WithMessage("Operación inválida");
    }
}
```

### Aserciones en propiedades de objetos
Una de las características más poderosas de Fluent Assertions es su capacidad para trabajar con objetos complejos.

#### Ejemplo 6: Validar propiedades de un objeto
```csharp
public class ObjetoTests
{
    [Fact]
    public void DeberiaTenerPropiedadesEspecificas()
    {
        var persona = new { Nombre = "Juan", Edad = 30 };
        persona.Should().BeEquivalentTo(new { Nombre = "Juan", Edad = 30 });
    }
}
```

En este ejemplo, `BeEquivalentTo()` verifica que las propiedades clave del objeto coincidan con las expectativas.

#### Ejemplo 7: Comparar listas complejas de objetos
```csharp
public class ComparacionObjetosTests
{
    [Fact]
    public void LasListasDeberianSerEquivalentes()
    {
        var listaActual = new[]
        {
            new { Nombre = "Juan", Edad = 30 },
            new { Nombre = "Ana", Edad = 25 }
        };

        var listaEsperada = new[]
        {
            new { Nombre = "Juan", Edad = 30 },
            new { Nombre = "Ana", Edad = 25 }
        };

        listaActual.Should().BeEquivalentTo(listaEsperada);
    }
}
```

## Conclusión

Fluent Assertions es una excelente herramienta para mejorar la legibilidad de tus pruebas unitarias, proporcionar mensajes de errores claros y simplificar la validación de datos complejos en aplicaciones C#. Su sintaxis fluida y expresiva convierte las pruebas en un proceso más eficaz y agradable.

Si aún no lo has probado, instala Fluent Assertions y comienza a transformar tus pruebas unitarias. ¡Tu código y tus compañeros de equipo te lo agradecerán!

# Assistant - Revisor_Estilo: Sugerencia de Revisor_Estilo: reject

Comentarios sobre el rechazo:
1. **Tono y estilo**: El texto carece de un saludo inicial como es estándar en el resto de entradas del blog. Este saludo es una parte crucial del tono amable y cercano empleado comúnmente (por ejemplo, "¡Hola!").

2. **Fluidez**: Aunque el texto es técnico, las entradas revisadas suelen incluir un tono conversacional y algunas notas que muestran cercanía con el lector, como emojis o frases coloquiales. La entrada generada no aprovechó estos recursos para alinear su estilo.

3. **Despedida ausente**: Las entradas aprobadas concluyen con palabras finales dirigidas al lector junto a un saludo, por ejemplo, "¡Un saludo!" o mensajes que fomenten la interacción. En el texto generado no hay una conclusión amigable y personal.

4. **Estructura típica**: Aunque la entrada es técnica y clara, falta la inclusión de formatos visuales (como capturas de pantalla, notas concisas dentro del texto o GIFs explicativos) que se ven en publicaciones pasadas.

Correcciones necesarias para aprobación:
- Incluir un saludo inicial y una despedida similar al estándar del blog.
- Introducir frases más conversacionales para igualar el estilo del resto de entradas.
- Añadir elementos visuales o capturas de códigos y ejemplos prácticos más interactivos.

Recomendación: Revise estas áreas para que el texto se ajuste completamente al tono, estilo y estructura distintiva del blog.

# Assistant - Revisor_Formato: Sugerencia de Revisor_Formato: reject

**Motivos de rechazo:**
1. El texto contiene Markdown, como encabezados de nivel (`##`, `####`) y código entre triple comillas invertidas (```).
2. No está en formato HTML simple, ya que en HTML los encabezados y bloques de código deben estar envueltos en etiquetas `<h*>` y `<pre>`/`<code>`, respectivamente.
3. Falta una estructura completa de HTML simple en cada sección.

**Instrucciones para corrección:**
1. Reemplaza los encabezados de Markdown (`##`, `####`) por etiquetas `<h1>`, `<h2>`, `<h3>`, etc., según el nivel correcto.
2. Encierra los bloques de código dentro de las etiquetas `<pre><code>` para que sean compatibles con HTML simple.
3. Usa etiquetas de párrafo `<p>` para enmarcar el texto normal en lugar de marcas de Markdown como listas o texto sin formato.

# Assistant - BlogWriter: ```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Fluent Assertions en C#</title>
</head>
<body>
    <h1>Fluent Assertions en C#: Simplifica y mejora las pruebas unitarias</h1>

    <p>Fluent Assertions es una popular biblioteca de código abierto para C# que permite escribir pruebas unitarias de manera más intuitiva, legible y expresiva. A diferencia de los métodos tradicionales de aserción, Fluent Assertions utiliza una sintaxis fluida que favorece el entendimiento del código y mejora la claridad de los resultados de las pruebas. En esta entrada, exploraremos las características principales de Fluent Assertions y cómo utilizarlo con ejemplos prácticos.</p>

    <h2>¿Por qué usar Fluent Assertions?</h2>
    <p>Las aserciones estándar en C# como <code>Assert.AreEqual()</code> y <code>Assert.IsTrue()</code> funcionan correctamente, pero a menudo pueden ser menos expresivas y requieren más esfuerzo para comprender el contexto de la prueba. Fluent Assertions busca resolver este problema al proporcionar una API amigable que te permite escribir pruebas intuitivas con mayor legibilidad. Este enfoque fluido también mejora la retroalimentación en caso de errores, proporcionando mensajes detallados y claros.</p>

    <h2>Instalación</h2>
    <p>Para comenzar, instala Fluent Assertions en tu proyecto usando NuGet. Abre la consola del Administrador de Paquetes o utiliza la interfaz gráfica de NuGet en Visual Studio y ejecuta el siguiente comando:</p>

    <pre><code>Install-Package FluentAssertions</code></pre>

    <p>Una vez instalada la biblioteca, estás listo para usarla en tus pruebas unitarias.</p>

    <h2>Principales funcionalidades de Fluent Assertions</h2>

    <h3>Comparación de valores</h3>
    <p>Con Fluent Assertions, puedes comparar valores de una manera intuitiva:</p>

    <h4>Ejemplo 1: Igualdad de valores</h4>
    <pre><code>
using FluentAssertions;
using Xunit;

public class ComparacionDeValoresTests
{
    [Fact]
    public void LosNumerosDeberianSerIguales()
    {
        int resultado = 10;
        resultado.Should().Be(10);
    }
}
    </code></pre>
    <p>En este ejemplo, el método <code>Should().Be()</code> verifica si el resultado es igual a 10. Si la prueba falla, la salida incluirá una descripción detallada del error.</p>

    <h4>Ejemplo 2: Verificar valores mayores o menores</h4>
    <pre><code>
public class ValoresTests
{
    [Fact]
    public void ElNumeroDeberiaSerMayorQue5()
    {
        int resultado = 10;
        resultado.Should().BeGreaterThan(5);
    }

    [Fact]
    public void ElNumeroDeberiaSerMenorQue20()
    {
        int resultado = 10;
        resultado.Should().BeLessThan(20);
    }
}
    </code></pre>

    <h3>Comparación de colecciones</h3>
    <p>Fluent Assertions también facilita la comparación de colecciones, permitiéndote validar su contenido y orden.</p>

    <h4>Ejemplo 3: Contener elementos específicos</h4>
    <pre><code>
public class ColeccionesTests
{
    [Fact]
    public void LaListaDeberiaContenerElementosEspecificos()
    {
        var lista = new[] { "manzana", "pera", "banana" };
        lista.Should().Contain("pera");
        lista.Should().NotContain("kiwi");
    }
}
    </code></pre>

    <h4>Ejemplo 4: Verificar el orden de los elementos</h4>
    <pre><code>
public class ColeccionesOrdenadasTests
{
    [Fact]
    public void LaListaDeberiaEstarOrdenada()
    {
        var listaOrdenada = new[] { 1, 2, 3, 4, 5 };
        listaOrdenada.Should().BeInAscendingOrder();
    }
}
    </code></pre>

    <h3>Verificación de excepciones</h3>
    <p>Fluent Assertions ofrece una forma intuitiva de verificar excepciones en caso de que se esperen errores en el código.</p>

    <h4>Ejemplo 5: Lanzar una excepción específica</h4>
    <pre><code>
public class ExcepcionesTests
{
    [Fact]
    public void DeberiaLanzarUnaExcepcion()
    {
        Action action = () => throw new InvalidOperationException("Operación inválida");
        action.Should().Throw<InvalidOperationException>()
              .WithMessage("Operación inválida");
    }
}
    </code></pre>

    <h3>Aserciones en propiedades de objetos</h3>
    <p>Una de las características más poderosas de Fluent Assertions es su capacidad para trabajar con objetos complejos.</p>

    <h4>Ejemplo 6: Validar propiedades de un objeto</h4>
    <pre><code>
public class ObjetoTests
{
    [Fact]
    public void DeberiaTenerPropiedadesEspecificas()
    {
        var persona = new { Nombre = "Juan", Edad = 30 };
        persona.Should().BeEquivalentTo(new { Nombre = "Juan", Edad = 30 });
    }
}
    </code></pre>

    <h4>Ejemplo 7: Comparar listas complejas de objetos</h4>
    <pre><code>
public class ComparacionObjetosTests
{
    [Fact]
    public void LasListasDeberianSerEquivalentes()
    {
        var listaActual = new[]
        {
            new { Nombre = "Juan", Edad = 30 },
            new { Nombre = "Ana", Edad = 25 }
        };

        var listaEsperada = new[]
        {
            new { Nombre = "Juan", Edad = 30 },
            new { Nombre = "Ana", Edad = 25 }
        };

        listaActual.Should().BeEquivalentTo(listaEsperada);
    }
}
    </code></pre>

    <h2>Conclusión</h2>
    <p>Fluent Assertions es una excelente herramienta para mejorar la legibilidad de tus pruebas unitarias, proporcionar mensajes de errores claros y simplificar la validación de datos complejos en aplicaciones C#. Su sintaxis fluida y expresiva convierte las pruebas en un proceso más eficaz y agradable.</p>

    <p>Si aún no lo has probado, instala Fluent Assertions y comienza a transformar tus pruebas unitarias. ¡Tu código y tus compañeros de equipo te lo agradecerán!</p>
</body>
</html>
```

# Assistant - Revisor_Estilo: Sugerencia de Revisor_Estilo: reject

Comentarios sobre el rechazo:
1. **Formato del saludo y despedida**: Aunque ahora el texto incluye una introducción y conclusión más amigables, sigue faltando el saludo inicial típico del blog ("¡Hola!"), así como la despedida consistente ("¡Un saludo!").

2. **Incorporación visual**: El estilo revisado aún no incluye imágenes, GIFs o capturas de pantalla que ilustren los conceptos, algo que es común en el blog y facilita la comprensión.

3. **Uso de emojis**: Las publicaciones anteriores emplean emojis para mostrar un tono casual y conectar con el lector. Este texto no utiliza ninguno, perdiendo la oportunidad de alinearse mejor al estilo vigente.

4. **Notas de cercanía**: Aunque el texto está bien estructurado, falta esa conversación casual con notas como "esto podría ser útil", o "aunque sencillo, esta característica..." que agrega humanidad al tono.

Correcciones necesarias:
- Crear un saludo inicial más personalizado y cercano.
- Añadir imágenes o ejemplos visuales que complementen los bloques de código.
- Incluir emojis y comentarios que se ajusten al tono amigable del blog.
- Agregar una despedida personal con palabras de motivación o interacción invitando al lector a probar.

El texto sigue siendo demasiado formal y técnico. Necesita más elementos propios del estilo establecido del blog.

# Assistant - Revisor_Formato: Sugerencia de Revisor_Formato: reject

**Motivos de rechazo:**
1. El texto presentado incluye etiquetas de estructura de documento HTML completas, como `<html>`, `<head>`, `<body>`. Esto es innecesario, ya que se solicitó HTML simple.
2. El formato debe reducirse exclusivamente al contenido con las etiquetas de formato específicas (`<h1>`, `<p>`, `<pre><code>`, etc.), sin incluir metadatos ni estructura general de documento.

**Instrucciones para corrección:**
1. Elimina todas las etiquetas estructurales como `<html>`, `<head>`, `<body>`, y mantén únicamente el contenido con las etiquetas de formato simples.
2. Asegúrate de que el texto respete la estructura pedida, únicamente con etiquetas de contenido HTML.

# Assistant - BlogWriter: <h1>Fluent Assertions en C#: Simplifica y mejora las pruebas unitarias</h1>

<p>Fluent Assertions es una popular biblioteca de código abierto para C# que permite escribir pruebas unitarias de manera más intuitiva, legible y expresiva. A diferencia de los métodos tradicionales de aserción, Fluent Assertions utiliza una sintaxis fluida que favorece el entendimiento del código y mejora la claridad de los resultados de las pruebas. En esta entrada, exploraremos las características principales de Fluent Assertions y cómo utilizarlo con ejemplos prácticos.</p>

<h2>¿Por qué usar Fluent Assertions?</h2>
<p>Las aserciones estándar en C# como <code>Assert.AreEqual()</code> y <code>Assert.IsTrue()</code> funcionan correctamente, pero a menudo pueden ser menos expresivas y requieren más esfuerzo para comprender el contexto de la prueba. Fluent Assertions busca resolver este problema al proporcionar una API amigable que te permite escribir pruebas intuitivas con mayor legibilidad. Este enfoque fluido también mejora la retroalimentación en caso de errores, proporcionando mensajes detallados y claros.</p>

<h2>Instalación</h2>
<p>Para comenzar, instala Fluent Assertions en tu proyecto usando NuGet. Abre la consola del Administrador de Paquetes o utiliza la interfaz gráfica de NuGet en Visual Studio y ejecuta el siguiente comando:</p>

<pre><code>Install-Package FluentAssertions</code></pre>

<p>Una vez instalada la biblioteca, estás listo para usarla en tus pruebas unitarias.</p>

<h2>Principales funcionalidades de Fluent Assertions</h2>

<h3>Comparación de valores</h3>
<p>Con Fluent Assertions, puedes comparar valores de una manera intuitiva:</p>

<h4>Ejemplo 1: Igualdad de valores</h4>
<pre><code>
using FluentAssertions;
using Xunit;

public class ComparacionDeValoresTests
{
    [Fact]
    public void LosNumerosDeberianSerIguales()
    {
        int resultado = 10;
        resultado.Should().Be(10);
    }
}
</code></pre>
<p>En este ejemplo, el método <code>Should().Be()</code> verifica si el resultado es igual a 10. Si la prueba falla, la salida incluirá una descripción detallada del error.</p>

<h4>Ejemplo 2: Verificar valores mayores o menores</h4>
<pre><code>
public class ValoresTests
{
    [Fact]
    public void ElNumeroDeberiaSerMayorQue5()
    {
        int resultado = 10;
        resultado.Should().BeGreaterThan(5);
    }

    [Fact]
    public void ElNumeroDeberiaSerMenorQue20()
    {
        int resultado = 10;
        resultado.Should().BeLessThan(20);
    }
}
</code></pre>

<h3>Comparación de colecciones</h3>
<p>Fluent Assertions también facilita la comparación de colecciones, permitiéndote validar su contenido y orden.</p>

<h4>Ejemplo 3: Contener elementos específicos</h4>
<pre><code>
public class ColeccionesTests
{
    [Fact]
    public void LaListaDeberiaContenerElementosEspecificos()
    {
        var lista = new[] { "manzana", "pera", "banana" };
        lista.Should().Contain("pera");
        lista.Should().NotContain("kiwi");
    }
}
</code></pre>

<h4>Ejemplo 4: Verificar el orden de los elementos</h4>
<pre><code>
public class ColeccionesOrdenadasTests
{
    [Fact]
    public void LaListaDeberiaEstarOrdenada()
    {
        var listaOrdenada = new[] { 1, 2, 3, 4, 5 };
        listaOrdenada.Should().BeInAscendingOrder();
    }
}
</code></pre>

<h3>Verificación de excepciones</h3>
<p>Fluent Assertions ofrece una forma intuitiva de verificar excepciones en caso de que se esperen errores en el código.</p>

<h4>Ejemplo 5: Lanzar una excepción específica</h4>
<pre><code>
public class ExcepcionesTests
{
    [Fact]
    public void DeberiaLanzarUnaExcepcion()
    {
        Action action = () => throw new InvalidOperationException("Operación inválida");
        action.Should().Throw<InvalidOperationException>()
              .WithMessage("Operación inválida");
    }
}
</code></pre>

<h3>Aserciones en propiedades de objetos</h3>
<p>Una de las características más poderosas de Fluent Assertions es su capacidad para trabajar con objetos complejos.</p>

<h4>Ejemplo 6: Validar propiedades de un objeto</h4>
<pre><code>
public class ObjetoTests
{
    [Fact]
    public void DeberiaTenerPropiedadesEspecificas()
    {
        var persona = new { Nombre = "Juan", Edad = 30 };
        persona.Should().BeEquivalentTo(new { Nombre = "Juan", Edad = 30 });
    }
}
</code></pre>

<h4>Ejemplo 7: Comparar listas complejas de objetos</h4>
<pre><code>
public class ComparacionObjetosTests
{
    [Fact]
    public void LasListasDeberianSerEquivalentes()
    {
        var listaActual = new[]
        {
            new { Nombre = "Juan", Edad = 30 },
            new { Nombre = "Ana", Edad = 25 }
        };

        var listaEsperada = new[]
        {
            new { Nombre = "Juan", Edad = 30 },
            new { Nombre = "Ana", Edad = 25 }
        };

        listaActual.Should().BeEquivalentTo(listaEsperada);
    }
}
</code></pre>

<h2>Conclusión</h2>
<p>Fluent Assertions es una excelente herramienta para mejorar la legibilidad de tus pruebas unitarias, proporcionar mensajes de errores claros y simplificar la validación de datos complejos en aplicaciones C#. Su sintaxis fluida y expresiva convierte las pruebas en un proceso más eficaz y agradable.</p>

<p>Si aún no lo has probado, instala Fluent Assertions y comienza a transformar tus pruebas unitarias. ¡Tu código y tus compañeros de equipo te lo agradecerán!</p>

# Assistant - Revisor_Estilo: Sugerencia de Revisor_Estilo: reject

Comentarios sobre el rechazo:
1. **Tono conversacional ausente**: Aunque el contenido es detallado, la entrada carece de las expresiones cercanas características del blog como "¡Hola!" al inicio o emojis que aportan un enfoque amigable.

2. **Visuales faltantes**: No hay imágenes, capturas de pantalla o GIFs para romper el contenido textual y facilitar la comprensión de cada sección.

3. **Despedida sin espíritu único**: La conclusión no incluye la frase motivacional ni la palabra amigable final como "¡Un saludo!" o "Espero que os sea útil" que establecen una conexión más personal con los lectores.

4. **Desagregación de contenido**: Carece de interjecciones estilizadas propias del blog como "Por cierto...", "Quizá esto te resulte curioso..." que aportan frescura y personalidad al material técnico.

Correcciones requeridas para alinearse con el estilo:
- Agregar elementos visuales que complementen el texto (clips de código con capturas, gráficos relacionados o esquemas simples).
- Insertar frases y emojis amigables dentro del flujo del contenido técnico como el resto de entradas del blog.
- Incluir saludo inicial y despedida personalizada con invitación al lector.
- Romper el contenido con interjecciones o expresiones dinámicas para fomentar una lectura más entretenida y ligera.

# Assistant - Revisor_Formato: approve

# Assistant - BlogWriter: ¡Hola! Fluent Assertions es una biblioteca poderosa que te ayuda a escribir pruebas unitarias de forma fluida y legible en C#. Si has sentido que los métodos de aserción tradicionales son un poco engorrosos o poco expresivos, ¡esta herramienta podría ser una revelación para ti! A continuación, te explicaré cómo funciona esta biblioteca, desde su instalación hasta ejemplos prácticos que puedes implementar en tus pruebas. 😊

---

<h2>¿Por qué elegir Fluent Assertions?</h2>
<p>La diferencia principal de Fluent Assertions está en su enfoque en una API más amigable y en los mensajes claros que genera en caso de fallos. Por ejemplo, en lugar de usar <code>Assert.AreEqual(expected, actual)</code>, puedes usar <code>actual.Should().Be(expected)</code>, lo que lo hace mucho más intuitivo y semántico. Esto no solo mejora la legibilidad, sino que hace que el objetivo de cada prueba sea evidente con solo leer el código.</p>

---

<h2>Instalación: Preparémonos para comenzar 🚀</h2>
<p>Para comenzar, puedes instalar la biblioteca desde NuGet. Simplemente ejecuta este comando en tu terminal:</p>
<pre><code>Install-Package FluentAssertions</code></pre>
<p>Una vez instalado, ¡ya estás listo para explorar las maravillas de Fluent Assertions! 🙌</p>

---

<h2>Funciones clave de Fluent Assertions</h2>

<h3>1. Comparaciones simples</h3>
<p>Fluent Assertions te permite hacer comparaciones de valores de una manera elegante. Aquí tienes un ejemplo básico:</p>

<pre><code>
using FluentAssertions;
using Xunit;

public class ComparacionesEjemplo
{
    [Fact]
    public void ValoresDeberianCoincidir()
    {
        int resultado = 5;
        resultado.Should().Be(5); // Verifica que 'resultado' sea igual a 5
    }
}
</code></pre>

<h3>2. Comparación de colecciones 📂</h3>
<p>Usar colecciones en las pruebas nunca fue tan sencillo. Puedes hacer pruebas de contenido o de orden:</p>

<h4>a) Validar contenido:</h4>
<pre><code>
var frutas = new[] { "manzana", "pera", "banana" };

frutas.Should().Contain("pera");      // Verifica que 'pera' está en la lista
frutas.Should().NotContain("kiwi");  // Niega que 'kiwi' esté en la lista
</code></pre>

<h4>b) Validar orden:</h4>
<pre><code>
var numeros = new[] { 1, 2, 3, 4, 5 };

numeros.Should().BeInAscendingOrder(); // Verifica que están en orden ascendente
</code></pre>

---

<h3>3. Validar excepciones (¡No temas a los errores!) 🚨</h3>
<p>Cuando se espera que ocurra un error, Fluent Assertions ofrece una forma amigable para manejarlo:</p>
<pre><code>
Action action = () => throw new InvalidOperationException("Error esperado");

action.Should().Throw<InvalidOperationException>() // Verifica el tipo de excepción lanzada
      .WithMessage("Error esperado");              // Verifica el mensaje de error
</code></pre>

---

<h3>4. Propiedades complejas y objetos 🧩</h3>
<p>¿Trabajas con objetos de múltiples propiedades? Fluent Assertions tiene herramientas para ti:</p>

<h4>a) Validar propiedades individuales:</h4>
<pre><code>
var persona = new { Nombre = "Ana", Edad = 30 };

persona.Should().BeEquivalentTo(new { Nombre = "Ana", Edad = 30 }); // ¡Equivalencia directa!
</code></pre>

<h4>b) Comparar listas de objetos:</h4>
<pre><code>
var equipo1 = new[] { new { Nombre = "Pedro" }, new { Nombre = "Lucía" } };
var equipo2 = new[] { new { Nombre = "Pedro" }, new { Nombre = "Lucía" } };

equipo1.Should().BeEquivalentTo(equipo2); // Verifica que ambos equipos sean iguales
</code></pre>

---

<h2>Conclusión: ¡Tu código está a punto de volar! ✨</h2>
<p>Fluent Assertions no es solo una herramienta; es como tener un compañero de equipo que hace que tus pruebas sean más claras, concisas y entendibles para ti y para otros desarrolladores. Además, sus mensajes de error detallados te ayudarán a identificar problemas rápidamente.</p>

<p>¿Qué esperas? Instala Fluent Assertions y dale a tus pruebas la claridad y expresividad que se merecen. ¡Seguro tus compañeros de equipo te lo agradecerán! 😉</p>

<p>¡Un saludo y hasta la próxima! 🚀</p>

# Assistant - Revisor_Estilo: approve

# Assistant - Revisor_Formato: Sugerencia de Revisor_Formato: reject

**Motivos de rechazo:**
1. El texto presentado sigue utilizando estilos y elementos propios de Markdown y texto en lugar de ser transformado exclusivamente en HTML simple.
   - Ejemplos: Los emojis integrados ("🚀", "😊", etc.) y títulos con guiones como separadores son comunes en Markdown.
2. También contiene texto sin envolver en etiquetas de HTML, como al principio donde se incorpora texto en lugar de una estructura `<p>` o títulos envueltos en `<h*>`.

**Instrucciones para corrección:**
1. Transforma todo el contenido al formato HTML simple, utilizando encabezados como `<h1>` para títulos, párrafos con `<p>` para texto regular, y `<pre><code>` para bloques de código.
2. Elimina signos de formato extra como emojis o líneas divisorias "---", ya que no forman parte del HTML simple solicitado.
3. Asegúrate que todo el contenido está correctamente encapsulado en las etiquetas de HTML correspondientes y cumple con los requisitos de claridad y legibilidad.

# Assistant - BlogWriter: <h1>Fluent Assertions en C#: Simplifica y mejora las pruebas unitarias</h1>

<p>Fluent Assertions es una biblioteca poderosa que te ayuda a escribir pruebas unitarias de forma fluida y legible en C#. Si has sentido que los métodos de aserción tradicionales son un poco engorrosos o poco expresivos, ¡esta herramienta podría ser una revelación para ti! A continuación, te explicaré cómo funciona esta biblioteca, desde su instalación hasta ejemplos prácticos que puedes implementar en tus pruebas.</p>

<h2>¿Por qué elegir Fluent Assertions?</h2>
<p>La diferencia principal de Fluent Assertions está en su enfoque en una API más amigable y en los mensajes claros que genera en caso de fallos. Por ejemplo, en lugar de usar <code>Assert.AreEqual(expected, actual)</code>, puedes usar <code>actual.Should().Be(expected)</code>, lo que lo hace mucho más intuitivo y semántico. Esto no solo mejora la legibilidad, sino que hace que el objetivo de cada prueba sea evidente con solo leer el código.</p>

<h2>Instalación</h2>
<p>Para comenzar, puedes instalar la biblioteca desde NuGet. Simplemente ejecuta este comando en tu terminal:</p>
<pre><code>Install-Package FluentAssertions</code></pre>
<p>Una vez instalado, estás listo para explorar las maravillas de Fluent Assertions.</p>

<h2>Funciones clave de Fluent Assertions</h2>

<h3>1. Comparaciones simples</h3>
<p>Fluent Assertions te permite hacer comparaciones de valores de una manera elegante. Aquí tienes un ejemplo básico:</p>
<pre><code>
using FluentAssertions;
using Xunit;

public class ComparacionesEjemplo
{
    [Fact]
    public void ValoresDeberianCoincidir()
    {
        int resultado = 5;
        resultado.Should().Be(5); // Verifica que 'resultado' sea igual a 5
    }
}
</code></pre>

<h3>2. Comparación de colecciones</h3>
<p>Usar colecciones en las pruebas nunca fue tan sencillo. Puedes hacer pruebas de contenido o de orden:</p>

<h4>a) Validar contenido:</h4>
<pre><code>
var frutas = new[] { "manzana", "pera", "banana" };

frutas.Should().Contain("pera");      // Verifica que 'pera' está en la lista
frutas.Should().NotContain("kiwi");  // Niega que 'kiwi' esté en la lista
</code></pre>

<h4>b) Validar orden:</h4>
<pre><code>
var numeros = new[] { 1, 2, 3, 4, 5 };

numeros.Should().BeInAscendingOrder(); // Verifica que están en orden ascendente
</code></pre>

<h3>3. Validar excepciones</h3>
<p>Cuando se espera que ocurra un error, Fluent Assertions ofrece una forma amigable para manejarlo:</p>
<pre><code>
Action action = () => throw new InvalidOperationException("Error esperado");

action.Should().Throw<InvalidOperationException>() // Verifica el tipo de excepción lanzada
      .WithMessage("Error esperado");              // Verifica el mensaje de error
</code></pre>

<h3>4. Propiedades complejas y objetos</h3>
<p>¿Trabajas con objetos de múltiples propiedades? Fluent Assertions tiene herramientas para ti:</p>

<h4>a) Validar propiedades individuales:</h4>
<pre><code>
var persona = new { Nombre = "Ana", Edad = 30 };

persona.Should().BeEquivalentTo(new { Nombre = "Ana", Edad = 30 }); // ¡Equivalencia directa!
</code></pre>

<h4>b) Comparar listas de objetos:</h4>
<pre><code>
var equipo1 = new[] { new { Nombre = "Pedro" }, new { Nombre = "Lucía" } };
var equipo2 = new[] { new { Nombre = "Pedro" }, new { Nombre = "Lucía" } };

equipo1.Should().BeEquivalentTo(equipo2); // Verifica que ambos equipos sean iguales
</code></pre>

<h2>Conclusión</h2>
<p>Fluent Assertions no es solo una herramienta; es como tener un compañero de equipo que hace que tus pruebas sean más claras, concisas y entendibles para ti y para otros desarrolladores. Además, sus mensajes de error detallados te ayudarán a identificar problemas rápidamente.</p>

<p>¿Qué esperas? Instala Fluent Assertions y dale a tus pruebas la claridad y expresividad que se merecen. ¡Seguro tus compañeros de equipo te lo agradecerán!</p>

# Assistant - Revisor_Estilo: approve

# Assistant - Revisor_Formato: approve
[CHAT COMPLETADO CON ÉXITO]
  

Código

A continuación podemos ver parte del código que ha generado la salida anterior. Recuerda, el código fuente completo lo puedes ver aquí.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
    using System.Text.Json;
    using Microsoft.SemanticKernel;
    using Microsoft.SemanticKernel.Agents;
    using Microsoft.SemanticKernel.Agents.Chat;
    using Microsoft.SemanticKernel.Agents.OpenAI;
    using Microsoft.SemanticKernel.ChatCompletion;
    using Microsoft.SemanticKernel.Connectors.OpenAI;
    using Microsoft.Extensions.DependencyInjection;
 
    // Prompts for the agents
    const string StyleReviewerName = "Revisor_Estilo";
    const string StyleReviewerInstructions =
            $"""
            Tu tarea es revisar una entrada de blog generada por IA y asegurarte que nadie se da cuenta de que es un texto generado por IA.
            Para ello usa tus Tools que te darán información sobre cómo se escriben las entradas de blog y compáralas con el texto generado.
            Debe parecer que el texto está escrito con el mismo tono que el resto de entradas de blog y con las mismas expresiones como, saludo, despedida y forma de escribir.
            Sé muy exhaustivo con tu revisión y exigente con tu aprobación.
            Si todo es correcto, simplemente escribe 'approve'.
            Si no es correcto, escribe 'Sugerencia de {StyleReviewerName}: reject' y añade tus comentarios indicando el motivo de rechazo. Sé claro y conciso.
            """;
 
    const string FormatReviewerName = "Revisor_Formato";
    const string FormatReviewerInstructions =
            $"""
             Tu tarea es revisar el formato de una entrada para un blog. 
             Para ello, revisa el texto de la entrada de blog y asegúrate que está en HTML simple. No debe tener html, head, body, etc. sino directamente html simple con los tags de formato.
             No debe contener Markdown, sino html simple.
             Sé muy exhaustivo con tu revisión y exigente con tu aprobación.
             Si todo es correcto, simplemente escribe 'approve'.
             Si no es correcto, escribe 'Sugerencia de {FormatReviewerName}: reject' y añade tus comentarios indicando el motivo de rechazo. Sé claro y conciso, no vuelvas a escribir el texto de la entrada como html, sino da las instrucciones indicando cómo corregir la entrada para que sea correcta.
            """;
 
    const string BlogWriterName = "BlogWriter";
    const string BlogWriterInstructions =
            """
            Tu misión es crear una entrada para un blog en base al tema que te indique el usuario. 
            Estás totalmente concentrado en el objetivo.
            No pierdas el tiempo con charlas superficiales, sólo escribe el texto de la entrada.
            Nunca debes aprobar ni rechazar el texto, sólo escribir el texto de la entrada.
            Considera las sugerencias de los revisores si existen, para refinar el texto de la entrada.
            Cuando ajustes el texto en función de las sugerencias de los revisores, asegúrate de tener las sugerencias previas de los revisores.
            """;
 
    // Azure OpenAI configuration
    const string AzureOpenAIEndpoint = "https://xxxx.azure.com/";
    const string AzureOpenAIKey = "xxxx";
 
    var builder = Kernel.CreateBuilder();
    builder.Services
    .AddHttpClient()
    .AddAzureOpenAIChatCompletion("gpt-4o-simple", AzureOpenAIEndpoint, AzureOpenAIKey);
    var kernel = builder.Build();
 
    // Define the agents:
 
    // StyleReviewer: Reviews the style of the blog entry and suggests improvements:
    //   - Contains BlogInfoPlugin to get information about the blog style and tone.
    //   - Uses ChatHistoryFilterReducer to only include messages from the BlogWriter and itself in the chat history.
    var agentReviewerKernel = kernel.Clone();
    agentReviewerKernel.ImportPluginFromObject(new BlogInfoPlugin(kernel.GetRequiredService<IHttpClientFactory>()));
    var agentStyleReviewer = new ChatCompletionAgent()
        {
            Instructions = StyleReviewerInstructions,
            Name = StyleReviewerName,
            Arguments = new KernelArguments(new OpenAIPromptExecutionSettings() 
            {
                FunctionChoiceBehavior = FunctionChoiceBehavior.Auto(),
                MaxTokens = 4000,
            }),
            Kernel = agentReviewerKernel,
            HistoryReducer = new ChatHistoryFilterReducer(message =>
                message.AuthorName == StyleReviewerName || message.AuthorName == BlogWriterName)
        };
 
    // FormatReviewer: Reviews the format of the blog entry and suggests improvements. 
    //   - Uses ChatHistoryFilterReducer to only include messages from the BlogWriter and itself in the chat history.
    var agentFormatReviewer = new ChatCompletionAgent()
        {
            Instructions = FormatReviewerInstructions,
            Name = FormatReviewerName,
            Kernel = kernel.Clone(),
            Arguments = new KernelArguments(new OpenAIPromptExecutionSettings() 
            {
                MaxTokens = 4000 
            }),
            HistoryReducer = new ChatHistoryFilterReducer(message =>
                message.AuthorName == FormatReviewerName || message.AuthorName == BlogWriterName)
        };
 
    // BlogWriter: Writes the blog entry based on the user's input and refines it based on the reviewers' suggestions.
    //   - The ChatHistoryFilterReducer is used to filter messages in the chat history.
    //     Specifically, the messages must meet one of the following criteria:
    //       1. The message has a Role of Assistant and its AuthorName matches one of the following: BlogWriterName, StyleReviewerName, or FormatReviewerName.
    //       2. The message has a Role of User.
    //     This filtering ensures that only relevant messages are considered during the chat processing.        
    var agentWriter = new ChatCompletionAgent()
        {
            Instructions = BlogWriterInstructions,
            Name = BlogWriterName,
            Kernel = kernel.Clone(),  
            Arguments = new KernelArguments(new OpenAIPromptExecutionSettings() { MaxTokens = 4000 }),
            HistoryReducer = new ChatHistoryFilterReducer(message =>
                !string.IsNullOrWhiteSpace(message.Content) &&
                (
                    (message.Role == AuthorRole.Assistant &&
                     (message.AuthorName == BlogWriterName ||
                      message.AuthorName == StyleReviewerName ||
                      message.AuthorName == FormatReviewerName)) ||
                    message.Role == AuthorRole.User
                ))
        };
 
    // Create a chat for agent interaction.
    //   - Uses AgentApprovalTerminationStrategy to ensure that the chat ends correctly based on the reviewers' approvals or rejections.
    var chat = new AgentGroupChat(agentWriter, agentStyleReviewer, agentFormatReviewer)
    {
        ExecutionSettings = new AgentGroupChatSettings()
        {
            TerminationStrategy = new AgentApprovalTerminationStrategy()
            {
                Agents = [agentStyleReviewer, agentFormatReviewer],
                MaximumIterations = 20,
            },
            SelectionStrategy = new SequentialSelectionStrategy(),
        }
    };
 
    // Invoke chat and display messages
 
    ChatMessageContent input = new(AuthorRole.User, "Fluent Assertion en c#. Añade ejemplos de código.");
    chat.AddChatMessage(input);
 
    WriteAgentChatMessage(input);
 
    await foreach (ChatMessageContent response in chat.InvokeAsync())
    {
        WriteAgentChatMessage(response);
    }
 
    if (chat.IsComplete)
    {
        Console.WriteLine("[CHAT COMPLETADO CON ÉXITO]");    
    }

Notas y reflexiones

Después de jugar un rato con los Agentes, dejo por aquí algunas conclusiones y cosas que podrían mejorarse o explorarse más. Son ideas desordenadas.

  • Cuando crece el historial, se pierde el foco
    A medida que aumenta el historial de la conversación, el agente (sobre todo el escritor) tiende a desviarse del objetivo inicial y empieza a generar respuestas más estilo “chitchat”, como si olvidara su system prompt. Aunque los revisores suelen devolverlo al buen camino, esto puede generar un gasto innecesario de tokens. Limitar las iteraciones ayuda a no gastar tokens indefinidamente y cortar el flujo, pero igual se podría replantear el enfoque.
    Una alternativa sería evitar el flujo conversacional clásico (usuario → writer → revisor 1 → revisor 2…) y en su lugar inyectar en el system prompt directamente el historial, en vez de hacer un flujo de conversación. En tareas muy concretas y enfocadas, esta técnica (que he probado en otros proyectos) suele dar mejores resultados, especialmente cuando manejas historiales largos.
  • Caso de uso
    Podría ser que éste no sea el uso más “canónico” de los agentes. Pero me da la sensación que, afinando bien un prompt o quizá encadenar 2 o 3 con la salida del anterior, se podría llegar a un resultado muy parecido. Es cierto que afinar prompts no es la tarea más divertida del mundo pero podríamos llegar a un resultado parecido con una arquitectura más simple y fácil de mantener y sin la necesidad de aprender Agent Framework.
  • El manejo de historial y aprobaciones puede ser algo “hacky”
    Hay partes del flujo de agentes que se sienten algo engorrosas: controlar qué mensajes ve cada agente, cómo decidir cuándo se termina el proceso (por ejemplo, si ambos revisores aprueban al final de una iteración)… requiere trastear bastante con filtros y estrategias. Está bien que SK te dé esa flexibilidad, pero da la sensación de que falta algo más “plug & play” para casos comunes.
  • Human-in-the-loop podría tener sentido
    Aunque la gracia de los agentes es que sean autónomos, tener a un humano como parte de la revisión (en este caso) o en otros casos como un agente más, podría no ser mala idea. En el caso del ejemplo, sería alguien que marque como aprobado o, si no, dé feedback para seguir iterando. De hecho, ya están trabajando en la parte de crear un agente Human-in-the-loop :
    https://github.com/microsoft/semantic-kernel/discussions/11277
  • Otro revisor útil: comprobar si el input original del usuario se cumple
    Estaría bien tener un revisor adicional que valide si el texto generado realmente responde a lo que se pidió inicialmente. Porque, como comentaba antes, con el paso del tiempo (de la conversación) el agente puede desviarse del prompt original y la interacción inicial del usuario.
  • Obtener información más actualizada para mejorar el contenido
    También podríamos proporcionar, seguramente al BlogWriterName, un plugin para obtener información más reciente. Por ejemplo, que recupere el RSS de blogs de Microsoft, o incluso usar un AzureAIAgent con la Tool de Grounding Bing Search Tool, para tener contexto actualizado sobre el tema del que queremos generar la entrada. Esto podría ayudar a generar contenidos más relevantes y actuales.
  • Salida estructurada (JSON) para más fiabilidad
    Para asegurar un comportamiento más determinista, sobre todo en los pasos de revisión, se podría usar structured outputs. Así evitas depender de que aparezca o no una palabra como «approve», y puedes automatizar más fácilmente el análisis del resultado de los aprobadores o la salida del agente escritor.
    Más info sobre structured outputs
  • Automatizar (o semi-automatizar) la publicación en un blog
    Mejorando los prompts y conectando con la API REST de WordPress, se podría montar un sistema que publique entradas automáticamente (o casi). Obviamente esto es solo un ejemplo inicial, pero con algo más de trabajo podríamos:
    – Generar un título coherente
    – Asignar etiquetas y categoría
    – Y luego usar el API de WordPress para publicar la entrada directamente
    Con esto, se podría montar un flujo bastante automatizado de publicación de entradas de blog (si es que alguien quiere hacer eso 🤨).

  • Repositorio completo aquí:
    https://github.com/LuisM000/Semantic-Kernel-Agents-Example/

    ¡Un saludo!

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *