Bạn sửa một câu trong prompt để xử lý một ca khó. Ca đó chạy tốt, nhưng ba ca khác vốn đang đúng lại âm thầm hỏng. Với ứng dụng dùng mô hình ngôn ngữ, cảm giác “thử vài câu thấy ổn” là không đủ. Bạn cần một bộ kiểm thử chạy lại được sau mỗi lần đổi prompt hay đổi model, giống như unit test cho code thường.
Bài này dùng pytest và hai hàm đã viết ở các bài trước: trich_don_hang() (structured output) và hoi() (RAG).
Mức 1: kiểm tra bằng quy tắc
Hãy bắt đầu với những gì kiểm tra được một cách chắc chắn: con số, định dạng, trường bắt buộc.
# test_don_hang.py
import pytest
from don_hang import trich_don_hang
TRUONG_HOP = [
("Lấy 2 áo 150k, giao 12 Trần Phú. Lan 0912345678", 300_000),
("Cho em 3 hộp bánh, mỗi hộp 85 nghìn ạ. Gửi về Cầu Giấy, Minh 0987 654 321", 255_000),
("1 tai nghe 1tr2 và 2 cáp sạc 90k, ship Q.7 giúp mình nhé, Hùng 0909111222", 1_380_000),
]
@pytest.mark.llm
@pytest.mark.parametrize("tin_nhan, tong_mong_doi", TRUONG_HOP)
def test_tong_tien(tin_nhan, tong_mong_doi):
don = trich_don_hang(tin_nhan)
assert sum(m.so_luong * m.don_gia for m in don.mat_hang) == tong_mong_doi
Các ca kiểm thử tốt nhất lấy từ tin nhắn thật đã từng xử lý sai. Mỗi lần sửa xong một lỗi, thêm chính tin nhắn đó vào danh sách để nó không quay lại.
Tách test tốn tiền ra riêng
Test gọi API thật vừa chậm vừa tốn tiền, nên đánh dấu chúng bằng marker llm:
# pytest.ini
[pytest]
markers =
llm: test gọi API thật, chậm và tốn phí
pytest -m "not llm" # chạy hằng ngày, nhanh
pytest -m llm # chạy trước khi đổi prompt hoặc đổi model
Phần code xung quanh (đọc file, chia đoạn, tính tổng) vẫn nên test riêng với API giả, như trong bài Mocking trong pytest.
Mức 2: dùng mô hình chấm câu trả lời mở
Câu trả lời của RAG không có một đáp án duy nhất để so khớp. Khi đó, nhờ một lời gọi khác chấm theo tiêu chí rõ ràng, và dùng structured output để lấy kết quả đạt hay không:
# test_rag.py
import anthropic
import pytest
from pydantic import BaseModel
from rag import hoi
client = anthropic.Anthropic()
class KetQua(BaseModel):
dat: bool
ly_do: str
def cham(cau_hoi, tra_loi, tieu_chi):
ph = client.messages.parse(
model="claude-opus-5-5",
max_tokens=1024,
system="Bạn là người chấm bài nghiêm khắc. Chỉ cho đạt khi câu trả lời thỏa MỌI tiêu chí.",
messages=[{"role": "user", "content":
f"Câu hỏi: {cau_hoi}\n\nCâu trả lời: {tra_loi}\n\nTiêu chí:\n{tieu_chi}"}],
output_format=KetQua,
)
return ph.parsed_output
@pytest.mark.llm
def test_khong_bia_khi_tai_lieu_khong_co():
cau_hoi = "Công ty có hỗ trợ tiền gửi xe không?"
ket_qua = cham(cau_hoi, hoi(cau_hoi),
"- Nói rõ là tài liệu không đề cập\n- Không tự đưa ra con số nào")
assert ket_qua.dat, ket_qua.ly_do
Viết assert ket_qua.dat, ket_qua.ly_do để khi test hỏng, pytest in luôn lý do người chấm đưa ra.
Đọc kết quả cho đúng
- Cùng một đầu vào, mô hình có thể trả lời hơi khác nhau giữa các lần chạy. Với ca quan trọng, hãy chạy vài lần và nhìn vào tỷ lệ đạt thay vì một lần đạt hay hỏng.
- Người chấm cũng có thể sai. Hãy tự chấm tay khoảng mười ca rồi so với kết quả của nó trước khi tin hẳn vào nó.
- Ghi lại tỷ lệ đạt của từng phiên bản prompt. Một thay đổi chỉ đáng giữ khi con số đó không đi xuống.
