Claude Opus 5 is now live on CometAPI โ†’

Cara Membangun Aplikasi AI yang Tidak Terikat pada Satu Penyedia

CometAPI
AnnaJun 7, 2026
Cara Membangun Aplikasi AI yang Tidak Terikat pada Satu Penyedia

Vendor lock-in dalam aplikasi AI biasanya tidak terjadi sekaligus. Ia merayap masuk โ€” sebuah import openai langsung di sini, sebuah nama model yang di-hardcode di sana, sebuah field respons yang Anda parse tanpa memeriksa apakah penyedia lain mengembalikan hal yang sama. Enam bulan kemudian, beralih penyedia berarti menulis ulang setengah backend Anda.

Empat cara lock-in terjadi

Kebanyakan developer mengira lock-in berarti "Saya menggunakan OpenAI SDK." Itu jenis yang paling tidak berbahaya. Perangkap yang sebenarnya lebih halus:

Jenis lock-inBagaimana terjadinyaKonsekuensi
SDK lock-infrom openai import OpenAI di mana-manaGanti SDK berarti menyentuh setiap file
Model name lock-inmodel="gpt-4o" di-hardcode di logika bisnisSetiap ganti model adalah perubahan kode
Parameter lock-inMenggunakan logprobs, n>1, atau reasoning_effortIni tidak ada di Claude atau Gemini
Response format lock-inMelakukan parsing field respons spesifik penyediaPenyedia berbeda mengembalikan bentuk berbeda

Tujuannya bukan menghilangkan semuanya โ€” beberapa adalah trade-off yang dapat diterima. Tujuannya adalah mengetahui mana yang Anda ambil.

Gunakan endpoint yang kompatibel dengan OpenAI sebagai lapisan abstraksi Anda

Cara paling bersih menghindari SDK lock-in adalah menggunakan satu endpoint yang kompatibel dengan OpenAI yang melakukan routing ke banyak penyedia. Anda tetap memakai OpenAI SDK, tetapi backend bisa penyedia mana pun.

CometAPI melakukan ini โ€” satu endpoint, satu key, 500+ model di OpenAI, Anthropic, Google, DeepSeek, xAI, dan lainnya:

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

Beralih dari GPT ke Claude ke Gemini adalah perubahan satu baris:

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

Catatan: Nama model seperti gpt-5.4 dan claude-sonnet-4-6 adalah identifier platform CometAPI โ€” hanya berfungsi melalui https://api.cometapi.com/v1, bukan melalui API OpenAI atau Anthropic langsung. Lihat daftar model lengkap untuk katalog dan harga lengkap.

Jauhkan nama model dari logika bisnis Anda

Nama model yang tersebar di seluruh kode adalah bentuk lock-in yang paling umum. Solusinya adalah konfigurasi terpusat yang membaca dari variabel lingkungan:

# config.py โ€” satu tempat untuk mengubah penetapan modelimport 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"),}โ€‹# Validasi saat startup โ€” gagal cepat alih-alih mendapat error API yang misteriusfor task, model in MODEL_CONFIG.items(): ย  ย if not model: ย  ย  ย  ย raise ValueError(f"Model config for '{task}' is not set")

Logika bisnis Anda tidak pernah merujuk nama model secara langsung:

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

Untuk mengganti model peringkasan di seluruh aplikasi, ubah satu variabel lingkungan. Tanpa grep, tanpa find-and-replace.

Bungkus respons agar kode Anda tidak bergantung pada field spesifik penyedia

Penyedia berbeda mengembalikan bentuk respons yang sedikit berbeda. Jika Anda melakukan parsing respons API mentah di seluruh codebase, Anda terkunci pada format penyedia tersebut.

Bungkus menjadi dataclass yang dinormalisasi:

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

Sekarang logika bisnis Anda bekerja dengan objek AIResponse, bukan respons API mentah. Jika sebuah penyedia mengubah format responsnya, Anda memperbaikinya di satu tempat.

Tambahkan dukungan streaming pada wrapper

Untuk antarmuka chat, Anda akan menginginkan streaming. Wrapper menanganinya sebagai jalur terpisah:

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)

Ketahui parameter mana yang menimbulkan lock-in

Beberapa parameter hanya ada pada penyedia tertentu. Menggunakannya tidak masalah โ€” cukup ketahui bahwa Anda membuat pilihan sadar:

ParameterBerfungsi padaRisiko lock-in
logprobsHanya GPTTinggi โ€” tidak ada padanan di Claude/Gemini
n > 1GPT, Gemini (bukan Claude)Sedang โ€” Claude membutuhkan looping
reasoning_effortHanya GPT o-seriesTinggi โ€” tidak ada padanan di tempat lain
temperature > 1.0GPT, Gemini (bukan Claude)Rendah โ€” Claude membatasi di 1.0
toolsSemua penyedia utamaTidak ada โ€” aman digunakan
response_formatSemua penyedia utamaRendah โ€” perbedaan skema minor

Jika Anda menggunakan logprobs untuk penilaian kepercayaan, Anda terkunci pada GPT untuk fitur itu. Itu trade-off yang masuk akal โ€” cukup dokumentasikan agar developer berikutnya tahu alasannya.

