Claude Opus 5 is now live on CometAPI →

Slik bygger du AI-apper som ikke er låst til én leverandør

CometAPI
AnnaJun 7, 2026
Slik bygger du AI-apper som ikke er låst til én leverandør

Leverandørinnelåsing i AI-apper skjer vanligvis ikke på én gang. Den sniker seg inn — et direkte import openai her, et hardkodet modellnavn der, et responsfelt du parser uten å sjekke om andre leverandører returnerer det samme. Seks måneder senere betyr bytte av leverandør å skrive om halve backend-en.

De fire måtene innelåsing skjer på

De fleste utviklere tror innelåsing betyr "Jeg bruker OpenAI SDK." Det er den minst farlige typen. De virkelige fellene er mer subtile:

Type innelåsingHvordan det skjerKonsekvens
SDK-innelåsingfrom openai import OpenAI overaltÅ bytte SDK betyr å måtte endre hver fil
Modellnavn-innelåsingmodel="gpt-4o" hardkodet i forretningslogikkenHver modellendring er en kodeendring
Parameter-innelåsingBruk av logprobs, n>1, eller reasoning_effortDisse finnes ikke på Claude eller Gemini
Responsformat-innelåsingParsing av leverandørspesifikke responsfeltUlike leverandører returnerer ulike strukturer

Målet er ikke å eliminere alle disse — noen er akseptable avveininger. Målet er å vite hvilke du påtar deg.

Bruk et OpenAI-kompatibelt endepunkt som abstraksjonslag

Den reneste måten å unngå SDK-innelåsing på er å bruke ett OpenAI-kompatibelt endepunkt som ruter til flere leverandører. Du beholder OpenAI SDK, men backend kan være hvilken som helst leverandør.

CometAPI gjør dette — ett endepunkt, én nøkkel, 500+ modeller på tvers av OpenAI, Anthropic, Google, DeepSeek, xAI og andre:

import osfrom openai import OpenAIfrom dotenv import load_dotenv​load_dotenv()​api_key = os.environ.get("AI_API_KEY")if not api_key:    raise ValueError("Miljøvariabelen AI_API_KEY er ikke satt")​client = OpenAI(    base_url=os.environ.get("AI_BASE_URL", "https://api.cometapi.com/v1"),    api_key=api_key,)

Å bytte fra GPT til Claude til Gemini er en endring på én linje:

# Førresponse = client.chat.completions.create(model="gpt-5.4", messages=[...])​# Etter — samme kode, annen modellresponse = client.chat.completions.create(model="claude-sonnet-4-6", messages=[...])

Merk: Modellnavn som gpt-5.4 og claude-sonnet-4-6 er CometAPI sine plattformidentifikatorer — de fungerer bare gjennom https://api.cometapi.com/v1, ikke direkte via OpenAI eller Anthropics API-er. Se full modelliste for komplett katalog og priser.

Hold modellnavn utenfor forretningslogikken

Modellnavn spredt gjennom koden er den vanligste formen for innelåsing. Løsningen er en sentral konfig som leser fra miljøvariabler:

# config.py — ett sted å endre modelltilordningerimport os​MODEL_CONFIG = {    "summarize": os.environ.get("MODEL_SUMMARIZE", "claude-opus-4-7"),    "code":      os.environ.get("MODEL_CODE",      "gpt-5.4"),    "classify":  os.environ.get("MODEL_CLASSIFY",  "claude-haiku-4-5"),    "chat":      os.environ.get("MODEL_CHAT",       "gpt-5.4-mini"),}​# Valider ved oppstart — feile raskt i stedet for mystiske API-feilfor task, model in MODEL_CONFIG.items():    if not model:        raise ValueError(f"Modellkonfig for '{task}' er ikke satt")

Forretningslogikken din refererer aldri et modellnavn direkte:

