Özgür Işık Damar
9 min Lesezeit

Von 4.000 auf 40 Dollar im Monat: die echte Kostenkurve von Agent-Guardrails

Was die Blutung stoppte, war nicht ein kleineres Modell — es war, dem Planner den Appetit auf zusätzliche Schritte zu nehmen.

agentic-aiproductioncost-engineering

Die Stripe-Mail kam an einem Dienstagmorgen im März, um 09:14. Ich war beim zweiten Kaffee. Betreff: "Ihr OpenAI-Verbrauch nähert sich der Schwelle." Inhalt: $1.247 ausgegeben, vier Tage in den Abrechnungszyklus hinein.

Ich rechnete auf der Rückseite eines Migros-Bons. 1.247 ÷ 4 × 30 = 9.350 Dollar. Dann fiel mir ein, dass der Traffic seit Montag stieg, also lag die realistische Hochrechnung näher an $4K. Trotzdem: $4K im Monat für etwas, das ein Tool-Selection-Helfer für eine Marktplatz-Suchleiste sein sollte. Drei Tage. Der Agent war seit drei Tagen live.

Ich klappte den Laptop zu, trank den Kaffee, klappte ihn wieder auf. Fing an, die Logs zu lesen.

Der CFO des Marktplatzes kennt meinen Namen nicht. Er kennt meine Kostenposition. Das ist die ganze Beziehung. Wenn die Position seltsam wird, schickt mir seine Finanzperson eine höfliche Mail, und die Höflichkeit wird jede Woche dünner. Bis Freitag war höflich schon auf Tag vier.

Was tatsächlich lief

Novas Tool-Selection-Agent steht vor einem Katalog mit 7 Millionen Produkten. Wenn jemand "kabellose Kopfhörer unter 2.000 TL, soll auch ANC können" sucht, entscheidet der Agent, welche Retrieval-Tools in welcher Reihenfolge aufgerufen werden. Wir hatten fünf: BM25-Suche, Vector-Suche, Image-Match (für "sieht aus wie das"), Brand-Affinity-Scoring und Stock-Check.

Die erste Version war — ich übernehme die Verantwortung dafür — ein "lass es einfach kochen"-Setup. GPT-4o als Planner. GPT-4o für das Interpretieren der Tool-Ergebnisse. Kein Tiefenlimit. Kein Budgetlimit. Der Reasoner durfte so viele Tool-Calls verketten, wie sich richtig anfühlten.

Was sich "richtig anfühlte", stellte sich heraus: durchschnittlich 8 LLM-Calls pro Nutzeranfrage. 32K Input-Tokens. 4K Output-Tokens. Multipliziert mit ~12K Tages-Anfragen ergibt ~95K Calls, ~3,5M Input-Tokens, ~480K Output-Tokens. Jeden Tag.

Der Planner liebte lange Planungsschritte. Er rief BM25 auf, las das Ergebnis, beschloss, es sei mehrdeutig, plante eine Vector-Suche, las die, beschloss, Image-Match könnte "der Vollständigkeit halber" helfen, verkettete einen weiteren Schritt, dann eine Zusammenfassung. Jeder Schritt war ein neuer Full-Context-Call. Das Modell wusste nichts von meinem Stripe-Limit. Es war einfach gründlich.

So wie ich es gebaut hatte, hatte "Gründlichkeit" keine Opportunitätskosten. Der Planner-Prompt sagte "du darfst zusätzliche Tools aufrufen, wenn das Ergebnis unvollständig ist." Nichts sagte ihm, dass unvollständig-aber-nah-genug ein akzeptabler Haltepunkt war. Also hielt er nie an.

Es gab keine Obergrenze, wie lang ein Query werden konnte. Kein Logic-Gate. Kein "wenn du schon X Tools aufgerufen hast, stopp". Der Appetit des Planners auf Verkettung war unendlich, und ich hatte ihm eine unbegrenzte Küche gegeben.

Guard 1 — der Budget-Envelope

Den Budget-Envelope habe ich am Dienstagnachmittag deployed. Das ist die Version, die ich niemandem zeigen würde:

// Erster Versuch — Dienstag 14:30, Panikmodus
class BudgetEnvelope {
  private spent = 0;
  charge(input: number, output: number) {
    this.spent += input + output;
    // hätte uns $800 gespart, wenn ich das eine Woche früher gehabt hätte
    if (this.spent > 50_000) {
      console.warn("Budget hoch");  // <- das ist kein Guardrail
    }
  }
}

Dieses console.warn ist der Code, den man um 14:30 schreibt, wenn man nicht geschlafen hat. Es loggt. Es tut nichts. Der Agent gibt weiter aus.

