Cách Mình Tích Hợp LLMs

Nguyên tắc tích hợp LLM API đáng tin cậy, hiệu quả và tiết kiệm chi phí

LLM API khác biệt cơ bản so với API truyền thống. Chậm, đắt, bị rate limit, và thỉnh thoảng trả lời lung tung. Gọi tuần tự từng item, không xử lý lỗi, blocking hết -- kiểu gì cũng dẫn tới app chậm, bill phình to, và lỗi dây chuyền. Hiểu rõ đặc thù này thì mới tích hợp cho bền được.

Câu hỏi cốt lõi: "Nếu lời gọi API này fail hoặc mất 30 giây, chuyện gì xảy ra với người dùng?"

Nguyên Tắc Tích Hợp

1. Batch và parallelize mặc định

Gọi API tuần tự, từng item một, chờ xong cái này mới tới cái kia -- nghe thì an toàn nhưng thực tế rất chậm. Một job 100 item với 500ms mỗi lời gọi mất 50 giây nếu chạy tuần tự. Với 5 worker song song và batch 20 thì chỉ mất 5 giây. Người ta không kiên nhẫn chờ loading spinner quay 50 giây đâu.

Hầu hết LLM API hỗ trợ batch input -- gom các item liên quan thành batch, chạy song song bằng thread pool hoặc async worker. Tận dụng đi, vừa nhanh vừa nằm trong rate limit.

2. Fallback cho mọi thứ

LLM API có outage, có rate limit, có content filter. Coi output là "chắc chắn sẽ đúng" thì sớm muộn gì cũng ăn quả đắng -- API fail một phát, cả batch crash.

Nên mỗi lời gọi LLM đều cần fallback hợp lý. Tạo label thất bại? Dùng "Cluster N". Embedding fail cho một item? Retry hoặc bỏ qua item đó -- chứ đừng abort cả batch vì một con sâu. Ứng dụng giảm chất lượng mượt mà vẫn hơn là fail sạch không còn gì.

3. API key phải linh hoạt

Hardcode API key thì dev nhanh thật, nhưng tới lúc deploy lên staging, production, hay chuyển cho team khác dùng là rối. Hỗ trợ nhiều nguồn key với thứ tự ưu tiên rõ ràng:

  1. User-provided key (multi-tenant app)
  2. Environment variable (development, CI/CD)
  3. Error với message hướng dẫn (key chưa có thì bảo rõ cần làm gì)

Validate key sớm, hiển thị lỗi rõ ràng. Đừng để user bấm nút xong mới biết key sai.

4. Prompt phải ràng buộc output

LLM sáng tạo theo mặc định -- mà sáng tạo ở đây nghĩa là output lúc dài lúc ngắn, lúc JSON lúc text, lúc có markdown lúc không. Không ràng buộc thì UI vỡ layout, vượt giới hạn storage, hoặc phải viết đống code hậu xử lý.

Chỉ định format, giới hạn độ dài, cấu trúc rõ ràng. Dùng example trong prompt khi format quan trọng. Và luôn parse response một cách phòng thủ -- đừng tin LLM sẽ trả đúng 100%.

5. Theo dõi chi phí như theo dõi lỗi

Nhiều team bỏ qua token usage cho đến khi hóa đơn đến thì giật mình. Một bug retry vô hạn hoặc feature over-fetch có thể tốn hàng ngàn đô trước khi ai đó nhận ra.

Log token count cho mọi lời gọi API. Tính chi phí ước tính. Đặt alert cho usage bất thường. Chi phí cũng là metric hạng nhất, ngang hàng với latency và error rate -- không phải chuyện "để sau tính".

Framework Quyết Định

Khi nào nên batch, khi nào gọi riêng lẻ?

Dùng batch khi:

  • Xử lý hơn 5 item
  • Các item độc lập (không phụ thuộc tuần tự)
  • API hỗ trợ batch input
  • Latency ảnh hưởng người dùng

