Danh sách bài viết

Bài 40: Re-ranking để cải thiện độ chính xác

Sau khi vector DB trả về top-K, danh sách đó thường có cả nhiễu. Re-ranking là bước thứ hai: dùng một model mạnh hơn (cross-encoder) để chấm lại điểm từng cặp (query, doc) và sắp xếp lại. Pattern chuẩn là 2-stage retrieval: stage 1 dùng bi-encoder lấy top-50/100 cho nhanh, stage 2 dùng cross-encoder rank lại còn top-5/10 cho LLM. Bài này so sánh bi-encoder vs cross-encoder, giải thích tại sao cross-encoder chính xác hơn nhưng không scale được, điểm qua các reranker phổ biến 2024-2026 (Cohere Rerank, BGE, Jina, MS MARCO MiniLM, Voyage, mxbai), code Python với Cohere API và BGE local, full pipeline ChromaDB + reranker, ý tưởng LLM-as-Reranker, listwise vs pointwise, hybrid retrieval + rerank, HyDE và query rewriting như các "pre-retrieval enhancement", cách đánh giá impact và khi nào reranker không đáng tiền.

25/05/2026
13 phút đọc
4 lượt xem
1

Mục tiêu bài học

Sau bài này, bạn cần:

  • Giải thích được pattern 2-stage retrieval và lý do dùng (balance speed và quality).
  • Phân biệt bi-encoder và cross-encoder về kiến trúc, output, khả năng pre-compute.
  • Biết tại sao cross-encoder chính xác hơn nhưng không scale được trên toàn corpus.
  • Liệt kê được các reranker phổ biến: Cohere Rerank, BGE Reranker, Jina Reranker, MS MARCO MiniLM, Voyage Rerank, mxbai-rerank.
  • Viết được code rerank với Cohere API và BGE local (sentence-transformers CrossEncoder).
  • Ghép được full pipeline: ChromaDB top-50 → BGE Reranker top-5 → LLM.
  • Quyết được khi nào reranker đáng thêm vào pipeline và khi nào không.
  • Hiểu các kỹ thuật pre-retrieval bổ sung: HyDE, multi-query, query rewriting.

Bài này nằm ở cuối Module 6, ngay trước capstone QA system PDF. Reranker là "improvement step" rõ rệt nhất bạn có thể thêm vào một Naive RAG để cải thiện chất lượng.

2

Vì sao retrieve thuần chưa đủ

Bài 39 đã chỉ ra: vector DB cho query một danh sách top-K chunk gần nhất theo cosine/dot. Trong thực tế, top-K này thường có vài vấn đề:

  • Embedding model "trung bình" hóa ngữ nghĩa cả câu — query và doc có thể gần nhau về chủ đề nhưng doc không thật sự trả lời được câu hỏi.
  • Top-1 không phải lúc nào cũng là câu trả lời tốt nhất; doc liên quan thật có thể nằm ở vị trí 7 hoặc 15.
  • HNSW/IVF là approximate search — vị trí xếp hạng trong top-K có nhiễu so với brute-force exact.
  • Hybrid (vector + BM25) merge bằng RRF cho thứ tự "hợp lý" nhưng không tối ưu cho relevance.

Giải pháp: lấy K rộng hơn ta cần (ví dụ 50-100), rồi chấm lại điểm từng cặp (query, doc) bằng model mạnh hơn. Đó là re-ranking.

3

Pattern 2-stage retrieval

STAGE 1 — Retrieval (recall, broad)
─────────────────────────────────────────────────────────
  Query → bi-encoder embed → vector DB (top-K)
  K thường 50-100. Mục tiêu: bao đủ doc liên quan.
  Tốc độ: vài chục ms cho corpus triệu chunk.

STAGE 2 — Reranking (precision, narrow)
─────────────────────────────────────────────────────────
  (Query, doc_i) → cross-encoder → score_i, với i = 1..K
  Sort theo score → top-N (N thường 5-10).
  Tốc độ: 50-500 ms cho K=50 (CPU/GPU).

