Claude Opus 5 is now live on CometAPI →

Jak tworzyć aplikacje AI, które nie są uzależnione od jednego dostawcy

CometAPI
AnnaJun 7, 2026
Jak tworzyć aplikacje AI, które nie są uzależnione od jednego dostawcy

Vendor lock-in w aplikacjach AI zwykle nie dzieje się od razu. Wkrada się stopniowo — tu bezpośredni import openai, tam twardo wpisana nazwa modelu, gdzie indziej pole odpowiedzi, które parsujesz bez sprawdzenia, czy inni dostawcy zwracają to samo. Sześć miesięcy później zmiana dostawcy oznacza przepisywanie połowy backendu.

Cztery sposoby, w jakie powstaje lock-in

Większość deweloperów uważa, że lock-in oznacza „używam SDK OpenAI”. To najmniej groźny rodzaj. Prawdziwe pułapki są subtelniejsze:

Lock-in typeHow it happensConsequence
SDK lock-infrom openai import OpenAI wszędzieZmiana SDK oznacza dotykanie każdego pliku
Model name lock-inmodel="gpt-4o" zahardkodowane w logice biznesowejKażda zmiana modelu to zmiana w kodzie
Parameter lock-inUsing logprobs, n>1, or reasoning_effortTego nie ma w Claude ani Gemini
Response format lock-inParsowanie specyficznych pól odpowiedzi dla dostawcyRóżni dostawcy zwracają różne struktury

Celem nie jest eliminacja wszystkiego — niektóre kompromisy są akceptowalne. Celem jest wiedzieć, które z nich przyjmujesz.

Użyj zgodnego z OpenAI endpointu jako warstwy abstrakcji

Najczyściejszym sposobem uniknięcia lock-in na poziomie SDK jest użycie jednego, zgodnego z OpenAI endpointu, który routuje do wielu dostawców. Zachowujesz SDK OpenAI, ale backend może być dowolnym dostawcą.

CometAPI robi właśnie to — jeden endpoint, jeden klucz, 500+ modeli od OpenAI, Anthropic, Google, DeepSeek, xAI i innych:

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("AI_API_KEY environment variable is not set")​client = OpenAI(    base_url=os.environ.get("AI_BASE_URL", "https://api.cometapi.com/v1"),    api_key=api_key,)

Przełączenie z GPT na Claude lub Gemini to zmiana jednej linijki:

# Beforeresponse = client.chat.completions.create(model="gpt-5.4", messages=[...])​# After — same code, different modelresponse = client.chat.completions.create(model="claude-sonnet-4-6", messages=[...])

Note: Nazwy modeli, takie jak gpt-5.4 i claude-sonnet-4-6, to identyfikatory platformy CometAPI — działają tylko przez https://api.cometapi.com/v1, nie bezpośrednio przez API OpenAI czy Anthropic. Zobacz pełną listę modeli dla kompletnego katalogu i cen.

Trzymaj nazwy modeli poza logiką biznesową

Nazwy modeli rozsiane po kodzie to najczęstsza forma lock-in. Rozwiązaniem jest centralna konfiguracja odczytywana ze zmiennych środowiskowych:

# config.py — jedno miejsce do zmiany przypisania modeliimport 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"),}​# Walidacja przy starcie — szybkie wywalenie błędu zamiast tajemniczych błędów APIfor task, model in MODEL_CONFIG.items():    if not model:        raise ValueError(f"Model config for '{task}' is not set")

Twoja logika biznesowa nigdy nie odwołuje się bezpośrednio do nazwy modelu:

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

Aby przełączyć model podsumowywania w całej aplikacji, zmień jedną zmienną środowiskową. Bez grepa, bez find-and-replace.

Owiń odpowiedź, aby kod nie zależał od pól specyficznych dla dostawcy

Różni dostawcy zwracają trochę inne struktury odpowiedzi. Jeśli parsujesz surowe odpowiedzi API w całym kodzie, jesteś przywiązany do formatu tego dostawcy.

Owiń je w zunifikowany dataclass:

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:    """    Single entry point for all LLM calls.    Returns a normalized AIResponse regardless of which model handled it.    Raises on 4xx (client errors). Logs and re-raises on 5xx/network errors.    """    model = MODEL_CONFIG.get(task, "gpt-5.4-mini")    if not model:        raise ValueError(f"No model configured for task '{task}'")​    try:        response: ChatCompletion = client.chat.completions.create(            model=model,            messages=messages,            **kwargs        )    except APIStatusError as e:        logging.error(f"API error for task={task} model={model}: {e.status_code} {e.message}")        raise    except (APIConnectionError, APITimeoutError) as e:        logging.error(f"Network error for task={task} model={model}: {e}")        raise​    # content is None when the model triggers a tool call instead of returning text    content = response.choices[0].message.content or ""​    # usage is None in streaming mode — default to 0 if not available    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,    )

