Làm sao để tránh bị một nhà cung cấp AI khóa ứng dụng của bạn?

07 tháng 4, 2026·7 phút đọc

Bài viết hướng dẫn cách thiết kế ứng dụng AI để không bị phụ thuộc vào một nhà cung cấp duy nhất. Tác giả trình bày cách xây dựng giao diện trừu tượng, adapter cho từng nhà cung cấp, cơ chế dự phòng và cấu hình linh hoạt nhằm tăng độ bền vững và linh hoạt cho hệ thống AI.

Làm sao để tránh bị một nhà cung cấp AI khóa ứng dụng của bạn?

Làm sao để tránh bị một nhà cung cấp AI khóa ứng dụng của bạn?

Thời gian gần đây, cuộc tranh luận về quyền kiểm soát tương lai của AI trở nên nóng hơn bao giờ hết. Nhưng trong khi các chuyên gia bàn luận về niềm tin và quản trị, tôi lại đang đối mặt với một vấn đề rất thực tế trong mã nguồn: toàn bộ ứng dụng của tôi bị gắn chặt với API của một nhà cung cấp AI duy nhất.

Nếu họ thay đổi chính sách giá, ngừng hỗ trợ model hay gặp sự cố downtime vài giờ, tôi coi như "đi đời". Và nếu bạn đã phát triển ứng dụng dùng API các mô hình ngôn ngữ lớn (LLM) trong hai năm qua, rất có thể bạn cũng rơi vào tình cảnh tương tự.

Hãy cùng xem cách để giải quyết vấn đề này.

Nguyên nhân chính: Phụ thuộc chặt chẽ vào một nhà cung cấp

Hầu hết các đoạn mã tích hợp AI hiện nay đều cứng nhắc gắn liền với SDK, tên model, định dạng trả về và các điểm đặc thù riêng biệt của một nhà cung cấp. Khi có hàng chục điểm gọi API rải rác trong hệ thống, việc chuyển đổi nhà cung cấp không còn là chuyện cho vài ngày cuối tuần – mà là cả một cuộc đại tu.

Vấn đề không phải bạn đã chọn sai nhà cung cấp, mà là bạn đã để chi tiết triển khai "rò rỉ" vào logic nghiệp vụ của mình. Đây là biểu hiện điển hình của vi phạm nguyên tắc đảo ngược phụ thuộc (dependency inversion) trong phần mềm, chỉ là với "chiếc áo mới".

Bước 1: Định nghĩa giao diện riêng của bạn

Giải pháp luôn bắt đầu từ việc tạo ra một ranh giới trừu tượng (abstraction boundary). Hãy định nghĩa rõ những gì ứng dụng của bạn cần từ một LLM, thay vì dựa dẫm vào những gì nhà cung cấp cung cấp.

from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Optional

@dataclass
class LLMResponse:
    """Định dạng phản hồi mà ứng dụng bạn sở hữu và kiểm soát."""
    content: str
    model: str
    input_tokens: int
    output_tokens: int
    finish_reason: str

@dataclass
class LLMMessage:
    role: str  # "system", "user", "assistant"
    content: str

class LLMProvider(ABC):
    @abstractmethod
    def complete(self, messages: list[LLMMessage], temperature: float = 0.7, max_tokens: Optional[int] = None) -> LLMResponse:
        pass

    @abstractmethod
    def is_available(self) -> bool:
        """Kiểm tra sức khỏe dịch vụ, hỗ trợ logic dự phòng."""
        pass

Như vậy, logic nghiệp vụ của bạn sẽ phụ thuộc vào các kiểu dữ liệu do bạn định nghĩa, chứ không phải của nhà cung cấp.

Bước 2: Triển khai adapter cho từng nhà cung cấp

Mỗi nhà cung cấp được đóng gói thành một adapter mỏng, chịu trách nhiệm chuyển đổi giữa giao diện tự định nghĩa của bạn với SDK của họ.

import openai
import anthropic

class OpenAIProvider(LLMProvider):
    def __init__(self, model: str = "gpt-4o", api_key: str = None):
        self.client = openai.OpenAI(api_key=api_key)
        self.model = model

    def complete(self, messages, temperature=0.7, max_tokens=None):
        response = self.client.chat.completions.create(
            model=self.model,
            messages=[{"role": m.role, "content": m.content} for m in messages],
            temperature=temperature,
            max_tokens=max_tokens,
        )
        choice = response.choices[0]
        return LLMResponse(
            content=choice.message.content,
            model=response.model,
            input_tokens=response.usage.prompt_tokens,
            output_tokens=response.usage.completion_tokens,
            finish_reason=choice.finish_reason,
        )

    def is_available(self) -> bool:
        try:
            self.client.models.list()
            return True
        except Exception:
            return False


class AnthropicProvider(LLMProvider):
    def __init__(self, model: str = "claude-sonnet-4-6", api_key: str = None):
        self.client = anthropic.Anthropic(api_key=api_key)
        self.model = model

    def complete(self, messages, temperature=0.7, max_tokens=None):
        # Anthropic xử lý message hệ thống riêng biệt
        system = None
        chat_messages = []
        for m in messages:
            if m.role == "system":
                system = m.content
            else:
                chat_messages.append({"role": m.role, "content": m.content})

        kwargs = {
            "model": self.model,
            "messages": chat_messages,
            "temperature": temperature,
            "max_tokens": max_tokens or 1024
        }
        if system:
            kwargs["system"] = system

        response = self.client.messages.create(**kwargs)
        return LLMResponse(
            content=response.content[0].text,
            model=response.model,
            input_tokens=response.usage.input_tokens,
            output_tokens=response.usage.output_tokens,
            finish_reason=response.stop_reason,
        )

    def is_available(self) -> bool:
        try:
            self.client.messages.create(
                model=self.model, max_tokens=1,
                messages=[{"role": "user", "content": "hi"}]
            )
            return True
        except Exception:
            return False

