LLM-Gateway-Architektur: Zentrales Routing, Budgetkontrolle und Provider-Fallbacks
Die meisten SaaS-Teams liefern ihr erstes AI-Feature auf dem direkten Weg aus. Der Anwendungscode ruft das Provider-SDK auf, der API-Key liegt in einer Umgebungsvariable, und alles funktioniert. Dann kommt ein zweites Feature, dann ein drittes, und plötzlich leben Budget-Checks, Retry-Logik und Modellauswahl an fünf Stellen mit fünf leicht unterschiedlichen Verhaltensweisen. LLM-Gateway-Architektur ist die strukturelle Antwort: ein einzelner zentraler Service, der zwischen deinem Anwendungscode und den Modell-Providern sitzt und Routing, Budget-Durchsetzung, Failover und Caching an einem Ort verantwortet. Dieser Beitrag zeigt, was ein Gateway abdecken sollte, wie du ein leichtgewichtiges auf einem Symfony-HTTP-Kernel baust und wie Consumer in Symfony und Next.js mit ihm sprechen.
Wir haben bereits über Token-Budgets und Kostenkontrolle und über Fallback-Muster bei Provider-Ausfällen als Patterns auf Anwendungsebene geschrieben. In diesem Beitrag geht es um das Infrastruktur-Pattern, das diese Sorgen komplett aus dem Anwendungscode verschwinden lässt.
Warum LLM-Gateway-Architektur verstreute Provider-Aufrufe schlägt
Der Direktaufruf-Ansatz scheitert in vorhersehbarer Reihenfolge. Zuerst geht die Kostentransparenz verloren. Wenn sechs Features zwei Provider mit unterschiedlichen Modellen aufrufen, kann niemand die Frage beantworten, welcher Tenant im letzten Monat am meisten gekostet hat, ohne Provider-Dashboards zusammenzustückeln, die nie für eine Zuordnung pro Kunde gedacht waren. Dann geht die Konsistenz verloren. Ein Feature wiederholt Anfragen bei Rate Limits, ein anderes nicht. Ein Team hat eine alte Modellversion gepinnt, ein anderes folgt der neuesten. Und schließlich werden Incidents teuer: Wenn ein Provider einen schlechten Nachmittag hat, patchst du Failover-Logik unter Druck in jede Aufrufstelle, statt einen einzigen Schalter umzulegen.
Ein Gateway dreht die Verantwortung um. Der Anwendungscode stellt eine einzige Art von Anfrage an einen internen Endpoint: Hier ist der Task-Typ, der Tenant, der Prompt. Das Gateway entscheidet, welcher Provider und welches Modell die Anfrage bedient, ob der Tenant noch Budget hat, ob eine gecachte Antwort gut genug ist und was passiert, wenn der primäre Provider Fehler zurückgibt. Jede Policy lebt in einem deploybaren Service mit einer Konfigurationsdatei, und ein Modellwechsel oder ein neuer Provider ist eine Gateway-Änderung, keine Suche durch Repositories.
Es gibt hier eine Build-or-Buy-Frage. Kommerzielle und Open-Source-Gateways existieren, und für manche Teams sind sie die richtige Wahl. Aber das Pattern ist klein genug, dass Selbstbauen realistisch ist, und Selbstbauen bedeutet Budget-Logik, die exakt zu deinem Abrechnungsmodell passt statt nur ungefähr. Die Entscheidung folgt derselben Logik wie jede Tech-Stack-Strategie: Kaufen, wenn das Problem generisch ist. Bauen, wenn die Policy dein Produkt ist.
Token-Budgets pro Tenant, die wirklich etwas durchsetzen
Budgetkontrolle ist das Feature, das das Gateway meistens rechtfertigt. Ausgabenlimits auf Provider-Seite schützen dich vor einem globalen Kostenausbruch, aber sie wissen nichts über deine Tenants. Das Gateway schon, denn jede Anfrage trägt eine Tenant-Kennung.
Das Durchsetzungsmodell, das sich in der Praxis bewährt, hat drei Ebenen. Ein hartes Limit ist die Obergrenze: Wenn der Token-Verbrauch eines Tenants für die Abrechnungsperiode sie überschreitet, lehnt das Gateway weitere Anfragen mit einem klaren, maschinenlesbaren Fehler ab, den die Anwendung in UI übersetzen kann. Ein weiches Limit liegt darunter, typischerweise bei 80 Prozent, und löst Alerts an dein Team und optional an den Kunden aus, damit niemand die Obergrenze erst durch Anstoßen entdeckt. Und ein monatlicher Reset bindet Verbrauchsfenster an deinen Abrechnungszyklus statt an den Kalender, was wichtig wird, sobald Tenants unterschiedliche Verlängerungsdaten haben.
Die Buchführung ist weniger trivial, als sie klingt. Token-Zahlen sind erst bekannt, nachdem die Antwort eingetroffen ist. Das Gateway verbucht den tatsächlichen Verbrauch also nachträglich, während es die Zulassungsentscheidung auf Basis der laufenden Summe trifft. Das lässt ein kleines Fenster, in dem parallele Anfragen desselben Tenants die Obergrenze leicht überschreiten können. Für Token-Budgets ist das in Ordnung. Reserviere pessimistisches Locking für Fälle, in denen Überschreitungen vertragliche Konsequenzen haben, und akzeptiere überall sonst Eventual Consistency. Eine PostgreSQL-Tabelle mit Schlüssel auf Tenant und Abrechnungsperiode, aktualisiert per atomarem Increment, bewältigt das in jeder Größenordnung, die ein mittelgroßes SaaS erleben wird.
Routing: das richtige Modell für jede Anfrage, nicht ein Modell für alles
Sobald jede Anfrage durch einen Service fließt, ist Routing keine hartcodierte Konstante mehr, sondern Policy. Das Gateway klassifiziert Anfragen nach Task-Typ, den der Aufrufer deklariert, und ordnet jedem Typ ein Modell zu, basierend auf drei Faktoren: Fähigkeit, Latenz und Kosten.
Die praktischen Gewinne sind unspektakulär und groß. Kurze Klassifikations- und Extraktionsaufgaben laufen auf kleine, günstige, schnelle Modelle. Langform-Generierung und komplexes Reasoning laufen auf Frontier-Modelle wie GPT-4o oder Claude Opus. Eine Zusammenfassungsaufgabe, die zwei Sekunden zusätzliche Latenz toleriert, läuft auf das qualifizierte Modell, das dieses Quartal am günstigsten ist. Wenn ein Provider ein besseres Modell zum halben Preis liefert, aktualisierst du ein Mapping, und jedes Feature profitiert noch am selben Nachmittag.
Routing-Policy gehört in versionierte Konfiguration, nicht in Code. Eine YAML-Map von Task-Typ auf eine geordnete Liste von Provider-Modell-Paaren, mit Timeouts und Kosten-Metadaten pro Eintrag, reicht aus. Die Reihenfolge dient gleichzeitig als Failover-Kette, und das ist das nächste Thema.
Failover bei 429 und 5xx, an einem Ort
Der Umgang mit Provider-Ausfällen ist der Bereich, in dem duplizierte Aufrufstellen am meisten schmerzen und Zentralisierung sich sofort auszahlt. Das Gateway beobachtet Response-Codes und wendet eine konsistente Policy an. Ein 429 bedeutet Backoff mit Jitter und, wenn der Druck anhält, Ausweichen auf das nächste Modell in der Kette. Ein 5xx oder ein Timeout bedeutet einmal wiederholen, dann Failover. Wiederholte Fehler innerhalb eines kurzen Fensters lösen einen Circuit Breaker aus, der allen Traffic für diesen Provider direkt zum Fallback leitet, bis eine Probe-Anfrage erfolgreich ist.
Weil die Routing-Tabelle bereits eine geordnete Liste pro Task-Typ definiert, ist Failover nur das Abarbeiten dieser Liste. Die wichtige Designentscheidung: Der Aufrufer sieht nichts davon. Die Anwendung hat eine Zusammenfassung angefragt; ob sie vom primären Modell oder mitten im Incident von einem sekundären Provider bedient wurde, ist eine Log-Zeile im Gateway, kein Thema für die Anwendung. Paare Provider bewusst so, dass der zweite Eintrag jeder Kette bei einem anderen Anbieter gehostet ist, sonst reißt ein regionaler Incident die ganze Liste auf einmal um.
Semantisches Caching mit pgvector
Exact-Match-Caching hilft bei LLM-Traffic weniger, als Teams hoffen, weil Prompts nutzerspezifische Inhalte enthalten und sich selten Byte für Byte wiederholen. Semantisches Caching löst das: Bette den eingehenden Prompt ein, suche nach zuvor beantworteten Prompts innerhalb eines Ähnlichkeitsschwellwerts und liefere die gespeicherte Antwort, wenn eine nah genug ist.
Da das Gateway für das Budget-Tracking ohnehin neben PostgreSQL läuft, ist pgvector günstig zu ergänzen. Speichere das Embedding, die Antwort, das Modell, das sie erzeugt hat, und eine TTL. Bei jeder Anfrage entscheidet eine indizierte Ähnlichkeitsabfrage zwischen einem Cache-Hit, der einen Bruchteil eines Cents kostet, und einem Modellaufruf, der hundertmal mehr kostet. Begrenze Cache-Einträge pro Tenant, außer Prompts sind wirklich tenant-neutral, und halte den Ähnlichkeitsschwellwert konservativ: Eine falsche, aber ähnliche Antwort beschädigt Vertrauen auf eine Weise, wie es ein Cache-Miss nie tut. FAQ-artige Features erreichen hier regelmäßig nennenswerte Hit-Rates, und die Technik ergänzt Prompt-Caching auf Provider-Seite, statt es zu ersetzen.
Logging für Kostenzuordnung und Debugging
Jede Anfrage durch das Gateway erzeugt einen strukturierten Log-Eintrag: Tenant, Task-Typ, angefragtes Modell, tatsächlich verwendetes Modell, Token-Zahlen rein und raus, Latenz, Cache-Hit oder -Miss, Failover-Ereignisse und berechnete Kosten. Dieser eine Stream beantwortet die Fragen, die sonst schmerzhaft sind. Welche Tenants sind auf ihrem aktuellen Plan unprofitabel. Welches Feature treibt die Token-Rechnung. War der Latenz-Spike letzten Dienstag dein Code oder der Provider. Wie hoch ist die echte Cache-Hit-Rate.
Retention braucht vom ersten Tag an eine Policy, denn Prompts enthalten Kundendaten. Logge Token-Zahlen und Metadaten unbegrenzt, aber speichere Prompt- und Response-Bodies kurzfristig, geschwärzt oder gar nicht, je nachdem, was deine Auftragsverarbeitungsverträge erlauben.
Eine leichtgewichtige Symfony-Implementierung
Das Gateway braucht nicht das volle Gewicht eines Frameworks, und es sollte es auch nicht haben. Ein schlanker Symfony-HTTP-Kernel mit einer Handvoll Routen hält kalte Pfade kurz und gibt dir gleichzeitig die HttpClient-Komponente, die Retries, Timeouts und Streaming bereits beherrscht.
Die Form des Service: ein POST-Endpoint, der Task-Typ, Tenant-ID, Messages und Optionen akzeptiert. Middleware authentifiziert den internen Aufrufer, lädt die Budget-Zeile des Tenants und lehnt Anfragen über dem Limit ab, bevor irgendein Provider kontaktiert wird. Redis übernimmt Rate Limiting pro Tenant und den Circuit-Breaker-Zustand, beides natürliche Einsatzfälle für seine atomaren Counter und TTLs. PostgreSQL hält die Budget-Ledger, den pgvector-Cache und das Request-Log. Die Provider-Adapter sind dünne Klassen, die das interne Anfrageformat auf die API des jeweiligen Anbieters abbilden, und dort normalisierst du auch das Streaming.
Definiere die API in einer OpenAPI-Spezifikation und behandle sie als Vertrag. Generiere oder schreibe zwei kleine Client-Bibliotheken dagegen: einen Symfony-HTTP-Client-Wrapper für deine PHP-Services und einen typisierten Fetch-Wrapper für Next.js-Server-Routen. Halte die Clients bewusst dumm. Jedes Stück Intelligenz, das sich in einen Client schleicht, widerspricht irgendwann dem Gateway, und du bist zurück bei verstreuter Policy. Der ganze Service landet bei wenigen tausend Zeilen, und genau das ist der Punkt: Das ist die Art fokussierter Infrastrukturkomponente, die wir in Custom-Software-Development-Projekten in Wochen bauen, nicht in Quartalen.
Wann du kein Gateway brauchst
Ein Feature, ein Provider, ein Team: Rufe den Provider direkt auf und behalte die Fallback-Logik im Anwendungscode. Das Gateway verdient seine Betriebskosten, wenn mindestens zwei dieser Punkte zutreffen: Mehrere Features konsumieren LLMs, du rechnest oder budgetierst pro Tenant, du nutzt mehr als einen Provider, oder die Modellwahl ändert sich so oft, dass Hardcoding wehtut. Das Pattern zu früh einzuführen bedeutet einen zusätzlichen Netzwerk-Hop und einen zu betreibenden Service für Policy, die du noch gar nicht hast. Es zu spät einzuführen bedeutet, fünf Aufrufstellen zu migrieren, die jeweils ihr eigenes Verhalten entwickelt haben. Die zweite Migration ist schlimmer. Wenn du unsicher bist, auf welcher Seite der Linie du stehst, macht ein Architektur-Review deiner aktuellen LLM-Aufrufstellen die Antwort meist offensichtlich.
Wo du anfängst
Baue das Gateway um das Problem herum, das heute wehtut. Sind es die Kosten, liefere zuerst Budget-Durchsetzung und Logging aus und ergänze Routing später. Ist es die Zuverlässigkeit, starte mit der Routing-Tabelle und dem Failover und schraube Budgets danach an. Die Architektur unterstützt schrittweise Einführung, weil jede Fähigkeit am selben Chokepoint hängt.
Wenn du ein LLM-Gateway für dein SaaS abwägst oder fünf direkte Provider-Integrationen und eine Token-Rechnung hast, die niemand erklären kann, helfen wir dir, die passend dimensionierte Version zu entwerfen und zu bauen. Schreib an hello@wolf-tech.io oder besuche uns auf wolf-tech.io.

