{"id":8,"date":"2025-01-30T10:53:38","date_gmt":"2025-01-30T10:53:38","guid":{"rendered":"https:\/\/elbrinner.com\/wordpress\/?p=8"},"modified":"2025-03-30T11:38:22","modified_gmt":"2025-03-30T11:38:22","slug":"integracion-de-azure-openai-con-stream-en-composer-frameworks-para-respuestas-mas-rapidas","status":"publish","type":"post","link":"https:\/\/elbrinner.com\/index.php\/2025\/01\/30\/integracion-de-azure-openai-con-stream-en-composer-frameworks-para-respuestas-mas-rapidas\/","title":{"rendered":"Integraci\u00f3n de Azure OpenAI con Stream en Composer Frameworks para respuestas m\u00e1s r\u00e1pidas"},"content":{"rendered":"<p>Estaba desarrollando una aplicaci\u00f3n con Composer Frameworks que manejaba una gran cantidad de tokens, y not\u00e9 que la respuesta del asistente era considerablemente lenta. Al analizar el c\u00f3digo de Composer, una de las ventajas de ser de c\u00f3digo abierto, identifiqu\u00e9 el cuello de botella. La idea era seguir aprovechando el modo low-code, pero implementando una soluci\u00f3n para enviar informaci\u00f3n que pudiera ser detectada en la parte de .NET, permitiendo as\u00ed recuperar y procesar las respuestas de Azure OpenAI en modo stream y mostrarlas de forma fluida en el frontend.<\/p>\n<p><strong>\u00bfQu\u00e9 es el Modo Stream?<\/strong><\/p>\n<p>Para quienes no est\u00e9n 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\u00f3n 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.<\/p>\n<p><strong>Implementaci\u00f3n de la Soluci\u00f3n<\/strong><\/p>\n<p>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\u00f3n en el frontend.<\/p>\n<p><strong>Paso 1: Creaci\u00f3n y Registro del Middleware<\/strong><\/p>\n<p>Primero, creamos un middleware y lo registramos en el archivo Startup.cs dentro del m\u00e9todo ConfigureServices:<\/p>\n<p>&nbsp;<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"csharp\">services.AddTransient&lt;IMiddleware, ComposerMiddleware&gt;();<\/pre>\n<p>&nbsp;<\/p>\n<p><strong>Paso 2: Implementaci\u00f3n del Middleware<\/strong><\/p>\n<p>A continuaci\u00f3n, abrimos el archivo ComposerMiddleware.cs y extendemos la interfaz <strong>IMiddleware<\/strong>:<\/p>\n<p>&nbsp;<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"csharp\">public class ComposerMiddleware : IMiddleware\r\n {\r\n     public async Task OnTurnAsync(ITurnContext turnContext, NextDelegate next, CancellationToken cancellationToken = default)\r\n     {\r\n         turnContext.OnSendActivities(async (context, activities, nextSend) =&gt;\r\n         {\r\n             foreach (var activity in activities)\r\n             {\r\n                \r\n             }\r\n\r\n         });\r\n\r\n         await next(cancellationToken);\r\n     }\r\n\r\n }\r\n<\/pre>\n<p>Dentro del bucle foreach, podemos insertar l\u00f3gica para detectar una se\u00f1al espec\u00edfica (un \u00abflag\u00bb) que indique que se debe realizar una llamada a OpenAI en modo stream. En mis pruebas, utilic\u00e9 el channelData con una estructura JSON. Al detectar que el JSON conten\u00eda el atributo type: \u00abstream\u00bb, extraje los dem\u00e1s valores necesarios para realizar la llamada a OpenAI y proces\u00e9 la respuesta en tiempo real.<\/p>\n<p><strong>Ejemplo de Estructura JSON en <\/strong><strong>channelData:<\/strong><\/p>\n<p>&nbsp;<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"json\">JSON\r\n{\r\n    \"type\": \"stream\",\r\n    \"prompt\": \"Escribe un poema sobre la naturaleza\",\r\n    \"model\": \"gpt-4o\",\r\n    \"conversationId\": \"12345\"\r\n}\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p><strong>Paso 3: Llamada a Azure OpenAI y Manejo del Stream en .NET<\/strong><\/p>\n<p>En la parte de .NET, al detectar el type: \u00abstream\u00bb, se realiza la llamada a la API de Azure OpenAI utilizando el SDK correspondiente, configurando la solicitud para recibir respuestas en modo stream.<\/p>\n<p><strong>Paso 4: Retorno de Datos por Stream y Preparaci\u00f3n para el Frontend<\/strong><\/p>\n<p>Aqu\u00ed es donde preparamos los datos para su visualizaci\u00f3n en el frontend. En mi implementaci\u00f3n, envi\u00e9 el primer \u00abchunk\u00bb de la respuesta como un mensaje normal de Composer, incluyendo el conversationId para identificarlo. Los chunks subsiguientes se env\u00edan como eventos.<\/p>\n<ul>\n<li><strong>Mensaje Inicial:<\/strong> El primer chunk se env\u00eda como un mensaje normal, asegurando que se muestre en los widgets de Composer. Incluye el conversationId para que el frontend pueda identificar la conversaci\u00f3n.<\/li>\n<li><strong>Eventos Subsiguientes:<\/strong> Los chunks restantes se env\u00edan como eventos. Los eventos no se muestran directamente en los widgets de Composer, pero pueden ser capturados por el frontend.<\/li>\n<\/ul>\n<p><strong>Paso 5: Visualizaci\u00f3n en el Frontend<\/strong><\/p>\n<p>En el frontend, se captura el mensaje inicial y se utiliza el conversationId para identificar la conversaci\u00f3n. Los eventos subsiguientes se capturan y se concatenan al mensaje inicial, mostrando la respuesta de OpenAI de forma gradual y fluida.<\/p>\n<p><strong>Consideraciones Importantes:<\/strong><\/p>\n<p>Es crucial utilizar un identificador \u00fanico (como conversationId) para cada mensaje, permitiendo al frontend rastrear y concatenar los chunks correctamente.<\/p>\n<p>Enviar cada chunk como un mensaje individual generar\u00eda burbujas de chat separadas, lo cual no es deseable. Utilizar eventos para los chunks subsiguientes evita este problema.<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Estaba desarrollando una aplicaci\u00f3n con Composer Frameworks que manejaba una gran cantidad de tokens, y not\u00e9 que la respuesta del asistente era considerablemente lenta. Al analizar el c\u00f3digo de Composer, una de las ventajas de ser de c\u00f3digo abierto, identifiqu\u00e9 el cuello de botella. La idea era seguir aprovechando el modo low-code, pero implementando una &#8230; <a title=\"Integraci\u00f3n de Azure OpenAI con Stream en Composer Frameworks para respuestas m\u00e1s r\u00e1pidas\" class=\"read-more\" href=\"https:\/\/elbrinner.com\/index.php\/2025\/01\/30\/integracion-de-azure-openai-con-stream-en-composer-frameworks-para-respuestas-mas-rapidas\/\" aria-label=\"Leer m\u00e1s sobre Integraci\u00f3n de Azure OpenAI con Stream en Composer Frameworks para respuestas m\u00e1s r\u00e1pidas\">Leer m\u00e1s<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[10,11],"tags":[15,12,14,13,16,17],"class_list":["post-8","post","type-post","status-publish","format-standard","hentry","category-net","category-bot-framework-composer","tag-net","tag-asistente","tag-c","tag-composer","tag-openai","tag-stream"],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/elbrinner.com\/index.php\/wp-json\/wp\/v2\/posts\/8","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/elbrinner.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/elbrinner.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/elbrinner.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/elbrinner.com\/index.php\/wp-json\/wp\/v2\/comments?post=8"}],"version-history":[{"count":3,"href":"https:\/\/elbrinner.com\/index.php\/wp-json\/wp\/v2\/posts\/8\/revisions"}],"predecessor-version":[{"id":17,"href":"https:\/\/elbrinner.com\/index.php\/wp-json\/wp\/v2\/posts\/8\/revisions\/17"}],"wp:attachment":[{"href":"https:\/\/elbrinner.com\/index.php\/wp-json\/wp\/v2\/media?parent=8"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/elbrinner.com\/index.php\/wp-json\/wp\/v2\/categories?post=8"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/elbrinner.com\/index.php\/wp-json\/wp\/v2\/tags?post=8"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}