Blog

Mantener los turnos del agente con muchas imágenes dentro de la memoria

Un turno del agente en producción se quedó sin memoria a 952 MB mientras recortaba productos de seis escaneos de catálogo y revisaba sus recortes. No hubo una sola fuga: hubo tres. Cada llamada al modelo volvía a enviar las imágenes del turno a tamaño completo; la biblioteca que usamos para llamar al modelo mantenía vivas todas las peticiones hasta que terminaba el turno; y el trazado del framework del agente conservaba las entradas de cada llamada, con sus imágenes, durante todo el turno. Lo arreglamos todo: las imágenes se reducen antes de enviarse, y las imágenes de cada llamada se liberan en cuanto la llamada termina.

Cómo ve las imágenes el agente

Cuando construyes en Mythex, el agente trabaja dentro del sandbox en la nube de tu proyecto. Puede mirar imágenes —una captura que adjuntas, una maqueta, un archivo de tu proyecto o una imagen que él mismo acaba de producir— enviándolas al modelo junto con la conversación.

Un turno es todo lo que hace el agente en respuesta a un mensaje tuyo. Dentro de un turno, el agente llama al modelo muchas veces: piensa, ejecuta una herramienta, mira el resultado y vuelve a pensar. Cada una de esas llamadas envía la conversación hasta ese momento, y eso incluye las imágenes del turno, codificadas como texto (base64, que las hace unos dos tercios más grandes).

El turno que se cayó hacía algo razonable. Tomó seis escaneos de catálogo, de unos 2,4 MB cada uno, recortó productos individuales y volvió a leer sus recortes para comprobarlos. Eso son muchas llamadas al modelo, cada una con varias imágenes grandes.

Causa 1: cada llamada reenviaba imágenes a tamaño completo

Cada llamada al modelo reconstruía el cuerpo de su petición con las imágenes del turno a tamaño completo: unos 20 MB de base64 por llamada. El cuerpo se construía como una sola cadena en la memoria del agente, así que cada llamada reservaba otros ~20 MB.

Enviarlas a tamaño completo ni siquiera aportaba nada. El modelo ya reduce las imágenes a unos 1.300 píxeles en su lado largo, así que los píxeles extra solo cuestan memoria y tiempo de subida.

La solución: reducir una vez, enviar pequeño

  • Las imágenes de más de 400 KB se convierten ahora a JPEG de como máximo 1.568 píxeles en el lado largo antes de enviarse. Las más pequeñas se envían tal cual.
  • La conversión usa ImageMagick, que viene en todos los sandboxes de Mythex, y se ejecuta una vez por archivo. Un turno que muestra los mismos escaneos en cada llamada reutiliza la copia reducida. Esa caché se indexa por la ruta, el tamaño y la fecha de modificación del archivo, así que un archivo editado se vuelve a convertir, y tiene su propio límite de tamaño para que no pueda convertirse en lo que llene la memoria.
  • El presupuesto de imágenes en una sola llamada al modelo baja de 16 MB a 6 MB.

Los mismos seis escaneos ahora cuestan 4,4 MB por llamada en lugar de 19,7 MB.

Causa 2: el cliente del modelo nunca soltaba una petición

El tamaño de cada petición era solo una parte. La memoria también crecía llamada tras llamada. En nuestro entorno de staging, el mismo tipo de turno retenía 369 MB de memoria viva incluso después de una recolección de basura completa —el punto en el que lo inalcanzable ya se ha liberado—. El muestreo del heap atribuyó todo el crecimiento a la parte de la biblioteca cliente del modelo que codifica los cuerpos de las peticiones.

La causa era un listener de aborto. Cuando pulsas Detener, la señal de aborto del turno se dispara, y cada petición que escucha se cancela. Nosotros pasábamos esa única señal del turno a cada petición. La biblioteca cliente añade un listener de «abort» a cualquier señal que reciba, y nunca lo elimina.

Así que cada petición de un turno quedaba alcanzable desde la señal del turno durante todo el tiempo que duraba: el listener, que retenía el controlador de la petición, que retenía la petición, que retenía el cuerpo entero con sus imágenes.

La solución: una señal por petición

Ahora cada petición tiene su propia señal. Se enlaza con la señal del turno solo mientras esa petición se ejecuta —en una respuesta en streaming, hasta que se ha leído el último fragmento— y se desenlaza justo después. Detener sigue llegando a la petición en curso, pero la señal del turno solo sostiene un listener a la vez.

Las pruebas cubren las tres partes:

  • Tras 12 llamadas, no queda ningún listener en la señal del turno. Antes del arreglo había 12.
  • Con 15 peticiones de 8 MB cada una, la memoria se mantiene plana. Antes crecía en 128 MB.
  • Pulsar Detener sigue abortando una petición a mitad de su streaming.

Causa 3: el trazado guardaba las entradas de cada llamada

Un volcado del heap mostró un tercer retenedor: unos 6 MB por llamada al modelo. Esto venía del streaming de eventos del framework del agente. Registra cada llamada al modelo de un turno como hija del turno, con sus entradas incluidas, hasta que el turno termina. Cada imagen adjunta a cada llamada permanecía en memoria junto con ella.

La solución: vaciar las imágenes una vez que la llamada devuelve

Las imágenes solo se adjuntan a los mensajes construidos para una llamada concreta al modelo. Nunca se guardan en el estado guardado de la conversación. Así que, una vez que una llamada devuelve su resultado, es seguro vaciar los datos de imagen de esos mensajes. El trazado conserva un registro en texto de la llamada; los píxeles se van.

Un detalle importaba: las imágenes había que vaciarlas en el sitio. El trazador puede llegar a los mismos datos de imagen a través de más de una referencia en un mensaje, así que sustituir la lista por otra nueva y vacía dejaría el original —con píxeles y todo— aún alcanzable. El estado de conversación guardado nunca se modifica.

Una prueba ejecuta el grafo del agente bajo el mismo streaming de eventos con una imagen de 4 MB en cada llamada. Sin el arreglo, la memoria crecía 77 MB en 16 llamadas. Con él, se mantiene plana.

Lo que aprendimos

  • Los problemas de memoria pueden venir en capas. Tres causas separadas retenían imágenes demasiado tiempo, y las tres tenían que desaparecer para que el turno se mantuviera dentro de la memoria.
  • Todo lo que entregas a una biblioteca puede retenerse. Una señal y un trazo sobrevivieron a las llamadas a las que se les entregaron. Cuando la memoria crece por llamada, mira qué objetos de larga vida toca la llamada.
  • No envíes lo que el otro lado va a descartar. Los píxeles que el modelo descartaría de todos modos nos costaban memoria en cada llamada.
  • Prueba la memoria directamente. Cada arreglo llegó con una prueba que mide el crecimiento del heap o el número de listeners, así que una regresión aparece como un número, no como una caída.

Esta es la segunda historia de memoria del agente en poco tiempo; la primera fue una etiqueta de progreso que se reenviaba 41.000 veces. Si quieres saber más sobre qué es el agente y cómo funciona, consulta nuestra guía sobre agentes de IA y los documentos del chat.

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 简体中文 繁體中文(香港) 繁體中文(台灣)