Tên gọi quen thuộc: retrieve-then-rerank, hoặc recall-then-precision. Stage 1 tối đa hóa recall (kéo về càng nhiều doc liên quan càng tốt). Stage 2 tối đa hóa precision (chọn ra cái thật sự đúng).

Lý do tách thành 2 stage thay vì dùng model mạnh cho mọi cặp: model mạnh chậm; chạy nó cho toàn corpus (hàng triệu chunk) bất khả thi. Stage 1 nhanh nhưng kém chính xác làm nhiệm vụ "lọc thô"; stage 2 chậm nhưng chính xác chỉ phải xử lý K candidate.

4

Bi-encoder vs Cross-encoder

BI-ENCODER (dual encoder)
─────────────────────────────────────────────────────────
  query  → Encoder → vec_q  ┐
                            ├─→ cosine(vec_q, vec_d)
  doc    → Encoder → vec_d  ┘

  • Hai input encode ĐỘC LẬP.
  • vec_d pre-compute được, lưu vector DB.
  • Query time: chỉ encode query + 1 phép similarity.

CROSS-ENCODER
─────────────────────────────────────────────────────────
  [CLS] query [SEP] doc [SEP] → Encoder → score

  • Query và doc concat lại, đưa CÙNG vào model.
  • Attention chạy GIỮA query và doc.
  • Output: 1 scalar = relevance score.
  • KHÔNG pre-compute được — query mới đến mới chạy.

Bi-encoder là kiến trúc của embedding model bạn đã dùng ở Bài 36 (sentence-transformers, BGE-M3, OpenAI text-embedding-3-*). Cross-encoder là kiến trúc của reranker. Hai dòng model song song trong literature thường gọi là dense retrieval (bi-encoder) và cross-attention re-ranker (cross-encoder).

Bản chất paper gốc: Reimers & Gurevych 2019 (Sentence-BERT, arXiv 1908.10084) giới thiệu cách tách BERT thành bi-encoder để pre-compute vector — chính là embedding model hôm nay.

5

Vì sao cross-encoder chính xác hơn

Bi-encoder produce hai vector độc lập, mỗi vector "nén" một câu thành vài trăm chiều. Khi tính cosine, mọi thông tin tương tác chi tiết giữa query và doc đã mất.

Cross-encoder cho query và doc cùng đi qua model với full attention: mỗi token của query có thể "nhìn" mọi token của doc và ngược lại. Model học được:

  • Từ nào của query khớp đoạn nào của doc.
  • Doc có thật sự trả lời câu hỏi không, hay chỉ chung chủ đề.
  • Phủ định, mâu thuẫn, ngữ cảnh phụ thuộc lẫn nhau.

Kết quả: trên các benchmark IR (MS MARCO, BEIR), cross-encoder thường outperform bi-encoder thuần 5-20% về NDCG@10 / MRR. Đổi lại tốc độ chậm hơn nhiều lần.

Tham khảo: Nogueira & Cho 2019 (Passage Re-ranking with BERT, arXiv 1901.04085) là paper sớm chỉ ra cross-encoder rerank cải thiện rõ rệt baseline BM25 trên MS MARCO.

6

Trade-off speed và scale

Trục                   Bi-encoder              Cross-encoder
─────────────────────────────────────────────────────────────────
Pre-compute doc        Có (lưu vec DB)         Không
Query time             Encode 1 query +        Encode K cặp
                       1 phép similarity        (query, doc_i)
                                                song song hoặc batch
Scale với corpus N     Tốt (HNSW ~ log N)      O(N) per query
                                                → không scale toàn corpus
Độ chính xác (NDCG)    Trung bình              Cao (+5–20%)
Latency (CPU, 1 cặp)   < 10 ms                 ~10-30 ms / cặp
Latency K=50 (GPU)     ~30 ms (search)         ~50-200 ms (batch)

