Zurück zum Blog

API & SDKAnleitung

AI-Agent-Token-Kosten in Python verfolgen: usage, Budget, Stopp

Protokolliere das usage-Feld jeder Anfrage, summiere es pro Aufgabe und stoppe den Agenten bei einem Dollar-Budget. Funktionierendes Python-Beispiel für eine OpenAI-kompatible API.

Von CheapTokens TeamVeröffentlicht am 6 Min. Lesezeit

Terminal-artige Code-Karte: ein usage-Protokoll pro Schritt und eine Budgetprüfung, die einen AI-Agenten stoppt
Auf dieser Seite
  1. Wo die Zahlen sind: das usage-Objekt
  2. Ein Messgerät, das den Agenten stoppt
  3. Was ein 40-Schritte-Lauf summiert
  4. Wenn das Modell denkt
  5. Streaming: usage kommt zuletzt
  6. Wenn die Zahlen falsch aussehen

Dein Agent lief zehn Minuten und die Rechnung ist größer als die Aufgabe. Um zu sehen, warum, lies das usage-Objekt in jeder API-Antwort, summiere es pro Aufgabe und multipliziere mit dem Preis des Modells pro 1M Token. Um es zu begrenzen, vergleiche diese laufende Summe nach jedem Schritt mit einem Budget und stoppe die Schleife, wenn es überschritten wird. Unten ist eine kleine Python-Klasse, die genau das tut.

Das Beispiel verwendet den OpenAI-kompatiblen Endpunkt https://api.cheaptokens.dev/v1 und die Modell-ID deepseek-ai/DeepSeek-V4-Flash-0731 (380K Kontext, bis zu 32.768 Ausgabe-Token pro Antwort). Wenn du einen Lauf vor dem Start schätzen möchtest, ist das eine andere Aufgabe, und How to estimate the cost of an AI agent behandelt das. Dieser Artikel handelt vom Messen und Begrenzen während des Laufs.

Wo die Zahlen sind: das usage-Objekt#

Jede nicht-streamende Chat-Completion kommt mit einem usage-Objekt zurück. Die API-Referenz von DeepSeek (geprüft am 26. Oktober 2026) listet diese Felder auf:

  • prompt_tokens — alles, was du diesmal gesendet hast: System-Prompt, Verlauf, Tool-Ergebnisse.
  • completion_tokens — was das Modell zurückgeschrieben hat.
  • total_tokens — die beiden zusammenaddiert.

DeepSeek dokumentiert auch optionale Extras: prompt_cache_hit_tokens und prompt_cache_miss_tokens (sie summieren sich zu prompt_tokens) und completion_tokens_details.reasoning_tokens für Thinking-Modelle. Die Seite zeigt kein Veröffentlichungsdatum, und diese Extras sind Erweiterungen des OpenAI-Formats, daher behandelt der Code unten sie als optional und verlässt sich nur auf die ersten beiden Felder.

Warum jeden Schritt protokollieren und nicht nur den letzten? Weil ein Agent seinen wachsenden Kontext jedes Mal erneut sendet. Schritt 1 könnte 2.000 Eingabe-Token senden, Schritt 30 könnte 9.000 senden, und die Rechnung ist die Summe aller. Der Grund wird in why a long chat costs more erklärt, und die Aufteilung Eingabe/Ausgabe in input vs output tokens.

Ein Messgerät, das den Agenten stoppt#

Hier ist das Ganze: ein Messgerät, das Token addiert, eine Zeile pro Schritt ausgibt und einen Fehler auslöst, wenn das Budget überschritten wird, plus eine nackte Agentenschleife darum. Installiere das SDK mit pip install openai und lege deinen Schlüssel in CHEAPTOKENS_API_KEY ab.

python
import os
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'

# example prices, USD per 1M tokens: use the current ones from the pricing table
PRICE_IN = 0.10
PRICE_OUT = 0.40


class BudgetExceeded(Exception):
    pass


