DEV Community

Cover image for LLM-API-Kosten im Griff: Token-Budget, Batching und ein teurer Lerntag
Stanislav Tonkich
Stanislav Tonkich

Posted on

LLM-API-Kosten im Griff: Token-Budget, Batching und ein teurer Lerntag

Ein API-Aufruf kostet meist Bruchteile eines Cents. Teuer wird es durch Schleifen, Retries und Prototypen, die nie für den Dauerbetrieb gedacht waren. Der Text zeigt, wovon die Rechnung abhängt, wie man Grenzen setzt und Tokens zählt, wie man Budgets im Code überwacht und warum Batching mehr bringt als jeder Preisvergleich.

Kosten hängen am Aufrufmuster, nicht am Preis pro Token

Abgerechnet werden Input-Token und Output-Token, Output ist bei vielen Modellen deutlich teurer. Als Rechenbeispiel (Preisstufe von GPT-4o laut Anbieter, Stand Herbst 2026; neuere Modelle weichen ab, bitte prüfen): Bei 2,50 USD pro 1 Mio. Input-Token und 10,00 USD pro 1 Mio. Output-Token kostet ein Aufruf mit 500 Token Input und 150 Token Output rund 0,00275 USD. Bei 10.000 Aufrufen im Monat sind das etwa 27,50 USD. Unauffällig, solange das Muster stimmt.

Das ändert sich mit dem Muster:

  • Zu viel Kontext: Wird bei jeder Aktualisierung ein kompletter Katalogkontext mit 8.000 Token gesendet statt der geänderten Felder mit 200 Token, ist der Input 40-mal so groß, ohne dass die Antwort besser wird.
  • Falsches Modell: Für einfache Klassifikationen genügt oft ein kleineres Modell, der Preisunterschied kann bei großen Mengen den Faktor 10 oder mehr erreichen.
  • Unkontrollierte Wiederholungen: Ein fehlerhafter Retry in einem Automatisierungs-Workflow kann die Kosten innerhalb weniger Tage vervielfachen, bevor der Fehler auffällt.

Die Stellschrauben sind also Prompt-Länge, Modellwahl und Anzahl der Aufrufe. Die Preisliste ist der kleinste Hebel.

Budgetgrenzen und Limits

Zwei Ebenen wirken zusammen. Auf Anbieterseite setzt man in der Konsole monatliche Limits. Das ist der letzte Schutz, wenn der Code versagt. Zusätzlich braucht es Grenzen im Code, weil das Konsolen-Limit nicht zwischen Anwendungen unterscheidet und erst am Monatsende sichtbar wird, was verbraucht wurde.

Drei Regeln haben sich bewährt:

  1. Ein Limit pro Anwendungsfall, etwa getrennt für Produkttexte und einen Support-Chatbot.
  2. Ein eigener API-Key pro Anwendungsfall, nie ein Master-Key für alles. So lässt sich der Verbrauch zuordnen und ein defekter Job gezielt abschalten.
  3. Kontrolliertes Pausieren statt Weiterlaufen: Bei Erreichen des Budgets stoppt der Job und meldet sich, statt unbegrenzt Ressourcen zu verbrauchen.

Zudem sollte max_tokens für die Antwort gesetzt werden. Es begrenzt den teureren Output-Anteil nach oben und liefert die Obergrenze für die Vorab-Schätzung im nächsten Abschnitt.

Tokens zählen und ein Budget-Wächter in Python

Mit tiktoken lassen sich Tokens lokal zählen, bevor ein Request rausgeht. Das Beispiel schätzt den ungünstigsten Fall (voller Input plus komplettes max_tokens als Output) und bricht ab, wenn das Budget dadurch überschritten würde. Die Preise sind Platzhalter und müssen gegen die aktuelle Preisliste des Anbieters geprüft werden.

import csv
import time

import tiktoken

# Platzhalter in USD pro 1 Mio. Token: laut Anbieter prüfen
PRICE_IN_PER_M = 2.50
PRICE_OUT_PER_M = 10.00


class BudgetExceeded(RuntimeError):
    """Wird geworfen, wenn ein Aufruf das Budget sprengen würde."""


def count_tokens(text: str, model: str = "gpt-4o") -> int:
    try:
        enc = tiktoken.encoding_for_model(model)
    except KeyError:
        enc = tiktoken.get_encoding("o200k_base")
    return len(enc.encode(text))