Buat endpoint penyedia dapat dikonfigurasi

Meng-hardcode base_url="https://api.cometapi.com/v1" tetap merupakan bentuk lock-in. Jadikan ini variabel lingkungan:

# .env โ€” menggunakan 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

Inisialisasi client dari Langkah 1 sudah membaca dari variabel ini. Beralih antara CometAPI dan koneksi penyedia langsung kini menjadi perubahan konfigurasi, bukan perubahan kode.

Versi 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 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);}

Lock-in mana yang dapat diterima

Tidak semua lock-in layak dilawan. Beberapa trade-off masuk akal:

  • Menggunakan OpenAI SDK โ€” Ini standar de facto. Mayoritas penyedia mendukungnya. Lock-in berisiko rendah.
  • Fitur spesifik penyedia yang benar-benar Anda butuhkan โ€” Jika Anda butuh logprobs, gunakan. Isolasi kode itu agar mudah ditemukan dan diganti nanti.
  • Model fine-tuned โ€” Model fine-tuned secara inheren terikat ke satu penyedia. Itu sudah sesuai harapan.

Lock-in yang layak dihindari adalah yang terjadi secara tidak sengaja โ€” nama model di logika bisnis, parsing respons mentah tersebar di banyak file, API key di-hardcode dalam source.

Langkah berikutnya

Anda kini memiliki lapisan abstraksi yang menjaga detail penyedia keluar dari logika bisnis Anda. Artikel terakhir dalam seri ini membahas apa yang terjadi saat ada masalah: cara debug kegagalan generasi, menafsirkan kode error, dan membangun penanganan error yang benar-benar memberi tahu Anda apa yang rusak.

Selanjutnya: How to Debug Failed AI API Generations

FAQ

Q: Apa perbedaan antara SDK lock-in dan model lock-in?

SDK lock-in berarti kode Anda mengimpor library tertentu dan perlu diubah jika Anda mengganti SDK. Model lock-in berarti nama model tersebar di seluruh logika bisnis. SDK lock-in kurang berbahaya karena sebagian besar penyedia sekarang mendukung format OpenAI SDK. Model lock-in lebih licik karena lebih sulit ditemukan dan diperbaiki.

Q: Jika saya menggunakan CometAPI, apakah saya hanya menukar lock-in OpenAI dengan lock-in CometAPI?

Sebagian. Anda menukar lock-in penyedia langsung dengan lapisan proxy. Keuntungannya: satu key, satu endpoint, mudah ganti model. Risikonya: jika CometAPI mengalami outage, semua penyedia Anda ikut down. Mitigasinya sudah ada di kode di atas โ€” AI_BASE_URL adalah variabel lingkungan. Jika Anda perlu melewati CometAPI dan memanggil penyedia langsung, itu perubahan konfigurasi, bukan perubahan kode.

Q: Bisakah saya menggunakan extended thinking milik Claude atau reasoning_effort milik OpenAI melalui pola ini?

Ya, teruskan sebagai **kwargs ke call_model. Ketahuilah bahwa jika Anda merutekan tugas itu ke model lain, parameter tersebut akan diabaikan atau menyebabkan error. Dokumentasikan tugas mana yang memakai fitur spesifik penyedia agar developer berikutnya tahu alasannya.

Q: Bagaimana saya menangani batas temperature Claude di 1.0 saat merutekan antara Claude dan GPT**?**

Tetapkan temperature pada atau di bawah 1.0 untuk tetap aman di keduanya. Jika Anda butuh temperature lebih tinggi untuk tugas kreatif khusus pada GPT, rute tugas tersebut ke GPT secara eksplisit di MODEL_CONFIG alih-alih membiarkannya jatuh ke router umum.

Q: Haruskah saya mengabstraksikan API pembuatan gambar dan video dengan cara yang sama?

Prinsipnya sama โ€” konfigurasi terpusat, wrapper respons yang dinormalisasi, tidak ada field spesifik penyedia di logika bisnis. API gambar dan video memiliki lebih banyak perbedaan struktural (async vs sync, set parameter berbeda) sehingga lapisan abstraksinya butuh lebih banyak kerja. Mulailah dari teks, lalu perluas polanya setelah strukturnya matang.

Q: Bagaimana dengan perbedaan context window antar model?

Ini risiko nyata saat melakukan routing. GPT-5.5 memiliki context window 1M token, model Claude mendukung hingga 200K, dan Gemini 3.5 Flash mendukung hingga 1M. Jika Anda merutekan tugas dokumen panjang ke model dengan context window lebih pendek, input akan terpotong secara diam-diam. Tambahkan pemeriksaan panjang konteks sebelum routing jika tugas Anda melibatkan input panjang โ€” atau selalu rute tugas ber-konteks-panjang ke model tertentu di MODEL_CONFIG alih-alih membiarkannya jatuh ke default.

Siap memangkas biaya pengembangan AI hingga 20%?

Mulai gratis dalam beberapa menit. Kredit uji coba gratis disertakan. Tidak perlu kartu kredit.

Baca Selengkapnya