8341TokenFactory
LLM APIAPI GatewayIntegration

Xưởng token8341: Phân tích lớp dịch giao thức của cổng mô hình, chia khối SSE, mã lỗi và chuẩn hóa mức sử dụng

SiCore TokenWorks Team·2026-10-27

Nếu bạn đang làm backend tích hợp đa mô hình, chắc hẳn đã từng trải qua sự sụp đổ kiểu này: mã nghiệp vụ nhồi đầy if (vendor === 'openai') ... else if (vendor === 'claude') ..., một giao diện hội thoại dạng streaming viết tới tám trăm dòng logic thích ứng, thêm một nhà cung cấp mới là lại phải động vào toàn bộ chuỗi lõi. Vấn đề không nằm ở bạn, mà ở chỗ giao thức API của các hãng mô hình lớn vốn dĩ chưa bao giờ định thống nhất.

Bản chất của cổng mô hình là một lớp dịch giao thức: thu gọn request/response không đồng nhất của N nhà cung cấp thành 1 bộ giao thức chuẩn nội bộ. Bài viết này chia theo ba đoạn tiệm tiến nguyên lý, thực hành, so sánh, làm rõ lớp dịch rốt cuộc nên làm thế nào.

I. Bốn loại điểm không đồng nhất: vì sao kết nối trực tiếp nhiều hãng ắt sẽ mất kiểm soát

Trước hết xem sự khác biệt thực tế, mỗi cái đều kèm con số.

Cách xác thực: OpenAI dùng Authorization: Bearer sk-xxx, độ dài trường header khoảng 51 ký tự; Claude dùng x-api-key; Gemini nhét key vào tham số truy vấn ?key= của URL; Qwen, ERNIE dùng Authorization nhưng thuật toán chữ ký khác nhau, access_token của ERNIE mặc định có hiệu lực 30 ngày, hết hạn trả về mã lỗi 110. Bốn hãng bốn kiểu viết.

Định dạng chia khối streaming SSE: chunk của OpenAI là data: {"choices":[{"delta":{"content":"x"}}]}, dấu kết thúc là data: [DONE]; loại sự kiện của Claude là event: content_block_delta kèm data: {"delta":{"text":"x"}}, tên trường từ content đổi thành text; Gemini đặt phần tăng dần trong candidates[0].content.parts[0].text, lồng sâu 3 tầng. Để phân tích cùng một câu ở tầng nghiệp vụ, phải viết bốn bộ parser.

Hệ thống mã lỗi: đây là thứ khó chịu nhất. OpenAI dùng mã trạng thái HTTP + error.type, giới hạn tần suất là 429; Claude giới hạn tần suất trả về 429 nhưng type trong body là rate_limit_error; Qwen giới hạn tần suất có thể trả về mã trạng thái 200 nhưng trong body mang code: "Throttling"; ERNIE dùng error_code: 17 để biểu thị vượt hạn mức. Cùng một chuyện, bốn cách biểu đạt.

Tiêu chuẩn thống kê Token: usage.total_tokens = prompt + completion của OpenAI, tiếng Trung tiếng Anh đều cắt theo BPE; Claude trả riêng input_tokens và output_tokens, và độ chi tiết cắt ký tự tiếng Trung mịn hơn, cùng một đoạn tiếng Trung 500 chữ, số token thống kê của hai hãng có thể lệch 20% đến 35%. Khi tính phí theo lượng dùng, sự khác biệt này trực tiếp biến thành sai số hóa đơn.

Kết nối trực tiếp bốn hãng, lượng mã ở tầng nghiệp vụ sẽ phình lên gấp 3 đến 4 lần so với một hãng, chi phí bảo trì tăng tuyến tính theo số nhà cung cấp.

II. Thiết kế lớp dịch: định dạng thống nhất, chuẩn hóa streaming, ánh xạ mã lỗi, tổng hợp mức sử dụng

Lớp dịch phải làm bốn việc, thiếu một cái là không được.

Định dạng request/response thống nhất: nội bộ định nghĩa một bộ Schema chuẩn, các trường căn theo phong cách OpenAI (vì hệ sinh thái của nó rộng nhất). Request bên ngoài vào trước tiên chuyển thành định dạng nội bộ, rồi bộ thích ứng chuyển thành định dạng của từng hãng. Mẫu adapter, mỗi hãng một lớp, triển khai hai phương thức buildRequest() và parseResponse().

Chuẩn hóa chia khối streaming: cốt lõi là chuyển chunk SSE của mọi hãng thành {delta: string, finish_reason: string|null} thống nhất. Cách làm là viết một máy trạng thái, phân phối theo loại hãng đến các parser khác nhau, xuất ra cấu trúc thống nhất. Điểm mấu chốt là xử lý biên: dấu phân cách \n\n của SSE, tiền tố data:, nối data nhiều dòng, đều phải được xử lý gọn trong parser. Cái hố tôi từng vấp phải là sự kiện ping và message_stop của Claude, nếu không chủ động bỏ qua, sẽ đẩy delta rỗng cho frontend khiến UI nhấp nháy.

