Blog

Agentbeurten met veel afbeeldingen binnen het geheugen houden

Een agentbeurt in productie liep vast op 952 MB geheugen terwijl hij zes catalogusscans bijsneed om producten eruit te halen en de bijgesneden stukken bekeek. Er was niet één lek, maar drie. Elke aanroep naar het model stuurde de afbeeldingen van de beurt opnieuw op volledige grootte mee; de bibliotheek die we gebruiken om het model aan te roepen hield elk verzoek levend tot het einde van de beurt; en de tracing van het agent-framework bewaarde de invoer van elke aanroep, afbeeldingen inbegrepen, de hele beurt door. We hebben alle drie opgelost: afbeeldingen worden verkleind voordat ze worden verstuurd, en de afbeeldingen van elke aanroep worden losgelaten zodra die aanroep klaar is.

Hoe de agent afbeeldingen ziet

Wanneer je op Mythex bouwt, werkt de agent binnen de cloud-sandbox van je project. Hij kan naar afbeeldingen kijken, zoals een screenshot die je bijvoegt, een mockup, een bestand in je project, of een afbeelding die hij zelf net heeft gemaakt, door ze samen met het gesprek naar het model te sturen.

Een beurt is alles wat de agent doet als antwoord op één bericht van jou. Binnen een beurt roept de agent het model vele keren aan: denken, een tool draaien, het resultaat bekijken, opnieuw denken. Elke aanroep stuurt het gesprek tot dan toe mee, inclusief de afbeeldingen van de beurt, gecodeerd als tekst (base64, wat ze ongeveer een derde groter maakt).

De beurt die crashte deed iets redelijks. Hij nam zes catalogusscans van elk ongeveer 2,4 MB, sneed afzonderlijke producten eruit en las de bijgesneden stukken steeds terug om ze te controleren. Dat zijn veel modelaanroepen, elk met meerdere grote afbeeldingen.

Oorzaak 1: elke aanroep stuurde volledige afbeeldingen opnieuw

Elke modelaanroep bouwde zijn verzoek opnieuw op met de afbeeldingen van de beurt op volledige grootte: ongeveer 20 MB base64 per aanroep. Het verzoek wordt in het geheugen van de agent als één string opgebouwd, dus elke aanroep reserveerde weer ongeveer 20 MB.

Volledige grootte sturen leverde ook niets op. Het model verkleint afbeeldingen zelf naar ongeveer 1.300 pixels aan de lange kant, dus de extra pixels kostten alleen geheugen en uploadtijd.

De oplossing: eenmaal verkleinen, klein versturen

  • Afbeeldingen groter dan 400 KB worden omgezet naar een JPEG van maximaal 1.568 pixels aan de lange kant voordat ze worden verstuurd. Kleinere afbeeldingen gaan zoals ze zijn.
  • De omzetting gebruikt ImageMagick, dat in elke Mythex-sandbox zit, en draait eenmaal per bestand. Een beurt die elke aanroep dezelfde scans laat zien, hergebruikt de verkleinde kopie. De cache is gekoppeld aan het pad, de grootte en de wijzigingsdatum van het bestand, dus een bewerkt bestand wordt opnieuw omgezet, en de cache heeft een eigen groottelimiet zodat hij niet zelf het geheugen kan vullen.
  • Het budget voor afbeeldingen in één modelaanroep gaat van 16 MB naar 6 MB.

Dezelfde zes scans kosten nu 4,4 MB per aanroep in plaats van 19,7 MB.

Oorzaak 2: de modelclient liet een verzoek nooit los

De grootte van elk verzoek was maar een deel ervan. Het geheugen groeide ook aanroep na aanroep. Op onze staging-omgeving hield dezelfde soort beurt 369 MB aan levend geheugen vast, zelfs na een volledige garbage collection, het punt waarop alles wat niet meer bereikbaar is is vrijgegeven. Heap-sampling wees alle groei aan in het deel van de modelclientbibliotheek dat verzoekinhoud codeert.

De oorzaak was een abort-listener. Wanneer je op Stop drukt, vuurt het abort-signaal van de beurt, en annuleert elk verzoek dat ernaar luistert. Wij gaven dat ene signaal voor de hele beurt aan elk verzoek mee. De clientbibliotheek voegt een “abort”-listener toe aan elk signaal dat het krijgt, en verwijdert die nooit.