class BudgetGuard:
    def __init__(self, limit_usd: float, log_path: str = "api_costs.csv"):
        self.limit_usd = limit_usd
        self.spent_usd = 0.0
        self.log_path = log_path

    @staticmethod
    def _cost(tokens_in: int, tokens_out: int) -> float:
        return (tokens_in * PRICE_IN_PER_M + tokens_out * PRICE_OUT_PER_M) / 1_000_000

    def check(self, prompt: str, max_output_tokens: int, model: str = "gpt-4o") -> int:
        tokens_in = count_tokens(prompt, model)
        worst_case = self._cost(tokens_in, max_output_tokens)
        if self.spent_usd + worst_case > self.limit_usd:
            raise BudgetExceeded(
                f"Limit {self.limit_usd:.2f} USD, bereits {self.spent_usd:.4f} USD, "
                f"nächster Aufruf bis zu {worst_case:.4f} USD"
            )
        return tokens_in

    def record(self, label: str, tokens_in: int, tokens_out: int) -> None:
        cost = self._cost(tokens_in, tokens_out)
        self.spent_usd += cost
        with open(self.log_path, "a", newline="", encoding="utf-8") as f:
            csv.writer(f).writerow(
                [int(time.time()), label, tokens_in, tokens_out, f"{cost:.6f}"]
            )


if __name__ == "__main__":
    guard = BudgetGuard(limit_usd=1.00)
    prompt = "Fasse den folgenden Text in drei Sätzen zusammen: ..."
    n_in = guard.check(prompt, max_output_tokens=300)
    # Hier folgt der echte API-Aufruf. Die tatsächlichen Werte stehen
    # in der Antwort unter usage (prompt_tokens, completion_tokens).
    guard.record("demo", tokens_in=n_in, tokens_out=120)
    print(f"Verbraucht: {guard.spent_usd:.6f} USD")
Enter fullscreen mode Exit fullscreen mode

Nach dem Aufruf: Die tatsächlichen Werte aus usage verbuchen, nicht die Schätzung. Die Schätzung dient nur als Schranke vor dem Aufruf. Chat-Nachrichten verursachen zudem einen kleinen Formatierungs-Overhead, den tiktoken auf reinem Text nicht mitzählt. Ein Sicherheitspuffer ist daher sinnvoll.

Batching: erst sammeln, dann senden

Das Teuerste: Einzelabfragen in einer Schleife, wenn der Dienst Arrays akzeptiert. Aus der Praxis: Bei einem Dienst für Keyword-Daten wurden an einem Tag 1.984 Einzelabfragen abgeschickt, eine pro Schlüsselbegriff. Das kostete 23,92 USD. Der Dienst erlaubt bis zu 700 Einträge pro Anfrage, gebündelt wären es nach Schätzung etwa 2 bis 3 USD gewesen. Gleiche Daten, rund zehnfacher Preis, allein wegen des Aufrufmusters.

Die Regel:

  1. Im Schema der Schnittstelle prüfen, ob ein Parameter Listen akzeptiert, und das Maximum notieren.
  2. Erst alle Eingaben sammeln, dann mit möglichst wenigen Anfragen nahe am Maximum senden.
  3. Ist der Parameter inhärent einzeln (etwa eine einzelne Koordinate), ist ein Aufruf pro Element korrekt. Das gilt es pro Endpunkt zu prüfen, statt es anzunehmen.

Bei Sprachmodellen gilt dasselbe Prinzip in abgewandelter Form. Wenn eine Antwortzeit von Stunden akzeptabel ist, kann asynchrone Batch-Verarbeitung günstiger sein. Anbieter bieten dafür häufig einen Rabatt gegenüber dem Echtzeitbetrieb (Konditionen beim Anbieter prüfen). Die Leitfrage lautet dann nicht "Was kostet ein Token?", sondern "Welche Latenz wird wirklich gebraucht?".

Monitoring

Ohne Protokoll ist die Rechnung am Monatsende die erste Information. Das CSV-Log aus dem Beispiel genügt für den Anfang: Zeitstempel, Label der Funktion, Input-Token, Output-Token, Kosten. Nach wenigen Wochen zeigt eine einfache Gruppierung nach Label, welche Funktion den größten Teil des Budgets verbraucht. Ergänzend sinnvoll:

  • Benachrichtigung bei Erreichen eines Teils des Limits, etwa der Hälfte, statt erst bei hundert Prozent.
  • Auffällige Sprünge im Tagesverbrauch auswerten, denn sie deuten oft auf Schleifen oder doppelte Aufrufe hin.
  • Retries mit Backoff begrenzen und fehlgeschlagene Aufrufe protokollieren.

Fazit

LLM-Kosten entstehen im Code, nicht in der Preisliste. Wer Tokens vorab zählt, ein hartes Budget pro Anwendungsfall setzt, Eingaben bündelt und jeden Aufruf protokolliert, vermeidet die teuersten Fehler. Eine ausführlichere Einordnung inklusive Preisbändern, Caching und Modell-Routing steht im Beitrag OpenAI API Kosten und Budgetkontrolle. Alle genannten Preise stammen aus Rechenbeispielen und müssen vor dem Einsatz gegen die aktuelle Preisliste des Anbieters geprüft werden.

Hinweis: Dieser Beitrag ist Eigenwerbung (Werbung) des Anbieters von Webentwickler.Pro. Mehr zur Budgetkontrolle im Blog von Webentwickler.Pro.

Top comments (0)