# Đổi model qua OpenRouter: hai dòng code và một bộ eval

> Chunk usage khi streaming, provider được chọn theo giá rẻ, fallback lẫn model: ba cái bẫy này không báo lỗi nhưng có thể làm sai kết quả trước khi ai kịp nhận ra.

Bản gốc: https://fdetimes.net/vi/cong-cu/openrouter-va-api-tuong-thich-openai/

Theo tài liệu quickstart của OpenRouter, một ứng dụng đang gọi OpenAI SDK chỉ cần sửa hai dòng là gọi được hàng trăm model khác: `base_url` và `api_key`. Tài liệu gọi đây là cách thay thế "drop-in".

Với một FDE, sự dễ dàng này vừa tốt vừa đáng ngại. Đến một lúc nào đó khách hàng sẽ hỏi có thể thử model khác không, rẻ hơn hay nhanh hơn chẳng hạn. Đổi thì mất năm phút, nhưng chứng minh model mới không làm hỏng sản phẩm thì phải có một bộ eval.

Vì thế FDE cần làm được cả hai việc: đổi model thật rẻ nhờ lớp tương thích OpenAI, và dựng một bộ eval cho biết lần đổi đó có đáng làm không.

## Hai dòng code thay đổi những gì?

OpenRouter, ra đời từ đầu năm 2023, tự mô tả là một endpoint duy nhất dẫn tới hàng trăm model. Ví dụ tối giản dưới đây dùng đúng hai tham số mà quickstart hướng dẫn, chỉ khác là model slug được đọc từ biến môi trường:

```python
import os
from openai import OpenAI

client = OpenAI(
base_url="https://openrouter.ai/api/v1",
api_key=os.environ["OPENROUTER_API_KEY"],
)

resp = client.chat.completions.create(
model=os.environ["MODEL_SLUG"],
messages=[{"role": "user", "content": "Tóm tắt ticket sau: ..."}],
)
```

Tài liệu viết rằng bạn có thể thay bất kỳ model slug nào vào chỗ `model`. Chính vì thế slug nên nằm trong file cấu hình chứ không nằm trong code. Khi khách hàng muốn thử model mới, bạn sửa cấu hình, chạy eval, không cần deploy lại logic.

## "Drop-in" chưa chắc đã giống hệt

Trang API reference của OpenRouter nói thẳng rằng schema request và response rất giống OpenAI Chat API, nhưng có vài khác biệt nhỏ. Điểm tiện là OpenRouter đưa mọi model và provider về chung một schema, nên bạn chỉ phải học một bộ.

Một khác biệt có ghi trong tài liệu: khi streaming, chunk cuối mang thông tin usage lại có mảng `choices` không rỗng, trái với spec của OpenAI. Thử hình dung code của khách hàng nhận ra chunk usage bằng cách kiểm tra `choices` rỗng.

Khi chuyển sang OpenRouter, nhánh đó có thể không bao giờ chạy, và số token không được ghi lại cho tới khi có người đối soát hoá đơn.

Bài học ở đây là "chạy được" chưa chắc là "chạy đúng". Trước khi đổi base_url ở môi trường của khách, hãy đọc lại từng chỗ code tự parse response, nhất là phần streaming.

## Cùng một slug, chưa chắc cùng một provider

Mặc định, OpenRouter cân bằng tải giữa các provider và ưu tiên giá rẻ. Vì vậy cùng một model có thể được các provider khác nhau phục vụ ở những lần gọi khác nhau, và kết quả eval hôm nay chưa chắc lặp lại được ngày mai.

Còn một chỗ dễ sót nữa. Tuỳ chọn `require_parameters` mặc định là false, nên provider không hỗ trợ một tham số trong request có thể lặng lẽ bỏ qua nó. Đặt giá trị true thì OpenRouter chỉ dùng những provider hỗ trợ đủ mọi tham số bạn gửi.

