Zurück zum Blog

KI für EinsteigerAnleitung

Die Kosten eines LLM-Batches in Python begrenzen: ein Ledger und ein hartes Budget

Loggen Sie Tokens und Kosten jedes API-Aufrufs in eine JSONL-Datei, stoppen Sie den Batch bei einem Budget und schätzen Sie die Rechnung, bevor Sie ihn ausführen. Python-Code inklusive.

Von CheapTokens TeamVeröffentlicht am 6 Min. Lesezeit

Terminal mit einem Batch-Lauf, der stoppt, wenn das Budget erreicht ist
Auf dieser Seite
  1. Was der Clocktower-Lauf macht, das Sie kopieren können
  2. Das Ledger: eine Datei, eine Zeile pro Aufruf
  3. Die Batch-Schleife: Überspringen, was erledigt ist, am Cap stoppen
  4. Die Rechnung schätzen, bevor Sie auf Start drücken
  5. Wenn die Rechnung größer ist als erwartet: das Ledger lesen
  6. Bevor Sie starten

Ihr Skript läuft über 200 Prompts, Sie klappen den Laptop zu, und am Morgen erfahren Sie, was die Nacht gekostet hat. Zwei kleine Gewohnheiten beheben das. Nach jedem API-Aufruf hängen Sie eine Zeile mit Tokens und Kosten an eine Ledger-Datei an. Vor jedem Aufruf vergleichen Sie die laufende Summe mit einem Budget und stoppen, wenn es erreicht ist. Das sind unter 50 Zeilen Python, und ein Testlauf mit 5 Prompts verrät Ihnen die Rechnung, bevor der echte Lauf beginnt.

Was der Clocktower-Lauf macht, das Sie kopieren können#

Dwarkesh Patel, Host des Dwarkesh Podcast, ließ 11 Modelle in Blood on the Clocktower gegeneinander antreten, laut README 386 Spiele, und veröffentlichte Code, Logs und Ratings (github.com/dwarkeshsp/clocktower, geprüft am 8. November 2026). Zwei Details aus dem Repo lohnen sich zum Nachahmen. Jedes Spielverzeichnis führt ein Log pro Aufruf mit Tokens und Kosten, und die Ausgaben gehen nach games/ledger.jsonl. Ein --budget-Flag begrenzt die Gesamtausgaben; der Standardwert ist $1.000. Die Benchmark-Seite fügt einen dritten Schutz hinzu: Jedes Modell bekommt pro Zug dieselbe Obergrenze von 8.000 Tokens (clocktowerbench.com).

Also: eine Obergrenze pro Aufruf, ein Log pro Aufruf, eine Obergrenze für die Summe. Ein Turnier braucht alle drei, und Ihr nächtlicher Lauf auch, nur kleiner.

Denken Sie an das Ledger wie an den Bon-Rollen in einer Ladenkasse. Jeder Verkauf druckt eine Zeile; der Manager kann die Rolle jederzeit aufsummieren und weiß, wann die Tageskasse leer ist.

Das Ledger: eine Datei, eine Zeile pro Aufruf#