Dùng gọi riêng lẻ khi:

  • Xử lý 1-2 item
  • Mỗi item phụ thuộc vào kết quả trước
  • Cần xử lý lỗi chi tiết từng item
  • Debug một lỗi cụ thể

Khi nào dùng synchronous, khi nào streaming?

Dùng synchronous khi:

  • Response ngắn (< 100 token)
  • Cần toàn bộ response trước khi tiếp tục
  • Đang batch nhiều lời gọi

Dùng streaming khi:

  • Response dài (> 500 token)
  • Người dùng đang chờ và cần feedback
  • Có thể render kết quả dần dần

Khi nào retry, khi nào fail ngay?

Retry khi:

  • Lỗi tạm thời (rate limit, timeout, 5xx)
  • Thao tác là idempotent
  • Còn retry budget

Fail ngay khi:

  • Lỗi vĩnh viễn (key không hợp lệ, request sai format, 4xx)
  • Người dùng đang chờ tương tác
  • Hết retry budget

Những Lỗi Hay Gặp

Không cấu hình timeout

Request treo vô hạn khi API gặp sự cố, user thấy loading spinner quay mãi không thôi. Đặt timeout rõ ràng (30-60s cho hầu hết lời gọi LLM). Lỗi lặp lại thì implement circuit breaker -- giống cầu dao điện, chập thì ngắt để bảo vệ.

Log toàn bộ prompt và response

File log phình lên hàng gigabyte, dữ liệu nhạy cảm nằm trong log, chi phí storage tăng vọt. Log metadata thôi (token, latency, model, status) chứ đừng log nội dung. Cần debug thì sample một phần, đừng lưu hết.

Phớt lờ rate limit

Lỗi 429 đột ngột vào giờ cao điểm, batch fail giữa chừng. Rate limiting phía client, exponential backoff, queue request khi tải cao -- ba thứ này nên có từ đầu chứ đừng đợi bị limit rồi mới lo.

Phụ thuộc chặt vào một provider

Chuyển từ OpenAI sang Anthropic mà phải viết lại nửa codebase thì rõ ràng là coupling quá chặt. Abstract hóa LLM interface, tách code đặc thù provider, dùng adapter pattern. Đổi provider chỉ nên là thay config, không phải refactor.

Coi mọi model như nhau

Dùng GPT-4 cho phân loại đơn giản -- tốn tiền. Dùng GPT-3.5 cho suy luận phức tạp -- kết quả tệ. Mỗi model có vùng giá/hiệu quả riêng: model nhỏ cho structured extraction, model lớn cho nuanced generation. Chọn đúng tool cho đúng việc thôi.

Checklist Đánh Giá

Ổn nếu:

  • Batch operation hoàn thành trong thời gian hợp lý (< 30s cho hầu hết job)
  • Lỗi từng item không crash cả batch
  • Vấn đề API key hiển thị lỗi rõ ràng, có thể hành động
  • Trả lời được "feature này tốn bao nhiêu tháng trước?"
  • Đổi provider là thay đổi cấu hình, không phải viết lại

Cần sửa nếu:

  • Người dùng thấy lỗi API thô ("rate_limit_exceeded", "context_length")
  • Một lời gọi chậm block toàn bộ ứng dụng
  • Đã bị bất ngờ bởi chi phí API
  • Outage của LLM provider gây fail hoàn toàn feature
  • Thay đổi prompt cần deploy code

Tham Khảo Nhanh

Vấn đềCách tiếp cận
Nhiều itemBatch + parallelize (ThreadPoolExecutor, async)
API failRetry lỗi tạm thời, fallback cho lỗi vĩnh viễn
API keyUser key > env var > error rõ ràng
Prompt outputRàng buộc format, độ dài, cấu trúc rõ ràng
Theo dõi chi phíLog token mỗi lời gọi, alert khi bất thường
TimeoutMặc định 30-60s, circuit breaker cho lỗi lặp lại