Cross-encoder chạy 1 forward pass cho mỗi cặp (query, doc). Với K=50, mỗi query cần 50 forward pass — chấp nhận được. Với N = 1M doc trong corpus, 1M forward pass mỗi query — không khả thi.

Đây chính là lý do không dùng cross-encoder thay vector DB, mà chỉ dùng nó sau stage 1 cho top-K candidate.

7

Workflow re-rank

  Query
    │
    │  (bi-encoder)
    ▼
  Vector DB ──── top-50 candidate ────┐
                                       │
                                       │  (cross-encoder, batch)
                                       ▼
                            Score cho từng cặp (query, doc_i)
                                       │
                                       │  sort desc
                                       ▼
                              Top-5 cho LLM
                                       │
                                       ▼
                              Augmented prompt → LLM → Answer

Thực hiện đúng yêu cầu hai điểm:

  • Batch cross-encoder. Gọi predict([[q, d1], [q, d2], ...]) một lần, không loop từng cặp gọi model riêng — chậm gấp K lần.
  • Giữ metadata (source, page, id) song song với chunk text, để sau khi rerank vẫn có nguồn cite cho LLM.
8

Các reranker phổ biến 2024-2026

Reranker                  Loại        Đặc điểm
────────────────────────────────────────────────────────────────────
Cohere Rerank v3          API         Multilingual, prod-ready, $2/1K
                                       search. Model rerank-multilingual-v3.0.
BGE Reranker (BAAI)       OSS         bge-reranker-base/large/v2-m3.
                                       MS MARCO + multilingual. HF.
Jina Reranker             OSS / API   jina-reranker-v2-base-multilingual.
                                       Hỗ trợ 100+ ngôn ngữ.
MS MARCO MiniLM           OSS         cross-encoder/ms-marco-MiniLM-L-6-v2.
                                       Lightweight (~80MB), tiếng Anh.
Voyage Rerank             API         voyage-rerank-1, focus domain tech /
                                       legal / finance.
mxbai-rerank              OSS         mixedbread-ai/mxbai-rerank-large-v1.
                                       Top MTEB rerank board cuối 2024.

Quy tắc chọn:

  • Prototype tiếng Anh, không muốn host → Cohere Rerank API.
  • Tiếng Việt / đa ngôn ngữ, on-prem → BGE Reranker v2-m3 hoặc Jina v2 multilingual.
  • CPU only, latency thấp → MS MARCO MiniLM.
  • Chất lượng cao nhất hiện tại (OSS) → mxbai-rerank-large hoặc BGE Reranker v2-m3.

Embedding model và reranker không cần "match"; quy tắc thực tế là dùng embedding multilingual tốt cho stage 1, reranker multilingual tốt cho stage 2.

9

Code — Cohere Rerank API

pip install cohere
import cohere
import os

co = cohere.Client(os.environ["COHERE_API_KEY"])

query = "RAG là gì?"
documents = [
    "RAG kết hợp LLM với knowledge base ngoài.",
    "Python là ngôn ngữ phổ biến cho AI.",
    "Vector database lưu embedding và hỗ trợ similarity search.",
    "Retrieval-Augmented Generation truy xuất doc trước khi generate.",
]

results = co.rerank(
    query=query,
    documents=documents,
    top_n=3,
    model="rerank-multilingual-v3.0",
)

for r in results.results:
    print(r.index, round(r.relevance_score, 3), documents[r.index][:60])

Output ví dụ (số có thể khác):

3 0.972 Retrieval-Augmented Generation truy xuất doc trước khi generate.
0 0.951 RAG kết hợp LLM với knowledge base ngoài.
2 0.412 Vector database lưu embedding và hỗ trợ similarity search.

Các điểm cần lưu ý:

  • relevance_score chuẩn hoá ~[0, 1] — có thể đặt threshold để loại doc dưới 0.3.
  • top_n chỉ điều khiển số kết quả trả về; Cohere vẫn rerank toàn bộ documents.
  • Cohere giới hạn ~1000 doc / call và 4096 token / doc.
  • rerank-multilingual-v3.0 hỗ trợ tốt tiếng Việt; bản tiếng Anh dùng rerank-english-v3.0.
