SiCore TokenWorks
LLM APIAPI GatewayAggregation

วิธีสลับผู้ให้บริการ LLM โดยไม่ต้องแก้โค้ดของคุณ

SiCore TokenWorks Team·2026-09-03

คนส่วนใหญ่ไม่ได้เริ่มต้นโปรเจกต์ด้วยการคิดเรื่องความย้ายผู้ให้บริการได้ พวกเขาหยิบคีย์จากผู้ขายที่สมัครง่ายที่สุด เขียนการเชื่อมต่อ แล้วก็ปล่อยขึ้น 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