Teraz twoja logika biznesowa pracuje z obiektami AIResponse, a nie surowymi odpowiedziami API. Jeśli dostawca zmieni format odpowiedzi, poprawiasz to w jednym miejscu.

Dodaj wsparcie dla strumieniowania do wrappera

Dla interfejsów czatu będziesz potrzebować strumieniowania. Wrapper obsługuje to jako osobną ścieżkę:

from typing import Iterator​def stream_model(task: str, messages: list, **kwargs) -> Iterator[str]:    """    Stream tokens from the routed model.    Note: streaming doesn't return usage data.    Fallback is not supported in streaming mode — you've already    started yielding tokens before you know if the full request succeeds.    """    model = MODEL_CONFIG.get(task, "gpt-5.4-mini")    if not model:        raise ValueError(f"No model configured for task '{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​# Usagefor token in stream_model("chat", [{"role": "user", "content": "Hello"}]):    print(token, end="", flush=True)

Wiedz, które parametry tworzą lock-in

Niektóre parametry istnieją tylko u konkretnych dostawców. Używanie ich jest w porządku — po prostu wiedz, że podejmujesz świadomy wybór:

ParameterWorks onLock-in risk
logprobsGPT onlyWysokie — brak odpowiednika w Claude ani Gemini
n > 1GPT, Gemini (nie Claude)Średnie — w Claude wymaga pętli
reasoning_effortTylko GPT o-seriesWysokie — brak odpowiednika gdzie indziej
temperature > 1.0GPT, Gemini (nie Claude)Niskie — Claude ma limit 1.0
toolsWszyscy główni dostawcyBrak — bezpieczne w użyciu
response_formatWszyscy główni dostawcyNiskie — drobne różnice w schematach

Jeśli używasz logprobs do oceny pewności, jesteś związany z GPT dla tej funkcji. To rozsądny kompromis — po prostu udokumentuj go, aby kolejny deweloper wiedział dlaczego.

Uczyń endpoint dostawcy konfigurowalnym

Hardcodowanie base_url="https://api.cometapi.com/v1" to nadal forma lock-in. Umieść to w zmiennej środowiskowej:

# .env — używając CometAPIAI_BASE_URL=https://api.cometapi.com/v1AI_API_KEY=your_cometapi_key​# Aby przejść bezpośrednio na OpenAI, zmień dwie linie:# AI_BASE_URL=https://api.openai.com/v1# AI_API_KEY=your_openai_key

Inicjalizacja klienta z Kroku 1 już odczytuje te zmienne. Przełączanie między CometAPI a bezpośrednim połączeniem z dostawcą to teraz zmiana konfiguracji, a nie kodu.

Wersja Node.js

import OpenAI from 'openai';​const apiKey = process.env.AI_API_KEY;if (!apiKey) throw new Error('AI_API_KEY is not set');​const client = new OpenAI({  baseURL: process.env.AI_BASE_URL ?? 'https://api.cometapi.com/v1',  apiKey,});​// Model IDs są identyfikatorami platformy CometAPI — zobacz 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',};​// Walidacja przy starciefor (const [task, model] of Object.entries(MODEL_CONFIG)) {  if (!model) throw new Error(`Model config for '${task}' is not set`);}​/** * Single entry point for all LLM calls. * Returns normalized response. Raises on 4xx, logs and re-raises on 5xx/network. */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) {    // Don't swallow errors — log and re-raise    console.error(`API error task=${task} model=${model}:`, err.message);    throw err;  }​  // content is null when model triggers a tool call  const content = response.choices[0].message.content ?? '';​  // usage may be absent in some configurations  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 };}​/** * Stream tokens from the routed model. * Usage data is not available in streaming mode. */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;  }}​// Usage — blockingconst result = await callModel('classify', [  { role: 'user', content: 'Positive or negative? "Loved it!"' }]);console.log(result.content);​// Usage — streamingfor await (const token of streamModel('chat', [  { role: 'user', content: 'Hello' }])) {  process.stdout.write(token);}

Jaki lock-in jest akceptowalny

Nie każdy lock-in warto zwalczać. Niektóre kompromisy mają sens:

  • Using the OpenAI SDK — To de facto standard. Większość dostawców go wspiera. Niskie ryzyko lock-in.
  • Provider-specific features you actually need — Jeśli potrzebujesz logprobs, używaj ich. Izo­luj ten kod, aby później łatwo było go znaleźć i podmienić.
  • Fine-tuned models — Model po fine-tuningu jest z natury związany z jednym dostawcą. To oczekiwane.

Lock-in, którego warto unikać, to ten przypadkowy — nazwy modeli w logice biznesowej, rozproszone parsowanie surowych odpowiedzi, klucze API zahardkodowane w kodzie źródłowym.

Co dalej

Masz teraz warstwę abstrakcji, która trzyma szczegóły dostawcy z dala od twojej logiki biznesowej. Ostatni artykuł w tej serii omawia, co się dzieje, gdy coś idzie nie tak: jak debugować nieudane generacje, interpretować kody błędów i budować obsługę błędów, która naprawdę mówi, co się popsuło.

Next: How to Debug Failed AI API Generations

FAQ

Q: Jaka jest różnica między SDK lock-in a model lock-in?

SDK lock-in oznacza, że twój kod importuje konkretną bibliotekę i musiałby się zmienić, jeśli przełączysz SDK. Model lock-in oznacza, że nazwy modeli są rozsiane po twojej logice biznesowej. SDK lock-in jest mniej groźny, ponieważ większość dostawców obsługuje teraz format SDK OpenAI. Model lock-in jest bardziej podstępny, bo trudniej go znaleźć i naprawić.

Q: Jeśli używam CometAPI, czy po prostu zamieniam lock-in na OpenAI na lock-in CometAPI?

Częściowo. Zamieniasz bezpośrednie uzależnienie od dostawcy na warstwę pośrednią. Plus: jeden klucz, jeden endpoint, łatwe przełączanie modeli. Ryzyko: jeśli CometAPI ma awarię, wszyscy twoi dostawcy padają naraz. Ograniczenie ryzyka jest już w powyższym kodzie — AI_BASE_URL to zmienna środowiskowa. Jeśli musisz ominąć CometAPI i dzwonić do dostawcy bezpośrednio, to zmiana konfiguracji, a nie kodu.

Q: Czy mogę używać rozszerzonego myślenia Claude albo OpenAI’s reasoning_effort w tym wzorcu?

Tak, przekaż je jako **kwargs do call_model. Po prostu wiedz, że jeśli zrouterujesz to zadanie do innego modelu, te parametry zostaną zignorowane albo spowodują błąd. Udokumentuj, które zadania używają funkcji specyficznych dla dostawcy, aby kolejny deweloper wiedział, dlaczego.

Q: Jak obsłużyć limit Claude dla temperature na poziomie 1.0 przy routingu między Claude a GPT**?**

Utrzymuj temperature na poziomie 1.0 lub niżej, aby pozostać w bezpiecznym zakresie dla obu. Jeśli potrzebujesz wyższej temperatury dla kreatywnych zadań konkretnie na GPT, kieruj te zadania jawnie do GPT w MODEL_CONFIG zamiast pozwalać, by trafiały do ogólnego routera.

Q: Czy powinienem w ten sam sposób abstrahować API generowania obrazów i wideo?

Te same zasady obowiązują — centralna konfiguracja, zunifikowany wrapper odpowiedzi, brak pól specyficznych dla dostawcy w logice biznesowej. API obrazów i wideo mają większe różnice strukturalne (async vs sync, różne zestawy parametrów), więc warstwa abstrakcji wymaga więcej pracy. Zacznij od tekstu, potem rozszerz wzorzec, gdy struktura się sprawdzi.

Q: A co z różnicami w context window między modelami?

To realne ryzyko przy routingu. GPT-5.5 ma okno kontekstu 1M tokenów, modele Claude wspierają do 200K, a Gemini 3.5 Flash wspiera do 1M. Jeśli skierujesz zadanie z długim dokumentem do modelu z krótszym oknem kontekstu, wejście zostanie cicho obcięte. Dodaj sprawdzanie długości kontekstu przed routingiem, jeśli twoje zadania obejmują długie wejścia — albo zawsze kieruj zadania z długim kontekstem do konkretnego modelu w MODEL_CONFIG zamiast pozwalać, by trafiały do domyślnego.

Gotowy na obniżenie kosztów rozwoju AI o 20%?

Zacznij za darmo w kilka minut. Dołączone kredyty na bezpłatny okres próbny. Karta kredytowa nie jest wymagana.

Czytaj więcej