10

Code — BGE Reranker local

pip install sentence-transformers
from sentence_transformers import CrossEncoder

model = CrossEncoder("BAAI/bge-reranker-base")

query = "RAG là gì?"
docs = [
    "RAG kết hợp LLM với knowledge base ngoài.",
    "Python là ngôn ngữ phổ biến cho AI.",
    "Vector database lưu embedding và hỗ trợ similarity search.",
    "Retrieval-Augmented Generation truy xuất doc trước khi generate.",
]

pairs = [[query, d] for d in docs]
scores = model.predict(pairs)  # numpy array, mỗi phần tử = score

ranked = sorted(zip(docs, scores), key=lambda x: -x[1])
for d, s in ranked[:3]:
    print(round(float(s), 3), d[:60])

Đặc điểm:

  • model.predict chạy batch tự động — gọi 1 lần cho cả danh sách pair.
  • Score là logit (chưa sigmoid). Để chuẩn ~[0, 1], áp 1 / (1 + np.exp(-scores)); nhưng cho việc rerank, chỉ cần thứ tự nên không cần normalize.
  • Có thể bật GPU bằng CrossEncoder(..., device="cuda").
  • BGE Reranker v2-m3 (BAAI/bge-reranker-v2-m3) chất lượng cao hơn cho đa ngôn ngữ, nặng hơn (~568M params).
11

Full pipeline ChromaDB + reranker

import chromadb
from sentence_transformers import SentenceTransformer, CrossEncoder
from openai import OpenAI

embedder = SentenceTransformer("BAAI/bge-m3")
reranker = CrossEncoder("BAAI/bge-reranker-base")
llm = OpenAI()

client = chromadb.PersistentClient(path="./chroma_db")
col = client.get_collection("docs")

def rag_with_rerank(query: str, k_retrieve: int = 50, k_final: int = 5):
    # Stage 1 — retrieve top-K
    q_vec = embedder.encode([query]).tolist()
    res = col.query(query_embeddings=q_vec, n_results=k_retrieve)
    candidates = res["documents"][0]
    metas = res["metadatas"][0]

    # Stage 2 — rerank
    pairs = [[query, d] for d in candidates]
    scores = reranker.predict(pairs)
    top = sorted(
        zip(candidates, metas, scores), key=lambda x: -x[2]
    )[:k_final]

    # Build prompt
    context = "\n".join(
        f"[{i+1}] {d}" for i, (d, _, _) in enumerate(top)
    )
    prompt = (
        "Trả lời dựa trên CONTEXT. Nếu không đủ, nói \"không có thông tin\".\n\n"
        f"CONTEXT:\n{context}\n\nQUESTION: {query}\nANSWER:"
    )

    resp = llm.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": prompt}],
    )
    return resp.choices[0].message.content, top

answer, sources = rag_with_rerank("RAG là gì?")
print(answer)
for d, m, s in sources:
    print(f"  score={float(s):.3f} src={m.get('source')}")

So với pipeline naive ở Bài 35 (chỉ n_results=3 rồi đưa thẳng cho LLM), pipeline này thêm một step rerank với chi phí thêm vài chục đến vài trăm ms. Trên domain có nhiều chunk gần nhau về chủ đề, top-5 sau rerank thường khác đáng kể top-5 từ vector DB.

12

Trade-off K khởi đầu

K (top-K stage 1)   Recall   Rerank latency   Note
─────────────────────────────────────────────────────────
K = 10              Thấp     Rất nhanh        Có thể miss doc liên quan
K = 20-30           Trung    Nhanh            Baseline tiết kiệm
K = 50              Tốt      ~80-200 ms       Sweet spot phổ biến
K = 100             Rất tốt  ~150-400 ms      Khi corpus nhiễu, cần recall cao
K = 200+            Tiệm cận Chậm             Hiếm khi đáng

