01 Prompt Evaluation là gì & vì sao cần?
Prompt evaluation là việc đo chất lượng đầu ra của một prompt một cách có hệ thống, trên một tập đầu vào cố định và theo tiêu chí rõ ràng — thay vì đọc vài ví dụ rồi "thấy ổn". Nó là test tự động cho prompt: cùng vai trò mà unit test đảm nhận với code.
Không đo thì không cải thiện được. LLM là phi xác định (non-deterministic) — đổi một câu trong prompt có thể sửa được case này nhưng làm hỏng case khác. Không có eval, bạn đang sửa mù & không biết mình tiến hay lùi.
Lặp lại được
Cùng bộ test → cùng cách chấm. So sánh phiên bản prompt A vs B trên đúng một thước đo.
Khách quan
Thay "tôi thấy hay" bằng điểm số & tỉ lệ đạt. Cả nhóm cùng nhìn một bảng kết quả.
Chống thụt lùi
Giữ bộ eval như một "lưới an toàn" — mỗi lần sửa prompt chạy lại để bắt hồi quy.
Ví dụ xuyên suốt: hệ Hỏi–đáp tài liệu (RAG)
Cả trang neo vào một hệ RAG thật: Python lo pipeline (chunk → embed → truy hồi → sinh câu trả lời), PHP lo web/API. Eval đứng bên ngoài pipeline: cho cùng một bộ câu hỏi đi qua hệ, rồi chấm điểm câu trả lời theo tiêu chí, lặp cho tới khi đạt ngưỡng.
flowchart LR
D[("Eval set: câu hỏi + đáp án vàng")]:::db --> R
subgraph SUT["Hệ đang đánh giá (RAG)"]
direction TB
R["Retrieve (Python)"]:::m --> G["Generate (LLM)"]:::m
end
G --> J{{"Chấm điểm: code-based · LLM-judge · human"}}:::core
J --> S["Điểm số & báo cáo lỗi"]:::out
S -.->|"sửa prompt rồi chạy lại"| R
classDef m fill:#dbeee8,stroke:#0f7d72,color:#1c1a14;
classDef db fill:#e2edf3,stroke:#2f6d93,color:#1c1a14;
classDef out fill:#e2edf3,stroke:#2f6d93,color:#1c1a14;
classDef core fill:#14233b,stroke:#14233b,color:#f3ede0;
02 Ba cách chấm điểm
Mỗi cách có vùng dùng tốt riêng — thường kết hợp cả ba: code-based cho cái đo được chính xác, LLM-judge cho cái mở, human để hiệu chuẩn.
Code-based
Khớp chính xác, regex, JSON hợp lệ, chứa từ khóa, sai số số học. Rẻ, nhanh, khách quan tuyệt đối — nhưng chỉ hợp output có cấu trúc rõ.
LLM-as-judge
Dùng một model mạnh chấm theo rubric. Linh hoạt cho output mở (tóm tắt, giải thích) & scale tốt — nhưng cần rubric chặt & có thể lệch (bias).
Human eval
Người chấm là tiêu chuẩn cao nhất nhưng đắt & chậm. Dùng để hiệu chuẩn judge & xử các phán đoán tinh tế, không chạy cho mọi lần.
| Cách chấm | Chi phí | Tốc độ | Hợp với |
|---|---|---|---|
| Code-based | Rất thấp | Tức thì | Output có cấu trúc: JSON, nhãn phân loại, con số, định dạng. |
| LLM-as-judge | Trung bình | Nhanh | Output mở: chất lượng văn bản, mức độ bám ngữ cảnh, giọng văn. |
| Human | Cao | Chậm | Ca tinh tế, gây tranh cãi, hoặc để hiệu chuẩn LLM-judge. |
RAG Code-based grader chạy ngay trong harness — không tốn token, không mơ hồ:
def grade_structured(answer: str, expect: dict) -> bool:
data = json.loads(answer) # 1) đúng JSON?
assert set(data) >= {"answer", "sources"} # 2) đủ field?
return (expect["keyword"] in data["answer"] # 3) chứa ý chính?
and len(data["sources"]) > 0) # 4) có trích nguồn?
json.loads() làm được.03 Thiết kế bộ eval (eval set)
Bộ eval là tập các cặp (đầu vào, kỳ vọng). Chất lượng eval phụ thuộc vào chất lượng tập này hơn là vào cách chấm. Một bộ tốt phủ bốn loại ca:
Happy path
Câu hỏi thường gặp, có câu trả lời rõ trong tài liệu — phải đúng gần như 100%.
Edge cases
Câu mơ hồ, nhiều phần, hoặc tài liệu chỉ trả lời được một phần — kiểm khả năng "biết thì nói, không thì thừa nhận".
Adversarial
Câu ngoài phạm vi tài liệu, gài bẫy, hoặc cố moi thông tin sai — phải từ chối / nói không biết, không bịa.
Regression
Mỗi lỗi thật từ production được "đóng băng" thành một ca, để không bao giờ tái diễn.
Bắt đầu nhỏ nhưng thật. 20 ca được chọn lọc kỹ phủ đúng các thất bại bạn quan tâm còn giá trị hơn 1.000 ca sinh tự động na ná nhau. Tăng dần khi gặp lỗi mới.
RAG Mỗi ca khai báo cách chấm riêng — một bộ eval trộn nhiều kiểu grader:
cases = [
{"q": "Chính sách hoàn tiền bao nhiêu ngày?",
"expect": {"keyword": "14 ngày"}, "grader": "code"}, # happy path
{"q": "Giá cổ phiếu công ty hôm nay?",
"expect": {"must_refuse": True}, "grader": "judge"}, # adversarial: phải từ chối
]
04 Tiêu chí chất lượng — "RAG triad"
Với hệ RAG, chất lượng tách thành hai tầng: truy hồi (retrieval) đưa đúng ngữ cảnh chưa, và sinh (generation) dùng ngữ cảnh đó đúng chưa. Ba tiêu chí lõi tạo thành "RAG triad":
Context relevance
Đoạn tài liệu lấy về có liên quan câu hỏi không. Lỗi ở đây thì khâu sinh "có cố mấy cũng sai".
Groundedness
Câu trả lời bám sát ngữ cảnh, không thêm thông tin ngoài tài liệu. Đây là tuyến phòng thủ chính chống hallucination.
Answer relevance
Câu trả lời thực sự giải quyết câu hỏi, không lạc đề dù vẫn "đúng & có căn cứ".
Bảng tiêu chí thường dùng & trọng số
Không phải tiêu chí nào cũng quan trọng như nhau — gán trọng số theo rủi ro của ứng dụng (càng nhiều chấm càng nặng cân):
| Tiêu chí | Đo cái gì | Trọng số điển hình |
|---|---|---|
| Groundedness / Faithfulness | Không bịa, bám ngữ cảnh. | ★★★★★ |
| Answer relevance | Trả đúng trọng tâm câu hỏi. | ★★★★★ |
| Safety / từ chối đúng chỗ | Không trả lời cái ngoài phạm vi / nguy hại. | ★★★★★ |
| Format compliance | Đúng JSON / độ dài / cấu trúc yêu cầu. | ★★★★★ |
| Tone / style | Giọng văn, mức độ chi tiết phù hợp. | ★★★★★ |
| Cost & latency | Token tiêu thụ & thời gian p95. | ★★★★★ |
Đừng quên chi phí & độ trễ. Một prompt "chính xác hơn 2%" nhưng dài gấp đôi & chậm gấp đôi có thể là nước lùi trên thực tế. Luôn đặt chất lượng cạnh token/latency.
05 LLM-as-judge đúng cách & quy trình lặp
LLM-as-judge mạnh nhưng dễ sai nếu làm cẩu thả. Bốn nguyên tắc giữ cho điểm số đáng tin:
Rubric cụ thể
Định nghĩa từng mức điểm bằng tiêu chí quan sát được (1 = bịa, 5 = bám 100% ngữ cảnh), không để judge tự hiểu "tốt".
Bắt giải thích trước
Yêu cầu judge nêu lý do trước khi cho điểm — chấm "bộc phát" kém tin cậy hơn hẳn.
Hiệu chuẩn với người
So điểm judge với điểm người trên một mẫu — chỉ tin judge khi nó đồng thuận cao với con người.
Tránh bias
Khi so cặp, đảo vị trí A/B; cảnh giác judge thiên vị câu dài hơn hoặc câu do chính model mình sinh ra.
RAG Judge bằng Claude — chấm groundedness theo rubric, có lý do kèm điểm:
judge_prompt = f"""Cho NGỮ CẢNH và CÂU TRẢ LỜI. Chấm groundedness 1–5
(1=bịa hoàn toàn, 5=bám 100% ngữ cảnh). Nêu lý do TRƯỚC, rồi 'SCORE: n'.
NGỮ CẢNH: {context}
TRẢ LỜI: {answer}"""
msg = client.messages.create(model="claude-opus-4-8", # judge nên mạnh hơn model bị chấm
max_tokens=512, messages=[{"role": "user", "content": judge_prompt}])
score = parse_score(msg.content[0].text) # tách 'SCORE: n'
RAG Ranh giới service: web (PHP) chỉ kích hoạt một lần chạy eval & đọc điểm tổng — không tự chấm:
// Web chỉ ra lệnh "chạy bộ eval v3", logic chấm nằm trọn bên Python
$report = $http->post('/eval/run', ['suite' => 'v3'])->json();
echo "Đạt: {$report['pass_rate']}% · Groundedness TB: {$report['groundedness']}";
Vòng lặp eval-driven
Lập baseline
Chạy prompt hiện tại trên bộ eval, ghi lại điểm gốc để mọi thay đổi đều so được.
Đọc ca lỗi
Soi các ca trượt để tìm nguyên nhân chung — đây là nơi học được nhiều nhất.
Sửa một thứ
Đổi một biến (một câu trong prompt / một tham số) để biết cái gì gây ra khác biệt.
Chạy lại & gác hồi quy
Re-run toàn bộ; chỉ giữ thay đổi khi điểm tổng tăng mà không làm tụt ca nào đang đạt.
Cạm bẫy thường gặp
Nên tránh
- Overfit eval set: tỉa prompt vừa khít bộ test nhỏ → giỏi trên test, dở ngoài đời.
- Judge = generator: cùng một model sinh & chấm → thiên vị chính mình.
- Rubric mơ hồ: "chấm độ hữu ích" mà không định nghĩa → điểm nhiễu, không lặp lại.
- Chỉ nhìn điểm trung bình: giấu các ca tệ thảm. Luôn xem phân bố & ca xấu nhất.
Nên làm
- Version hóa eval set: coi nó như tài sản, review khi thêm/sửa ca.
- Tự động trong CI: chạy eval mỗi lần đổi prompt, chặn merge nếu tụt ngưỡng.
- Trộn grader: code-based cho cái đo được, judge cho cái mở.
- Hiệu chuẩn định kỳ: đối chiếu judge với người để nó không "trôi".
06 Ghi nhớ nhanh
Eval là test cho prompt — không đo được thì không cải thiện được; "vibe check" không scale.
Trộn ba cách chấm — code-based cho cái đo chính xác, LLM-judge cho output mở, human để hiệu chuẩn.
Bộ eval > cách chấm — 20 ca chọn lọc phủ đúng thất bại quan trọng hơn 1.000 ca na ná. Mỗi bug thật → một ca regression.
Với RAG, ưu tiên groundedness — chống bịa là tuyến phòng thủ số một; tách eval truy hồi khỏi eval sinh.
Đổi một biến mỗi lần — lập baseline, sửa một thứ, chạy lại, gác hồi quy. Đưa eval vào CI.