Dus bleef elk verzoek van een beurt bereikbaar vanaf het signaal van de beurt, zolang de beurt duurde: de listener, die de controller vasthield, die het verzoek vasthield, dat het hele verzoekbody met zijn afbeeldingen vasthield.

De oplossing: één signaal per verzoek

Elk verzoek krijgt nu zijn eigen signaal. Dat wordt alleen gekoppeld aan het signaal van de beurt terwijl dat verzoek loopt, bij een gestreamd antwoord tot het laatste stuk is gelezen, en direct daarna weer losgekoppeld. Stop bereikt nog steeds elk verzoek dat onderweg is, maar het signaal van de beurt houdt maar één listener tegelijk vast.

De tests dekken alle drie onderdelen:

  • Na 12 aanroepen staan er geen listeners meer op het signaal van de beurt. Voor de fix waren dat er 12.
  • Over 15 verzoeken van elk 8 MB blijft het geheugen vlak. Voorheen groeide het met 128 MB.
  • Stop breekt nog steeds een verzoek af dat midden in zijn stream zit.

Oorzaak 3: de tracing bewaarde de invoer van elke aanroep

Een heap-snapshot toonde een derde houder: ongeveer 6 MB per modelaanroep. Dit kwam uit de event-streaming van het agent-framework. Het legt elke modelaanroep in een beurt vast als kind van de beurt, inclusief de invoer, tot de beurt eindigt. Elke afbeelding die bij elke aanroep hoorde, bleef zo in het geheugen.

De oplossing: de afbeeldingen leegmaken zodra de aanroep klaar is

Afbeeldingen worden alleen aan de berichten gehangen die voor een specifieke modelaanroep zijn opgebouwd. Ze worden nooit in de opgeslagen status van het gesprek bewaard. Dus zodra een aanroep klaar is, is het veilig om de afbeeldingsdata in die berichten leeg te maken. De trace bewaart een tekstuele registratie van de aanroep; de pixels verdwijnen.

Eén detail was belangrijk: de afbeeldingen moeten ter plekke leeggemaakt worden. De tracer kan dezelfde afbeeldingsdata via meer dan één verwijzing op een bericht bereiken, dus een nieuwe, lege lijst invoegen zou het origineel, met pixels en al, bereikbaar laten. De opgeslagen gespreksstatus wordt nooit gewijzigd.

Een test draait de graph van de agent onder dezelfde event-streaming met een afbeelding van 4 MB bij elke aanroep. Zonder de fix groeide het geheugen met 77 MB over 16 aanroepen. Met de fix blijft het geheugen vlak.

Wat we hieruit hebben geleerd

  • Geheugenproblemen kunnen in lagen zitten. Drie aparte oorzaken hielden elk afbeeldingen te lang vast, en alle drie moesten weg voordat de beurt binnen het geheugen bleef.
  • Alles wat je aan een bibliotheek geeft, kan worden bewaard. Een signaal en een trace overleefden de aanroepen waaraan ze werden gegeven. Als het geheugen per aanroep groeit, kijk dan naar welke langlevende objecten die aanroep raakt.
  • Stuur niet wat de ontvanger weggooit. Pixels die het model toch zou weggooien, kostten ons bij elke aanroep geheugen.
  • Test geheugen rechtstreeks. Elke fix kwam met een test die heap-groei of het aantal listeners meet, zodat een regressie als getal zichtbaar wordt, niet als crash.

Dit is het tweede geheugenverhaal over de agent in korte tijd; het eerste was een voortgangslabel dat 41.000 keer opnieuw werd verstuurd. Wil je meer weten over wat de agent is en hoe hij werkt, lees dan onze gids over AI-agents en de chatdocumentatie.

Mythex · Pricing · Bouw alles met AI · Website- en app-templates · Mythex vergeleken · Woordenlijst voor apps bouwen met AI · Gratis tools voor apps bouwen met AI · English

English አማርኛ العربية Български বাংলা Bosanski Català Čeština Dansk Deutsch Ελληνικά Español Español (España) Eesti فارسی Suomi Français Français (Canada) ગુજરાતી हिन्दी Hrvatski Magyar Հայերեն Bahasa Indonesia Íslenska Italiano 日本語 ქართული Қазақ тілі ಕನ್ನಡ 한국어 Lietuvių Latviešu Македонски മലയാളം Монгол मराठी Bahasa Melayu မြန်မာ Norsk bokmål ਪੰਜਾਬੀ 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 简体中文 繁體中文(香港) 繁體中文(台灣)