/v1/chat/completions-Endpunkt bereitstellt, Ihre eigenen Bearer-Token akzeptiert, Anfragen an Venice weiterleitet, Streaming-Antworten unterstützt und nützliche OpenTelemetry-Spans und -Metriken ausgibt.
Interessiert an der vollständigen Code-Implementierung? Schauen Sie sich das GitHub-Repo an.
Voraussetzungen
- Rust 1.92+
- Docker und Docker Compose
- Ein Venice-API-Schlüssel
curl- Grundlegende Vertrautheit mit Rust-Web-Services
Was wir bauen
Die Referenzimplementierung ist ein kleiner Rust-Dienst mit einigen klar abgegrenzten Teilen:| Teil | Was er tut |
|---|---|
| Axum-Router | Bedient /healthz und /v1/chat/completions |
| Auth-Extractor | Validiert Gateway-Bearer-Token gegen gehashte Schlüssel in Postgres |
| Rate-Limiter | Verwendet feste Fenster, die in Postgres gespeichert sind |
| Venice-Client | Leitet nicht-streaming und streaming Chat-Completion-Anfragen weiter |
| Telemetrie | Zeichnet GenAI-Spans, Token-Nutzung, Venice-Abrechnungskosten, Latenz und Streaming-Timings auf |
| Docker Compose | Führt Postgres und das Gateway lokal aus |

