Integración de Azure OpenAI con Stream en Composer Frameworks para respuestas más rápidas

Spread the love

Estaba desarrollando una aplicación con Composer Frameworks que manejaba una gran cantidad de tokens, y noté que la respuesta del asistente era considerablemente lenta. Al analizar el código de Composer, una de las ventajas de ser de código abierto, identifiqué el cuello de botella. La idea era seguir aprovechando el modo low-code, pero implementando una solución para enviar información que pudiera ser detectada en la parte de .NET, permitiendo así recuperar y procesar las respuestas de Azure OpenAI en modo stream y mostrarlas de forma fluida en el frontend.

¿Qué es el Modo Stream?

Para quienes no estén familiarizados, el modo stream es la forma en que GPT, por ejemplo, muestra el texto gradualmente, palabra por palabra. Esto mejora significativamente la experiencia del usuario al reducir la percepción de latencia. Sin embargo, al realizar llamadas externas desde Composer, este espera la respuesta completa antes de proceder, lo que puede resultar en una mala experiencia de usuario si la respuesta de OpenAI tarda varios segundos.

Implementación de la Solución

Para solucionar este problema, implementamos un middleware personalizado que intercepta las llamadas y permite procesar las respuestas en modo stream y prepararlas para su visualización en el frontend.

Paso 1: Creación y Registro del Middleware

Primero, creamos un middleware y lo registramos en el archivo Startup.cs dentro del método ConfigureServices:

 

services.AddTransient<IMiddleware, ComposerMiddleware>();

 

Paso 2: Implementación del Middleware

A continuación, abrimos el archivo ComposerMiddleware.cs y extendemos la interfaz IMiddleware:

 

public class ComposerMiddleware : IMiddleware
 {
     public async Task OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken = default)
     {
         turnContext.OnSendActivities(async (context, activities, nextSend) =>
         {
             foreach (var activity in activities)
             {
                
             }

         });

         await next(cancellationToken);
     }

 }

Dentro del bucle foreach, podemos insertar lógica para detectar una señal específica (un «flag») que indique que se debe realizar una llamada a OpenAI en modo stream. En mis pruebas, utilicé el channelData con una estructura JSON. Al detectar que el JSON contenía el atributo type: «stream», extraje los demás valores necesarios para realizar la llamada a OpenAI y procesé la respuesta en tiempo real.

Ejemplo de Estructura JSON en channelData:

 

JSON
{
    "type": "stream",
    "prompt": "Escribe un poema sobre la naturaleza",
    "model": "gpt-4o",
    "conversationId": "12345"
}

 

Paso 3: Llamada a Azure OpenAI y Manejo del Stream en .NET

En la parte de .NET, al detectar el type: «stream», se realiza la llamada a la API de Azure OpenAI utilizando el SDK correspondiente, configurando la solicitud para recibir respuestas en modo stream.

Paso 4: Retorno de Datos por Stream y Preparación para el Frontend

Aquí es donde preparamos los datos para su visualización en el frontend. En mi implementación, envié el primer «chunk» de la respuesta como un mensaje normal de Composer, incluyendo el conversationId para identificarlo. Los chunks subsiguientes se envían como eventos.

  • Mensaje Inicial: El primer chunk se envía como un mensaje normal, asegurando que se muestre en los widgets de Composer. Incluye el conversationId para que el frontend pueda identificar la conversación.
  • Eventos Subsiguientes: Los chunks restantes se envían como eventos. Los eventos no se muestran directamente en los widgets de Composer, pero pueden ser capturados por el frontend.

Paso 5: Visualización en el Frontend

En el frontend, se captura el mensaje inicial y se utiliza el conversationId para identificar la conversación. Los eventos subsiguientes se capturan y se concatenan al mensaje inicial, mostrando la respuesta de OpenAI de forma gradual y fluida.

Consideraciones Importantes:

Es crucial utilizar un identificador único (como conversationId) para cada mensaje, permitiendo al frontend rastrear y concatenar los chunks correctamente.

Enviar cada chunk como un mensaje individual generaría burbujas de chat separadas, lo cual no es deseable. Utilizar eventos para los chunks subsiguientes evita este problema.

 

Deja un comentario