from config import MODEL_CONFIG​def summarize(text: str) -> str:    response = client.chat.completions.create(        model=MODEL_CONFIG["summarize"],        messages=[{"role": "user", "content": f"Oppsummer: {text}"}],        max_tokens=300  # flytt til config i produksjon    )    return response.choices[0].message.content

For å bytte oppsummeringsmodell i hele appen, endre én miljøvariabel. Ingen grep, ingen finn-og-erstatt.

Pakk responsen slik at koden din ikke er avhengig av leverandørspesifikke felt

Ulike leverandører returnerer litt forskjellige responsstrukturer. Hvis du parser rå API-responser overalt i kodebasen, låses du til den leverandørens format.

Pakk det inn i en normalisert dataklasse:

from dataclasses import dataclassfrom typing import Optionalfrom openai import OpenAI, APIStatusError, APIConnectionError, APITimeoutErrorfrom openai.types.chat import ChatCompletionimport logging​@dataclassclass AIResponse:    content: str    model: str    input_tokens: int    output_tokens: int​def call_model(task: str, messages: list, **kwargs) -> AIResponse:    """    Én enkelt inngang for alle LLM-kall.    Returnerer en normalisert AIResponse uavhengig av hvilken modell som håndterte det.    Kaster ved 4xx (klientfeil). Logger og kaster på nytt ved 5xx-/nettverksfeil.    """    model = MODEL_CONFIG.get(task, "gpt-5.4-mini")    if not model:        raise ValueError(f"Ingen modell konfigurert for oppgaven '{task}'")​    try:        response: ChatCompletion = client.chat.completions.create(            model=model,            messages=messages,            **kwargs        )    except APIStatusError as e:        logging.error(f"API-feil for task={task} model={model}: {e.status_code} {e.message}")        raise    except (APIConnectionError, APITimeoutError) as e:        logging.error(f"Nettverksfeil for task={task} model={model}: {e}")        raise​    # content er None når modellen utløser et verktøykall i stedet for å returnere tekst    content = response.choices[0].message.content or ""​    # usage er None i strømmemodus — standardiser til 0 hvis ikke tilgjengelig    usage = response.usage    input_tokens = usage.prompt_tokens if usage else 0    output_tokens = usage.completion_tokens if usage else 0​    logging.info(        f"task={task} model={model} "        f"input_tokens={input_tokens} output_tokens={output_tokens}"    )​    return AIResponse(        content=content,        model=response.model,        input_tokens=input_tokens,        output_tokens=output_tokens,    )

Nå jobber forretningslogikken din med AIResponse-objekter, ikke rå API-responser. Hvis en leverandør endrer responsformatet, fikser du det ett sted.

Legg til streaming-støtte i wrapperen

For chat-grensesnitt vil du ha strømming. Wrapperen håndterer det som en separat vei:

from typing import Iterator​def stream_model(task: str, messages: list, **kwargs) -> Iterator[str]:    """    Strøm tokens fra den rutede modellen.    Merk: strømming returnerer ikke bruksdata.    Fallback støttes ikke i strømmemodus — du har allerede    startet å sende tokens før du vet om hele forespørselen lykkes.    """    model = MODEL_CONFIG.get(task, "gpt-5.4-mini")    if not model:        raise ValueError(f"Ingen modell konfigurert for oppgaven '{task}'")​    stream = client.chat.completions.create(        model=model,        messages=messages,        stream=True,        **kwargs    )​    for chunk in stream:        delta = chunk.choices[0].delta.content        if delta:            yield delta​# Brukfor token in stream_model("chat", [{"role": "user", "content": "Hei"}]):    print(token, end="", flush=True)

Vit hvilke parametere som skaper innelåsing

Noen parametere finnes bare hos spesifikke leverandører. Å bruke dem er greit — bare vær klar over at du tar et bevisst valg:

ParameterFungerer påInnelåsingsrisiko
logprobsKun GPTHøy — ingen ekvivalent på Claude eller Gemini
n > 1GPT, Gemini (ikke Claude)Middels — Claude krever løkker
reasoning_effortKun GPT o-serienHøy — ingen ekvivalent andre steder
temperature > 1.0GPT, Gemini (ikke Claude)Lav — Claude har maks 1.0
toolsAlle store leverandørerIngen — trygt å bruke
response_formatAlle store leverandørerLav — mindre skjemaforskjeller

Hvis du bruker logprobs for konfidensscoring, er du låst til GPT for den funksjonen. Det er en rimelig avveining — bare dokumenter det slik at neste utvikler vet hvorfor.

Gjør leverandørendepunktet konfigurerbart

Å hardkode base_url="https://api.cometapi.com/v1" er fortsatt en form for innelåsing. Gjør det til en miljøvariabel:

# .env — bruker CometAPIAI_BASE_URL=https://api.cometapi.com/v1AI_API_KEY=your_cometapi_key​# For å bytte til OpenAI direkte, endre to linjer:# AI_BASE_URL=https://api.openai.com/v1# AI_API_KEY=your_openai_key

Klientinitialiseringen fra trinn 1 leser allerede fra disse variablene. Å bytte mellom CometAPI og en direkte leverandørtilkobling er nå en konfig-endring, ikke en kodeendring.

Node.js-versjon

import OpenAI from 'openai';​const apiKey = process.env.AI_API_KEY;if (!apiKey) throw new Error('AI_API_KEY er ikke satt');​const client = new OpenAI({  baseURL: process.env.AI_BASE_URL ?? 'https://api.cometapi.com/v1',  apiKey,});​// Modell-ID-er er CometAPI-plattformidentifikatorer — se cometapi.com/modelsconst MODEL_CONFIG = {  summarize: process.env.MODEL_SUMMARIZE ?? 'claude-opus-4-7',  code:      process.env.MODEL_CODE      ?? 'gpt-5.4',  classify:  process.env.MODEL_CLASSIFY  ?? 'claude-haiku-4-5',  chat:      process.env.MODEL_CHAT      ?? 'gpt-5.4-mini',};​// Valider ved oppstartfor (const [task, model] of Object.entries(MODEL_CONFIG)) {  if (!model) throw new Error(`Modellkonfig for '${task}' er ikke satt`);}​/** * Én enkelt inngang for alle LLM-kall. * Returnerer normalisert respons. Kaster ved 4xx, logger og kaster på nytt ved 5xx/nettverk. */async function callModel(task, messages, options = {}) {  const model = MODEL_CONFIG[task] ?? 'gpt-5.4-mini';​  let response;  try {    response = await client.chat.completions.create({      model,      messages,      ...options,    });  } catch (err) {    // Ikke svelg feil — logg og kast på nytt    console.error(`API-feil task=${task} model=${model}:`, err.message);    throw err;  }​  // content er null når modellen utløser et verktøykall  const content = response.choices[0].message.content ?? '';​  // usage kan mangle i noen konfigurasjoner  const inputTokens  = response.usage?.prompt_tokens     ?? 0;  const outputTokens = response.usage?.completion_tokens ?? 0;​  console.log(`task=${task} model=${model} input=${inputTokens} output=${outputTokens}`);​  return { content, model: response.model, inputTokens, outputTokens };}​/** * Strøm tokens fra den rutede modellen. * Bruksdata er ikke tilgjengelig i strømmemodus. */async function* streamModel(task, messages, options = {}) {  const model = MODEL_CONFIG[task] ?? 'gpt-5.4-mini';​  const stream = await client.chat.completions.create({    model,    messages,    stream: true,    ...options,  });​  for await (const chunk of stream) {    const delta = chunk.choices[0]?.delta?.content;    if (delta) yield delta;  }}​// Bruk — blokkerendekonst result = await callModel('classify', [  { role: 'user', content: 'Positivt eller negativt? "Elsket det!"' }]);console.log(result.content);​// Bruk — strømmingfor await (const token of streamModel('chat', [  { role: 'user', content: 'Hei' }])) {  process.stdout.write(token);}

Hvilken innelåsing er akseptabel

Ikke all innelåsing er verdt å kjempe mot. Noen avveininger gir mening:

  • Å bruke OpenAI SDK — Det er de facto-standarden. De fleste leverandører støtter det. Lav risiko for innelåsing.
  • Leverandørspesifikke funksjoner du faktisk trenger — Hvis du trenger logprobs, bruk dem. Isoler den koden slik at den er enkel å finne og bytte senere.
  • Finjusterte modeller — En finjustert modell er i utgangspunktet knyttet til én leverandør. Det er forventet.

Innelåsingen det er verdt å unngå, er den utilsiktede — modellnavn i forretningslogikken, rå responsparsing spredt over filer, API-nøkler hardkodet i kildekoden.

Hva er neste

Nå har du et abstraksjonslag som holder leverandørdetaljer utenfor forretningslogikken. Den siste artikkelen i denne serien dekker hva som skjer når ting går galt: hvordan du feilsøker mislykkede genereringer, tolker feilkoder og bygger feilhåndtering som faktisk forteller deg hva som gikk i stykker.

Neste: Hvordan feilsøke mislykkede AI API-genereringer

FAQ

Q: Hva er forskjellen mellom SDK-innelåsing og modellinnelåsing?

SDK-innelåsing betyr at koden din importerer et spesifikt bibliotek og må endres hvis du bytter SDK. Modellinnelåsing betyr at modellnavn er spredt gjennom forretningslogikken. SDK-innelåsing er mindre farlig fordi de fleste leverandører nå støtter OpenAI SDK-formatet. Modellinnelåsing er mer snikende fordi det er vanskeligere å finne og rette.

Q: Hvis jeg bruker CometAPI, bytter jeg bare OpenAI -innelåsing mot CometAPI-innelåsing?

Delvis. Du bytter direkte leverandørinnelåsing mot et mellomlag. Fordelen: én nøkkel, ett endepunkt, enkel modellbytting. Risikoen: hvis CometAPI har et utfall, går alle leverandørene dine ned samtidig. Avbøtningen er allerede i koden over — AI_BASE_URL er en miljøvariabel. Hvis du trenger å gå utenom CometAPI og kalle en leverandør direkte, er det en konfig-endring, ikke en kodeendring.

Q: Kan jeg bruke Claudes utvidede tenkning eller OpenAIs reasoning_effort gjennom dette mønsteret?

Ja, send dem som **kwargs til call_model. Bare vær klar over at hvis du ruter den oppgaven til en annen modell, vil disse parameterne bli ignorert eller forårsake en feil. Dokumenter hvilke oppgaver som bruker leverandørspesifikke funksjoner slik at neste utvikler vet hvorfor.

Q: Hvordan håndterer jeg Claudes temperature tak på 1.0 når jeg ruter mellom Claude og GPT**?**

Hold temperature på eller under 1.0 for å holde deg i det trygge området for begge. Hvis du trenger høyere temperatur for kreative oppgaver på GPT spesifikt, ruter du de oppgavene eksplisitt til GPT i MODEL_CONFIG i stedet for å la dem falle gjennom den generiske ruteren.

Q: Bør jeg abstrahere bilde- og videogenererings-API-ene på samme måte?

De samme prinsippene gjelder — sentral konfig, normalisert respons-wrapper, ingen leverandørspesifikke felt i forretningslogikken. Bilde- og video-API-er har flere strukturelle forskjeller (asynk vs synk, ulike parametersett), så abstraksjonslaget krever mer arbeid. Start med tekst, og utvid mønsteret når strukturen er bevist.

Q: Hva med kontekstvindu forskjeller mellom modeller?

Dette er en reell risiko ved ruting. GPT-5.5 har et kontekstvindu på 1M token, Claude-modeller støtter opptil 200K, og Gemini 3.5 Flash støtter opptil 1M. Hvis du ruter en lang dokumentoppgave til en modell med kortere kontekstvindu, blir inndataene stille trunkert. Legg til en kontekstlengdesjekk før ruting hvis oppgavene dine innebærer lange inndata — eller ruter alltid oppgaver med langt kontekstvindu til en spesifikk modell i MODEL_CONFIG i stedet for å la dem falle gjennom til en standard.

Klar til å redusere AI-utviklingskostnadene med 20 %?

Kom i gang gratis på minutter. Gratis prøvekreditter inkludert. Ingen kredittkort nødvendig.

Les mer