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åsing | Hvordan det skjer | Konsekvens |
|---|---|---|
| SDK-innelåsing | from openai import OpenAI overalt | Å bytte SDK betyr å måtte endre hver fil |
| Modellnavn-innelåsing | model="gpt-4o" hardkodet i forretningslogikken | Hver modellendring er en kodeendring |
| Parameter-innelåsing | Bruk av logprobs, n>1, eller reasoning_effort | Disse finnes ikke på Claude eller Gemini |
| Responsformat-innelåsing | Parsing av leverandørspesifikke responsfelt | Ulike 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_dotenvload_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 osMODEL_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_CONFIGdef 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: intdef 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 Iteratordef 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:
| Parameter | Fungerer på | Innelåsingsrisiko |
|---|---|---|
| logprobs | Kun GPT | Høy — ingen ekvivalent på Claude eller Gemini |
| n > 1 | GPT, Gemini (ikke Claude) | Middels — Claude krever løkker |
| reasoning_effort | Kun GPT o-serien | Høy — ingen ekvivalent andre steder |
| temperature > 1.0 | GPT, Gemini (ikke Claude) | Lav — Claude har maks 1.0 |
| tools | Alle store leverandører | Ingen — trygt å bruke |
| response_format | Alle store leverandører | Lav — 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.