Quy tắc thực tế:

  • Bắt đầu K = 50, đo recall@K bằng test set Q&A.
  • Nếu doc liên quan đã nằm trong top-50 stage 1 → reranker làm phần còn lại.
  • Nếu recall@50 thấp (~< 80%) → vấn đề ở stage 1 (embedding model, chunking, hybrid), không phải ở reranker.

Tăng K không "free": cost rerank tuyến tính với K. K = 100 cost rerank gấp đôi K = 50.

13

Latency thực tế

Reranker                         Hardware       K=50 latency
─────────────────────────────────────────────────────────────
ms-marco-MiniLM-L-6-v2           CPU            ~80 ms
ms-marco-MiniLM-L-6-v2           GPU (T4)       ~20 ms
BGE Reranker base                CPU            ~250 ms
BGE Reranker base                GPU (T4)       ~50 ms
BGE Reranker v2-m3 (large)       GPU (T4)       ~150 ms
Cohere Rerank API                Cloud          ~150-300 ms (network)

Con số tham khảo; phụ thuộc batch size, độ dài chunk, GPU. Với pipeline RAG tổng end-to-end thường 1-3s (LLM call dominant), thêm 100-300 ms rerank chấp nhận được cho hầu hết app.

Tối ưu nếu cần:

  • Quantize reranker (INT8) — giảm 2-4 lần latency.
  • Dùng onnxruntime / ctranslate2 cho CPU serving.
  • Cache score cho cặp (query phổ biến, doc) bằng Redis.
14

Cost ước tính

Option              Cost ước tính
─────────────────────────────────────────────────────────
Cohere Rerank API   ~$2 / 1000 search (1 search = 1 query, K doc bất kỳ)
                    → 100K query/tháng ≈ $200
Voyage Rerank API   ~$0.05 / 1M token (giá năm 2026, kiểm tra lại)
Self-host BGE       $0 per call, ~$100-500/tháng cho GPU server (T4/L4)
Self-host MiniLM    $0 per call, chạy CPU cũng đủ cho ~100 QPS

Reranker là cost mới thêm vào RAG, không "free". Đánh giá đáng tiền hay không:

  • Đo improvement quality (Recall@5, MRR, hoặc LLM-as-Judge faithfulness).
  • Nếu cải thiện 3-10% trên metric quan trọng → thường đáng cho domain quan trọng.
  • Nếu cải thiện < 1% → bỏ rerank, đầu tư vào chunking / embedding.
15

Reranker training ngắn

Reranker pre-train sẵn (BGE, MiniLM) đủ tốt cho phần lớn case. Khi cần fine-tune trên domain riêng (legal Việt, medical, code repo nội bộ):

  • Dữ liệu: triplet (query, positive_doc, negative_doc). Positive = doc thực sự liên quan; negative = doc gần nhưng không trả lời.
  • Loss: BCE (binary cross-entropy) hoặc margin ranking.
  • Tool: sentence-transformers CrossEncoder.fit() hoặc trainer của BGE.
  • Lượng data: vài nghìn triplet đã đủ cải thiện đo đếm được; production thường vài chục nghìn.
from sentence_transformers import CrossEncoder, InputExample
from torch.utils.data import DataLoader

model = CrossEncoder("BAAI/bge-reranker-base", num_labels=1)

train_samples = [
    InputExample(texts=[query, pos], label=1.0),
    InputExample(texts=[query, neg], label=0.0),
    # ... thousands more
]
loader = DataLoader(train_samples, batch_size=16, shuffle=True)

model.fit(train_dataloader=loader, epochs=1, warmup_steps=100,
          output_path="./my-reranker")

Khó nhất không phải code mà là thu thập triplet sạch — đặc biệt là hard negative (doc gần nhưng không phải answer). Một cách phổ biến: dùng bi-encoder top-K, lấy doc rank thấp làm hard negative.

16