Nhìn cách mỗi adapter xử lý các điểm đặc thù nhà cung cấp riêng biệt mà không để rò rỉ lên tầng trên có thể thấy việc thiết kế tốt giúp giữ sự trong sạch của kiến trúc.

Bước 3: Thêm cơ chế dự phòng (fallback)

Khi đã có giao diện trừu tượng, việc xây dựng độ bền trở nên đơn giản hẳn:

import logging

logger = logging.getLogger(__name__)

class FallbackProvider(LLMProvider):
    """Thử tuần tự các nhà cung cấp, nhà nào ổn trước sẽ dùng."""

    def __init__(self, providers: list[LLMProvider]):
        self.providers = providers

    def complete(self, messages, temperature=0.7, max_tokens=None):
        errors = []
        for provider in self.providers:
            try:
                return provider.complete(messages, temperature, max_tokens)
            except Exception as e:
                logger.warning(f"{provider.__class__.__name__} failed: {e}")
                errors.append((provider.__class__.__name__, e))

        raise RuntimeError(f"All providers failed: {[(n, str(e)) for n, e in errors]}")

    def is_available(self) -> bool:
        return any(p.is_available() for p in self.providers)

# Khởi tạo ví dụ
llm = FallbackProvider([
    AnthropicProvider(model="claude-sonnet-4-6"),
    OpenAIProvider(model="gpt-4o"),
])

Ứng dụng giờ chỉ cần gọi llm.complete() mà không cần quan tâm nhà cung cấp nào đang phục vụ. Khi nhà cung cấp chính gặp sự cố, tự động chuyển sang dự phòng.

Việc thêm một adapter cho một mô hình local như Ollama cũng rất đơn giản, giúp giảm chi phí và tăng độ ổn định.

Bước 4: Cấu hình linh hoạt từ bên ngoài

Không nên hardcode thứ tự nhà cung cấp, cấu hình nên được kéo từ biến môi trường hoặc tập tin:

import os
import json

PROVIDER_REGISTRY = {
    "openai": OpenAIProvider,
    "anthropic": AnthropicProvider,
}

def build_provider_from_config() -> LLMProvider:
    # Ví dụ LLM_PROVIDERS='[{"name": "anthropic", "model": "claude-sonnet-4-6"}, ...]'
    config = json.loads(os.environ.get("LLM_PROVIDERS", "[]"))
    providers = []
    for entry in config:
        cls = PROVIDER_REGISTRY.get(entry["name"])
        if cls:
            model_default = cls.__init__.__defaults__[0]
            providers.append(cls(model=entry.get("model", model_default)))

    if not providers:
        raise ValueError("No LLM providers configured")

    return FallbackProvider(providers) if len(providers) > 1 else providers[0]

Kết quả là việc thay đổi nhà cung cấp chỉ cần chỉnh biến môi trường, không phải thay code, giúp quy trình triển khai trở nên linh động và dễ bảo trì hơn.

Vấn đề mở rộng: Streaming và gọi hàm

Giao diện complete() cơ bản chưa đủ cho mọi trường hợp sử dụng. Các bạn có thể bổ sung thêm các phương thức như:

  • stream() trả về dữ liệu dạng stream theo chunks
  • complete_with_tools() cho gọi hàm / gọi API
  • embed() dùng trong các ứng dụng Retrieval-Augmented Generation (RAG)

Cách làm tương tự: định nghĩa interface theo nhu cầu, viết adapter tương ứng. Dù phần adapter gọi tool khá phức tạp do khác biệt giữa các nhà cung cấp nhưng vẫn tốt hơn việc rải rác các đặc tả riêng khắp ứng dụng.

Cách phòng tránh tái diễn

  • Xem API AI như cơ sở dữ liệu. Bạn không rải SQL trực tiếp khắp code đâu, thì cũng đừng rải API AI. Giữ phần tích hợp gọn trong phạm vi ranh giới.

  • Gắn test adapter với kết quả thực tế. Ghi lại phản hồi API làm fixture để khi nhà cung cấp thay đổi định dạng, bạn phát hiện sớm trước khi chạy thật.

  • Theo dõi chi phí theo nhà cung cấp. Với thuộc tính đếm token trong LLMResponse, bạn lưu log chi phí từng provider để đánh giá, đàm phán giá hoặc định thứ tự dự phòng.

  • Chạy mô hình local trong phát triển. Ví dụ dùng Ollama với một model nhỏ, không chỉ tiết kiệm credit, mà còn tránh sự cố internet ảnh hưởng đến workflow dev.

Nhìn tổng quan

Cuộc tranh luận về quyền kiểm soát tương lai AI rất quan trọng, nhưng với nhà phát triển, điều cần làm nhất lúc này không phải là tranh luận quản trị mà là xây dựng hệ thống không có điểm thất bại đơn lẻ do một thực thể độc quyền chi phối.

Mô hình trên tôi thực hiện trong một dự án vừa phải trong một ngày. Cơ chế dự phòng đã cứu tôi vài lần mỗi khi nhà cung cấp bị lỗi. Lần sau khi giá thay đổi hay model mới ra, tôi chỉ cần thêm một adapter chứ không phải viết lại ứng dụng.

Đó chính là mô hình niềm tin tôi tin tưởng: tin tưởng vào giao diện do tôi kiểm soát, để các triển khai cạnh tranh lẫn nhau.

Bài viết được tổng hợp và biên soạn bằng AI từ các nguồn tin tức công nghệ. Nội dung mang tính tham khảo. Xem bài gốc ↗