GLM-5.3 FlashX and MiniMax H3 Max are now live on CometAPI →
technology/Nghiên cứu CometAPI

Cách xây dựng ứng dụng AI không bị ràng buộc với một nhà cung cấp duy nhất

Cách xây dựng ứng dụng AI không bị ràng buộc vào một nhà cung cấp duy nhất: Tránh tình trạng ràng buộc với nhà cung cấp AI bằng cách cấu trúc mã theo hướng trung lập với nhà cung cấp. Hãy dùng thử trên CometAPI.

CometAPI
AnnaĐội ngũ nghiên cứu mô hình AI và API
Đã cập nhật Sep 3, 2026 11 phút đọc
Cách xây dựng ứng dụng AI không bị ràng buộc với một nhà cung cấp duy nhất
Sử dụng mẫu này

Thực hiện API call đầu tiên.

from openai import OpenAI

client = OpenAI(
    api_key="YOUR_COMETAPI_KEY",
    base_url="https://api.cometapi.com/v1",
)

response = client.chat.completions.create(
    model="gpt-5-mini",
    messages=[{"role": "user", "content": "Build this workflow."}],
)

print(response.choices[0].message.content)

Ràng buộc nhà cung cấp (vendor lock-in) trong ứng dụng AI thường không xảy ra một lần. Nó len lỏi dần — chỗ này thêm một import openai, chỗ kia hardcode tên model, rồi bạn parse một trường phản hồi mà không kiểm tra nhà cung cấp khác có trả về giống vậy không. Sáu tháng sau, đổi nhà cung cấp đồng nghĩa với việc phải viết lại nửa backend.

Bốn cách lock-in xảy ra

Hầu hết lập trình viên nghĩ lock-in nghĩa là “Tôi đang dùng OpenAI SDK.” Đó là kiểu ít nguy hiểm nhất. Bẫy thật sự tinh vi hơn:

Loại lock-inCách nó xảy raHệ quả
Lock-in SDKfrom openai import OpenAI everywhereĐổi SDK nghĩa là phải chạm vào mọi file
Lock-in tên modelmodel="gpt-4o" hardcoded trong business logicMỗi lần đổi model là một lần đổi code
Lock-in tham sốUsing logprobs, n>1, or reasoning_effortKhông tồn tại trên Claude hoặc Gemini
Lock-in định dạng phản hồiParsing provider-specific response fieldsMỗi nhà cung cấp trả về cấu trúc khác nhau

Mục tiêu không phải loại bỏ tất cả — một số là đánh đổi chấp nhận được. Mục tiêu là biết mình đang chấp nhận những lock-in nào.

Dùng một endpoint tương thích OpenAI làm lớp trừu tượng

Cách sạch nhất để tránh lock-in SDK là dùng một endpoint tương thích OpenAI duy nhất để định tuyến tới nhiều nhà cung cấp. Bạn giữ OpenAI SDK, nhưng backend có thể là bất kỳ nhà cung cấp nào.

CometAPI làm điều này — một endpoint, một key, 500+ model từ OpenAI, Anthropic, Google, DeepSeek, xAI, và các bên khác:

import osfrom openai import OpenAIfrom dotenv import load_dotenv​load_dotenv()​api_key = os.environ.get("AI_API_KEY")if not api_key:    raise ValueError("AI_API_KEY environment variable is not set")​client = OpenAI(    base_url=os.environ.get("AI_BASE_URL", "https://api.cometapi.com/v1"),    api_key=api_key,)

Chuyển từ GPT sang Claude hay Gemini chỉ cần đổi một dòng:

# Beforeresponse = client.chat.completions.create(model="gpt-5.4", messages=[...])​# After — same code, different modelresponse = client.chat.completions.create(model="claude-sonnet-4-6", messages=[...])

Lưu ý: Tên model như gpt-5.4claude-sonnet-4-6 là định danh trên nền tảng CometAPI — chúng chỉ hoạt động qua https://api.cometapi.com/v1, không hoạt động trực tiếp qua API của OpenAI hay Anthropic. Xem danh sách model đầy đủ để biết toàn bộ catalog và giá.

Tránh đưa tên model vào business logic

Tên model rải rác khắp code là dạng lock-in phổ biến nhất. Cách khắc phục là đặt cấu hình tập trung đọc từ biến môi trường:

# config.py — one place to change model assignmentsimport os​MODEL_CONFIG = {    "summarize": os.environ.get("MODEL_SUMMARIZE", "claude-opus-4-7"),    "code":      os.environ.get("MODEL_CODE",      "gpt-5.4"),    "classify":  os.environ.get("MODEL_CLASSIFY",  "claude-haiku-4-5"),    "chat":      os.environ.get("MODEL_CHAT",       "gpt-5.4-mini"),}​# Validate at startup — fail fast rather than getting mysterious API errorsfor task, model in MODEL_CONFIG.items():    if not model:        raise ValueError(f"Model config for '{task}' is not set")

Business logic của bạn sẽ không tham chiếu trực tiếp tên model:

from config import MODEL_CONFIG​def summarize(text: str) -> str:    response = client.chat.completions.create(        model=MODEL_CONFIG["summarize"],        messages=[{"role": "user", "content": f"Summarize: {text}"}],        max_tokens=300  # move to config in production    )    return response.choices[0].message.content

Để đổi model tóm tắt trên toàn bộ ứng dụng, chỉ cần đổi một biến môi trường. Không cần grep hay find-and-replace.

Bọc phản hồi để code không phụ thuộc vào trường riêng của từng nhà cung cấp

Mỗi nhà cung cấp trả về cấu trúc phản hồi hơi khác nhau. Nếu bạn parse raw API responses khắp codebase, bạn sẽ bị khóa vào định dạng của nhà cung cấp đó.

Hãy bọc chúng thành một dataclass chuẩn hóa:

from dataclasses import dataclassfrom typing import Optionalfrom openai import OpenAI, APIStatusError, APIConnectionError, APITimeoutErrorfrom openai.types.chat import ChatCompletionimport logging​@dataclassclass AIResponse:    content: str    model: str    input_tokens: int    output_tokens: int​def call_model(task: str, messages: list, **kwargs) -> AIResponse:    """    Single entry point for all LLM calls.    Returns a normalized AIResponse regardless of which model handled it.    Raises on 4xx (client errors). Logs and re-raises on 5xx/network errors.    """    model = MODEL_CONFIG.get(task, "gpt-5.4-mini")    if not model:        raise ValueError(f"No model configured for task '{task}'")​    try:        response: ChatCompletion = client.chat.completions.create(            model=model,            messages=messages,            **kwargs        )    except APIStatusError as e:        logging.error(f"API error for task={task} model={model}: {e.status_code} {e.message}")        raise    except (APIConnectionError, APITimeoutError) as e:        logging.error(f"Network error for task={task} model={model}: {e}")        raise​    # content is None when the model triggers a tool call instead of returning text    content = response.choices[0].message.content or ""​    # usage is None in streaming mode — default to 0 if not available    usage = response.usage    input_tokens = usage.prompt_tokens if usage else 0    output_tokens = usage.completion_tokens if usage else 0​    logging.info(        f"task={task} model={model} "        f"input_tokens={input_tokens} output_tokens={output_tokens}"    )​    return AIResponse(        content=content,        model=response.model,        input_tokens=input_tokens,        output_tokens=output_tokens,    )

Giờ business logic của bạn làm việc với đối tượng AIResponse, không phải phản hồi API thô. Nếu một nhà cung cấp đổi định dạng phản hồi, bạn chỉ cần sửa ở một chỗ.

Thêm hỗ trợ streaming vào wrapper

Với giao diện chat, bạn sẽ cần streaming. Wrapper xử lý nó theo một luồng riêng:

from typing import Iterator​def stream_model(task: str, messages: list, **kwargs) -> Iterator[str]:    """    Stream tokens from the routed model.    Note: streaming doesn't return usage data.    Fallback is not supported in streaming mode — you've already    started yielding tokens before you know if the full request succeeds.    """    model = MODEL_CONFIG.get(task, "gpt-5.4-mini")    if not model:        raise ValueError(f"No model configured for task '{task}'")​    stream = client.chat.completions.create(        model=model,        messages=messages,        stream=True,        **kwargs    )​    for chunk in stream:        delta = chunk.choices[0].delta.content        if delta:            yield delta​# Usagefor token in stream_model("chat", [{"role": "user", "content": "Hello"}]):    print(token, end="", flush=True)

Biết tham số nào tạo ra lock-in

Một số tham số chỉ tồn tại trên các nhà cung cấp nhất định. Dùng chúng là điều bình thường — chỉ cần biết bạn đang đưa ra lựa chọn có chủ đích:

Tham sốHoạt động trênRủi ro lock-in
logprobsChỉ GPTCao — không có tương đương trên Claude hoặc Gemini
n > 1GPT, Gemini (không Claude)Trung bình — Claude cần lặp thủ công
reasoning_effortChỉ GPT o-seriesCao — không có tương đương ở nơi khác
temperature > 1.0GPT, Gemini (không Claude)Thấp — Claude giới hạn ở 1.0
toolsTất cả nhà cung cấp lớnKhông — an toàn để dùng
response_formatTất cả nhà cung cấp lớnThấp — khác biệt nhỏ về schema

Nếu bạn dùng logprobs để chấm điểm độ tin cậy, bạn bị khóa vào GPT cho tính năng đó. Đây là đánh đổi hợp lý — chỉ cần ghi chú lại để developer tiếp theo biết lý do.

Cho phép cấu hình endpoint của nhà cung cấp

Hard-code base_url="https://api.cometapi.com/v1" vẫn là một dạng lock-in. Hãy đưa nó thành biến môi trường:

# .env — using CometAPIAI_BASE_URL=https://api.cometapi.com/v1AI_API_KEY=your_cometapi_key​# To switch to OpenAI directly, change two lines:# AI_BASE_URL=https://api.openai.com/v1# AI_API_KEY=your_openai_key

Khởi tạo client từ Bước 1 đã đọc từ các biến này. Chuyển giữa CometAPI và kết nối trực tiếp tới nhà cung cấp giờ là thay đổi cấu hình, không phải thay đổi code.

Phiên bản Node.js

import OpenAI from 'openai';​const apiKey = process.env.AI_API_KEY;if (!apiKey) throw new Error('AI_API_KEY is not set');​const client = new OpenAI({  baseURL: process.env.AI_BASE_URL ?? 'https://api.cometapi.com/v1',  apiKey,});​// Model IDs are CometAPI platform identifiers — see cometapi.com/modelsconst MODEL_CONFIG = {  summarize: process.env.MODEL_SUMMARIZE ?? 'claude-opus-4-7',  code:      process.env.MODEL_CODE      ?? 'gpt-5.4',  classify:  process.env.MODEL_CLASSIFY  ?? 'claude-haiku-4-5',  chat:      process.env.MODEL_CHAT      ?? 'gpt-5.4-mini',};​// Validate at startupfor (const [task, model] of Object.entries(MODEL_CONFIG)) {  if (!model) throw new Error(`Model config for '${task}' is not set`);}​/** * Single entry point for all LLM calls. * Returns normalized response. Raises on 4xx, logs and re-raises on 5xx/network. */async function callModel(task, messages, options = {}) {  const model = MODEL_CONFIG[task] ?? 'gpt-5.4-mini';​  let response;  try {    response = await client.chat.completions.create({      model,      messages,      ...options,    });  } catch (err) {    // Don't swallow errors — log and re-raise    console.error(`API error task=${task} model=${model}:`, err.message);    throw err;  }​  // content is null when model triggers a tool call  const content = response.choices[0].message.content ?? '';​  // usage may be absent in some configurations  const inputTokens  = response.usage?.prompt_tokens     ?? 0;  const outputTokens = response.usage?.completion_tokens ?? 0;​  console.log(`task=${task} model=${model} input=${inputTokens} output=${outputTokens}`);​  return { content, model: response.model, inputTokens, outputTokens };}​/** * Stream tokens from the routed model. * Usage data is not available in streaming mode. */async function* streamModel(task, messages, options = {}) {  const model = MODEL_CONFIG[task] ?? 'gpt-5.4-mini';​  const stream = await client.chat.completions.create({    model,    messages,    stream: true,    ...options,  });​  for await (const chunk of stream) {    const delta = chunk.choices[0]?.delta?.content;    if (delta) yield delta;  }}​// Usage — blockingconst result = await callModel('classify', [  { role: 'user', content: 'Positive or negative? "Loved it!"' }]);console.log(result.content);​// Usage — streamingfor await (const token of streamModel('chat', [  { role: 'user', content: 'Hello' }])) {  process.stdout.write(token);}

Lock-in nào chấp nhận được

Không phải lock-in nào cũng đáng để chống lại. Một số đánh đổi hợp lý:

  • Dùng SDK của OpenAI — Đây là tiêu chuẩn thực tế. Hầu hết nhà cung cấp hỗ trợ nó. Rủi ro lock-in thấp.
  • Tính năng riêng của nhà cung cấp mà bạn thật sự cần — Nếu bạn cần logprobs, hãy dùng. Cô lập đoạn code đó để sau này dễ tìm và thay thế.
  • Model fine-tuned — Model fine-tuned vốn gắn với một nhà cung cấp. Điều này là hiển nhiên.

Lock-in đáng tránh là kiểu vô tình — tên model trong business logic, parse phản hồi thô rải khắp file, API key hardcode trong source.

Tiếp theo là gì

Bạn đã có một lớp trừu tượng giúp chi tiết về nhà cung cấp không rò rỉ vào business logic. Bài cuối trong chuỗi này nói về chuyện khi mọi thứ trục trặc: cách debug các lượt sinh thất bại, diễn giải mã lỗi, và xây dựng xử lý lỗi thực sự cho biết điều gì bị hỏng.

Tiếp theo: How to Debug Failed AI API Generations

FAQ

Hỏi: Sự khác biệt giữa lock-in SDK và lock-in model là gì?

Lock-in SDK nghĩa là code của bạn import một thư viện cụ thể và sẽ phải thay đổi nếu bạn chuyển SDK. Lock-in model nghĩa là tên model rải rác trong business logic. Lock-in SDK ít nguy hiểm hơn vì hầu hết nhà cung cấp hiện hỗ trợ định dạng OpenAI SDK. Lock-in model tinh vi hơn vì khó tìm và sửa.

Hỏi: Nếu tôi dùng CometAPI, có phải tôi chỉ đổi lock-in OpenAI sang lock-in CometAPI?

Một phần. Bạn đang đổi lock-in nhà cung cấp trực tiếp sang một lớp proxy. Lợi ích: một key, một endpoint, đổi model dễ dàng. Rủi ro: nếu CometAPI gặp sự cố, tất cả các nhà cung cấp của bạn sẽ cùng ngừng hoạt động. Biện pháp giảm thiểu đã có trong code ở trên — AI_BASE_URL là biến môi trường. Nếu cần bỏ qua CometAPI và gọi trực tiếp một nhà cung cấp, đó chỉ là thay đổi cấu hình, không phải thay đổi code.

Hỏi: Tôi có thể dùng extended thinking của Claude hoặc reasoning_effort của OpenAI theo mẫu này không?

Có, truyền chúng qua **kwargs vào call_model. Chỉ cần biết rằng nếu bạn định tuyến tác vụ đó sang model khác, các tham số đó sẽ bị bỏ qua hoặc gây lỗi. Hãy ghi chú tác vụ nào dùng tính năng riêng của nhà cung cấp để developer tiếp theo biết lý do.

Hỏi: Tôi xử lý giới hạn temperature 1.0 của Claude thế nào khi định tuyến giữa Claude và GPT?

Giữ temperature ở mức 1.0 hoặc thấp hơn để an toàn cho cả hai. Nếu bạn cần temperature cao hơn cho tác vụ sáng tạo trên GPT, hãy định tuyến rõ các tác vụ đó tới GPT trong MODEL_CONFIG thay vì để chúng rơi vào router mặc định.

Hỏi: Tôi có nên trừu tượng hóa API tạo ảnh và video theo cách tương tự?

Nguyên tắc tương tự áp dụng — cấu hình tập trung, wrapper chuẩn hóa phản hồi, không đưa trường riêng của nhà cung cấp vào business logic. API ảnh và video có nhiều khác biệt về cấu trúc (bất đồng bộ vs đồng bộ, bộ tham số khác nhau) nên lớp trừu tượng sẽ tốn công hơn. Hãy bắt đầu với văn bản, rồi mở rộng khi cấu trúc đã vững.

Hỏi: Còn sự khác nhau về cửa sổ ngữ cảnh giữa các model thì sao?

Đây là rủi ro thật khi định tuyến. GPT-5.5 có cửa sổ ngữ cảnh 1M token, các model Claude hỗ trợ tới 200K, và Gemini 3.5 Flash hỗ trợ tới 1M. Nếu bạn định tuyến tài liệu dài sang model có cửa sổ ngữ cảnh ngắn hơn, input sẽ bị cắt lặng lẽ. Hãy thêm bước kiểm tra độ dài ngữ cảnh trước khi định tuyến nếu tác vụ của bạn liên quan tới đầu vào dài — hoặc luôn định tuyến các tác vụ ngữ cảnh dài tới một model cụ thể trong MODEL_CONFIG thay vì để chúng rơi vào mặc định.

Tiếp tục học

Kết nối bài viết này với quyết định tiếp theo.

Xem tất cả chủ đề
Được xuất bản Jun 7, 2026
Cập nhật lần cuối Sep 3, 2026
15 lượt xem
Đã được xem xét về độ rõ ràng, ghi nguồn và thuật ngữ API hiện tại.

Sẵn sàng giảm 20% chi phí phát triển AI?

Bắt đầu miễn phí trong vài phút. Bao gồm tín dụng dùng thử miễn phí. Không cần thẻ tín dụng.

Đọc thêm