Am Mittwochmorgen stand die echte Version:

class BudgetEnvelope {
  private spent = 0;
  constructor(private readonly capPerQuery = 50_000) {}
 
  charge(input: number, output: number) {
    this.spent += input + output;
    if (this.spent > this.capPerQuery) {
      // werfen, nicht warnen. der Planner fängt das ab und
      // antwortet mit dem, was er bisher an Tool-Ergebnissen hat.
      throw new BudgetExceeded(this.spent, this.capPerQuery);
    }
  }
}

50K Tokens pro Anfrage, harte Obergrenze. Der Planner fing die Exception ab und gab eine Best-Effort-Antwort mit den Tools zurück, die schon gelaufen waren. Manche Antworten wurden schlechter. Die meisten blieben gleich — weil die meisten "extra" Schritte kein Signal, sondern Planner-Luxus waren.

Diese eine Änderung brachte die Tagesprojektion von $4K auf $1,4K. Ein 66%-Drop aus einer einzigen Klasse. Der Planner hatte den Großteil seines Budgets in den achten Tool-Call gesteckt, wo der Grenzinformationsgewinn fast null war.

Ich möchte das unterstreichen, weil es der Teil ist, den ich immer wieder vergesse und neu lerne: der Budget-Envelope ist keine Kostenkontrolle. Er ist eine Planungsform-Kontrolle. Sobald der Planner wusste, dass er ein festes Budget hat, fing er an, die wertvollsten Tool-Calls nach vorne zu ziehen, weil im Prompt stand "du hast ein Token-Budget." Die Verhaltensänderung im ersten Call war größer als der Cutoff im letzten Call.

Guard 2 — Model-Tiering

Der nächste offensichtliche Schritt war: brauchen wir wirklich GPT-4o für den Planner? Planung ist strukturiert. Der Planner gibt ein JSON-Objekt aus — "das nächste Tool ist X". Er schreibt keine Prosa. Er denkt nicht über Quantenmechanik nach.

Ich habe den Planner auf Claude Haiku 4.5 umgestellt. Den Result-Interpretation-Schritt auf ein kleines Modell mit striktem Structured-Output-Schema. Reasoner-Qualität blieb nur an einer Stelle: die finale Antwortkomposition, wo natürliche Sprache zählt.

In der Produktion sah das Tiering so aus:

SchrittVorherNachher
PlannerGPT-4oHaiku 4.5
Tool-Ergebnis-ParseGPT-4oHaiku 4.5 + Schema
Finale KompositionGPT-4oGPT-4o

Die Monatsprojektion fiel von $1,4K auf $380. Die Planungsqualität fiel auf meinem Eval-Set vielleicht um 2% — gut innerhalb des Rauschens, mit dem ich "guter Plan" sowieso bewertet habe.

Eine Klammer zum Eval-Set: was ich "Eval-Set" nenne, sind 230 von Hand gelabelte Queries, die ich an drei Nachmittagen in meinem Stammcafé zusammengestellt habe. Kein Gold-Standard, auf den du blind hin deployen würdest, aber genug, um die Frage zu beantworten "verhält sich der neue Planner wie der von letzter Woche?". Statt Perfektion zu suchen, reichte es, "Regime nicht geändert" zu bestätigen.

Das ist der Schritt, den die meisten zuerst gemacht hätten. Ich habe ihn bewusst als zweiten gemacht. Modell tauschen ist der einfache Fix, dafür gibt es Applaus; Schritte streichen ist der langweilige Fix, und der bewegt die Rechnung wirklich.

Guard 3 — Caching, das sich auszahlt

Der erste Cache, den ich probiert habe, war ein dummer LRU auf dem rohen Query-String. Hit-Rate: 4%. Leute suchen nicht denselben String.

Der zweite Cache verwendete Satz-Embeddings. Embedding hashen, in einen groben Bucket einsortieren, ähnliche vergangene Queries nachschlagen, den Tool-Selection-Plan wiederverwenden, wenn die Cosine-Similarity > 0,92 ist. Am ersten Dienstag nach dem Deploy sah ich morgens in der Stoßzeit 38% Hit — Leute suchten nach denselben Dingen in leicht anderen Worten. Der Wochendurchschnitt pendelte sich bei 22% ein.

async function planWithCache(query: string) {
  const emb = await embed(query);
  const hit = await cache.findSimilar(emb, 0.92);
  if (hit) {
    // den Plan wiederverwenden, nicht die Antwort.
    // Antworten können veralten; Tool-Selection selten.
    return runPlan(hit.plan, query);
  }
  const plan = await planner(query);
  await cache.put(emb, plan, ttl="6h");
  return runPlan(plan, query);
}