Erstellen des Rust-Dienstes
Beginnen Sie mit einem neuen Rust-Binärprojekt:Cargo.toml hinzu – Erklärungen sind im Codeausschnitt ergänzt:
Laden der Konfiguration
Das Gateway liest alles aus Umgebungsvariablen. Für Infrastrukturcode wie diesen sind Umgebungsvariablen eine gute Standardwahl, weil dieselbe Binärdatei lokal, in Docker Compose oder in einer gehosteten Umgebung ausgeführt werden kann, ohne dass ein separates Konfigurationsformat nötig ist. Zudem erlauben viele Anbieter, eigene Umgebungsvariablen als Secrets in ihrer Container-Laufzeit zu hinterlegen. Das ist oft deutlich sicherer, als etwas wiedotenv (oder dotenvy in Rust, da die ursprüngliche dotenv-Crate weitgehend veraltet ist) zu verwenden.
Erstellen Sie src/config.rs:
- Die Datenbank-URL
- Ihren Venice-API-Schlüssel
VENICE_BASE_URL zeigt auf https://api.venice.ai/api/v1, und CAPTURE_GENAI_CONTENT steht standardmäßig auf false, sodass Prompt-Inhalte nicht protokolliert werden, es sei denn, Sie aktivieren dies bewusst.
Der zweite Standardwert ist der wichtigere. Ein Gateway kann jeden durchfließenden Prompt und jede Antwort sehen, aber Observability sollte nicht automatisch zur Inhaltserfassung werden. In den meisten Produktionssystemen sind Token-Zahlen, Latenz, Modellnamen, Statuscodes und Abrechnungsmetadaten für den Betrieb ausreichend. Ganz allgemein kann das Protokollieren von Prompts und Konversationen in der Produktion nicht nur eine Datenschutzhaftung darstellen – es kann auch eine Speicherhaftung sein. Ihre Aufnahme führt zur Erstellung von Spans und Traces mit extrem hoher Kardinalität (also der Einzigartigkeit der Daten innerhalb eines Datensatzes). Das kann das Durchsuchen Ihrer Observability-Daten sehr kostspielig machen und die Performance beim Durchsuchen der Daten möglicherweise beeinträchtigen.
Erstellen des Datenbankschemas
Erstellen Sie als Nächstesmigrations/0001_api_keys.sql.
Wir speichern nur die ersten 12 Zeichen jedes Gateway-API-Schlüssels als Nachschlage-Präfix sowie den SHA-256-Hash des vollständigen Schlüssels. So kann das Gateway eine Kandidatenzeile schnell finden, ohne rohe Zugangsdaten zu speichern.
Der Präfix ist nicht geheim. Er existiert zur Indizierung. Der Hash ist das, was beweist, dass der Aufrufer den vollständigen Schlüssel vorgelegt hat. Dies ist die gleiche Grundform, die viele API-Schlüsselsysteme verwenden: Zeigen Sie den rohen Schlüssel einmal, speichern Sie eine nicht umkehrbare Darstellung und halten Sie einen kurzen Präfix für Nachschlage- und Support-Workflows bereit.
- API-Schlüssel werden niemals im Klartext gespeichert.
- Rate-Limit-Einstellungen müssen positiv sein.
- Widerrufene Schlüssel können nicht aktiv bleiben.
- Ein Rate-Limit-Fenster ist eindeutig durch Schlüssel, Startzeit und Fensterlänge identifiziert.
Bauen des Venice-Clients
Als Nächstes erstellen wirsrc/venice.rs. Der Client muss nur die vorgelagerte Chat-Completions-URL kennen, den Venice-API-Schlüssel und wie oft transiente Fehler wiederholt werden sollen.
Diesen Wrapper klein zu halten, ist Absicht – das Gateway sollte nicht die gesamte Venice-API neu implementieren. Auf grundlegender Ebene besteht die Aufgabe des Gateways darin, die serverseitige Anmeldung anzuhängen, einen Timeout anzuwenden, Anfragen, die sicher wiederholt werden können, erneut zu senden, und die vorgelagerte Antwort in einer Form zurückzugeben, die der Router weiterleiten kann.
EventSource aus derselben Anfrage:
serde_json::Value ist. Das ist eine bewusste Kompatibilitätsentscheidung. Wenn wir jedes mögliche Chat-Completion-Feld in Rust modellieren, müssen wir das Gateway jedes Mal aktualisieren, wenn die vorgelagerte API eine nützliche Option hinzufügt. Indem wir nur das parsen, was wir anderswo benötigen, lassen wir neuere Venice-Parameter ohne ein Gateway-Release durchreichen.
Gemeinsamen Anwendungszustand teilen
Erstellen Siesrc/state.rs:
PgPool ist bereits ein gemeinsam genutztes Pool-Handle, und Arc<Config> hält die Konfiguration ebenfalls günstig.
Dies gibt jedem Handler Zugriff auf dieselben drei Dinge: unveränderliche Konfiguration, gepoolte Datenbankverbindungen und den Venice-Client. Sie in einem AppState zu bündeln, erleichtert später auch das Testen, weil Handler ihre Abhängigkeiten über den Axum-State erhalten, statt Globals zu lesen.
Authentifizierung von Gateway-API-Schlüsseln
Der Client sendet seinen Gateway-Schlüssel so:src/auth.rs und implementieren Sie einen Axum-Extractor. Der Extractor erlaubt geschützten Handlern, zu deklarieren, dass sie einen authentifizierten Schlüssel benötigen:
- Bearer-Token parsen.
- Die ersten 12 Bytes als Schlüsselpräfix nehmen.
- Das vollständige Kandidaten-Token mit SHA-256 hashen.
- Die aktive Schlüsselzeile per Präfix laden.
- Gespeicherten Hash und Kandidaten-Hash in konstanter Zeit vergleichen.
AuthenticatedApiKey akzeptiert, kann die Authentifizierung nicht versehentlich innerhalb des Funktionskörpers überspringen; Axum muss diesen Wert konstruieren, bevor der Handler läuft. Das macht den geschützten Pfad leicht überprüfbar.
Feste-Fenster-Rate-Limits hinzufügen
Erstellen Siesrc/rate_limit.rs. Der Rate-Limiter verwendet eine SQL-Anweisung, um ein neues Fenster einzufügen oder das bestehende zu inkrementieren:
WHERE api_key_rate_limit_windows.request_count < $3-Klausel ist der entscheidende Teil. Wenn das Fenster bereits voll ist, aktualisiert Postgres die Zeile nicht und RETURNING liefert keine Zeile. Der Handler kann das in eine 429 Too Many Requests-Antwort mit einem Retry-After-Header umwandeln.
Ein festes Fenster ist nicht der ausgefeilteste Rate-Limiter, aber er ist leicht zu erklären, leicht zu inspizieren und für ein Gateway-Tutorial gut genug. Der Kompromiss ist, dass sich Traffic um die Fenstergrenzen herum stauen kann. Wenn Sie bei Skalierung ein gleichmäßigeres Verhalten benötigen, ist ein Token-Bucket- oder Sliding-Window-Limiter mit Redis-Backing ein natürlicher nächster Schritt.
Fehler im OpenAI-Stil zurückgeben
Erstellen Siesrc/error.rs und lassen Sie Anwendungsfehler IntoResponse implementieren:
Bauen des Routers
Nun können wir die HTTP-Routen insrc/router.rs verdrahten:
AuthenticatedApiKey zu verlangen. Wenn die Authentifizierung fehlschlägt, betritt Axum den Handler-Body niemals:
model, messages und stream. Alles andere im JSON-Body wird an Venice weitergereicht. Das hält das Gateway kompatibel mit Provider-Funktionen, die Sie später verwenden möchten.
Der Handler macht auch die beiden Antwortmodi explizit. Nicht-streaming Anfragen warten, bis Venice eine vollständige JSON-Antwort zurückgibt, und zeichnen dann die Antwortmetadaten auf, bevor sie die Bytes stromabwärts senden. Streaming-Anfragen geben sofort mit einem text/event-stream-Body zurück, der von einem async Stream unterstützt wird. Diese Aufteilung hält den nicht-streaming Pfad einfach und gibt dem Streaming-Pfad genug Kontrolle, um Chunks während des Durchflusses zu beobachten.
Streaming-Antworten unterstützen
Streaming-Chat-Completions verwenden Server-Sent Events. Venice sendet SSE-Daten, und das Gateway leitet diese Daten an den Client zurück. Das Gateway sollte es vermeiden, den gesamten Stream zu puffern, weil das den Zweck des Streamings zunichtemachen würde. Nutzern ist die Zeit bis zum ersten Token wichtig, nicht nur die Zeit bis zum letzten Token. Indem jedes vorgelagerte Event beim Eintreffen weitergeleitet wird, können Clients Teilausgaben rendern, während das Modell noch generiert. Erstellen Siesrc/sse.rs:
data: ...-Events an, und der Stream endet mit data: [DONE].
Der Stream-Beobachter ist auch der Ort, an dem wir Metadaten sammeln können, ohne zu ändern, was der Client sieht. Jeder Chunk wird im SSE-Format weitergeleitet, aber das Gateway kann trotzdem auf Antwort-IDs, Finish-Reasons, Token-Nutzung, Kostenfelder und Timing-Informationen achten, während diese Chunks durchfließen.
Aufzeichnen von GenAI-Telemetrie
Gateways sind nützlich, weil jede Anfrage einen zentralen Ort passiert. Das macht sie zu einem großartigen Ort, um Modell, Latenz, Token-Nutzung, Finish-Reasons, Abrechnungskosten und Streaming-Timings aufzuzeichnen. Erstellen Siesrc/telemetry.rs und beginnen Sie mit dem Parsen der Anfrage:
id der Venice-Antwort:
Server starten
Nun verdrahten wir alles insrc/main.rs:
- Konfiguration lesen.
- Telemetrie initialisieren.
- Mit Postgres verbinden.
- SQLx-Migrationen ausführen.
- Gemeinsamen App-State bauen.
- Axum-Server starten.
docker compose up den gesamten Stack in einen funktionierenden Zustand bringen kann. In einem größeren Produktions-Deployment bevorzugen Sie es möglicherweise, Migrationen als separaten Release-Schritt auszuführen, sodass Schemaänderungen überprüft und angewendet werden, bevor neue Gateway-Instanzen starten.
Einen lokalen Gateway-Schlüssel seeden
Für die lokale Entwicklung erstellen Siescripts/seed_api_key.sh. Das Skript fügt einen Gateway-API-Schlüssel in Postgres ein, indem es dessen Präfix und SHA-256-Hash speichert:
Lokal ausführen
Für die lokale Ausführung verwenden wir Docker Compose, um sowohl das Gateway als auch Postgres zu starten. Dadurch bleibt das Tutorial reproduzierbar: Leser benötigen keine manuell konfigurierte Datenbank, und das Gateway kann dieselbeDATABASE_URL-Form verwenden, die es in einem containerisierten Deployment verwenden würde.
-d-Flag im Hintergrund ausführen können, falls Sie Ihr Terminal danach für andere Dinge nutzen möchten (und dann docker compose down verwenden, um ihn zu entfernen).
Seeden Sie in einem anderen Terminal den Entwicklungs-Gateway-Schlüssel:
5432 läuft, entfernen Sie das Host-Port-Mapping für den Compose-Postgres-Service. Das Gateway muss Postgres nur im internen Docker-Netzwerk erreichen.
Das Wichtige ist, dass der Venice-API-Schlüssel nur in die Gateway-Umgebung gehört. Client-Anfragen sollten den geseedeten Gateway-Schlüssel verwenden. Diese Trennung ist der eigentliche Sinn, ein Gateway vor den Modell-Provider zu setzen.
Das Gateway testen
Prüfen Sie zunächst die Gesundheit:Dieses Gateway erweitern
Dieses Gateway ist bewusst klein, gibt Ihnen aber ein solides Fundament. Gute nächste Schritte sind:- Budgets pro Subjekt und monatliche Ausgabenlimits hinzufügen.
- Unterstützung mehrerer vorgelagerter Provider hinter derselben OpenAI-kompatiblen API.
- Anfragemetadaten für Audit-Logs speichern, wobei das Logging von Prompts standardmäßig deaktiviert bleibt.
- Eine Admin-API zum Erstellen, Widerrufen und Rotieren von Gateway-Schlüsseln hinzufügen.
- Modell-Allowlists pro API-Schlüssel hinzufügen.
- Redis oder einen anderen gemeinsamen Speicher hinzufügen, wenn Sie ein Rate-Limiting mit geringerer Latenz über viele Gateway-Instanzen benötigen.