Claude Opus 5 is now live on CometAPI →

Tek bir sağlayıcıya bağımlı olmayan yapay zekâ uygulamaları nasıl geliştirilir

CometAPI
AnnaJun 7, 2026
Tek bir sağlayıcıya bağımlı olmayan yapay zekâ uygulamaları nasıl geliştirilir

Yapay zeka uygulamalarında satıcıya kilitlenme genellikle bir anda olmaz. Sinsice sızar — burada doğrudan import openai, şurada hardcode edilmiş bir model adı, başka sağlayıcıların aynı şeyi döndürüp döndürmediğini kontrol etmeden ayrıştırdığınız bir yanıt alanı. Altı ay sonra, sağlayıcı değiştirmek arka ucunuzun yarısını yeniden yazmak anlamına gelir.

Kilitlenmenin gerçekleştiği dört yol

Çoğu geliştirici, kilitlenmenin “OpenAI SDK’sını kullanıyorum” anlamına geldiğini düşünür. Bu en az tehlikeli türdür. Asıl tuzaklar daha sinsi:

Kilitlenme türüNasıl olurSonuç
SDK kilitlenmesifrom openai import OpenAI her yerdeSDK değiştirmek her dosyaya dokunmayı gerektirir
Model adı kilitlenmesimodel="gpt-4o" iş mantığına hardcode edilirHer model değişikliği bir kod değişikliğidir
Parametre kilitlenmesilogprobs, n>1 veya reasoning_effort kullanmakBunlar Claude veya Gemini’de yoktur
Yanıt biçimi kilitlenmesiSağlayıcıya özgü yanıt alanlarını ayrıştırmakFarklı sağlayıcılar farklı biçimler döndürür

Amaç bunların hepsini ortadan kaldırmak değil — bazıları kabul edilebilir ödünlerdir. Amaç, hangilerini üstlendiğinizi bilmek.

Soyutlama katmanı olarak OpenAI uyumlu bir uç nokta kullanın

SDK kilitlenmesinden kaçınmanın en temiz yolu, birden çok sağlayıcıya yönlendiren tek bir OpenAI uyumlu uç nokta kullanmaktır. OpenAI SDK’sını korursunuz, ancak arka uç herhangi bir sağlayıcı olabilir.

CometAPI bunu yapar — tek uç nokta, tek anahtar, OpenAI, Anthropic, Google, DeepSeek, xAI ve diğerleri genelinde 500+ model:

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,)

GPT’den Claude’a, oradan Gemini’ye geçiş tek satırlık bir değişikliktir:

# 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=[...])

Not: gpt-5.4 ve claude-sonnet-4-6 gibi model adları CometAPI’nin platform tanımlayıcılarıdır — yalnızca https://api.cometapi.com/v1 üzerinden çalışır, OpenAI veya Anthropic’in API’leri üzerinden doğrudan çalışmaz. Tam katalog ve fiyatlandırma için tam model listesi bölümüne bakın.

Model adlarını iş mantığınızın dışına çıkarın

Koda dağılmış model adları, en yaygın kilitlenme türüdür. Çözüm, ortam değişkenlerinden okuyan merkezi bir yapılandırmadır:

# config.py — one place to change model assignmentsimport 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"),}​# Validate at startup — fail fast rather than getting mysterious API errorsfor task, model in MODEL_CONFIG.items():    if not model:        raise ValueError(f"Model config for '{task}' is not set")

İş mantığınız asla doğrudan bir model adına başvurmaz:

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

Uygulamanız genelinde özetleme modelini değiştirmek için bir ortam değişkenini değiştirmeniz yeterlidir. Ne grep, ne bul-değiştir.

Kodu sağlayıcıya özgü alanlara bağımlı kılmamak için yanıtı sarmalayın

Farklı sağlayıcılar biraz farklı yanıt biçimleri döndürür. Kod tabanı boyunca ham API yanıtlarını ayrıştırırsanız, o sağlayıcının biçimine kilitlenmiş olursunuz.

