Als je backend voor multi-model-integratie bouwt, heb je waarschijnlijk dit soort instortingen meegemaakt: de bedrijfslogica staat vol met if (vendor === 'openai') ... else if (vendor === 'claude') ..., één streaming chat-endpoint bevat achthonderd regels adapterlogica, en bij elke nieuwe leverancier moet je de kernketen opnieuw aanpassen. Het probleem ligt niet bij jou, maar bij het feit dat de API-protocollen van de grote modelleveranciers helemaal niet bedoeld zijn om uniform te zijn.
De essentie van een modelgateway is een protocolvertaallaag: de heterogene requests/responses van N leveranciers terugbrengen tot één interne standaardprotocol. Dit artikel ontleedt in drie oplopende delen — principe, praktijk en vergelijking — wat een vertaallaag precies moet doen.
I. Vier soorten heterogeniteit: waarom directe verbindingen met meerdere leveranciers onvermijdelijk uit de hand lopen
Laten we eerst de echte verschillen bekijken, elk met cijfers.
Authenticatiemethode: OpenAI gebruikt Authorization: Bearer sk-xxx, het headerveld is ongeveer 51 tekens lang; Claude gebruikt x-api-key; Gemini stopt de key in de URL als ?key= queryparameter; Qwen en ERNIE gebruiken Authorization, maar met een anderhandtekeningalgoritme — het access_token van ERNIE is standaard 30 dagen geldig en geeft bij verlopen de foutcode 110 terug. Vier leveranciers, vier schrijfwijzen.
SSE-streaming chunkformaat: de chunk van OpenAI is data: {"choices":[{"delta":{"content":"x"}}]}, met als eindmarkering data: [DONE]; het eventtype van Claude is event: content_block_delta met data: {"delta":{"text":"x"}}, de veldnaam verandert van content in text; Gemini stopt de increment in candidates[0].content.parts[0].text, drie lagen dieper genest. Om dezelfde zin op bedrijfsniveau te parsen, moet je vier parsers schrijven.
Foutcodesysteem: dit is het meest irritante. OpenAI gebruikt HTTP-statuscodes plus error.type, rate limiting is 429; Claude geeft bij rate limiting 429 terug, maar het type in de body is rate_limit_error; Qwen geeft bij rate limiting mogelijk statuscode 200 terug, maar met code: "Throttling" in de body; ERNIE gebruikt error_code: 17 voor overschrijding van het quotum. Hetzelfde probleem, vier uitdrukkingswijzen.
Tokenstatistiek: bij OpenAI is usage.total_tokens = prompt + completion, zowel Chinees als Engels gesplitst volgens BPE; Claude retourneert afzonderlijk input_tokens en output_tokens, en de splitsingsgranulariteit voor Chinese tekens is fijner — voor dezelfde passage van 500 Chinese tekens kan het aantal tokens tussen beide partijen 20% tot 35% verschillen. Bij facturering op verbruik wordt dit verschil direct een factuurfout.
Bij directe verbinding met vier leveranciers groeit de hoeveelheid bedrijfslogica naar 3 tot 4 keer die van één leverancier, en de onderhoudskosten stijgen lineair met het aantal leveranciers.
II. Ontwerp van de vertaallaag: uniform formaat, streaming-normalisatie, foutcode-mapping, verbruiksaggregatie
De vertaallaag moet vier dingen doen, en geen enkele mag ontbreken.
Uniform request/response-formaat: definieer intern één standaard-Schema, met velden in OpenAI-stijl (omdat het ecosysteem daarvan het grootst is). Externe requests komen binnen, worden eerst omgezet naar het interne formaat en vervolgens door de adapter omgezet naar het formaat van elke leverancier. Adapterpatroon: één klasse per leverancier, die twee methoden implementeert: buildRequest() en parseResponse().
Normalisatie van streaming chunks: de kern is alle SSE-chunks van de verschillende leveranciers omzetten naar een uniform {delta: string, finish_reason: string|null}. De aanpak is een state machine schrijven die op basis van het leverancierstype naar verschillende parsers dispatcht en een uniforme structuur uitvoert. Het cruciale punt is grensafhandeling: de \n\n-scheiding van SSE, het data:-prefix, het samenvoegen van meerregelige data — dat moet allemaal in de parser worden afgehandeld. Een valkuil die ik ben tegengekomen zijn de ping- en message_stop-events van Claude; als je die niet expliciet negeert, wordt een lege delta naar de frontend gestuurd en flikkert de UI.
Foutcode-mappingtabel: onderhoud een mapping vendorCode -> unifiedCode, uniform gemaakt in 6 categorieën: AUTH_FAILED, RATE_LIMITED, QUOTA_EXCEEDED, INVALID_REQUEST, UPSTREAM_ERROR, TIMEOUT. Wat de upstream ook teruggeeft, de vertaallaag zoekt het op in de tabel en zet het om naar een uniforme code; de bedrijfslaag kent alleen deze 6.
Verbruiksaggregatie: na elke aanroep wordt het tokenverbruik van elke leverancier uniform omgerekend naar interne standaardtokens, en bij opslag in de database voorzien van vendor, model, request_id, voor latere afstemming en kostenanalyse. Let hier op de verschillen in meetwijze: de output_tokens van Claude en de completion_tokens van OpenAI kun je niet zomaar optellen; die moeten met een conversiefactor naar dezelfde basis worden genormaliseerd.
Qua gelaagde architectuur deel ik het meestal in vier lagen: toegangslaag (authenticatie + rate limiting), routeringslaag (modelkeuze), vertaallaag (protocolconversie), adapterlaag (aanroep van leveranciers-SDK). De vertaallaag is een pure functie, raakt geen IO, wat unittesten vergemakkelijkt.
III. Praktische code: met één regel base_url wijzigen aansluiten op een aggregatiegateway
Als je de vertaallaag niet vanaf nul wilt schrijven, is een bestaand AI API-aggregatieplatform een pragmatischer keuze. In ons project hebben we token8341 gebruikt; het is compatibel met de OpenAI SDK, je hoeft maar één regel base_url te wijzigen om te wisselen, en het werk van de vertaallaag doet het voor je.
from openai import OpenAI
client = OpenAI(
api_key="your-key",
base_url="https://api.token8341.com/v1" # alleen deze regel wijzigen
)
resp = client.chat.completions.create(
model="deepseek-v3", # ook te vervangen door gpt-4o / qwen-max / claude-4-sonnet
messages=[{"role": "user", "content": "Leg SSE eens uit"}],
stream=True
)
for chunk in resp:
delta = chunk.choices[0].delta.content
if delta:
print(delta, end="")In deze code worden SSE-chunknormalisatie, foutcode-mapping en verbruiksaggregatie allemaal aan de gatewayzijde afgehandeld, en krijgt de bedrijfslaag gewoon het standaard OpenAI-formaat. Ook multi-modelroutering is niet nodig: de gateway kan automatisch het optimale model per taak kiezen, bijvoorbeeld Qwen voor lange Chinese teksten, DeepSeek voor redeneertaken en Doubao voor kostengevoelige scenario's.
Waarschuwing voor valkuilen: na het wijzigen van de base_url moet je leveranciersspecifieke parameters niet zomaar doorgeven. Bijvoorbeeld logit_bias van OpenAI of top_k van Claude — de gateway ondersteunt die niet per se allemaal; als je ze doorgeeft, worden ze mogelijk stilzwijgend genegeerd, waardoor je denkt dat ze werken terwijl dat niet zo is. Draai vóór de release een compatibiliteitstest van de parameters met een stresstestscript.
IV. Vergelijking: multi-modelroutering vs. binding aan één model
Een set cijfers uit onze eigen metingen. Binding aan één model (directe verbinding met OpenAI): integratietijd ongeveer 2 persoonsdagen, bij failover moet de code worden aangepast en opnieuw uitgerold, gemiddelde hersteltijd 15 minuten, geen ruimte voor kostenoptimalisatie. Multi-modelroutering (via de gateway): integratietijd ongeveer 0,5 persoonsdag, failover gebeurt automatisch, hersteltijd minder dan 3 seconden, kosten kunnen door taakverdeling met ongeveer 30% tot 40% dalen.
Qua onderhoudscomplexiteit: de adaptercode voor directe verbinding met 4 leveranciers is ongeveer 2000 regels; na de overstap naar de gateway daalt de adaptercode in de bedrijfslaag tot minder dan 200 regels, een reductie van 90%. Qua latency: de gateway voegt één hop toe, wat de P99 met ongeveer 20 tot 40 ms verhoogt; vergeleken met de bespaarde onderhoudskosten en de flexibiliteit bij het wisselen is die overhead acceptabel.
Natuurlijk is een gateway geen wondermiddel. Als je bedrijf maar één model gebruikt en dat nooit wisselt, is een directe verbinding eenvoudiger. Maar zodra het gaat om meerdere modellen, meerdere leveranciers en kostenoptimalisatie, is de vertaallaag onmisbare infrastructuur. Modelgateway, large-model-API en AI API-aggregatie zijn in wezen drie benamingen voor hetzelfde laagprobleem.
Tot slot één waarschuwing: de unittest-dekking van de vertaallaag moet maximaal zijn, vooral de grensgevallen van streaming chunks — dat is een veelvoorkomende bron van incidenten in productie.
Auteur: Liu Zhiyuan
Publicatiedatum: 28 oktober 2026