Khi nào dùng reranker

  • Quality quan trọng hơn vài trăm ms latency: legal, medical, finance, compliance.
  • Top-K vector thuần đo được precision kém — top-5 hay có 1-2 doc nhiễu.
  • Corpus có nhiều doc chung chủ đề, khác chi tiết (FAQ với câu hỏi tương tự nhau).
  • Đã có hybrid (vector + BM25) và muốn merge kết quả thông minh hơn RRF.
  • Có budget thêm vài trăm ms + cost (Cohere API hoặc GPU host).

Trong stack production phổ biến 2024-2026, reranker gần như là default. Ngoại lệ là app cần latency < 500ms tổng hoặc cost cực thấp.

17

Khi nào KHÔNG cần reranker

  • Top-K retrieval đã đủ tốt — đo trên test set thấy Recall@5 đã ≥ 95%.
  • Latency critical (chat realtime, voice agent): mỗi 100 ms quan trọng.
  • Cost sensitive — workload triệu query, mỗi $0.002 thêm vào là số lớn.
  • Corpus nhỏ vài trăm chunk: bi-encoder top-5 thường đã chính xác.
  • Đang ở giai đoạn prototype, chưa có golden set để đo improvement.

Quyết định cuối cùng nên đến từ test set có nhãn chứ không phải intuition. A/B test: pipeline có rerank vs không, đo Recall@5, MRR, faithfulness. Chỉ giữ rerank nếu improvement > chi phí.

18

LLM-as-Reranker

Thay vì cross-encoder chuyên dụng, dùng một LLM (GPT-4o, Claude Sonnet 4) chấm relevance từng doc:

prompt = f"""Đánh giá mức độ liên quan của DOC với QUERY trên thang 0-10.
Chỉ trả về số.

QUERY: {query}
DOC: {doc}

SCORE:"""

resp = llm.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": prompt}],
    max_tokens=4,
)
score = int(resp.choices[0].message.content.strip())

Đặc điểm:

  • Chất lượng có thể vượt cross-encoder OSS trên domain phức tạp (đa ngôn ngữ, suy luận).
  • Đắt: K = 50 → 50 LLM call mỗi query, latency hàng giây, cost $0.05-0.5.
  • Phù hợp cho query rất quan trọng (legal precedent, medical decision) hoặc cho offline eval.
  • Có thể giảm cost bằng listwise: cho LLM xem cả K doc một lần và yêu cầu rank (xem bước 19).

Tham khảo: Sun et al. 2023 (Is ChatGPT Good at Search?, arXiv 2304.09542) — đánh giá GPT-3.5/4 làm reranker, cho thấy đạt hoặc vượt cross-encoder trên một số benchmark.

19

Listwise vs Pointwise

Pointwise reranking
─────────────────────────────────────────────────────────
  Mỗi cặp (query, doc_i) chấm điểm độc lập.
  Sort theo điểm. Cross-encoder cổ điển = pointwise.
  Ưu: batch dễ, scale tốt theo K.
  Nhược: không thấy được "doc nào tốt hơn doc nào".

Listwise reranking
─────────────────────────────────────────────────────────
  Đưa cả K doc vào model cùng lúc, model trả ra thứ tự.
  LLM list-rerank: prompt "rank các doc sau theo relevance".
  Ưu: thấy bức tranh toàn cảnh, có thể so sánh chéo.
  Nhược: token cost cao, khó với K lớn (LLM context).

Pairwise reranking ở giữa: so sánh từng cặp doc (doc_i vs doc_j), tốn O(K²) — ít dùng production.

Thực tế phổ biến nhất vẫn là pointwise cross-encoder. Listwise LLM dùng khi quality quan trọng tột bậc và budget cho phép.

20

Hybrid retrieval + rerank

Stack "modern" cho production RAG là kết hợp cả hybrid retrieval (Bài 35 bước 17) và reranking:

                ┌─ Vector retrieve (top-50) ─┐
Query ──────────┤                              ├─→ Union (RRF)
                └─ BM25 retrieve   (top-50) ─┘    → top-100
                                                       │
                                                       │  (cross-encoder)
                                                       ▼
                                                Rerank → top-5
                                                       │
                                                       ▼
                                                  LLM → Answer