Hier ist das Ganze. Es nutzt das openai-Paket, ausgerichtet auf CheapTokens (https://api.cheaptokens.dev/v1, siehe die Dokumentation für den Key und die Modell-IDs).

python
import json
import os
from pathlib import Path

from openai import OpenAI

client = OpenAI(
    base_url='https://api.cheaptokens.dev/v1',
    api_key=os.environ['CHEAPTOKENS_API_KEY'],
)
MODEL = 'deepseek-ai/DeepSeek-V4-Flash-0731'
PRICE_IN, PRICE_OUT = 1.00, 2.00  # USD per 1M tokens: example, substitute current ones


class BudgetExceeded(Exception):
    pass


class Ledger:
    def __init__(self, path, budget_usd):
        self.path = Path(path)
        self.budget = budget_usd
        self.rows = []
        if self.path.exists():
            with self.path.open() as f:
                self.rows = [json.loads(line) for line in f if line.strip()]

    @property
    def spent(self):
        return sum(r['cost_usd'] for r in self.rows)

    def done(self):
        return {r['unit'] for r in self.rows}

    def check(self, unit):
        if self.spent >= self.budget:
            raise BudgetExceeded(
                f'stopped before {unit}: spent {self.spent:.4f} of {self.budget} USD'
            )

    def add(self, unit, usage, finish_reason):
        cost = (usage.prompt_tokens * PRICE_IN
                + usage.completion_tokens * PRICE_OUT) / 1_000_000
        row = {
            'unit': unit,
            'model': MODEL,
            'prompt_tokens': usage.prompt_tokens,
            'completion_tokens': usage.completion_tokens,
            'finish_reason': finish_reason,
            'cost_usd': round(cost, 6),
        }
        self.rows.append(row)
        with self.path.open('a') as f:
            f.write(json.dumps(row) + '\n')


def ask(ledger, unit, prompt, max_tokens=400):
    ledger.check(unit)
    resp = client.chat.completions.create(
        model=MODEL,
        messages=[{'role': 'user', 'content': prompt}],
        max_tokens=max_tokens,
    )
    ledger.add(unit, resp.usage, resp.choices[0].finish_reason)
    return resp.choices[0].message.content

Jeder Aufruf durchläuft dieselben vier Schritte:

Vier Schritte um jeden API-Aufruf: Budget prüfen, aufrufen, Usage lesen, an das Ledger anhängen
Der Zyklus hinter jedem Aufruf im Wrapper

Drei Entscheidungen darin sind bewusst getroffen.

  • Ein JSON-Objekt pro Zeile (JSONL), im Append-Modus geöffnet. Wenn das Skript bei Aufruf 130 abstürzt, liegen die Aufrufe 1 bis 129 bereits auf der Platte.
  • Die Summe kommt aus der Datei, nicht aus einer Variable. Starten Sie das Skript neu, und die Obergrenze hält weiterhin, weil das Ledger sich merkt, was der vorherige Lauf ausgegeben hat.
  • finish_reason wird neben den Tokens geloggt. Eine Zeile mit length bedeutet, dass die Antwort Ihr max_tokens erreicht und abgeschnitten wurde. Das wollen Sie später wissen.

Die beiden PRICE_-Konstanten sind Platzhalter. Nehmen Sie die aktuellen Zahlen aus der Preistabelle. Für DeepSeek-V4-Flash sind das zum Zeitpunkt, an dem Sie dies lesen, $0.015 pro 1M Input-Tokens und $0.10 pro 1M Output-Tokens. Ändern Sie MODEL zu zai-org/GLM-5.3-Flash oder MiniMaxAI/MiniMax-M2.7 und die beiden Konstanten, und dasselbe Ledger funktioniert für sie.

Die Batch-Schleife: Überspringen, was erledigt ist, am Cap stoppen#

Die Schleife ist kurz. Sagen wir, Ihre Prompts liegen in einem Dict, verschlüsselt nach einer Unit-ID, dem Namen eines Arbeitspakets, etwa doc-001:

python
prompts = {
    'doc-001': 'Summarize this ticket in one sentence: ...',
    'doc-002': 'Summarize this ticket in one sentence: ...',
    # ... your 200 prompts
}

ledger = Ledger('ledger.jsonl', budget_usd=0.50)
already = ledger.done()

for unit, prompt in prompts.items():
    if unit in already:
        continue
    try:
        answer = ask(ledger, unit, prompt)
    except BudgetExceeded as e:
        print(e)
        break
    Path(f'out-{unit}.txt').write_text(answer)

Führen Sie es aus, drücken Sie auf halbem Weg Ctrl+C, führen Sie es erneut aus: Erledigte Units werden übersprungen, sodass das Ledger zugleich Ihre Fortschrittsdatei ist. Lassen Sie das Budget ausgehen, und Sie bekommen eine Zeile statt einer Überraschung, etwa stopped before doc-142: spent 0.5003 of 0.5 USD.

Beachten Sie das Wort vor. Die Prüfung erfolgt vor jedem Aufruf, sodass der letzte Aufruf die Summe leicht über das Budget heben kann. Wie weit darüber, ist begrenzt: Ein Aufruf kann nicht mehr kosten als Ihr längster Prompt plus max_tokens. Bei einem Prompt mit 1.500 Tokens und max_tokens=400 sind das höchstens $0.000063. Deshalb hat jeder Aufruf im Wrapper ein max_tokens: Es verwandelt ein unbegrenztes Risiko in eine Zahl, mit der Sie planen können.

Zeilen einer Ledger-Datei mit Tokens pro Aufruf und einem abschließenden Budget-Stopp
Beispiel-Ledger-Zeilen und die Stopp-Nachricht (illustrative Werte)

Die Rechnung schätzen, bevor Sie auf Start drücken#

Jetzt die andere Hälfte: die Kosten vor dem Start kennen. Lassen Sie die ersten fünf Prompts mit einem kleinen Budget durch ask() laufen und lesen Sie das Ledger. Sagen wir, die fünf Zeilen ergeben im Schnitt 1.500 Input-Tokens und 400 Output-Tokens. Das sind Ihre Zahlen für den gesamten Batch.

Für 200 Prompts sind das 200 × 1.500 = 300.000 Input-Tokens und 200 × 400 = 80.000 Output-Tokens. Hier ist diese Arbeitslast zu Live-Preisen:

Modell Pro Anfrage 200 Anfragen OpenRouter, 200 Anfragen
GLM-5.3-Flash $0.000046 $0.0092 $0.0167
DeepSeek-V4-Flash $0.000063 $0.0125 $0.0322
MiniMax-M2.7 $0.00014 $0.027 $0.324

1.500 Input- und 400 Output-Tokens pro Anfrage. Die Preise pro 1 Mio. Tokens sind aktuell und ändern sich zusammen mit unseren Tarifen; die OpenRouter-Spalte zeigt Referenzpreise für dieselbe Arbeitslast.

Die Tabelle hat auch eine OpenRouter-Spalte. Zählt man Input und Output zusammen, kommt DeepSeek-V4-Flash für diese Last günstiger weg als OpenRouter, und diese Summe zahlt der Batch tatsächlich.

Prompts sind unterschiedlich lang, fünf Stichproben sind also eine Schätzung, kein Versprechen. Nehmen Sie den längsten der fünf, wenn Sie eine sichere Obergrenze wollen, und lassen Sie den Budget-Cap den Rest abfangen. Wollen Sie zwei Modelle auf denselben 200 Prompts vergleichen? Geben Sie jedem Modell eine eigene Ledger-Datei, zum Beispiel Ledger(f'ledger-{MODEL.split("/")[-1]}.jsonl', 0.50), und die beiden Summen stehen danach nebeneinander.

Probieren Sie Ihren eigenen Batch hier aus: Setzen Sie die Tokens pro Anfrage aus Ihrer Stichprobe und wie viele Anfragen Sie planen.

Kostenrechner

Ihr Batch: Tokens aus Ihrer 5-Prompt-Stichprobe

Eingabe-Tokens pro Anfrage: 1.500 · Ausgabe-Tokens pro Anfrage: 400 · Anfragen pro Tag: 200

ModellPro TagPro Monat
GLM-5.3-Flash$0.0092$0.276
DeepSeek-V4-Flash$0.0125$0.375
MiniMax-M2.7$0.027$0.81

Mehr zum Zählen von Tokens vor dem Senden finden Sie unter Tokens mit tiktoken zählen.

Wenn die Rechnung größer ist als erwartet: das Ledger lesen#

Das dritte Leser-Szenario ist das unangenehme. Ein Agent-Lauf geht schief und schleift, und das Dashboard zeigt nur eine Summe. Das Ledger zeigt, wo es passiert ist. Sortieren Sie die Zeilen nach Kosten:

python
rows = [json.loads(line) for line in open('ledger.jsonl')]
for r in sorted(rows, key=lambda r: r['cost_usd'], reverse=True)[:5]:
    print(r['unit'], r['prompt_tokens'], r['completion_tokens'], r['finish_reason'])

Benennen Sie bei einem Agenten die Unit nach dem Schritt (step-17), damit Sie die Form des Laufs sehen. Ein paar Muster tauchen immer wieder auf; ordnen Sie jedem seine übliche Ursache zu:

Paare zuordnen

Ordnen Sie jedes Ledger-Muster seiner üblichen Ursache zu

  • prompt_tokens wächst mit jeder Zeile ein wenigDie Schleife sendet bei jedem Schritt die gesamte Historie erneut
  • completion_tokens entspricht max_tokens und finish_reason ist lengthAntworten werden an Ihrem Cap abgeschnitten, und Sie zahlen für den abgeschnittenen Text
  • Dieselbe Unit erscheint viele Male hintereinanderEine Retry-Schleife oder ein Agent, der einen Schritt wiederholt
  • Eine Zeile kostet weit mehr als alle anderenEine überdimensionierte Eingabe, etwa eine ganze Datei, die in den Prompt eingefügt wurde

Jedes Muster ist im Ledger lange vor der Rechnung sichtbar. Das erste wird in warum ein Agent seine Historie erneut liest behandelt.

Wenn Sie zusätzlich Ausgaben pro Projekt und Alerts außerhalb des Skripts wollen, deckt dieser Leitfaden zu Budget-Alerts pro Projekt diese Seite ab. Das Ledger hier ist der Teil, der in Ihrem Code lebt und Stopp sagen kann.

Bevor Sie starten#

Checkliste

Vor dem ersten echten Lauf

Die Kontextgröße und das Antwortlimit von DeepSeek-V4-Flash, praktisch bei der Wahl von max_tokens, stehen auf seiner Seite: DeepSeek-V4-Flash auf CheapTokens.