Kebanyakan orang tidak memulai proyek dengan memikirkan portabilitas penyedia. Mereka mengambil kunci dari vendor mana pun yang paling mudah didaftarkan, menulis integrasinya, dan merilisnya. Beberapa bulan kemudian mereka membaca benchmark, atau membuka tagihan, atau menabrak rate limit, dan mereka ingin mencoba sesuatu yang lain. Saat itulah masalahnya muncul, karena mereka telah menanamkan asumsi tentang penyedia pertama ke dalam lapisan HTTP.
Solusinya bukanlah semacam pustaka abstraksi. Solusinya adalah format wire. API chat completions dari OpenAI diam-diam telah menjadi protokol default untuk berkomunikasi dengan LLM, dan begitu Anda menulis terhadapnya, berpindah penyedia sebagian besar hanyalah soal mengubah dua string.
API yang ditiru semua orang
OpenAI mendefinisikan kontrak sederhana: Anda POST JSON ke /v1/chat/completions, Anda mendapatkan kembali string choices[0].message.content. Request-nya terlihat seperti ini:
curl https://api.openai.com/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{
"model": "gpt-4o",
"messages": [{"role": "user", "content": "Explain DNS in one paragraph"}]
}'Hampir setiap model utama sekarang berbicara dalam dialek ini. DeepSeek, Qwen, ERNIE, Doubao, dan yang lainnya semuanya mengekspos endpoint yang menerima body yang sama dan mengembalikan bentuk yang sama. Artinya kode klien Anda tidak peduli siapa yang ada di ujung sana. Anda dapat menukar string model dan endpoint-nya dan tidak ada hal lain yang berubah.
Berikut adalah panggilan yang sama yang diarahkan ke aggregator yang membawa beberapa model ini di bawah satu base URL:
curl https://api.token8341.com/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKENWORKS_API_KEY" \
-d '{
"model": "deepseek-chat",
"messages": [{"role": "user", "content": "Explain DNS in one paragraph"}]
}'Satu klien, banyak model
Di Python Anda biasanya menginstansiasi OpenAI SDK sekali dan memberikan nama model per request. Jika penyedia Anda mendukung API yang kompatibel, Anda mengatur base_url sekali dan membiarkan semuanya identik:
from openai import OpenAI
client = OpenAI(
base_url="https://api.token8341.com/v1",
api_key="sk-your-tokenworks-key",
)
def ask(model: str, prompt: str) -> str:
resp = client.chat.completions.create(
model=model,
messages=[{"role": "user", "content": prompt}],
)
return resp.choices[0].message.content
# Same function, three different model families.
print(ask("gpt-4o", "Summarize this log for me."))
print(ask("deepseek-chat", "Summarize this log for me."))
print(ask("qwen-max", "Summarize this log for me."))Fungsi tersebut tidak berubah. String model yang berubah. Itulah seluruh triknya. Streaming bekerja dengan cara yang sama:
stream = client.chat.completions.create(
model="deepseek-chat",
messages=[{"role": "user", "content": "Write a haiku about databases"}],
stream=True,
)
for chunk in stream:
delta = chunk.choices[0].delta.content or ""
print(delta, end="")Function calling, JSON mode, dan embeddings semuanya mengikuti permukaan yang kompatibel yang sama, jadi Anda tidak terbatas pada chat biasa saat berpindah.
Apa yang tidak Anda dapatkan dari perpindahan
Saya ingin menjelaskan dengan gamblang bagian yang tidak berubah secara gratis. Transportasinya portabel; modelnya tidak.
Model yang berbeda merespons prompt secara berbeda. System prompt yang disetel untuk GPT-4o bisa berkinerja buruk pada model reasoning yang menginginkan struktur berbeda. Context window berbeda-beda, kadang sangat jauh: satu model mungkin menerima 128k token sementara yang lain menerima 32k. Tokenizer juga berbeda, jadi dokumen yang sama memakan jumlah token yang berbeda pada setiap model. Panjang output maksimum adalah knob lain yang bersifat per-model, bukan per-protokol.
Jadi "berpindah tanpa mengubah kode" sebenarnya adalah "berpindah tanpa menulis ulang klien HTTP Anda." Anda tetap perlu menjalankan evaluasi sebelum mengarahkan trafik produksi ke model baru. Kabar baiknya adalah API yang kompatibel membuat evaluasi itu murah untuk dijalankan, karena harness-nya hanyalah loop atas nama-nama model.
Pola yang praktis
Yang sebenarnya saya lakukan di proyek sampingan adalah menyimpan tabel konfigurasi kecil, bukan perubahan kode:
MODELS = {
"fast": "deepseek-chat",
"smart": "qwen-max",
"frontier": "gpt-4o",
}
def run(task, prompt):
return ask(MODELS[task], prompt)Ingin mencoba model yang lebih murah untuk jalur fast? Ubah satu baris. Ingin fallback ke penyedia kedua saat yang pertama terkena rate limit? Tangkap exception-nya dan coba lagi dengan string model berikutnya. Tidak ada dari ini yang menyentuh konstruksi request.
Pola fallback itulah tempat endpoint multi-model membuktikan nilainya. Dengan satu base URL dan satu kunci Anda dapat melakukan routing, retry, dan A/B testing lintas penyedia, semuanya melalui dua baris pengaturan klien yang sama yang Anda tulis pada hari pertama.
Keanehan respons yang perlu diperhatikan
Transportasinya kompatibel, tetapi beberapa perbedaan di tingkat respons masih bocor. Model reasoning terkadang mengembalikan field tambahan seperti reasoning_content di samping pesan normal. Kode yang membaca choices[0].message.content tetap berfungsi terlepas dari itu, tetapi Anda mungkin ingin menampilkan teks reasoning tersebut di tampilan debug. Pesan error juga bervariasi: satu penyedia mengembalikan string insufficient_quota yang membantu, yang lain mengembalikan 429 kosong tanpa body. Jika Anda membangun retry, perlakukan setiap 429 atau 5xx sebagai "back off dan coba lagi" daripada mem-parsing teks spesifik vendor.
Tidak ada dari ini yang menjadi alasan untuk tetap terkunci pada satu penyedia. Ini adalah alasan untuk menjaga penanganan error Anda tetap generik dan pilihan model Anda sebagai variabel.
Inilah posisi yang diambil SiCore TokenWorks: endpoint yang kompatibel dengan OpenAI di https://api.token8341.com/v1 di mana string model adalah satu-satunya hal yang berubah saat Anda berpindah antara GPT-4o, Claude, Gemini, DeepSeek, Qwen, ERNIE, Doubao, Spark, dan Pangu.