Einige Leute haben meinen Beitrag darüber gelesen, wie der API-Key der ersten Version von GeminiOmni in siebzehn Minuten extrahiert wurde und haben um die vollständige Version der Proxy-Architektur gebeten. Dieser Beitrag ist die Antwort.
Wenn Sie etwas entwickeln, das eine kostenpflichtige KI-API aufruft – Gemini, OpenAI, Anthropic, Replicate, alles mit einem nutzungsabhängigen Schlüssel – dann steht das folgende Muster zwischen Ihnen und einer Dienstagmorgen-Überraschung durch Ihre Abrechnungsalarme. Es ist nicht kompliziert. Es ist tragend.
Ich werde die Architektur erläutern, die vier konkreten Dinge, die Sie richtig machen müssen, und die Edge Cases, auf die ich beim Betrieb in der Produktion über einen Monat gestoßen bin.
Das Kernmuster, skizziert
Die Struktur ist unabhängig vom verwendeten KI-Anbieter gleich:
┌─────────────────┐ ┌──────────────────────┐ ┌──────────────┐
│ │ │ │ │ │
│ Browser │ │ Ihre API-Route │ │ Anbieter │
│ (nicht │ ───→ │ (Server, hat Key) │ ───→ │ (Gemini / │
│ vertrauens- │ │ │ │ OpenAI...) │
│ würdig) │ │ - Auth-Prüfung │ │ │
│ │ │ - Rate Limit │ └──────────────┘
│ - Prompt │ │ - Kontingentprüfung │
│ - Upload │ │ - API-Key anhängen │
│ │ │ - Anbieter aufrufen │
│ │ │ - Ergebnis zurück │
│ │ ←─── │ │
└─────────────────┘ └──────────────────────┘Der Punkt ist, dass der Browser einen eingehenden Prompt und ein ausgehendes Ergebnis sieht. Er hat keine Ahnung, welches Modell aufgerufen wurde, wie der API-Key lautet, wie der Upstream-Anbieter aussieht oder wie viel der Aufruf gekostet hat. Das ist die richtige Menge an Informationen für den Browser.
Was "richtig" konkret bedeutet
Vier Dinge müssen zutreffen, damit diese Architektur Sie tatsächlich schützt. Fehlt nur eines davon, bricht der Schutz zusammen.
1. Der API-Key lebt ausschließlich auf dem Server
In Next.js-Begriffen: Die Env-Variable darf NICHT mit NEXT_PUBLIC_ beginnen. In Laufzeitbegriffen: Nur Code, der in einem Route-Handler (app/api/.../route.ts), einer Server-Komponente oder einer Server-Aktion läuft, berührt den Schlüssel.
// app/api/ai/generate/route.ts — nur Server
const GEMINI_KEY = process.env.GEMINI_API_KEY; // ✅ nur Server
if (!GEMINI_KEY) throw new Error('GEMINI_API_KEY nicht gesetzt');Eine Startprüfung, die die App zum Absturz bringt, wenn GEMINI_API_KEY fehlt, ist ihr Gewicht in Gold wert. Der schlimmste Fehlermodus ist, dass die App still ohne Schlüssel läuft und auf einen Standard zurückfällt, der Ihre Ersparnisse leert.
2. Der Browser sieht niemals Roh-Antworten des Anbieters
Wenn der Anbieter-Aufruf zurückkommt, wählen Sie aus, was zurückgesendet wird. Der Browser muss NICHT wissen:
- Welches genaue Modell aufgerufen wurde (Veo 3.1 Fast vs Veo 3.1 Standard)
- Die Anfrage-ID des Anbieters (nützlich für Support, nutzlos für Clients)
- Rate-Limit-Header vom Anbieter
- Die Token-Anzahl oder die Kosten des Aufrufs
Warum das wichtig ist: Informationsoffenlegung potenziert sich. Wenn Ihr Browser "model: gemini-2.5-flash, tokens: 4096" sehen kann, kann ein Scraper Ihr Frontend mit der Preisseite Ihres Anbieters abgleichen und Ihre Stückkosten zurückrechnen. Das ist nicht katastrophal, aber auch nicht großartig. Entfernen Sie es.
// Nur das zurückgeben, was die UI braucht
return Response.json({
imageUrl: result.generated_images[0].url,
watermarked: !user.isPro,
// Nicht zurückgeben: Modellname, verwendete Tokens, Roh-Antwort des Anbieters
});3. Auth-, Rate-Limit- und Kontingentprüfung erfolgen VOR dem Anbieter-Aufruf
Der Proxy ist nicht nur ein Durchgang. Er ist der Ort, an dem Sie die Regeln durchsetzen:
// Innerhalb der API-Route, in dieser Reihenfolge:
1. Authentifiziere die Anfrage (Session-Cookie, JWT, was auch immer).
Anonyme Anfragen sind für Free-Tier-Funktionen erlaubt, werden aber per IP verfolgt.
2. Rate-Limit nach Benutzer-ID (oder IP für anonyme Nutzer).
GeminiOmni: 10 Anfragen/min anonym, 60 Anfragen/min Pro, 200 Anfragen/min Team.
3. Prüfe das Kontingent des Benutzers für die spezifische Operation.
Kostenlose Benutzer haben 5 Video-Generierungen pro Monat – prüfe die Datenbank
VOR dem teuren Aufruf, nicht danach.
4. Erst jetzt – rufe den Anbieter auf.
5. Erhöhe die Nutzungszähler bei Erfolg.Die Reihenfolge ist wichtig. Wenn Sie zuerst den Anbieter aufrufen und dann das Kontingent prüfen, haben Sie für die Inferenz bereits bezahlt, unabhängig davon, ob der Benutzer dazu berechtigt war oder nicht. Jede Kontingentprüfung erfolgt im Voraus.
4. Der Proxy hat seine eigene Beobachtbarkeit
Protokollieren Sie jeden Anbieter-Aufruf vom Proxy mit: Zeitstempel, Benutzer-ID, Route, Modell, Eingabegröße, Ausgabegröße, Latenz, Erfolg/Misserfolg und den ungefähren Dollarkosten. Dies ist Ihre Abrechnungsquittung und Ihre Debugging-Oberfläche.
await logUsage({
userId,
route: '/api/ai/generate',
model: 'gemini-2.5-flash-image',
inputBytes: prompt.length + imageBytes,
outputBytes: result.length,
latencyMs: Date.now() - start,
costUsd: estimateCost(model, inputBytes, outputBytes),
status: 'success',
});Wenn Ihr Dienstagmorgen-Abrechnungsalarm auslöst, weil sich die Ausgaben über Nacht verdoppelt haben, ist diese Tabelle das, was Sie per SQL abfragen, um herauszufinden, welcher Benutzer, welche Route, welches Modell. Ohne sie raten Sie nur.
Edge Cases, auf die ich beim Betrieb in der Produktion gestoßen bin
Ein Monat Betrieb des Proxys in der Produktion hat einige nicht offensichtliche Dinge zutage gefördert.
Streaming-Antworten benötigen Streaming-Proxies. Der streamGenerateContent-Endpunkt von Gemini gibt einen Stream von partiellen Generierungen zurück. Wenn Sie in Ihrem Proxy naiv response.json() aufrufen, puffern Sie den gesamten Stream und verlieren den Latenzvorteil. Die Lösung besteht darin, den Upstream-Stream als ReadableStream durch Ihren Proxy zu leiten, was bedeutet, dass Ihre API-Route new Response(stream) statt Response.json(...) zurückgibt. Dies lohnt sich – die wahrgenommene Latenz bei der Videogenerierung sinkt von "90 Sekunden warten" auf "sofort etwas passieren sehen".
Große Datei-Uploads benötigen Direkt-Uploads in den Speicher. Wenn ein Benutzer ein 50MB-Video für die Bild-zu-Video-Generierung hochlädt, möchten Sie nicht, dass diese 50MB durch Ihre Next.js-API-Route geleitet werden. Das Muster ist: Der Client lädt direkt über eine vorab signierte URL in Cloud Storage / R2 hoch und sendet dann nur die URL an Ihren Proxy. Der Proxy übergibt die URL an den "media URI"-Parameter von Gemini, und die Bytes berühren niemals Ihren Server. Dies reduziert Ihre ausgehende Datenübertragung bei upload-intensiven Pfaden um ~95%.
Fehler vom Upstream müssen übersetzt werden. Gemini gibt Fehler wie "PROMPT_BLOCKED" oder "QUOTA_EXCEEDED" oder "INVALID_ARGUMENT" zurück. Der Browser muss diese Zeichenfolgen nicht sehen – sie sind verwirrend und geben Details zum Anbieter preis. Übersetzen Sie jeden in eine benutzerfreundliche Nachricht: "Ihr Prompt wurde durch die Sicherheitsfilter blockiert", "Sie haben Ihre kostenlosen Generierungen für diesen Monat aufgebraucht", "Dieses Bild scheint beschädigt zu sein." Eine Drei-Zeilen-Nachschlagetabelle.
Die Kostenberechnung erfolgt pro Aufruf, nicht pro Token. Ich habe versucht, die Dollarkosten jedes Aufrufs durch Zählen der Token zu schätzen. Dann stellte ich fest, dass Veo nach SEKUNDEN des ausgegebenen Videos abrechnet, Imagen nach BILDERN und Nano Banana 2 einen vierstufigen Preis basierend auf der Auflösung hat. Die Kostenfunktion für den Proxy ist eine Switch-Anweisung, keine Multiplikation. Es gibt keinen sauberen Weg, dies zu umgehen – jeder Anbieter rechnet anders ab, und Sie müssen jedes kodieren.
Wie das in der GeminiOmni-Codebasis aussieht
Jeder Modellaufruf in /tools/text-to-video, /tools/image-to-video, /tools/nano-banana-edit, /tools/pdf-chat und dem Live-Chat unter /chat durchläuft app/api/ai/generate/route.ts und app/api/ai/query/route.ts. Zwei Endpunkte. Zusammen etwa 400 Zeilen Code. Die vier oben genannten Prüfungen werden alle durchgesetzt, bevor ein Anbieter-Aufruf meinen Server verlässt.
Ich habe das Modell in der benutzerorientierten UI benannt, aber nicht in der API-Antwort. Das Badge unter jedem generierten Clip sagt "Veo 3.1 Fast" – das stammt von einer serverseitigen Suche zum Rendern, nicht von der API-Antwort. Das ist die Art von Detail, die albern klingt, bis Ihnen klar wird, dass jeder String, den die API zurückgibt, nur einen Screenshot davon entfernt ist, ein Ziel zu sein.
Die ehrliche Zusammenfassung
Das Proxy-Muster ist nicht aufregend. Es enthält keine cleveren Algorithmen. Es ist einfach das absolute Minimum, das erforderlich ist, um eine kostenpflichtige KI-API im öffentlichen Internet zu betreiben, ohne Geld an Leute zu verlieren, die nicht bezahlt haben.
Aber es ist das absolute Minimum. Jeder unabhängige Entwickler, den ich kenne und der ohne es gestartet ist, hat die Lektion auf die gleiche Weise gelernt: ein Abrechnungsalarm zu einer ungünstigen Stunde, eine panische Sperrung des Schlüssels und ein langer Abend mit Umschreiben des Datenflusses. Günstiger, es von Tag eins an zu tun.
Weiterlesen
- Das Erste, was kaputt ging, als ich GeminiOmni veröffentlichte
- 1M Token Kontext ist nicht kostenlos – echte Kosten pro Seite des Gemini-PDF-Chats
- GeminiOmni Preise – wie der Proxy die Stufen gestaltet
— Lena
