tokentoll: Kostenänderungen von LLM-API-Aufrufen im Code-Review

Aufbau der ersten Version von tokentoll, das die monatlichen Kosten von LLM-API-Aufrufen aus dem Quellcode schätzt und zeigt, wie ein Git-Diff sie verändert.

Was tokentoll macht

tokentoll findet LLM-API-Aufrufe im Quellcode, schätzt ihre monatlichen Kosten und zeigt, wie ein Git-Diff diese Schätzung verändert. Infracost macht dasselbe für Terraform und Cloud-Ressourcen. Codeänderungen, die LLM-Kosten beeinflussen, sind zum Beispiel ein anderes Modell, eine neue Aufrufstelle oder ein entfernter max_tokens-Parameter.

Dieser Beitrag beschreibt die erste Version vom Mai 2026, die nur Python gescannt hat. Der letzte Abschnitt nennt, was sich seitdem geändert hat.

tokentoll Demo

Architektur

tokentoll besteht aus fünf Komponenten:

  1. Scanner: durchläuft Python-Dateien und übergibt jede Datei an die Detektoren.
  2. Detektoren: einer pro SDK (OpenAI, Anthropic, Google, LiteLLM, LangChain). Jeder findet die API-Aufrufe seines SDKs im Python-AST.
  3. Pricing-Engine: ordnet einem Modellnamen Kosten pro Token zu, mit gestaffelter Namensauflösung, SDK-spezifischen Default-Modellen für dynamische Modellnamen und einem lokalen Cache.
  4. Diff-Engine: ordnet alte und neue Aufrufe über Datei und Zeilennähe einander zu.
  5. Output-Formatter: Tabelle (CLI), Markdown (PR-Kommentar), JSON.

Designentscheidungen:

  • Die erste Version hatte keine Laufzeitabhängigkeiten. Sie nutzte nur die Standardbibliothek: ast zum Parsen, json für Daten, subprocess für Git, argparse für die CLI und urllib für den Abruf der Preise.
  • Jeder Detektor ist eine Datei, die can_handle() und detect() implementiert. Ein neues SDK erfordert keine Änderung am Scanner oder an der Pipeline.
  • Die Preise stammen aus der Pricing-Datenbank von LiteLLM (2.200+ Modelle) und werden in ~/.tokentoll/ zwischengespeichert. tokentoll warnt bei einem veralteten Cache und bricht bei einem sehr alten Cache mit einem Fehler ab.
  • Ist ein Modellname dynamisch, wird er also zum Beispiel zur Laufzeit aus einer Config oder einer Umgebungsvariable gelesen, bepreist tokentoll den Aufruf mit einem Default-Modell des SDKs: gpt-4o für OpenAI, claude-sonnet für Anthropic, gemini-flash für Google. Auch Aufrufe mit dynamischen Modellnamen bekommen so eine Kostenschätzung.

Auflösung von Modellnamen zu Preiseinträgen

Im Code steht model="gpt-4o", die Pricing-Datenbank enthält Einträge wie gpt-4o, openai/gpt-4o, gpt-4o-2024-08-06 und azure/gpt-4o. tokentoll probiert diese Schritte der Reihe nach:

  1. Exakte Übereinstimmung
  2. Übereinstimmung ohne Beachtung der Groß- und Kleinschreibung
  3. SDK-Präfix anhängen und abgleichen (openai/gpt-4o)
  4. Provider-Präfix aus den Datenbank-Keys entfernen und abgleichen
  5. Regionspräfix entfernen (us., eu., apac.)
  6. Datumssuffix entfernen (-2024-08-06, -20240806)

In den Open-Source-Projekten, die ich gescannt habe, hat das die meisten Modellnamen aufgelöst.

Mehrstufige Konstantenpropagation

Modellnamen stehen an der Aufrufstelle selten als String-Literal. Sie laufen durch Variablen, Klassenattribute, Config-Objekte und **kwargs:

DEFAULT_MODEL = os.getenv("MODEL", "gpt-4o")

class Config:
    model: str = DEFAULT_MODEL

config = Config()
kwargs = {"model": config.model, "max_tokens": 2000}
client.chat.completions.create(**kwargs)
# tokentoll löst auf: model="gpt-4o", max_tokens=2000

Die Engine wiederholt ihre Durchläufe, bis sich kein aufgelöster Wert mehr ändert. Sie folgt Variablenzuweisungen, os.getenv()-Fallbacks, Funktions-Defaults, Defaults von Klassenattributen, Konstruktorargumenten, Dict-Inhalten und **kwargs-Unpacking.

Konfiguration

Eine .tokentoll.yml-Datei legt die Annahmen pro Projekt fest:

calls_per_month: 5000
default_models:
  openai: gpt-4o-mini
  anthropic: claude-haiku-3-20240307
overrides:
  - path: tests/
    calls_per_month: 100

Pfad-Overrides nutzen Longest-Prefix-Matching. So können Testcode, Agent-Pipelines und Batchjobs unterschiedliche Aufrufvolumen und Default-Modelle haben.

Test an zwanzig Open-Source-Codebases

Ich habe tokentoll auf zwanzig Open-Source-Codebases laufen lassen, darunter NadirClaw, PraisonAI, agentops, swarms, honcho und atomic-agents. Es hat in allen OpenAI-, Anthropic-, Google- und LiteLLM-Aufrufe erkannt. In Projekten, die Modellnamen zur Laufzeit aus der Config laden, haben die SDK-spezifischen Default-Modelle trotzdem Kostenschätzungen geliefert.

Änderungen seit der ersten Version

Spätere Versionen scannen auch JavaScript und TypeScript (geparst mit tree-sitter, das jetzt eine Laufzeitabhängigkeit ist) und erkennen weitere SDKs wie das Vercel AI SDK. Eine Policy in .tokentoll.yml ergänzt den PR-Kommentar um ein Urteil PASS, WARN oder FAIL und kann den Workflow fehlschlagen lassen. tokentoll läuft außerdem als MCP-Server. Die Preisdaten werden mit dem Paket ausgeliefert und lassen sich mit tokentoll update aus LiteLLM aktualisieren.

Nutzung

pip install tokentoll
tokentoll scan .
tokentoll diff HEAD~1

GitHub: github.com/Jwrede/tokentoll