RAG Evaluation
Đánh giá RAG — Bộ ba Faithfulness, Answer & Context Relevance
Bạn build RAG trên Confluence nội bộ. Chatbot trả lời rất mượt, nguồn có đủ. Nhưng người dùng report 'sai'. Lỗi có thể đến từ đâu nhất?
Chọn một câu truy vấn thực tế, rồi bật từng kiểu lỗi (fault injection) và quan sát đỉnh nào của tam giác co lại. Đây chính là cách bạn sẽ chẩn đoán RAG production hằng ngày.
Hình minh họa
Chọn truy vấn
Tiêm lỗi (fault injection)
QUERY
Nếu hàng Shopee bị lỗi, tôi có bao nhiêu ngày để yêu cầu hoàn tiền?
Retrieved context (2 docs)
shopee-vn/policy/return.mdrelevantQuy định Trả hàng & Hoàn tiền Shopee (2026)
Người mua có 15 ngày kể từ ngày nhận hàng để gửi yêu cầu Trả hàng / Hoàn tiền với lý do hàng lỗi hoặc không đúng mô tả.
shopee-vn/help/return-methodsrelevantCác hình thức hoàn tiền
Hoàn tiền qua ShopeePay trong vòng 24 giờ sau khi bên bán xác nhận. Hoàn về ngân hàng 3–7 ngày làm việc.
LLM ANSWER
Theo quy định hiện hành của Shopee VN, bạn có 15 ngày kể từ ngày nhận hàng để yêu cầu hoàn tiền khi hàng lỗi hoặc không đúng mô tả. Tiền sẽ về ShopeePay trong 24 giờ hoặc ngân hàng trong 3–7 ngày làm việc.
RAG Triad Triangle — Không lỗi (baseline)
composite = 94Chẩn đoán: Baseline tốt: context liên quan, câu trả lời bám sát nguồn, đúng trọng tâm.
Chẩn đoán: đỉnh nào bị kéo xuống?
Quy tắc: Context Rel đỏ → retrieval hỏng. Faithfulness đỏ → generation bịa. Answer Rel đỏ → prompt/lan man.
Từ tam giác vừa chơi, một sự thật lộ ra: RAG không phải một hệ thống — nó là hai hệ thống nối tiếp (retriever + generator) được keo lại bằng prompt. Lỗi ở mỗi khâu làm sụp đổ một đỉnh khác nhau của tam giác: retrieval hỏng → Context Relevance đỏ, generation bịa → Faithfulness đỏ, prompt/lan man → Answer Relevance đỏ. Đo một con số tổng ("answer tốt hay không") sẽ che mất đúng khâu cần sửa — đó là lý do triad là công cụ chẩn đoán, không phải công cụ chấm điểm.
(query, gold_docs, gold_answer) 50–200 mẫu được chọn kỹ, đại diện cho use case quan trọng, là tài sản vận hành của team RAG. Nó cho phép đo reference-based chính xác và dựng CI gate. Cập nhật golden set mỗi 2–4 tuần bằng cách sample từ production log — đừng để nó lỗi thời. Xem thêm RAG và Agentic RAG để hiểu kiến trúc đang đo.Bạn chạy eval: Context Relevance = 92%, Faithfulness = 45%, Answer Relevance = 80%. Nguyên nhân gốc có khả năng cao nhất?
User hỏi tiếng Việt: 'Học phí UIT ngành Trí tuệ Nhân tạo là bao nhiêu?'. Retriever (multilingual-e5 rẻ) trả về 4 doc tiếng Anh về AI programs ở Stanford. LLM dịch rồi trả lời theo đó. Metric nào tụt rõ nhất?
Giải thích
Câu hỏi kế tiếp: đo từng đỉnh như thế nào? Mỗi metric trong bộ ba có một cách tính reference-free (không cần gold answer) và một cách reference-based (có gold). Công thức gốc xuất phát từ RAGAS và được cả TruLens/DeepEval kế thừa.
1. Faithfulness — câu trả lời có bám nguồn không?
Chia answer A thành tập atomic claims (những mệnh đề đơn nhất có thể kiểm chứng), rồi với mỗi claim, hỏi một judge LLM: claim này có được retrieved context C support không?
F = 1 nghĩa là mọi mệnh đề trong answer đều có đoạn trong context hậu thuẫn. F = 0 là bịa hoàn toàn. Reference trace (pointer từ claim → câu nguồn) giúp debug nhanh.
2. Context Relevance — doc lấy về có liên quan không?
Với context C (có thể gồm nhiều đoạn/câu s), đếm tỷ lệ câu thực sự liên quan tới câu hỏi Q:
CR thấp là signature của retrieval / chunking / embedding kém. Fix bằng: đổi embedding, tăng chunk size, thêm reranker. Xem re-ranking để hiểu cơ chế và chunking cho kích thước tối ưu.
3. Answer Relevance — câu trả lời có đúng câu hỏi không?
Có hai kỹ thuật phổ biến:
- Embedding cosine: cosine similarity giữa câu hỏi và câu trả lời (đơn giản nhưng hay sai khi answer dài).
- Generated-questions: LLM sinh ra N câu hỏi Q'_i giả định từ answer, rồi tính cosine trung bình:
Trực giác: nếu answer đúng trọng tâm, thì câu hỏi suy ngược ra sẽ giống câu hỏi gốc.
Bảng chẩn đoán tóm tắt:
| Đỉnh đỏ | Khâu hỏng | Fix |
|---|---|---|
| Context Rel | Retrieval / chunking / embedding | Reranker, embedding chuyên domain/ngôn ngữ, chunk size, hybrid BM25 + vector |
| Faithfulness | Generation bịa (hallucination) | Prompt chặt ("only use CONTEXT"), cite-required, đổi model — xem hallucination |
| Answer Rel | Prompt / intent parsing / model lan man | Intent clarification, prompt buộc trả lời trực tiếp, post-filter độ dài |
from dataclasses import dataclass
from statistics import mean
from typing import List
@dataclass
class RAGSample:
query: str
retrieved_context: List[str] # các đoạn retrieved
answer: str # câu trả lời cuối của LLM
class RAGEvaluator:
"""Bộ ba RAG Triad — reference-free, dùng LLM-as-judge."""
def __init__(self, judge_llm, embedder, n_generated: int = 3):
self.judge = judge_llm
self.embed = embedder
self.n_generated = n_generated
# ── 1. FAITHFULNESS ──────────────────────────────────────
def faithfulness(self, s: RAGSample) -> float:
claims = self.judge.decompose_to_claims(s.answer)
if not claims:
return 1.0 # không khẳng định gì = không bịa
supported = 0
for c in claims:
if self.judge.is_supported(claim=c, context=s.retrieved_context):
supported += 1
return supported / len(claims)
# ── 2. CONTEXT RELEVANCE ─────────────────────────────────
def context_relevance(self, s: RAGSample) -> float:
sentences = self.judge.split_into_sentences(s.retrieved_context)
if not sentences:
return 0.0
relevant = sum(
1 for sent in sentences
if self.judge.is_relevant(sentence=sent, query=s.query)
)
return relevant / len(sentences)
# ── 3. ANSWER RELEVANCE ──────────────────────────────────
def answer_relevance(self, s: RAGSample) -> float:
generated_qs = self.judge.generate_questions(
answer=s.answer, n=self.n_generated
)
q_vec = self.embed(s.query)
sims = [cosine(q_vec, self.embed(q)) for q in generated_qs]
return mean(sims) if sims else 0.0
# ── TRIAD ────────────────────────────────────────────────
def evaluate(self, samples: List[RAGSample]) -> dict:
rows = []
for s in samples:
rows.append({
"faithfulness": self.faithfulness(s),
"context_rel": self.context_relevance(s),
"answer_rel": self.answer_relevance(s),
})
agg = {
"faithfulness": mean(r["faithfulness"] for r in rows),
"context_rel": mean(r["context_rel"] for r in rows),
"answer_rel": mean(r["answer_rel"] for r in rows),
}
agg["triad_mean"] = mean(agg.values())
return agg
def cosine(a, b):
import numpy as np
a, b = np.array(a), np.array(b)
return float(a @ b / (np.linalg.norm(a) * np.linalg.norm(b) + 1e-9))Trong CI, team thường pin một file YAML định nghĩa SLO cho từng metric để gate deploy. Một ví dụ thực tế:
pipeline:
dataset: s3://ai-edu/evals/rag-golden-v3.jsonl # 180 samples
sample_budget: 180
judge:
model: anthropic/claude-opus-4.7
temperature: 0.0
embedder:
model: bge-m3 # đa ngôn ngữ VN + EN tốt
dim: 1024
metrics:
- faithfulness
- context_relevance
- answer_relevance
slo:
faithfulness_min: 0.90
context_relevance_min: 0.80
answer_relevance_min: 0.85
regression_tolerance: 0.02 # sụt > 2% so với baseline = fail
reporting:
format: json+markdown
sink: bigquery://ai.evals.rag_runs
on_regression:
- slack://#rag-quality
- create_issue: repo/ragappCuối cùng, cần nhớ triad chỉ là công cụ chẩn đoán, không phải đích đến. Một số chiều còn thiếu mà production thật quan tâm: latency p95, $/query, recall coverage (% câu hỏi user mà hệ thống có tài liệu để trả), và harm rate (% câu trả lời gây hại). Triad là xương sống, bạn đắp thịt quanh nó theo use case.
Bối cảnh. Topica (edtech VN) build RAG trên giáo trình tiếng Việt. Stack: embedding paraphrase-multilingual-mpnet, chunk 200 tokens, không rerank, generation Claude Haiku.
Tuần 1 — triad.
- Faithfulness: 94% — LLM bám context tốt.
- Context Relevance: 71% — nhiều doc lạ trôi vào top-k.
- Answer Relevance: 88% — đi vào trọng tâm.
Chẩn đoán. Context Rel yếu nhất → khâu retrieval bệnh. Hai giả thuyết: (1) embedding multilingual không đủ mạnh cho tiếng Việt chuyên ngành, (2) chunk 200 tokens cắt vụn ngữ cảnh — câu đầu ở chunk A, câu giải thích ở chunk B, retriever không lấy đủ.
Thử nghiệm. 4 variant song song trên golden set 150 câu:
- V1 (baseline): chunk 200, no rerank → CR 71%.
- V2: chunk 512, no rerank → CR 82%.
- V3: chunk 200 + Cohere Rerank v3 → CR 85%.
- V4: chunk 512 + Cohere Rerank v3 → CR 89%, Faithfulness 96%, Answer Rel 91%.
Kết quả. Triển khai V4. Latency +180ms, $/query +0.0007 (chấp nhận được). CSAT học viên từ 4.1 → 4.6 sau 3 tuần.
Bài học.Nếu Topica chỉ đo "điểm trả lời tổng thể" kiểu 0-5 sao, họ đã không biết retrieval hay generation đang yếu. Bộ ba tách riêng giúp họ đi thẳng đến retriever, thử đúng 2 biến số (chunk_size + rerank), và fix chỉ trong 1 sprint. Đo tách riêng mới biết khâu nào hỏng.
- RAG = 2 hệ nối tiếp (retriever + generator) + prompt. Lỗi ở mỗi khâu làm sụp một đỉnh khác của tam giác.
- Bộ ba cốt lõi: Faithfulness (bám nguồn), Context Relevance (doc liên quan), Answer Relevance (đúng câu hỏi). Đo CẢ BA, không gộp.
- Chẩn đoán: Context Rel đỏ → fix retrieval (embedding, chunk, rerank). Faithfulness đỏ → fix generation (prompt chặt, đổi model, cite-check). Answer Rel đỏ → fix prompt/intent.
- Framework 2026: RAGAS (mặc định), TruLens, ARES, DeepEval. Tất cả hỗ trợ reference-free LLM-as-judge + reference trace để debug.
- Golden retrieval set 50–200 mẫu là tài sản vận hành — cập nhật mỗi 2–4 tuần từ production log, dùng làm CI gate.
- Multilingual gotcha VN: embedding đa ngôn ngữ yếu hay kéo doc sai ngôn ngữ. Dùng embedding chuyên VN (bge-m3) hoặc hybrid BM25.
Kiểm tra hiểu biết
Faithfulness trong RAG eval được định nghĩa chính xác nhất là gì?