Bunu normalize edilmiş bir dataclass içine sarın:

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,    )

Artık iş mantığınız ham API yanıtlarıyla değil, AIResponse nesneleriyle çalışır. Bir sağlayıcı yanıt biçimini değiştirirse, bunu tek bir yerde düzeltirsiniz.

Sarmalayıcıya akış desteği ekleyin

Sohbet arayüzleri için akış isteyeceksiniz. Sarmalayıcı bunu ayrı bir yol olarak ele alır:

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)

Hangi parametrelerin kilitlenme yarattığını bilin

Bazı parametreler yalnızca belirli sağlayıcılarda vardır. Bunları kullanmak sorun değil — sadece bilinçli bir tercih yaptığınızdan emin olun:

ParametreNerede çalışırKilitlenme riski
logprobsYalnızca GPTYüksek — Claude veya Gemini’de karşılığı yok
n > 1GPT, Gemini (Claude değil)Orta — Claude için döngü gerekir
reasoning_effortYalnızca GPT o-serisiYüksek — başka yerde karşılığı yok
temperature > 1.0GPT, Gemini (Claude değil)Düşük — Claude 1.0’da sınırlar
toolsTüm büyük sağlayıcılarYok — kullanmak güvenli
response_formatTüm büyük sağlayıcılarDüşük — küçük şema farklılıkları

Eğer güven puanlaması için logprobs kullanıyorsanız, bu özellik için GPT’ye kilitlenmişsiniz demektir. Bu makul bir ödündür — yalnızca bunu belgelendirin ki sonraki geliştirici nedenini bilsin.

Sağlayıcı uç noktasını yapılandırılabilir yapın

base_url="https://api.cometapi.com/v1" değerini hardcode etmek hâlâ bir kilitlenme biçimidir. Bunu bir ortam değişkenine bağlayın:

# .env — using CometAPIAI_BASE_URL=https://api.cometapi.com/v1AI_API_KEY=your_cometapi_key​# To switch to OpenAI directly, change two lines:# AI_BASE_URL=https://api.openai.com/v1# AI_API_KEY=your_openai_key
  1. Adımdaki istemci başlatma zaten bu değişkenlerden okuyor. CometAPI ile doğrudan bir sağlayıcı arasında geçiş yapmak artık bir kod değişikliği değil, bir yapılandırma değişikliğidir.

Node.js sürümü

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 are CometAPI platform identifiers — see 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',};​// Validate at startupfor (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);}

Hangi kilitlenme kabul edilebilir

Her kilitlenme ile savaşmaya değmez. Bazı ödünler mantıklıdır:

  • Kullanmak OpenAI SDK — De facto standarttır. Çoğu sağlayıcı bunu destekler. Düşük riskli kilitlenme.
  • Gerçekten ihtiyaç duyduğunuz sağlayıcıya özgü özelliklerlogprobs gerekiyorsa, kullanın. Bu kodu izole edin ki daha sonra bulup değiştirmek kolay olsun.
  • İnce ayarlı modeller — İnce ayarlı bir model doğası gereği tek bir sağlayıcıya bağlıdır. Bu beklenir.

Kaçınmaya değer kilitlenme, kazara olanıdır — iş mantığında model adları, dosyalara yayılmış ham yanıt ayrıştırma, kaynakta hardcode edilmiş API anahtarları.

Sırada ne var

Artık sağlayıcı ayrıntılarını iş mantığınızın dışında tutan bir soyutlama katmanına sahipsiniz. Bu serinin son yazısı, işler ters gittiğinde ne olduğunu kapsar: başarısız üretimleri nasıl hata ayıklayacağınız, hata kodlarını nasıl yorumlayacağınız ve gerçekten neyin bozulduğunu söyleyen hata yönetimini nasıl kuracağınız.

Sıradaki: Başarısız AI API Üretimlerini Nasıl Hata Ayıklarsınız

SSS