Tính năng fallback cũng cần hiểu cho kỹ. Bạn truyền vào một mảng model ID, và khi model đầu tiên trả lỗi, OpenRouter tự thử model kế tiếp, nên model dự phòng sẽ trả lời thay cho model chính.

Lúc đó người dùng có thể nhận câu trả lời từ một model bạn chưa eval kỹ, còn hoá đơn được tính theo model thực sự đã dùng, và tên model này có trong response.

**Điểm mấu chốt:** Đổi model chỉ mất vài phút; chứng minh model mới tốt hơn mới là việc của kỹ sư.

## Bộ eval biến cảm giác thành bằng chứng

Hướng dẫn của OpenAI định nghĩa eval là việc kiểm tra output của model theo các tiêu chí về văn phong và nội dung do chính bạn đặt ra. Quy trình gồm ba bước: mô tả tác vụ, chạy với bộ input thử, rồi phân tích và lặp lại.

Thử hình dung một khách hàng muốn chuyển việc tóm tắt ticket hỗ trợ sang model rẻ hơn. Bạn soạn 50 ticket thật đã ẩn thông tin cá nhân, mỗi ticket kèm tiêu chí đạt, ví dụ phải nêu đúng mã đơn và không bịa cam kết hoàn tiền.

Giả sử model hiện tại đạt 46/50 còn model mới đạt 41/50. Mức chênh 5 ticket đó, cùng danh sách 5 ticket hỏng, là thứ khách hàng cần thấy trước khi quyết định.

Mỗi dòng log của eval nên ghi thêm model thực sự đã trả lời, lấy từ response. Nếu fallback đã chen vào giữa chừng, con số 41/50 kia đang đo hai model trộn lẫn chứ không phải một.

## Nên học gì trước

Hãy bắt đầu từ thứ rẻ nhất: tự viết parser cho response streaming và đối chiếu với phần khác biệt trong API reference. Sau đó chuyển sang kỹ năng khó hơn là viết tiêu chí eval.

Khi đọc JD, hãy để ý những cụm như "multi-model", "LLM evaluation", "model routing". Trong CV, thay vì ghi "có kinh nghiệm với OpenRouter", hãy viết rằng bạn đã chuyển một ứng dụng sang model khác qua lớp tương thích OpenAI và có bộ eval 50 case làm căn cứ cho quyết định đó.

Có một bài tự kiểm tra đơn giản cho tiêu chí của bạn. Lấy 10 output, đưa một đồng nghiệp chấm theo đúng tiêu chí bạn viết mà không giải thích thêm, rồi so với kết quả bạn tự chấm.

Nếu hai người chấm lệch nhau ở nhiều case, lỗi nằm ở tiêu chí chứ chưa phải ở model. Chỉ khi hai người chấm ra cùng một kết quả, con số 46/50 hay 41/50 mới đủ vững để đặt lên bàn khách hàng.

**Thử ngay tuần này:**

- Lấy một script đang gọi OpenAI SDK, đổi base_url sang https://openrouter.ai/api/v1, đưa model slug vào biến môi trường và chạy thử hai slug khác nhau
- Viết 20 cặp input/tiêu chí đạt cho một tác vụ quen thuộc, chạy với hai model và ghi tỉ lệ đạt cùng model thực sự trả lời từng request
- Bật require_parameters thành true trong cấu hình provider routing rồi so sánh kết quả với lúc để mặc định

## Nguồn

- [Quickstart | OpenRouter Documentation](https://openrouter.ai/docs/quickstart)

- [API Reference | OpenRouter Documentation](https://openrouter.ai/docs/api-reference/overview)

- [Model Fallbacks | OpenRouter Documentation](https://openrouter.ai/docs/guides/routing/model-fallbacks)

- [Provider Routing | OpenRouter Documentation](https://openrouter.ai/docs/features/provider-routing)

- [About - The Unified Interface For LLMs | OpenRouter](https://openrouter.ai/about)

- [Working with evals](https://developers.openai.com/api/docs/guides/evals)