Bảng ánh xạ mã lỗi: duy trì một ánh xạ vendorCode -> unifiedCode, thống nhất thành 6 loại: AUTH_FAILED, RATE_LIMITED, QUOTA_EXCEEDED, INVALID_REQUEST, UPSTREAM_ERROR, TIMEOUT. Thượng nguồn trả về gì, lớp dịch tra bảng chuyển thành mã thống nhất, tầng nghiệp vụ chỉ nhận 6 cái này.

Tổng hợp mức sử dụng: sau mỗi lần gọi, quy đổi tiêu chuẩn token của các hãng thành token chuẩn nội bộ, khi lưu vào cơ sở dữ liệu mang theo vendor, model, request_id, dùng cho đối soát và phân tích chi phí sau này. Ở đây cần chú ý khác biệt tiêu chuẩn, output_tokens của Claude và completion_tokens của OpenAI không thể cộng trực tiếp, phải quy đổi theo hệ số về cùng một cơ sở.

Về kiến trúc phân tầng, tôi thường chia thành bốn tầng: tầng truy cập (xác thực + giới hạn tần suất), tầng định tuyến (chọn mô hình), tầng dịch (chuyển đổi giao thức), tầng thích ứng (gọi SDK của hãng). Tầng dịch là hàm thuần, không động vào IO, thuận tiện cho unit test.

III. Mã thực hành: đổi một dòng base_url để kết nối cổng tổng hợp

Nếu bạn không muốn viết lớp dịch từ đầu, dùng nền tảng tổng hợp AI API có sẵn là lựa chọn thực tế hơn. Trong dự án của chúng tôi đã dùng token8341, nó tương thích với OpenAI SDK, đổi một dòng base_url là có thể chuyển đổi, việc của lớp dịch nó làm thay bạn.

from openai import OpenAI

client = OpenAI(
    api_key="your-key",
    base_url="https://api.token8341.com/v1"  # chỉ đổi dòng này
)

resp = client.chat.completions.create(
    model="deepseek-v3",  # cũng có thể đổi sang gpt-4o / qwen-max / claude-4-sonnet
    messages=[{"role": "user", "content": "giải thích về SSE"}],
    stream=True
)

for chunk in resp:
    delta = chunk.choices[0].delta.content
    if delta:
        print(delta, end="")

Trong đoạn mã này, chuẩn hóa chia khối SSE, ánh xạ mã lỗi, tổng hợp mức sử dụng đều đã hoàn thành ở phía cổng, tầng nghiệp vụ nhận được chính là định dạng OpenAI chuẩn. Định tuyến đa mô hình cũng đỡ phải làm, cổng có thể tự động chọn mô hình tối ưu theo tác vụ, ví dụ văn bản dài tiếng Trung đi Qwen, tác vụ suy luận đi DeepSeek, kịch bản nhạy cảm chi phí đi Doubao.

Nhắc nhở về hố: sau khi đổi base_url, đừng truyền thẳng các tham số đặc thù của hãng. Ví dụ logit_bias của OpenAI, top_k của Claude, cổng không nhất thiết hỗ trợ hết, truyền thẳng qua có thể bị âm thầm bỏ qua, khiến bạn tưởng đã có hiệu lực mà thực ra không. Trước khi lên production, dùng script kiểm thử áp lực chạy một lượt tương thích tham số.

IV. So sánh: định tuyến đa mô hình vs ràng buộc một mô hình

Đưa ra một nhóm số liệu chúng tôi thực đo. Ràng buộc một mô hình (kết nối trực tiếp OpenAI): thời gian tích hợp khoảng 2 người-ngày, chuyển đổi khi sự cố cần sửa mã phát hành lại, thời gian khôi phục trung bình 15 phút, không có không gian tối ưu chi phí. Định tuyến đa mô hình (qua cổng): thời gian tích hợp khoảng 0.5 người-ngày, chuyển đổi khi sự cố tự động hoàn thành, thời gian khôi phục dưới 3 giây, chi phí có thể phân luồng theo tác vụ giảm khoảng 30% đến 40%.

Về độ phức tạp bảo trì, mã thích ứng kết nối trực tiếp 4 hãng khoảng 2000 dòng, sau khi qua cổng mã thích ứng tầng nghiệp vụ giảm xuống dưới 200 dòng, giảm 90%. Về độ trễ, cổng thêm một chặng, P99 tăng khoảng 20 đến 40ms, so với chi phí bảo trì và tính linh hoạt chuyển đổi mà nó tiết kiệm, chi phí này có thể chấp nhận.

Tất nhiên, cổng không phải viên đạn bạc. Nếu nghiệp vụ của bạn chỉ dùng một mô hình và mãi mãi không đổi, kết nối trực tiếp đơn giản hơn. Nhưng chỉ cần liên quan đến đa mô hình, đa nhà cung cấp, tối ưu chi phí, lớp dịch là hạ tầng bắt buộc. Ba thứ cổng mô hình, API mô hình lớn, tổng hợp AI API, bản chất là ba cách nói của cùng một vấn đề ở cùng một tầng.

Cuối cùng nhắc một câu: độ phủ unit test của lớp dịch phải kéo lên tối đa, đặc biệt là các case biên của chia khối streaming, đây là khu vực dễ xảy ra sự cố trên production.

Tác giả: Lưu Tri Viễn

Ngày phát hành: 28 tháng 10 năm 2026