S: SDK kilitlenmesi ile model kilitlenmesi arasındaki fark nedir?

SDK kilitlenmesi, kodunuzun belirli bir kütüphaneyi içe aktardığı ve SDK’ları değiştirdiğinizde değişmesi gerektiği anlamına gelir. Model kilitlenmesi ise model adlarının iş mantığınız boyunca dağılmış olduğu anlamına gelir. SDK kilitlenmesi daha az tehlikelidir çünkü çoğu sağlayıcı artık OpenAI SDK biçimini desteklemektedir. Model kilitlenmesi daha sinsi bir durumdur çünkü bulması ve düzeltmesi daha zordur.

S: CometAPI kullanırsam, sadece OpenAI kilitlenmesini CometAPI kilitlenmesiyle mi değiştiriyorum?

Kısmen. Doğrudan sağlayıcı kilitlenmesini bir proxy katmanıyla değiştiriyorsunuz. Artıları: tek anahtar, tek uç nokta, kolay model değiştirme. Riski: CometAPI bir kesinti yaşarsa, tüm sağlayıcılarınız birlikte gider. Azaltım, yukarıdaki koddadır — AI_BASE_URL bir ortam değişkenidir. CometAPI’yi atlayıp bir sağlayıcıyı doğrudan çağırmanız gerekirse, bu bir yapılandırma değişikliği, kod değişikliği değil.

S: Claude’un genişletilmiş düşünmesini veya OpenAI’nin reasoning_effort özelliğini bu desenle kullanabilir miyim?

Evet, bunları call_model’a **kwargs olarak aktarın. Yalnız, bu görevi farklı bir modele yönlendirirseniz bu parametreler yok sayılabilir veya hata oluşturabilir. Hangi görevlerin sağlayıcıya özgü özellikler kullandığını belgelendirin ki sonraki geliştirici nedenini bilsin.

S: Claude’un temperature değerini 1.0’da sınırlamasını, Claude ile GPT**?** arasında yönlendirirken nasıl ele alırım?

Her iki tarafta da güvenli bölgede kalmak için temperature’ı 1.0 veya altında tutun. Özellikle GPT’de yaratıcı görevler için daha yüksek sıcaklığa ihtiyacınız varsa, bu görevleri MODEL_CONFIG içinde GPT’ye açıkça yönlendirin; genel yönlendiriciye bırakmayın.

S: Görüntü ve video üretim API’lerini de aynı şekilde soyutlamalı mıyım?

Aynı ilkeler geçerlidir — merkezi yapılandırma, normalize edilmiş yanıt sarmalayıcı, iş mantığında sağlayıcıya özgü alanlar yok. Görüntü ve video API’lerinde daha fazla yapısal fark vardır (eşzamansız vs eşzamanlı, farklı parametre setleri), bu yüzden soyutlama katmanı daha fazla çalışma gerektirir. Metinle başlayın, yapı kanıtlandıktan sonra kalıbı genişletin.

S: Modeller arasındaki bağlam penceresi farklılıkları ne olacak?

Yönlendirme yaparken bu gerçek bir risktir. GPT-5.5 1M token bağlam penceresine sahiptir, Claude modelleri 200K’ya kadar destekler ve Gemini 3.5 Flash 1M’e kadar destekler. Uzun bir belge görevini daha kısa bağlam penceresine sahip bir modele yönlendirirseniz, giriş sessizce kırpılır. Görevleriniz uzun girdiler içeriyorsa yönlendirmeden önce bir bağlam uzunluğu kontrolü ekleyin — veya uzun bağlamlı görevleri her zaman MODEL_CONFIG içinde belirli bir modele yönlendirin; varsayılanla düşmesine izin vermeyin.

Yapay zeka geliştirme maliyetlerinizi %20 azaltmaya hazır mısınız?

Dakikalar içinde ücretsiz başlayın. Ücretsiz deneme kredileri dahildir. Kredi kartı gerekmez.

Devamını Oku