← Blog

AI agents

OpenAI agent loops: drie plekken waar je budget weglekt

Een founder pingt ons op Slack om 9:14 op een dinsdag. De OpenAI-rekening van vorige maand is 4.200 euro. De agent doet hetzelfde werk als in maart, toen de rekening 600 was.

Jacob Molkenboer· Oprichter · A Brand New Company· 8 feb 2024· 6 min
Koperen schakelbord met drie groene patchkabels en een lichtgroen papieren label op ivoorpapier, naast een crème indexkaart.

Een founder pingt ons op Slack om 9:14 op een dinsdag. De OpenAI-rekening van vorige maand is 4.200 euro. De agent doet ruwweg hetzelfde werk als in maart, toen de rekening 600 was. Niks in de prompt is veranderd. Het model is niet veranderd. Het verkeer is misschien 30% gestegen. De rekening is 700% gestegen.

Dit gesprek voeren we ongeveer twee keer per maand, met andere getallen. De oorzaak is bijna altijd dezelfde drie dingen, in wisselende combinaties.

De rekening is zelden het model

Als de OpenAI-factuur omhoog schiet terwijl de workload dat niet doet, is het model zelden de oorzaak. De oorzaak is de loop eromheen. Agents zijn per definitie loops. Elke loop heeft ergens een plek waar hij kan lekken.

Drie failure modes verklaren het grootste deel van het lek dat we in productie zien. Ze zijn onafhankelijk en ze stapelen op elkaar. Een nette agent op hetzelfde model bij hetzelfde volume kan 5 tot 20x kosten van wat het dashboard suggereerde, en je ziet het niet terug per call. Je ziet het pas op de maandfactuur.

Context die elke turn groeit

Het eerste lek is context. Elke turn van een agent loop stuurt de volledige berichtgeschiedenis terug naar de API. Heeft de agent acht tools aangeroepen die elk vier kilobyte JSON teruggeven, dan stuur je bij turn twintig ongeveer 640 KB aan input tokens per call. De output is honderd tokens. De rekening is de input.

Op gpt-4o-mini input rates is dat goedkoop. Op gpt-4.1 doet het pijn. Op o3-achtige reasoning-modellen is dat je hele budget. De fix is niet het model. De fix is wat je in het context window houdt.

Prompt caching helpt, mits je het goed bedraadt. OpenAI rekent een lagere prijs op elk input-prefix dat zich binnen ongeveer vijf minuten herhaalt (prompt caching docs). De catch: dat prefix moet byte-identiek zijn. Eén timestamp bovenaan je system prompt, één wisselend tool-resultaat boven de cache-regel, en je hit rate zakt naar nul. In de meeste agent-code die we doorlichten ligt caching per ongeluk onbenut.

Het patroon dat werkt: een stabiele system prompt, dan stabiele tool-definities, dan een rolling summary, dan de huidige turn. De summary regenereer je alleen om de N turns, niet elke turn. De wisselende stukken horen onderaan de context, waar ze thuishoren.

Tool calls die hun eigen failure opnieuw proberen

Het tweede lek zijn tool retries. Een agent roept een tool aan. De tool geeft een error terug. De agent besluit het opnieuw te proberen. Zelfde argumenten. Zelfde error. De agent besluit het nog een keer te proberen. Bij turn zes heeft hij meer aan retries uitgegeven dan de hele taak end-to-end gekost zou hebben.

Dit zagen we vorige maand bij een inbox-triage-agent. De calendar API gaf een 429 op een drukke maandagochtend. De agent retryde zes keer achter elkaar, en elke retry duwde de mislukte response in de context. De token count voor die taak was 38.000 in plaats van de gebruikelijke 4.000. Vermenigvuldig dat met een paar honderd maandagochtend-taken en je hebt je spending graph.

Waarschuwing

Als je tool-laag het verschil niet ziet tussen een tijdelijke error en een fatale, behandelt het model ze allemaal hetzelfde. Wikkel je tools zo in dat ze een gestructureerde error teruggeven: retryable, suggested wait, suggested alternative. Cap je retry-budget op het niveau van de loop, niet in de prompt.

Het model dat je voor de verkeerde klus koos

Het derde lek is één model gebruiken voor elke stap. Een agent loop heeft minstens drie verschillende cognitieve klussen: routing (welke tool, welke branch), generatie (wat zet je in de tool-argumenten of het antwoord), en synthese (N tool-resultaten samenpersen tot één antwoord voor een mens).

Routing is een beslissing van 50 tokens. Dat heeft geen o3 nodig. Generatie meestal ook niet. Synthese soms wel, als het eindantwoord naar een mens gaat en de input rommelig is.

Een agent loop opsplitsen over twee of drie modellen is de wijziging met de hoogste hefboom die we in productie maken. Een typische opzet: een klein model (gpt-4o-mini of gpt-5-mini klasse) voor routing en argumentgeneratie, een groter model alleen voor de finale synthesestap. De routinglaag roept de API tien keer per taak aan. De synthesizer één keer. Het goedkope model neerzetten waar het volume zit, weegt zwaarder dan de per-call kosten op beide tiers.