Lý do gộp:

  • BM25 bắt keyword cụ thể (mã sản phẩm, tên function) tốt hơn vector.
  • Vector bắt diễn giải / paraphrase tốt hơn BM25.
  • RRF gộp nhanh, không tối ưu relevance — rerank đứng cuối thay vai trò "final ranking".

Đây là pattern của Elasticsearch + dense vector, Qdrant hybrid, Weaviate hybrid. LangChain có EnsembleRetriever + ContextualCompressionRetriever (wrap reranker).

21

HyDE — pre-retrieval enhancement

HyDE (Hypothetical Document Embeddings, Gao et al. 2022, arXiv 2212.10496) là kỹ thuật pre-retrieval: cải thiện retrieve thay vì rerank sau.

Vấn đề:
  Câu hỏi và câu trả lời nằm ở "không gian khác".
  Embed query rồi search có thể không gần doc đích.

HyDE:
  1. LLM sinh ra một câu trả lời GIẢ ĐỊNH cho query
     (có thể sai, nhưng có dạng văn bản giống câu trả lời thật).
  2. Embed câu trả lời giả này, không phải query gốc.
  3. Search vector DB với embedding này.
  4. Doc thực sự liên quan thường gần "câu trả lời giả" hơn query.
def hyde_retrieve(query, k=10):
    hypo = llm.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{
            "role": "user",
            "content": f"Viết một đoạn trả lời ngắn (3-4 câu) cho câu hỏi: {query}",
        }],
    ).choices[0].message.content

    hypo_vec = embedder.encode([hypo]).tolist()
    return col.query(query_embeddings=hypo_vec, n_results=k)

HyDE thường cải thiện retrieve 5-15% trên benchmark, đặc biệt với query ngắn / mơ hồ. Cost thêm 1 LLM call nhỏ.

HyDE và reranking không loại nhau: HyDE cải thiện stage 1, reranker cải thiện stage 2; có thể dùng cả hai.

22

Query rewriting / expansion

  • Query rewriting — LLM viết lại câu hỏi cho rõ hơn, thêm context, sửa ambiguity. Ví dụ "ngày mai họp lúc mấy giờ" → "Cuộc họp ngày 26/05/2026 bắt đầu lúc mấy giờ".
  • Multi-query — LLM sinh N paraphrase của query, retrieve từng cái, union kết quả (rồi rerank).
  • Step-back prompting — LLM sinh câu hỏi tổng quát hơn để retrieve doc nền tảng, sau đó retrieve doc cụ thể cho câu hỏi gốc.
def multi_query(query, n=3):
    prompt = (
        f"Sinh {n} cách diễn đạt khác của câu hỏi sau, mỗi dòng một câu:\n"
        f"{query}"
    )
    out = llm.chat.completions.create(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": prompt}],
    ).choices[0].message.content
    return [query] + [line for line in out.split("\n") if line.strip()]

Mỗi query con retrieve top-K, union với dedup, rồi cho qua reranker. Tốn thêm N call vector DB + 1 call LLM cho sinh paraphrase, đổi lại recall cao hơn cho query ngắn / chỉ một từ.

23

RAG pipeline evolution

Phiên bản           Pipeline
─────────────────────────────────────────────────────────
1. Naive RAG        query → embed → vector top-K → LLM

2. + Rerank         query → embed → vector top-50 →
                     rerank → top-5 → LLM

3. + Hybrid         query → (BM25 + vector) → fusion top-100 →
                     rerank → top-5 → LLM

4. + Query rewrite  query → LLM rewrite (or HyDE / multi-query)
                     → (BM25 + vector) → fusion top-100 →
                     rerank → top-5 → LLM

5. + Agentic        LLM agent tự quyết: cần retrieve không?
                     query nào? Có cần round thứ 2 không?

