Skip to main content
Achter de schermen
Achter de schermenEngineeringDecisions

Toen onze AI aanbieder het model uit de roulatie nam

Een modelwissel, lege antwoorden zonder foutmelding en een rekening die ineens een veelvoud hoger uitviel. Wat er kapotging, en waarom de oorzaak zo moeilijk te zien was.

Gepubliceerd op door AlphaCloud4 min leestijd

In juli 2026 stopten meerdere functies tegelijk met werken: AI chat, AI antwoorden in tickets, samenvattingen van tickets, de serverbouwer, plus de moderatie en vertaling van ideeën en bugmeldingen.

De oorzaak lag niet in onze code. Onze AI aanbieder had het model dat we gebruikten uit de roulatie genomen.

Deel één: het model is weg

Dat is het voor de hand liggende deel, en het was snel opgelost. Een aanbieder stopt met een model, je vult de opvolger in, klaar.

Het eigenlijke incident begon daarna.

Deel twee: lege antwoorden, zonder fouten

Na de wissel kwamen er weer antwoorden terug. Alleen waren ze soms leeg. Niet afgekapt, niet misvormd, maar een volledig leeg antwoord zonder enige foutmelding.

De oorzaak ligt in een verschil dat de modelnaam niet verraadt: de opvolger is een reasoningmodel. Voor het eigenlijke antwoord schrijft hij eerst een interne gedachtegang in een apart veld. Beide putten uit hetzelfde tokenbudget.

Is het budget opgebruikt door het nadenken, dan komt er een antwoord terug dat formeel in orde is: de aanbieder meldt "lengte bereikt" en het inhoudsveld is leeg. Geen fout, geen hint, niets wat een try/catch zou zien.

We hebben het met een echte test nagebootst: een budget van vijf tokens is al genoeg om bij elk verzoek een leeg antwoord te krijgen.

Deel drie: de rekening

Op de duurste plek waar we het model aanroepen, de AI serverbouwer, hadden we een royaal budget ingesteld, omdat daar lange antwoorden ontstaan. Het nadenken liep daar dus niet tegen een grens aan, het liep gewoon door.

De gevolgen kwamen uit twee richtingen tegelijk. Verzoeken duurden zo lang dat ze tegen timeouts aanliepen. En ze kostten een veelvoud, omdat er elke keer een volledige onzichtbare gedachtegang werd betaald. Aan het eind kwam een foutmelding waar we niet op hadden gerekend: onvoldoende saldo.

De fix is één regel. Het nadenken kan in de body van het verzoek worden uitgeschakeld, en het model gedraagt zich dan weer als het oude. Sindsdien hoort die regel bij elke plek waar we het model aanroepen, en daar zijn er in de repository meer dan een handvol van.

Deel vier: wat we bij het opruimen vonden

Als je toch elke plek met een AI aanroep aanraakt, kijk je er ook naar. Daarbij kwam een tweede onderwerp boven, los van het incident maar vergelijkbaar duur.

Aanbieders rekenen flink minder voor een prompt waarvan het begin ongewijzigd blijft en uit een cache kan worden bediend. Het verschil tussen een hit en een miss is ongeveer een factor dertig.

Die korting krijg je niet via een vlag. We hebben het gericht getest: een veld in de body van het verzoek dat daarvoor bedoeld is, had nul effect, de cache werkt sowieso automatisch. De enige hefboom is de volgorde binnen de prompt, van stabiel naar vluchtig.

Vier plekken waar we precies het omgekeerde hadden gedaan:

Een vluchtige waarde in het vaste instructieblok. De duplicaatcontrole voor bugmeldingen had de modulenaam in de instructietekst staan. Daardoor kreeg een verder identiek blok per module een eigen cacheregel. De waarde hoort in het variabele deel, waar hij toch al verandert.

Het nieuwe vóór het stabiele. Dezelfde controle stuurde de nieuwe bugmelding voor de tot vijftig vergelijkingskandidaten. Daardoor was de grootste prompt van de API permanent een volledige miss.

Gegevens per gebruiker in het vaste deel. De openbare AI chat had de notities over de afzonderlijke gebruiker in het instructieblok staan, dus voor de gedeelde kanaalgeschiedenis. Daardoor had elke gebruiker in hetzelfde kanaal een eigen begin, en werd de gedeelde geschiedenis per gebruiker apart betaald.

Een schuivend venster over de geschiedenis. Bij elk bericht de laatste N berichten meesturen verschuift het begin bij elk bericht. De volledige geschiedenis is dan permanent een miss. De juiste aanpak is in blokken inkorten, dus zelden en flink.

Plus een detail dat eruitziet als muggenziften en het niet is: een lijst in een prompt die op maar één criterium is gesorteerd, kan bij gelijke waarden van volgorde veranderen. Eén verwisselde regel midden in het instructieblok kost de hit voor alles erachter. Sorteringen in prompts hebben een eenduidige tiebreaker nodig.

Wat we eruit hebben meegenomen

Een modelwissel is geen configuratiewijziging. De naam ziet eruit als een waarde in een variabele. Erachter zat ander gedrag, en dat gedrag maakte onze code kapot, niet de interface.

De gevaarlijke fout was de stille. Dat het oude model verdween, was direct zichtbaar en in minuten opgelost. De lege antwoorden kwamen terug als formeel geldige antwoorden en hadden onbeperkt zo kunnen blijven.

Meetbaarheid hoort bij de plek van de aanroep. We loggen nu bij elke plek het percentage cachehits. Een correct gebouwde plek komt na het eerste verzoek op een hoog percentage uit, een slecht gebouwde blijft op nul. Dat zie je in een logregel en nergens anders, want ze werken allebei.

En de ongemakkelijke conclusie: wie het model van een ander gebruikt, maakt een deel van zijn product afhankelijk van een beslissing die elders wordt genomen. Daarom kan sinds augustus elke server de AI functies naar zijn eigen endpoint laten wijzen in plaats van op het onze te vertrouwen.

Meer artikelen