De huidige OpenAI-pricing tiers verschuiven vaak, maar de verhouding tussen input- en outputkosten tussen tiers (op dit moment ongeveer 6x tot 20x) is de hefboom om in de gaten te houden.

Wat je eerst meet

Drie getallen, één middag om aan te sluiten.

Tokens per taak, niet per turn. Een turn-level metric verbergt looplengte. Een taak van 20 turns met 8.000 tokens per turn levert hetzelfde per-call getal op als een taak van 4 turns met 40.000 tokens per turn. De fixes zijn anders. Track input en output apart, want hun kostenstructuur en hun root cause verschillen.

Turns tot voltooiing op P50 en P95. Gemiddeldes liegen als vijf procent van je taken veertig turns doorloopt. De P95 is waar het budget heen gaat, en de P95 is wat je optimaliseert.

Cache hit rate op input tokens. Zit die onder de 50%, dan is je cache key kapot. Zit hij boven de 80%, dan werkt caching en zit het lek ergens anders. Makkelijk af te lezen, lastig te faken.

Een vierde getal dat het waard is om aan te sluiten: tool-call success rate per poging. Zit die onder de 90% op één tool, dan besteedt de agent meer tijd aan ruzie maken met die tool dan aan zijn werk.

De instrumentatie is onsexy. Wikkel je client call één keer in en log tegen een task ID die jij beheert:

import json, time
from openai import OpenAI

client = OpenAI()

def log_event(event):
    print(json.dumps(event))

def chat_with_telemetry(task_id, model, messages, tools=None):
    t0 = time.time()
    resp = client.chat.completions.create(
        model=model,
        messages=messages,
        tools=tools,
    )
    u = resp.usage
    details = getattr(u, "prompt_tokens_details", None)
    cached_in = getattr(details, "cached_tokens", 0) if details else 0
    log_event({
        "task_id": task_id,
        "model": model,
        "ms": int((time.time() - t0) * 1000),
        "in_tokens": u.prompt_tokens,
        "in_cached": cached_in,
        "out_tokens": u.completion_tokens,
    })
    return resp

Aggregeer aan het eind van elke run op task_id. Binnen een dag verkeer zie je welk looptype lekt en waarom. De meeste teams ontdekken dat het antwoord niet is wat ze hadden gegokt.

Takeaway

Als de OpenAI-rekening omhoog schiet terwijl de workload dat niet doet, is het model niet de oorzaak. De loop eromheen wel. Meet tokens per taak, turns tot voltooiing en cache hit rate voordat je iets anders aanraakt.

De goedkoopste fix is degene die je kunt zien

Bij de inbox-triage-agent die we bovenaan noemden, daalde de maanduitgave van 4.200 euro terug naar zo'n 600 nadat we deze drie getallen hadden bedraad en de loop over drie modellen hadden gesplitst. AI-agents in productie draaien zonder per-taak token accounting is blind vliegen.

Het kleinste dat je vandaag kunt doen: open je OpenAI-usage dashboard, sorteer op model, en kijk of één model meer dan 80% van je uitgaven veroorzaakt. Doet het dat, en gebruik je het voor routing-beslissingen, dan heb je je middagklus.

Kern

Als je OpenAI-agentrekening is gestegen zonder dat de workload veranderde, is het model niet de oorzaak. Meet eerst tokens per taak, turns tot voltooiing en cache hit rate.

FAQ

Hoeveel kunnen OpenAI-agentkosten realistisch dalen met deze aanpak?

We zien doorgaans reducties van 60 tot 85 procent op productieloops nadat we tokens per taak hebben geïnstrumenteerd, de cache key hebben gefikst en routing op een goedkoper model hebben gezet. Het exacte getal hangt af van welk lek domineert.

Helpt prompt caching bij alle OpenAI-modellen?

Het werkt op de meeste huidige chat completion-modellen. De korting wordt alleen geactiveerd als het input-prefix byte-voor-byte matcht binnen ongeveer vijf minuten. Wisselende timestamps bovenaan system prompts breken het stilletjes.

Moet ik per agentstap een ander model gebruiken?

Vaak wel. Routing en argumentgeneratie hebben zelden een frontier-model nodig. Reserveer het dure model voor de synthesestap waar het antwoord naar een mens gaat. Twee of drie modellen per loop is normaal in productie.

Wat is de snelste manier om token-uitgaven per taak te meten?

Wikkel je OpenAI client call in en log prompt_tokens, completion_tokens en cached tokens tegen een task_id die jij beheert. Aggregeer aan het eind van de taak. Eén middag werk, geen vendor-tooling nodig.

ai agentsautomationtoolingarchitectureoperations

Iets bouwen?

Start een project