ज़्यादातर लोग प्रोजेक्ट शुरू करते समय प्रदाता पोर्टेबिलिटी के बारे में नहीं सोचते। वे जिस भी विक्रेता से साइन अप करना सबसे आसान होता है, उससे एक key लेते हैं, इंटीग्रेशन लिखते हैं, और शिप कर देते हैं। कुछ महीनों बाद वे कोई benchmark पढ़ते हैं, या बिल खोलते हैं, या किसी rate limit से टकरा जाते हैं, और वे कुछ और आज़माना चाहते हैं। तभी दर्द सामने आता है, क्योंकि उन्होंने पहले प्रदाता के बारे में अपनी धारणाएँ HTTP लेयर में बेक कर दी थीं।
इसका समाधान कोई abstraction library नहीं है। यह एक wire format है। OpenAI chat completions API चुपचाप LLM से बात करने का डिफ़ॉल्ट प्रोटोकॉल बन गया है, और एक बार जब आप इसके खिलाफ लिखते हैं, तो प्रदाता बदलना ज़्यादातर दो strings बदलने की बात है।
वह API जिसे सबने कॉपी किया
OpenAI ने एक सरल contract परिभाषित किया: आप /v1/chat/completions पर JSON POST करते हैं, आपको वापस एक choices[0].message.content string मिलती है। request ऐसी दिखती है:
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"}]
}'अब लगभग हर बड़ा model यह dialect बोलता है। DeepSeek, Qwen, ERNIE, Doubao, और अन्य सभी एक endpoint उजागर करते हैं जो वही body स्वीकार करता है और वही shape लौटाता है। इसका मतलब है कि आपके client code को इस बात की परवाह नहीं है कि दूसरी तरफ कौन है। आप model string और endpoint बदल सकते हैं और कुछ और नहीं बदलता।
यहाँ वही call एक aggregator की ओर इंगित है जो इनमें से कई models को एक 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"}]
}'एक client, कई models
Python में आप सामान्यतः OpenAI SDK को एक बार instantiate करते हैं और प्रति request एक model name पास करते हैं। यदि आपका प्रदाता compatible API का समर्थन करता है, तो आप base_url एक बार सेट करते हैं और बाकी सब समान रखते हैं:
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."))function नहीं बदलता। model string बदलती है। यही पूरी trick है। Streaming भी इसी तरह काम करता है:
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, और embeddings सभी उसी compatible surface पर चलते हैं, इसलिए स्विच करते समय आप केवल सादे chat तक सीमित नहीं हैं।
स्विच करने से आपको क्या नहीं मिलता
मैं उस हिस्से के बारे में स्पष्ट रहना चाहता हूँ जो मुफ़्त में नहीं बदलता। transport पोर्टेबल है; models नहीं।
अलग-अलग models prompts पर अलग-अलग प्रतिक्रिया देते हैं। GPT-4o के लिए ट्यून किया गया system prompt किसी reasoning model पर खराब प्रदर्शन कर सकता है जो अलग संरचना चाहता है। Context windows अलग होते हैं, कभी-कभी काफी ज़्यादा: एक model 128k tokens ले सकता है जबकि दूसरा 32k। Tokenizers भी अलग होते हैं, इसलिए वही document हर model पर अलग संख्या में tokens का खर्चा करता है। Max output length एक और knob है जो प्रति-model है, प्रति-protocol नहीं।
तो "बिना कोड बदले स्विच करें" वास्तव में "अपना HTTP client दोबारा लिखे बिना स्विच करें" है। किसी नए model पर production traffic भेजने से पहले आपको अभी भी एक evaluation run करना होगा। अच्छी खबर यह है कि compatible API उस evaluation को चलाना सस्ता बनाता है, क्योंकि harness केवल model names पर एक loop है।
एक व्यावहारिक पैटर्न
side projects में मैं वास्तव में एक छोटी config table रखता हूँ, code changes नहीं:
MODELS = {
"fast": "deepseek-chat",
"smart": "qwen-max",
"frontier": "gpt-4o",
}
def run(task, prompt):
return ask(MODELS[task], prompt)fast lane के लिए कोई सस्ता model आज़माना चाहते हैं? एक line बदलें। जब पहला प्रदाता rate limited हो तो दूसरे पर fall back करना चाहते हैं? exception पकड़ें और अगली model string के साथ retry करें। इनमें से कोई भी request construction को नहीं छूता।
यही fallback pattern वह जगह है जहाँ multi-model endpoint अपनी उपयोगिता साबित करता है। एक base URL और एक key के साथ आप प्रदाताओं के बीच route, retry, और A/B test कर सकते हैं, वह सब उसी दो lines के client setup के माध्यम से जो आपने पहले दिन लिखा था।
ध्यान देने योग्य response विचित्रताएँ
transport compatible है, लेकिन कुछ response-स्तरीय अंतर अभी भी रिसते हैं। Reasoning models कभी-कभी सामान्य message के साथ एक अतिरिक्त field जैसे reasoning_content लौटाते हैं। choices[0].message.content पढ़ने वाला code बिना फ़र्क पड़े काम करता रहता है, लेकिन आप उस reasoning text को debug view में दिखाना चाह सकते हैं। Error messages भी अलग होते हैं: एक प्रदाता एक उपयोगी insufficient_quota string लौटाता है, दूसरा बिना body के एक सादा 429 लौटाता है। यदि आप retries बना रहे हैं, तो किसी भी 429 या 5xx को "पीछे हटो और फिर कोशिश करो" मानें, न कि vendor-विशिष्ट text parse करें।
इनमें से कोई भी एक प्रदाता से बंधे रहने का कारण नहीं है। यह आपके error handling को generic और आपकी model पसंद को एक variable रखने का कारण है।
यही SiCore TokenWorks की स्थिति है: https://api.token8341.com/v1 पर एक OpenAI-compatible endpoint जहाँ GPT-4o, Claude, Gemini, DeepSeek, Qwen, ERNIE, Doubao, Spark, और Pangu के बीच जाने पर model string ही एकमात्र चीज़ है जो बदलती है।