SiCore TokenWorks
LLM APIAPI GatewayAggregationIntegration

Hoe je tussen LLM-providers kunt wisselen zonder je code te wijzigen

SiCore TokenWorks Team·2026-09-03

De meeste mensen beginnen een project niet met nadenken over provider-portabiliteit. Ze pakken een sleutel van welke leverancier dan ook het makkelijkst is om je voor aan te melden, schrijven de integratie en leveren op. Een paar maanden later lezen ze een benchmark, of openen een factuur, of lopen tegen een rate limit aan, en dan willen ze iets anders proberen. Dat is het moment waarop de pijn zich laat zien, omdat ze aannames over de eerste provider in de HTTP-laag hebben ingebakken.

De oplossing is niet een of andere abstractiebibliotheek. Het is een wire format. De OpenAI chat completions API is stilzwijgend het standaardprotocol geworden voor communicatie met LLMs, en zodra je daartegen schrijft, is van provider wisselen vooral een kwestie van twee strings veranderen.

De API die iedereen heeft gekopieerd

OpenAI definieerde een simpel contract: je POST JSON naar /v1/chat/completions, en je krijgt een choices[0].message.content-string terug. Het request ziet er zo uit:

curl https://api.openai.com/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $OPENAI_API_KEY" \
  -d '{
    "model": "gpt-4o",
    "messages": [{"role": "user", "content": "Explain DNS in one paragraph"}]
  }'

Bijna elk groot model spreekt nu dit dialect. DeepSeek, Qwen, ERNIE, Doubao en de anderen bieden allemaal een endpoint dat dezelfde body accepteert en dezelfde vorm teruggeeft. Dat betekent dat je clientcode er niet om geeft wie er aan de andere kant zit. Je kunt de model-string en het endpoint vervangen en verder verandert er niets.

Hier is dezelfde call gericht op een aggregator die meerdere van deze modellen onder één base URL aanbiedt:

curl https://api.token8341.com/v1/chat/completions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKENWORKS_API_KEY" \
  -d '{
    "model": "deepseek-chat",
    "messages": [{"role": "user", "content": "Explain DNS in one paragraph"}]
  }'

Eén client, vele modellen

In Python instantieer je normaal de OpenAI SDK één keer en geef je per request een modelnaam mee. Als je provider de compatibele API ondersteunt, stel je base_url één keer in en laat je al het andere identiek:

from openai import OpenAI

client = OpenAI(
    base_url="https://api.token8341.com/v1",
    api_key="sk-your-tokenworks-key",
)

def ask(model: str, prompt: str) -> str:
    resp = client.chat.completions.create(
        model=model,
        messages=[{"role": "user", "content": prompt}],
    )
    return resp.choices[0].message.content

# Same function, three different model families.
print(ask("gpt-4o", "Summarize this log for me."))
print(ask("deepseek-chat", "Summarize this log for me."))
print(ask("qwen-max", "Summarize this log for me."))

De functie verandert niet. De model-string verandert. Dat is de hele truc. Streaming werkt op dezelfde manier:

stream = client.chat.completions.create(
    model="deepseek-chat",
    messages=[{"role": "user", "content": "Write a haiku about databases"}],
    stream=True,
)
for chunk in stream:
    delta = chunk.choices[0].delta.content or ""
    print(delta, end="")

Function calling, JSON mode en embeddings rijden allemaal op hetzelfde compatibele oppervlak, dus je bent niet beperkt tot gewone chat wanneer je wisselt.

Wat wisselen je niet oplevert

Ik wil duidelijk zijn over het deel dat niet gratis verandert. Het transport is portabel; de modellen niet.

Verschillende modellen reageren verschillend op prompts. Een system prompt die is afgestemd op GPT-4o kan ondermaats presteren op een reasoning-model dat een andere structuur wil. Context windows verschillen, soms behoorlijk: het ene model neemt misschien 128k tokens, terwijl een ander 32k neemt. Tokenizers verschillen ook, dus hetzelfde document kost een verschillend aantal tokens per model. Maximale outputlengte is nog een knop die per model geldt, niet per protocol.

Dus "wisselen zonder code te wijzigen" is eigenlijk "wisselen zonder je HTTP-client te herschrijven." Je bent jezelf nog steeds een evaluatieronde verschuldigd voordat je productieverkeer op een nieuw model richt. Het goede nieuws is dat de compatibele API die evaluatie goedkoop maakt om te draaien, omdat de harness niets meer is dan een loop over modelnamen.

Een praktisch patroon

Wat ik in zijprojecten daadwerkelijk doe, is een kleine configuratietabel bijhouden, geen codewijzigingen:

MODELS = {
    "fast": "deepseek-chat",
    "smart": "qwen-max",
    "frontier": "gpt-4o",
}

def run(task, prompt):
    return ask(MODELS[task], prompt)

Wil je een goedkoper model proberen voor de fast-lane? Verander één regel. Wil je terugvallen op een tweede provider wanneer de eerste rate limited is? Vang de exception op en probeer opnieuw met de volgende model-string. Niets hiervan raakt de opbouw van het request.

Dat fallback-patroon is waar een multi-model endpoint zijn geld waard is. Met één base URL en één key kun je routeren, opnieuw proberen en A/B-testen across providers, allemaal via dezelfde twee regels client-setup die je op dag één hebt geschreven.

Response-eigenaardigheden om op te letten

Het transport is compatibel, maar een paar verschillen op response-niveau lekken alsnog door. Reasoning-modellen retourneren soms een extra veld zoals reasoning_content naast het normale bericht. Code die choices[0].message.content leest, blijft sowieso werken, maar je wilt die reasoning-tekst misschien tonen in een debug-weergave. Foutmeldingen variëren ook: de ene provider retourneert een behulpzame insufficient_quota-string, een andere retourneert een kale 429 zonder body. Als je retries bouwt, behandel elke 429 of 5xx als "back off en probeer opnieuw" in plaats van vendor-specifieke tekst te parsen.

Niets hiervan is een reden om aan één provider vast te blijven zitten. Het is een reden om je foutafhandeling generiek te houden en je modelkeuze een variabele te maken.

Dit is het standpunt dat SiCore TokenWorks inneemt: een OpenAI-compatibel endpoint op https://api.token8341.com/v1 waar de model-string het enige is dat verandert wanneer je wisselt tussen GPT-4o, Claude, Gemini, DeepSeek, Qwen, ERNIE, Doubao, Spark en Pangu.