Wichtiges Detail: ich habe den Plan gecacht, nicht die Antwort. Bestand und Preis ändern sich. Welche Retrieval-Tools für "kabellose Kopfhörer unter 3K" gefeuert werden müssen, ändert sich nicht. Tool-Result-Cache lief nur auf deterministischen Queries — gleiches SKU-Lookup, gleicher Brand-Affinity-Score — mit 15-Minuten-TTL, weil die Warehouse-Daten so lange konsistent bleiben.

$380 → $120. Caching holte nochmal zwei Drittel raus.

Cache-Invalidierung wird klassischerweise als das schwerste Problem genannt — diesmal war es das nicht. Die TTL des Plan-Caches war kurz (6 Stunden), und der Plan wurde nach Intent gespeichert, nicht nach Nutzer. Zwei verschiedene Nutzer mit derselben "Kaffeemaschine"-Absicht teilten denselben Plan, aber bekamen unterschiedliche Ausführungsergebnisse. Dieses Detail zu übersehen hätte bedeutet, die Antwort von Nutzer A an Nutzer B zu schicken — und das ist die Art Bug, aus der du nicht mehr rauskommst.

Guard 4 — adaptive Tiefe

Inzwischen waren die Kosten erträglich, aber eine Sache hatte ich noch. Beim Durchsehen der Logs fiel mir etwas auf, das der Planner vor sich selbst versteckt hatte: die meisten Queries brauchen keine fünf Tools. Sie brauchen zwei.

Ein Junior im Team hatte einen Confidence-Scorer für Tool-Ergebnisse gebaut — im Grunde "haben wir saubere genug Ergebnisse, um zu antworten?" — und er lag ungenutzt in einem anderen Service herum. Ich habe ihn verdrahtet:

async function adaptivePlan(query: string, plan: Tool[]) {
  const results = [];
  for (const tool of plan) {
    const r = await run(tool, query);
    results.push(r);
    const confidence = scoreResults(results, query);
    // wenn wir schon sicher sind, aufhören. der Planner
    // will weitermachen. der Planner liegt falsch.
    if (confidence > 0.85 && results.length >= 2) break;
  }
  return results;
}

Zahlen aus den Logs der nächsten Woche: 18% der Queries brauchten mehr als zwei Tool-Calls. Die anderen 82% stoppten früh mit derselben Antwort, die sie nach Schritt 5 gehabt hätten. Der Planner hatte Fünf-Schritt-Pläne gemacht, weil die Prompt-Beispiele so aussahen; die echte Welt brauchte das nicht.

$120 → $40.

Dieser Schritt dauerte länger als die vorherigen, weil ich die "confidence > 0.85"-Zahl zwei Tage lang aus den Logs heraus kalibriert habe. Was passiert bei 0,75? Fällt die Antwortqualität? Bei 0,90? Statt 18% werden 12% gestoppt. Solche Schwellenwerte kannst du nicht in der Theorie einstellen, du brauchst echten Produktionstraffic, um deine Hypothese gegen ihn zu testen. Eine Woche gewartet, aus den Logs feinjustiert.

Was ich mir selbst im März gesagt hätte

Der größte Einzel-Drop kam nicht vom günstigeren Modell. Er kam aus Guard 1 — dem Budget-Envelope — kombiniert mit Guard 4 — früh stoppen, wenn man fertig ist. Zusammen waren das 80% der Einsparungen. Der Modell-Swap war 20%.

Der kontraintuitive Teil: günstigere Modelle sind die faule Antwort. Das Internet liebt sie, weil sie ein einzeiliger PR sind. "Bin auf Haiku gewechselt, 80% gespart" ist ein guter Tweet. Die echte Engineering-Arbeit ist, herauszufinden, welche Schritte gar nicht laufen sollten, und dafür musst du dich ein paar Abende mit deinen Logs hinsetzen.

Mathematik, wo ich gelandet bin: $40 im Monat, 360K Queries (12K täglich × 30). Das sind $0,0003 pro Query. Die Reaktion des Marktplatz-CFOs auf die neuen Zahlen war ein einzelnes Daumen-hoch-Emoji, was bei ihm als Parade durchgeht.

Das Schwierigste ist nicht, den Agent zu schreiben. Es ist, die Teile zu schreiben, die nicht laufen.


Mein zweitteuerster Monat war der, in dem ich vergessen hatte, ein Debug-Log-Statement zu löschen, das das LLM aufrief, um sich selbst zusammenzufassen. Ich habe es an Tag sechs bemerkt. Ich möchte nicht darüber reden.

// wenn du schon hier bist