Quy tắc: chỉ thêm step khi đo được lợi ích. Bắt đầu naive, thêm rerank trước (impact cao, dễ làm), sau đó hybrid, query rewrite. Agentic thường để cho task phức tạp, đa hop.

Bài 41 capstone (QA system PDF) sẽ kết hợp ChromaDB + reranker = pipeline phiên bản 2.

24

Đánh giá impact của reranker

Quy trình quyết định reranker có đáng dùng không:

  1. Build test set 50-100 query có nhãn doc liên quan (ground truth).
  2. Đo Recall@5 và MRR cho pipeline không rerank.
  3. Đo Recall@5 và MRR cho pipeline có rerank (cùng test set).
  4. Tính improvement: ví dụ Recall@5 từ 0.72 → 0.86 = +14 điểm.
  5. Quy ra business impact: với 100K query/tháng, 14% query thêm được trả lời đúng = X user happy hơn / Y ticket support giảm.
  6. So với cost thêm (cohere $200/tháng hoặc GPU $300/tháng) → quyết.
def recall_at_k(retrieved_ids, gold_ids, k=5):
    top_k = retrieved_ids[:k]
    return len(set(top_k) & set(gold_ids)) / max(1, len(gold_ids))

def mrr(retrieved_ids, gold_ids):
    for i, doc_id in enumerate(retrieved_ids):
        if doc_id in gold_ids:
            return 1.0 / (i + 1)
    return 0.0

Bài 58 (Module 9) sẽ deep-dive metric RAG với RAGAS.

25

Pitfall hay gặp

  • Loop từng cặp gọi reranker — quên batch, chậm gấp K lần. Luôn dùng model.predict(pairs) hoặc tương đương.
  • K khởi đầu quá nhỏ — chỉ rerank top-5 thì reranker không có gì để "fix"; phải lấy top-30/50 mới có không gian để sắp lại.
  • Mất metadata sau rerank — sort score nhưng không sort song song metadata → không cite nguồn đúng.
  • Reranker mismatch ngôn ngữ — dùng ms-marco-MiniLM (English-only) cho corpus tiếng Việt → score gần random.
  • Chunk quá dài — reranker thường giới hạn 512 token / cặp; chunk dài hơn bị truncate, mất thông tin cuối doc.
  • Quên đo — thêm reranker mà không đo improvement → có thể đang trả tiền cho không gì.
  • Overcomplicate — thêm rerank, HyDE, multi-query, query rewrite cùng lúc, không biết cái nào đóng góp bao nhiêu. Thêm từng cái và đo.
  • Cohere rate limit — quên handle 429 / retry với backoff khi gọi API.
26

Bài tập

  1. Chạy lại pipeline RAG từ Bài 38/39 trên một corpus quen thuộc (note Obsidian, PDF cá nhân, blog cũ). Lấy top-5 từ vector DB cho 5 câu hỏi và ghi lại.
  2. Thêm BGE Reranker (BAAI/bge-reranker-base) vào pipeline với K stage 1 = 50. So sánh top-5 trước và sau rerank cho cùng 5 câu hỏi. Note câu hỏi nào thay đổi đáng kể, câu nào không đổi.
  3. Thử cả Cohere Rerank rerank-multilingual-v3.0 và so sánh với BGE Reranker base. Note thời gian chạy, score, top-5.
  4. Cài HyDE: LLM (GPT-4o-mini) sinh câu trả lời giả 3-4 câu, embed nó thay vì query gốc, retrieve top-10. So sánh top-10 với cách dùng query gốc. Có khác đáng kể không?
  5. Xây test set 20 query có nhãn doc liên quan (do bạn tự đánh dấu). Đo Recall@5 và MRR cho 4 pipeline: (a) naive, (b) + rerank, (c) + HyDE, (d) + cả HyDE và rerank. Vẽ bảng so sánh.
  6. (Tùy chọn) Thử LLM-as-Reranker: chấm relevance 0-10 cho từng cặp (query, doc) bằng GPT-4o-mini. So với BGE Reranker về chất lượng và cost.