SiCore TokenWorks
LLM APIAPI GatewayAggregation

Cách chuyển đổi giữa các nhà cung cấp LLM mà không cần thay đổi code

SiCore TokenWorks Team·2026-09-03

Hầu hết mọi người không bắt đầu một dự án với suy nghĩ về khả năng di chuyển giữa các nhà cung cấp. Họ lấy key từ bất kỳ vendor nào dễ đăng ký nhất, viết phần tích hợp, và ship. Vài tháng sau, họ đọc một benchmark, hoặc mở một hóa đơn, hoặc đụng phải rate limit, và họ muốn thử thứ khác. Đó là lúc nỗi đau xuất hiện, bởi vì họ đã đóng cứng những giả định về nhà cung cấp đầu tiên vào tầng HTTP.

Cách khắc phục không phải là một thư viện abstraction nào đó. Đó là một wire format. API chat completions của OpenAI đã âm thầm trở thành giao thức mặc định để giao tiếp với LLM, và một khi bạn viết code dựa trên nó, việc chuyển đổi nhà cung cấp chủ yếu chỉ là thay đổi hai chuỗi ký tự.

API mà tất cả mọi người đều sao chép

OpenAI đã định nghĩa một hợp đồng đơn giản: bạn POST JSON đến /v1/chat/completions, bạn nhận lại một chuỗi choices[0].message.content. Request trông như thế này:

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"}]
  }'

Gần như mọi model lớn hiện nay đều nói ngôn ngữ này. DeepSeek, Qwen, ERNIE, Doubao, và những model khác đều cung cấp một endpoint chấp nhận cùng body và trả về cùng cấu trúc. Điều đó có nghĩa là code phía client của bạn không quan tâm ai ở đầu bên kia. Bạn có thể thay chuỗi model và endpoint, và không có gì khác thay đổi.

Đây là cùng một lệnh gọi hướng đến một aggregator mang nhiều model trong số này dưới một 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"}]
  }'

Một client, nhiều model

Trong Python, bạn thường khởi tạo OpenAI SDK một lần và truyền tên model theo từng request. Nếu nhà cung cấp của bạn hỗ trợ API tương thích, bạn chỉ cần set base_url một lần và giữ mọi thứ khác giống hệt:

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."))

Hàm không thay đổi. Chuỗi model thay đổi. Đó là toàn bộ mẹo. Streaming cũng hoạt động theo cùng cách:

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, và embeddings đều chạy trên cùng một bề mặt tương thích, vì vậy bạn không bị giới hạn ở chat đơn thuần khi chuyển đổi.

Những gì việc chuyển đổi không mang lại cho bạn

Tôi muốn nói rõ về phần không thay đổi miễn phí. Phần transport thì di động được; các model thì không.

Các model khác nhau phản hồi với prompt theo cách khác nhau. Một system prompt được tinh chỉnh cho GPT-4o có thể hoạt động kém hơn trên một reasoning model vốn muốn một cấu trúc khác. Context window khác nhau, đôi khi rất nhiều: một model có thể nhận 128k token trong khi model khác chỉ nhận 32k. Tokenizer cũng khác nhau, nên cùng một tài liệu tốn số token khác nhau trên mỗi model. Độ dài output tối đa cũng là một tham số thuộc về từng model, không phải từng giao thức.

Vậy nên "chuyển đổi mà không thay đổi code" thực ra là "chuyển đổi mà không viết lại HTTP client của bạn." Bạn vẫn nợ bản thân một lượt đánh giá trước khi hướng traffic production đến một model mới. Tin tốt là API tương thích giúp việc đánh giá đó trở nên rẻ để chạy, bởi vì harness chỉ là một vòng lặp qua các tên model.

Một pattern thực tế

Điều tôi thực sự làm trong các side project là giữ một bảng config nhỏ, không phải thay đổi code:

MODELS = {
    "fast": "deepseek-chat",
    "smart": "qwen-max",
    "frontier": "gpt-4o",
}

def run(task, prompt):
    return ask(MODELS[task], prompt)

Muốn thử một model rẻ hơn cho làn fast? Thay một dòng. Muốn fallback sang nhà cung cấp thứ hai khi nhà cung cấp đầu bị rate limit? Bắt exception và retry với chuỗi model tiếp theo. Không có gì trong số này đụng đến việc xây dựng request.

Pattern fallback đó là nơi một endpoint đa model chứng minh giá trị của nó. Với một base URL và một key, bạn có thể route, retry, và A/B test xuyên suốt các nhà cung cấp, tất cả thông qua cùng hai dòng thiết lập client bạn đã viết ngay từ ngày đầu.

Những điểm bất thường trong response cần lưu ý

Transport thì tương thích, nhưng một vài khác biệt ở tầng response vẫn lộ ra. Reasoning model đôi khi trả về thêm một field như reasoning_content bên cạnh message thông thường. Code đọc choices[0].message.content vẫn hoạt động bất kể điều đó, nhưng bạn có thể muốn hiển thị phần reasoning text đó trong một debug view. Thông báo lỗi cũng khác nhau: một nhà cung cấp trả về chuỗi insufficient_quota hữu ích, nhà cung cấp khác trả về một 429 trần không có body. Nếu bạn đang xây dựng retry, hãy coi bất kỳ 429 hoặc 5xx nào là "back off và thử lại" thay vì parse text đặc thù của từng vendor.

Không có gì trong số này là lý do để tiếp tục bị khóa vào một nhà cung cấp. Đó là lý do để giữ error handling của bạn ở dạng tổng quát và lựa chọn model của bạn như một biến số.

Đây là quan điểm của SiCore TokenWorks: một endpoint tương thích OpenAI tại https://api.token8341.com/v1 nơi chuỗi model là thứ duy nhất thay đổi khi bạn di chuyển giữa GPT-4o, Claude, Gemini, DeepSeek, Qwen, ERNIE, Doubao, Spark, và Pangu.