class TaskMeter:
    def __init__(self, budget_usd):
        self.budget = budget_usd
        self.steps = 0
        self.tokens_in = 0
        self.tokens_out = 0
        self.reasoning = 0

    @property
    def cost(self):
        return (self.tokens_in * PRICE_IN + self.tokens_out * PRICE_OUT) / 1_000_000

    def add(self, usage):
        self.steps += 1
        self.tokens_in += usage.prompt_tokens
        self.tokens_out += usage.completion_tokens
        details = getattr(usage, 'completion_tokens_details', None)
        self.reasoning += getattr(details, 'reasoning_tokens', 0) or 0
        print(f'step {self.steps}: in={usage.prompt_tokens} '
              f'out={usage.completion_tokens} task=${self.cost:.4f}')
        if self.cost > self.budget:
            raise BudgetExceeded(
                f'budget ${self.budget:.2f} crossed at step {self.steps}: '
                f'${self.cost:.4f}')


def run_task(goal, budget_usd=0.50, max_steps=40):
    meter = TaskMeter(budget_usd)
    messages = [
        {'role': 'system', 'content': 'Work step by step. Reply DONE when finished.'},
        {'role': 'user', 'content': goal},
    ]
    for _ in range(max_steps):
        resp = client.chat.completions.create(
            model=MODEL, messages=messages, max_tokens=1024)
        meter.add(resp.usage)  # log first, then decide
        reply = resp.choices[0].message.content or ''
        messages.append({'role': 'assistant', 'content': reply})
        if 'DONE' in reply:
            break
        messages.append({'role': 'user', 'content': 'Continue.'})
    return meter


try:
    meter = run_task('Summarise the main risks of a database migration.')
    print(f'finished: {meter.steps} steps, ${meter.cost:.4f}')
except BudgetExceeded as err:
    print('stopped:', err)

Drei Entscheidungen darin sind es wert, kopiert zu werden.

Protokolliere, bevor du entscheidest. meter.add zeichnet zuerst den Schritt auf und prüft erst dann das Budget, sodass ein gestoppter Lauf immer noch eine vollständige Spur hinterlässt. Die Prüfung erfolgt nach dem Aufruf, der die Grenze überschritten hat, sodass du um höchstens einen Schritt überschießen kannst. max_tokens=1024 begrenzt die Ausgabe dieses letzten Schritts.

Zwei harte Stopps, nicht einer. max_steps fängt einen Agenten ab, der schleift, ohne viel auszugeben; das Dollar-Budget fängt einen ab, der viel pro Schritt ausgibt. Du willst beides.

Preise leben an einem Ort. PRICE_IN und PRICE_OUT sind Platzhalterzahlen. Kopiere die aktuellen Werte für dein Modell von der pricing page; derzeit kostet DeepSeek-V4-Flash $0.015 pro 1M Eingabe-Token und $0.10 pro 1M Ausgabe-Token.

Was ein 40-Schritte-Lauf summiert#

Angenommen, dein Agent macht 40 Schritte, und weil der Kontext wächst, sendet ein durchschnittlicher Schritt 6.000 Eingabe-Token und bekommt 400 Token zurück. Das sind 40 × 6.000 = 240.000 Eingabe-Token und 40 × 400 = 16.000 Ausgabe-Token für die Aufgabe.

Schritt 1 sendet 2.000 Eingabe-Token, Schritt 30 sendet 9.000; die Aufgabensumme ist die Summe aller Schritte
Illustration mit den Annahmen des Artikels, kein Laufergebnis

Auf DeepSeek-V4-Flash kostet das $0.0052. Die Tabelle führt dieselbe Arbeitslast auf den anderen Modellen aus und stellt OpenRouters Referenzpreis neben unseren:

Modell Kosten OpenRouter
GLM-5.3-Flash $0.0054 $0.0111
DeepSeek-V4-Flash $0.0052 $0.0139
MiniMax-M2.7 $0.0144 $0.0912

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

