คนส่วนใหญ่ไม่ได้เริ่มต้นโปรเจกต์ด้วยการคิดเรื่องความย้ายผู้ให้บริการได้ พวกเขาหยิบคีย์จากผู้ขายที่สมัครง่ายที่สุด เขียนการเชื่อมต่อ แล้วก็ปล่อยขึ้น production ไม่กี่เดือนต่อมา พวกเขาอ่านผลเปรียบเทียบ หรือเปิดดูบิล หรือชนกำแพง rate limit และอยากลองอย่างอื่น นั่นแหละที่ความเจ็บปวดโผล่ขึ้นมา เพราะพวกเขาได้ฝังสมมติฐานเกี่ยวกับผู้ให้บริการรายแรกไว้ในชั้น HTTP
วิธีแก้ไม่ใช่ไลบรารี abstraction บางตัว แต่เป็นรูปแบบการรับส่งข้อมูล (wire format) OpenAI chat completions API ได้กลายเป็นโปรโตคอลเริ่มต้นสำหรับการสื่อสารกับ LLM อย่างเงียบ ๆ และเมื่อคุณเขียนโค้ดให้สอดคล้องกับมัน การสลับผู้ให้บริการก็เป็นเพียงเรื่องของการเปลี่ยนสองสตริง
API ที่ทุกคนลอกแบบ
OpenAI กำหนดสัญญาง่าย ๆ ไว้: คุณ POST JSON ไปที่ /v1/chat/completions คุณจะได้สตริง choices[0].message.content กลับมา คำขอมีลักษณะดังนี้:
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"}]
}'ตอนนี้โมเดลชั้นนำแทบทุกตัวพูดภาษาเดียวกันนี้ DeepSeek, Qwen, ERNIE, Doubao และตัวอื่น ๆ ต่างก็เปิด endpoint ที่รับ body เดียวกันและคืนรูปร่างเดียวกัน นั่นหมายความว่าโค้ดไคลเอนต์ของคุณไม่สนใจว่าใครอยู่ปลายทางอีกฝั่ง คุณสามารถสลับสตริง model และ endpoint ได้โดยไม่มีอะไรอื่นเปลี่ยน
นี่คือการเรียกเดียวกันที่ชี้ไปยัง aggregator ที่ให้บริการหลายโมเดลเหล่านี้ภายใต้ 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"}]
}'ไคลเอนต์เดียว หลายโมเดล
ใน Python โดยปกติคุณสร้างอินสแตนซ์ OpenAI SDK ครั้งเดียวและส่งชื่อโมเดลต่อคำขอ หากผู้ให้บริการของคุณรองรับ 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."))ฟังก์ชันไม่เปลี่ยน สตริงโมเดลเปลี่ยน นั่นคือเคล็ดลับทั้งหมด การสตรีมก็ทำงานแบบเดียวกัน:
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 ล้วนใช้งานบนพื้นที่ที่เข้ากันได้เดียวกัน ดังนั้นคุณไม่ได้จำกัดอยู่แค่การแชทธรรมดาเมื่อคุณสลับ
สิ่งที่การสลับไม่ได้ให้คุณ
ผมอยากพูดให้ชัดเกี่ยวกับส่วนที่ไม่เปลี่ยนฟรี การรับส่งข้อมูลนั้นพอร์ตได้ แต่โมเดลนั้นไม่
โมเดลต่าง ๆ ตอบสนองต่อ prompt ต่างกัน system prompt ที่ปรับจูนสำหรับ GPT-4o อาจทำงานได้แย่ลงบนโมเดล reasoning ที่ต้องการโครงสร้างต่างออกไป context window แตกต่างกัน บางครั้งต่างกันมาก: โมเดลหนึ่งอาจรับ 128k tokens ในขณะที่อีกตัวรับ 32k tokenizer ก็ต่างกันด้วย ดังนั้นเอกสารเดียวกันจึงมีค่าใช้จ่ายเป็นจำนวน token ที่ต่างกันในแต่ละโมเดล ความยาวเอาต์พุตสูงสุดก็เป็นอีกปุ่มหนึ่งที่เป็นรายโมเดล ไม่ใช่รายโปรโตคอล
ดังนั้น "สลับโดยไม่แก้โค้ด" จริง ๆ แล้วคือ "สลับโดยไม่ต้องเขียนไคลเอนต์ HTTP ใหม่" คุณยังคงต้องรันการประเมินก่อนที่จะชี้ทราฟฟิก production ไปยังโมเดลใหม่ ข่าวดีก็คือ API ที่เข้ากันได้ทำให้การประเมินนั้นรันได้อย่างถูก เพราะ harness ก็เป็นเพียงลูปเหนือชื่อโมเดลเท่านั้น
รูปแบบที่ใช้ได้จริง
สิ่งที่ผมทำจริง ๆ ในโปรเจกต์ส่วนตัวคือเก็บตาราง config เล็ก ๆ ไม่ใช่แก้โค้ด:
MODELS = {
"fast": "deepseek-chat",
"smart": "qwen-max",
"frontier": "gpt-4o",
}
def run(task, prompt):
return ask(MODELS[task], prompt)อยากลองโมเดลที่ถูกกว่าสำหรับเลน fast? แก้หนึ่งบรรทัด อยาก fallback ไปยังผู้ให้บริการรายที่สองเมื่อรายแรกติด rate limit? จับ exception แล้ว retry ด้วยชื่อโมเดลถัดไป ไม่มีอะไรในนี้ที่แตะการสร้างคำขอ
รูปแบบ fallback นั่นแหละที่ทำให้ endpoint แบบหลายโมเดลคุ้มค่า ด้วย base URL เดียวและคีย์เดียว คุณสามารถ route, retry และ A/B test ข้ามผู้ให้บริการได้ทั้งหมด ผ่านการตั้งค่าไคลเอนต์สองบรรทัดเดียวกันกับที่คุณเขียนในวันแรก
ลักษณะเฉพาะของการตอบสนองที่ต้องระวัง
การรับส่งข้อมูลเข้ากันได้ แต่ความแตกต่างระดับการตอบสนองบางอย่างก็ยังรั่วไหลออกมา โมเดล reasoning บางครั้งคืนฟิลด์เพิ่มเติมเช่น reasoning_content ควบคู่ไปกับข้อความปกติ โค้ดที่อ่าน choices[0].message.content ยังทำงานต่อไปได้ไม่ว่าในกรณีใด แต่คุณอาจต้องการแสดงข้อความ reasoning นั้นในมุมมอง debug ข้อความ error ก็แตกต่างกันด้วย: ผู้ให้บริการรายหนึ่งคืนสตริง insufficient_quota ที่มีประโยชน์ อีกรายคืน 429 เปล่า ๆ โดยไม่มี body หากคุณกำลังสร้าง retry ให้ถือว่า 429 หรือ 5xx ใด ๆ เป็น "ถอยออกแล้วลองใหม่" แทนที่จะแยกวิเคราะห์ข้อความเฉพาะผู้ขาย
ไม่มีสิ่งใดในนี้เป็นเหตุผลที่จะอยู่กับผู้ให้บริการรายเดียว มันเป็นเหตุผลที่จะทำให้การจัดการ error ของคุณเป็นแบบทั่วไปและตัวเลือกโมเดลของคุณเป็นตัวแปร
นี่คือจุดยืนที่ SiCore TokenWorks ยึดถือ: endpoint ที่เข้ากันได้กับ OpenAI ที่ https://api.token8341.com/v1 ซึ่งสตริงโมเดลเป็นสิ่งเดียวที่เปลี่ยนเมื่อคุณย้ายระหว่าง GPT-4o, Claude, Gemini, DeepSeek, Qwen, ERNIE, Doubao, Spark และ Pangu