Blog

El fallo de memoria oculto en una etiqueta de progreso

Una tarde de finales de septiembre, el agente de IA del espacio de trabajo de un usuario se bloqueó con un error de falta de memoria en mitad de un turno. Los sospechosos evidentes, demasiados subagentes ejecutándose a la vez o un contexto que había crecido demasiado, estaban equivocados. La causa real era una etiqueta de estado: mientras el modelo transmitía un comando de shell grande a trozos de pocos caracteres, reenviábamos el comando completo hasta ese momento como etiqueta de progreso con cada trozo. Un comando de 124 KB se convirtió en unos 41.000 eventos y unos 690 MB de cadenas en cola.

Cómo transmite el agente su trabajo

Cuando construyes en Mythex, el agente se ejecuta dentro del entorno en la nube privado de tu proyecto. Mientras el modelo escribe, su salida se transmite a tu navegador: el texto aparece palabra a palabra, y cuando ejecuta una herramienta, por ejemplo un comando de shell que escribe un archivo, ves una etiqueta que se actualiza en directo, así sabes qué está haciendo.

El modelo transmite en trozos pequeños. Con el modelo que estábamos usando, esos trozos solían tener solo tres caracteres.

Las primeras hipótesis estaban equivocadas

El fallo parecía un problema de escalado, así que empezamos por ahí.

  • ¿Subagentes en paralelo? El turno solo había usado dos, cada uno con unos 1,5 MB de estado. No era suficiente.
  • ¿Trazas retenidas? Nuestro sistema de trazas mantenía un árbol de ejecución en memoria, pero no se acercaba al tamaño del montón en el momento del fallo.

Ambas explicaciones eran plausibles y ambas eran fáciles de creer. Ninguna reproducía el fallo. La lección que seguimos volviendo a aprender: una hipótesis no es una causa hasta que reproduce el fallo.

Reproducirlo de verdad

Creamos un entorno de pruebas desechable con los mismos límites de memoria que producción y un modelo falso que transmitía un archivo grande en trozos realistas de tres caracteres. También hicimos que el lector del otro extremo del socket fuera lento, como una red real.

Eso lo consiguió. El montón creció y el proceso murió con el mismo error.

El mecanismo era sencillo una vez que pudimos observarlo. Mientras el modelo escribía un comando grande, en este caso un heredoc que escribía un archivo de 124 KB, nuestro código de transmisión actualizaba la etiqueta de la herramienta con cada trozo. La etiqueta no era de los tres caracteres nuevos; era el comando completo hasta ese momento. Así que los eventos crecían: 3 caracteres, luego 6, luego 9… hasta 124 KB, unas 41.000 veces. El socket no podía enviarlos tan rápido como se producían, así que se acumulaban en su búfer de escritura como cadenas: unos 690 MB.

La solución

  • Limitar la etiqueta. Una etiqueta de estado solo tiene que decir qué está pasando. Ahora está limitada a 200 caracteres, y limitamos la frecuencia con la que volvemos a analizar el comando parcial.
  • Eliminar la concatenación cuadrática. Unir los trozos transmitidos con concatenaciones repetidas de cadenas es O(n²) para salidas grandes. Pasamos a un enfoque por fragmentos que añade en tiempo lineal.

Después buscamos cualquier otra cosa que pudiera crecer sin límite con una avalancha de salida, y la acotamos:

  • La salida de comandos en directo que se muestra en el chat se agrupa y se conserva solo con los últimos 8.000 caracteres.
  • Cada línea de evento individual está limitada a 4 MB.
  • El búfer de reproducción que usa un navegador para ponerse al día tras reconectarse está limitado a 8 MB por proyecto.
  • Los puntos de control antiguos se eliminan a medida que avanza el turno, conservando el estado más reciente y los anclajes que necesitan los turnos anteriores.

Probar las avalanchas con honestidad

Hubo dos cosas que hicieron esto más difícil de probar de lo que debería haber sido:

  1. Los modelos reales no colaboran. Si pides a un modelo que imprima un archivo gigantesco, normalmente se niega, lo resume o lo escribe de forma más sensata. Para probar avalanchas necesitas un modelo guionizado que haga exactamente lo que le indiques, ejecutándose a través del código real del agente.
  2. Los guiones tienen que comportarse como modelos. Nuestro primer modelo guionizado reutilizaba el mismo ID de respuesta en cada respuesta, así que el framework del agente trataba cada respuesta como un reemplazo de la anterior y cerraba los turnos antes de tiempo. Parecía un error de actualización de la interfaz. No lo era: era el arnés de pruebas mintiéndonos. También tuvimos que aplicar en el guion el límite real de contexto del modelo, o las cifras de memoria no eran realistas.

Lo que sacamos de todo esto

  • Las interfaces de transmisión multiplican los pequeños errores. Todo lo que envías «por trozo» se envía decenas de miles de veces. Envía solo los cambios, o limita la carga.
  • Acota cada búfer que cruza una frontera lenta. Sockets, registros de reproducción y puntos de control necesitan un techo.
  • Reproduce antes de corregir. Nuestras dos primeras explicaciones sonaban bien y estaban equivocadas.

Si has visto recientemente un archivo muy largo aparecer en el chat de Mythex sin que el agente se detuviera, esta es una de las razones.

Mythex · Pricing · Crea lo que quieras con IA · Plantillas de sitios web y apps · Mythex comparado · Glosario de creación de apps con IA · Herramientas gratuitas para crear apps con IA · English

English አማርኛ العربية Български বাংলা Bosanski Català Čeština Dansk Deutsch Ελληνικά Español Eesti فارسی Suomi Français Français (Canada) ગુજરાતી हिन्दी Hrvatski Magyar Հայերեն Bahasa Indonesia Íslenska Italiano 日本語 ქართული Қазақ тілі ಕನ್ನಡ 한국어 Lietuvių Latviešu Македонски മലയാളം Монгол मराठी Bahasa Melayu မြန်မာ Norsk bokmål Nederlands ਪੰਜਾਬੀ Polski Português (Brasil) Português (Portugal) Română Русский Slovenčina Slovenščina Soomaali Shqip Srpski Svenska Kiswahili தமிழ் తెలుగు ไทย Tagalog Türkçe Українська اردو Tiếng Việt 简体中文 繁體中文(香港) 繁體中文(台灣)