Insgesamt, mit Eingabe und Ausgabe zusammen gezählt, kommt DeepSeek-V4-Flash für diese Aufgabe günstiger heraus als OpenRouter. Beachte auch, wo die Token sind: 240.000 der 256.000 sind Eingabe. Der Kontext, den dein Agent mitschleppt, ist die Rechnung, weshalb die in=-Zahl pro Schritt in deinem Protokoll diejenige ist, die du im Auge behalten solltest. Ein Budget von $0,50 pro Aufgabe im obigen Beispiel ist eine Obergrenze für einen Durchgeher, kein Ziel.

Modelle zu wechseln bedeutet nur, MODEL zu ändern. Wähle die ID aus den CheapTokens docs: Die Namen unterscheiden sich zwischen Anbietern, und eine kurze ID aus einem Preislabel funktioniert nicht in der API. Für lange Agentenschleifen hat DeepSeek-V4-Flash zu jeder Tageszeit einen Preis, sodass eine Aufgabe mittags und um 3 Uhr morgens gleich viel kostet. Siehe die DeepSeek page für seine Limits.

Wenn das Modell denkt#

Wenn eine Antwort in Token viel länger ist als der Text, den du siehst, schau dir reasoning_tokens an. Thinking-Token zählen als Ausgabe, sodass ein reasoning-schwerer Schritt weit mehr Ausgabe-Token produzieren kann, als der sichtbare Text vermuten lässt, und sie werden als Ausgabe abgerechnet. Das Messgerät oben sammelt sie in meter.reasoning, wenn die API das Feld zurückgibt, und lässt es bei 0, wenn nicht, sodass es nie an einem fehlenden Schlüssel abstürzt. Gib es neben out= aus, wenn du sehen willst, wie viel der Ausgabe Thinking war. Reasoning effort and thinking tokens erklärt warum.

Streaming: usage kommt zuletzt#

Wenn dein Agent streamt, gibt es kein usage in den normalen Chunks. Im OpenAI-Format fragst du es mit stream_options={'include_usage': True} an. Die Referenz von DeepSeek (geprüft am 26. Oktober 2026) sagt, dass die Option nur zusammen mit stream=True funktioniert (andernfalls antwortet die API mit 400), dass usage in jedem Chunk außer einem null ist und dass der letzte Chunk vor data: [DONE] die Summen für die gesamte Anfrage trägt. Also rufe in einer Streaming-Schleife meter.add einmal auf, und zwar bei dem Chunk, bei dem usage nicht leer ist. Die nicht-streamende Schleife oben ist der einfachste Einstieg; streaming in Python enthält die Schleife selbst.

Wenn die Zahlen falsch aussehen#

  • usage ist None. Du streamst ohne include_usage. Füge es hinzu oder wechsle zu einem nicht-streamenden Aufruf.
  • Die Summe ist niedriger als der Kontostand-Rückgang. Prüfe, dass jeder Aufruf durch das Messgerät geht: Wiederholungen, ein zusammenfassender Aufruf oder ein Tool-Auswahl-Aufruf außerhalb von run_task werden leicht übersehen. Packe client.chat.completions.create in eine Funktion und leite alles durch sie.
  • Die Kosten pro Schritt steigen weiter. Das ist normal für eine Schleife, die alles an messages anhängt. Kürze alte Tool-Ausgaben oder fasse den Verlauf zusammen; tool output out of context zeigt die Idee.
  • BudgetExceeded feuert bei Schritt 2. Dein Budget ist kleiner als ein Schritt. Prüfe, dass PRICE_IN pro 1M Token ist, nicht pro 1K.

Um die Zahlen zu speichern, schreibe meter.steps, meter.tokens_in, meter.tokens_out und meter.cost als eine JSON-Zeile pro Aufgabe. Eine Woche dieser Zeilen sagt dir die echten Token pro Schritt für deinen Agenten, was die Zahl ist, die du danach in jede Schätzung einsetzen solltest. Modelle, Preise und die erste Anfrage sind in den docs; Tool-Aufrufe werden in function calling in Python behandelt.