$ cat ./blog/gen-ai/retrieval-trong-rag.md
seriesRAG từ cơ bản đến nâng caobài 2

Retrieval trong RAG: từ search đến reranking

Bài thứ hai trong series RAG từ cơ bản đến nâng cao: vì sao câu hỏi của user không phải retrieval query tốt nhất, BM25 chấm relevance bằng ba tín hiệu nào, dense retrieval học gì, exact KNN khác ANN ở đâu, hybrid search và Reciprocal Rank Fusion giải quyết vấn đề gì, pre-filter khác post-filter thế nào, bi-encoder khác cross-encoder ra sao, ba chữ k nào đang bị gộp làm một, và đo chất lượng retrieval bằng metric nào.

~/diagrams/retrieval-trong-rag/retrieval-stack.htmltương tác
mở riêng ↗
HìnhHàng trên là ba tầng nằm giữa câu hỏi và evidence: hiểu đúng câu hỏi, tìm thật rộng, rồi xếp hạng thật kỹ. Hàng dưới là chỗ evidence đúng rơi mất tương ứng với từng tầng - rewrite làm lệch ý, tài liệu không lọt candidate set, hoặc lọt rồi nhưng đứng hạng quá thấp. Mỗi tầng tối ưu một thứ khác nhau, và không tầng nào sửa được lỗi của tầng đứng trước nó.
đường đi chínhchặng xếp hạngchỗ có thể rơi mất

Ở bài trước, chúng ta nhìn Retrieval-Augmented Generation như một hệ thống hoàn chỉnh: hai pipeline gặp nhau ở knowledge index, và một câu hỏi đi qua chuỗi query processing, retrieval, reranking, context builder rồi mới tới LLM.

Bài này zoom vào đúng đoạn nằm giữa câu hỏi của người dùng và evidence cuối cùng được chuyển sang Context Builder.

Đây là đoạn quyết định chất lượng RAG nhiều nhất. Nếu LLM không bao giờ nhìn thấy evidence đúng, model mạnh đến đâu cũng khó tạo được một câu trả lời có căn cứ từ knowledge base. Nhưng retrieval tốt cũng không có nghĩa là “lấy càng nhiều tài liệu càng tốt”: một context chứa quá nhiều đoạn gần đúng, trùng lặp, lỗi thời hoặc mâu thuẫn có thể làm generation khó hơn chứ không dễ hơn.

Vì vậy mental model của bài này không phải:

documents = vector_store.similarity_search(query)

mà là ba tầng trong Hình 1: hiểu đúng câu hỏi, tìm thật rộng, rồi xếp hạng thật kỹ.

Nếu chỉ giữ lại một câu sau bài này:

Retrieval trong RAG là một ranking pipeline nhiều tầng, không phải một lần gọi vector similarity search.


01Retrieval thực sự quyết định điều gì

Giả sử knowledge base chứa đúng tài liệu để trả lời:

Câu hỏi:
"Quy trình rotate PostgreSQL production password là gì?"

Tài liệu cần thiết:
PostgreSQL Production Runbook
  Authentication
    Password Rotation

Nhưng retriever trả về:

Top 5

1. MySQL Credential Reset
2. PostgreSQL Replication Guide
3. Database Backup Policy
4. PostgreSQL Authentication Errors
5. Production Network Checklist

Tài liệu đúng không có trong top 5.

Ta có thể đổi prompt, giảm temperature hoặc dùng một LLM lớn hơn. Nhưng nếu yêu cầu sản phẩm là “chỉ trả lời dựa trên knowledge base nội bộ”, thì hệ thống đơn giản là không có đủ evidence để trả lời. Đây là lý do retrieval nên được debug và đánh giá độc lập với generation.

Retrieval không chỉ hỏng theo một kiểu

~/diagrams/retrieval-trong-rag/retrieval-failure-modes.htmltương tác
mở riêng ↗
HìnhHàng trên là đường đi từ câu hỏi tới context: tìm ứng viên, xếp hạng, cắt top-k, rồi ghép thành context. Hàng dưới là kiểu hỏng tương ứng với từng chặng - tài liệu đúng không lọt candidate set, lọt rồi nhưng đứng hạng 17, đúng chủ đề nhưng sai version, hoặc đủ recall nhưng quá nhiều nhiễu. Nhìn vào riêng câu trả lời cuối thì cả bốn trường hợp trông giống hệt nhau, nhưng mỗi kiểu cần một cách sửa khác nhau.
đường đi của câu hỏichặng xếp hạngkiểu hỏng tương ứng

Kiểu 1 - evidence không xuất hiện trong candidate set. Nếu chunk đúng không lọt qua first-stage retrieval thì reranker phía sau không thể cứu nó. Đây là vấn đề candidate recall.

Kiểu 2 - evidence có xuất hiện nhưng rank quá thấp. Chunk đúng đứng hạng 17 trong khi application chỉ lấy 5 kết quả đầu. Đây là bài toán ranking, không phải bài toán tìm kiếm.

Kiểu 3 - có evidence đúng nhưng quá nhiều nhiễu. Trong top 10 có hai đoạn liên quan, còn lại là đoạn lỗi thời, đoạn trùng lặp, đoạn sai version và đoạn chỉ nằm cạnh về chủ đề. Recall có thể tốt, nhưng precision kém.

Kiểu 4 - đúng topic nhưng sai constraint. Với câu hỏi “chính sách nghỉ phép Engineering hiện tại là gì”, search có thể trả về cả bản 2024, bản 2025, bản 2026 và cả chính sách chung của HR. Tất cả đều gần nghĩa với câu hỏi, nhưng yêu cầu thật là department = Engineering và version = current. Similarity không tự giải quyết loại ràng buộc này.

Retrieval vì vậy thực chất là bài toán:

relevance  +  ranking  +  constraints  +  coverage

Hai câu hỏi phải được tách rời

Khi một câu trả lời RAG sai, hãy hỏi theo đúng thứ tự này:

Câu hỏi A
Evidence cần thiết có nằm trong retrieved set không?
    ->  nếu không: đây là retrieval problem

Câu hỏi B
Nếu evidence đúng đã có trong context,
LLM có sử dụng nó đúng không?
    ->  nếu không: đây là generation / grounding problem

Nếu không tách hai lớp này, rất dễ tối ưu nhầm component. Các hướng dẫn RAG hiện đại cũng tách retrieval quality khỏi final-answer quality vì lỗi ở phía trước có thể lan xuống toàn pipeline.

Còn một điều nữa cần nhớ trước khi đi tiếp: retriever chỉ có thể search trên representation mà indexing pipeline đã tạo ra. Nếu một chunk bị cắt mất tên công ty và mốc thời gian, thì không thuật toán search nào khôi phục lại được phần semantics đã mất từ parsing hoặc chunking. Đó là lý do khi retrieval miss một tài liệu, đừng chỉ hỏi “embedding model có tệ không” - hãy đi ngược lên tận khâu parse và chunk.

Retrieval quality  <=  quality of indexed representation

02Một mini-corpus để đi xuyên suốt bài

Để tránh mỗi section dùng một ví dụ rời rạc, ta sẽ dùng chung một corpus giả lập:

D1  PostgreSQL Production Password Rotation
    "Procedure for rotating PostgreSQL production credentials..."

D2  MySQL Credential Reset
    "How to reset credentials for MySQL database users..."

D3  Production Database Security Policy
    "Production database credentials must be rotated every 90 days..."

D4  PostgreSQL Authentication Troubleshooting
    "Troubleshooting authentication errors and failed logins..."

D5  Incident ERR_AUTH_4821
    "ERR_AUTH_4821 indicates an expired service credential..."

D6  PostgreSQL Replication Operations
    "Operational guide for replication slots and standby nodes..."

D7  Database Security Policy 2024
    "Previous policy required 180-day password rotation..."

Và ba query đại diện cho ba failure mode khác nhau:

Query A  "How do I change the postgres production credential?"
         ->  semantic paraphrase

Query B  "ERR_AUTH_4821"
         ->  exact lexical matching

Query C  "What about prod?"
         ->  query understanding / rewriting

Query C chỉ có nghĩa khi biết ngữ cảnh: ngay trước đó user đã hỏi “How does password rotation work for PostgreSQL?”. Ba query này sẽ quay lại ở từng stage của pipeline.


03Từ user query đến retrieval query

Một giả định phổ biến:

search(query=user_message)

Nhưng user đang nói chuyện với application, không phải với search engine. Raw user message có thể phụ thuộc conversation, chứa đại từ, dùng shorthand, thiếu entity, chứa nhiều intent, quá dài, quá ngắn, hoặc dùng từ ngữ không hề xuất hiện trong corpus.

~/diagrams/retrieval-trong-rag/query-translation.htmltương tác
mở riêng ↗
HìnhBên trái là thứ hệ thống thật sự nhận được: một tin nhắn kèm lịch sử hội thoại. Cột giữa là ba phép biến đổi tương ứng với ba vấn đề khác nhau - rewriting khôi phục chủ ngữ đã bị lược, expansion phủ nhiều cách nói của cùng một ý, decomposition tách một câu hỏi nhiều mảnh thành các truy vấn nhỏ. Cột phải là thứ mỗi phép sinh ra. Điểm quan trọng là cả ba đều có cái giá, và chỉ nên bật khi một nhóm query cụ thể đang thật sự fail.
phép biến đổi từ vựngđầu ra tương ứngphép biến đổi cấu trúc

Query rewriting

Quay lại Query C. Nếu embed trực tiếp chuỗi "What about prod?", retriever thiếu gần như toàn bộ chủ ngữ. Conversation lại cho biết user đang nói về PostgreSQL password rotation, nên một query rewriter có thể tạo:

"PostgreSQL production password rotation procedure"

Mục tiêu của rewriting không phải làm query dài hơn, mà là tạo một representation giữ đúng intent nhưng phù hợp hơn với retrieval engine: resolve đại từ, khôi phục entity bị lược, mở rộng viết tắt, bỏ filler hội thoại, chuẩn hoá thuật ngữ, và giữ nguyên các ràng buộc.

Rủi ro là LLM rewriter cũng có thể đổi luôn intent:

User hỏi:
"Can contractors request paid leave?"

Rewrite sai:
"Paid leave policy for full-time employees"

Search sau đó có thể rất tốt - nhưng đang search sai câu hỏi. Vì vậy query rewriting nên được log và đánh giá như một stage riêng, với user_query và retrieval_query là hai trường tách biệt, chứ không biến mất như một implementation detail.

Query expansion và multi-query

Một ý có thể có nhiều lexical form. Corpus có thể viết single sign-on failure, SAML login issue, identity provider error cho cùng thứ mà user gọi là SSO authentication error. Một strategy là tạo nhiều retrieval query rồi search từng cái và hợp nhất kết quả.

Multi-query có thể tăng recall vì một candidate bị miss ở query này có thể xuất hiện ở query khác. Nhưng nó cũng nhân lên số search call, latency, số candidate và số kết quả trùng lặp:

3 query  x  50 results  =  tối đa 150 raw candidate
                           trước khi dedup và fusion

Do đó multi-query không phải một cải thiện miễn phí.

Query decomposition

Một câu hỏi phức tạp có thể chứa nhiều subproblem:

"So sánh chính sách password rotation hiện tại với policy
năm 2024 và cho biết thay đổi nào ảnh hưởng tới production database."

    ->  Q1: chính sách rotation hiện tại là gì?
    ->  Q2: chính sách năm 2024 là gì?
    ->  Q3: khác nhau ở đâu?
    ->  Q4: thay đổi nào áp dụng cho production?

Decomposition hữu ích khi câu trả lời không nằm trong một chunk duy nhất. Nhưng nó cũng thêm queries, thêm retrieval call, thêm evidence phải tổng hợp và thêm cơ hội phát sinh lỗi. Có một distinction đáng giữ:

Fixed decomposition
    ->  orchestrator luôn áp dụng một rule hoặc prompt định trước

Agentic retrieval
    ->  agent quyết định tại runtime có cần chia query,
        search thêm nguồn hay retrieve lần nữa hay không

Bài này tập trung vào retrieval mechanics, không đi sâu agent loop.


04HyDE: đi đường vòng qua một câu trả lời giả định

Dense retrieval thông thường encode chính câu hỏi rồi đi tìm láng giềng. HyDE - Hypothetical Document Embeddings - đi đường vòng: nhờ LLM viết ra một đoạn văn trông giống câu trả lời, rồi encode chính đoạn giả định đó để tìm neighborhood trong embedding space.

~/diagrams/retrieval-trong-rag/hyde-detour.htmltương tác
mở riêng ↗
HìnhHàng trên là đường vòng: câu hỏi được đưa cho LLM, LLM viết ra một đoạn trả lời giả định, đoạn đó mới được encode, và vector của nó mới là thứ đi tìm trên corpus thật. Hàng dưới là cái được và cái mất ở từng bước - dùng được ở zero-shot, nhưng đoạn giả định có thể chứa chi tiết sai, và mỗi truy vấn nay phải trả thêm một lần gọi LLM. Đoạn giả định không bao giờ được đưa vào context của câu trả lời cuối.
đường vòng của HyDEđoạn văn giả địnhcái được và cái mất

Với Query A, đoạn giả định có thể là:

"To rotate PostgreSQL production credentials, administrators
should generate a new secret, update the credential store,
restart dependent services, validate connectivity, and revoke
the old credential..."

Đoạn này không phải ground truth - chính paper HyDE cũng lưu ý nó có thể chứa chi tiết sai. Nhưng embedding của nó có thể nằm gần các answer-like passages hơn embedding của một câu hỏi ngắn:

Query                 "question-shaped text"
      ->  HyDE
Hypothetical passage  "answer-shaped text"
      ->
Dense retrieval

HyDE đáng thử trong một số zero-shot hoặc vocabulary-gap scenario, nhưng nên xem nó là một retrieval hypothesis cần benchmark, không phải một default component.


05Sparse retrieval và inverted index

Ta bắt đầu với Query B:

ERR_AUTH_4821

Những exact identifier như ERR_AUTH_4821, SKU-84721, CVE-2026-xxxx, API_V2_PAYMENT hay PostgreSQL 18.1 thường mang semantic value rất lớn mặc dù bản thân token gần như tuỳ ý. Dense embedding có thể biểu diễn chúng không ổn định, trong khi lexical retrieval có lợi thế rõ ràng: khớp đúng mặt chữ.

~/diagrams/retrieval-trong-rag/inverted-index.htmltương tác
mở riêng ↗
HìnhHàng trên là chuỗi biến đổi khi đánh index: văn bản đi qua analyzer để tách token, sinh ra từ điển term, mỗi term trỏ ngược về các document chứa nó, và tra theo term cho ra tập ứng viên. Hàng dưới là điều đáng nhớ ở từng bước - analyzer chính là chỗ một mã lỗi bị cắt vụn và biến mất, posting list còn lưu cả tần suất và vị trí, và có ứng viên vẫn chưa phải là có thứ hạng.
chuỗi đánh indexcấu trúc đảo ngượcđiều đáng nhớ

Với ba document:

D1: postgres password reset
D2: mysql password rotation
D3: postgres replication

một inverted index đơn giản trông như sau:

postgres     ->  D1, D3
password     ->  D1, D2
reset        ->  D1
mysql        ->  D2
rotation     ->  D2
replication  ->  D3

Với query postgres reset, search engine không cần scan từng document. Nó đi từ term sang posting list rồi ra candidate. Trong thực tế posting list còn chứa term frequency, position, field information và document statistics để phục vụ scoring và phrase query.

Nhưng còn một câu hỏi chưa được trả lời: nếu nhiều document đều chứa query term, document nào nên rank cao hơn? Đó là nơi BM25 xuất hiện.


06BM25 không chỉ là đếm keyword

BM25 thuộc họ probabilistic information retrieval và là một trong những ranking function kinh điển của search. Công thức nhìn có vẻ phức tạp:

score(D, Q)  =  tổng theo từng query term qi của

                             f(qi, D) x (k1 + 1)
              IDF(qi)  x  ----------------------------------------
                          f(qi, D) + k1 x (1 - b + b x |D| / avgdl)

nhưng trực giác thì khá đơn giản. BM25 đang cộng ba tín hiệu độc lập lại với nhau.

~/diagrams/retrieval-trong-rag/bm25-signals.htmltương tác
mở riêng ↗
HìnhTừ một cặp query và document, BM25 hỏi ba câu hoàn toàn khác nhau: term này hiếm tới mức nào trong corpus, nó xuất hiện mấy lần trong document này, và document này dài hay ngắn so với mặt bằng chung. Cột phải là điều mỗi câu hỏi mang lại - độ hiếm tạo sức phân biệt, tần suất có trần chứ không tăng vô hạn, và một khớp trong đoạn ngắn đáng tin hơn một khớp tình cờ trong tài liệu rất dài.
tín hiệu tần suấtđiều mỗi tín hiệu mang lạitín hiệu corpus và độ dài

IDF - term hiếm thường mang nhiều tín hiệu hơn. Giả sử corpus có một triệu document. Term database xuất hiện trong 500.000 document, còn ERR_AUTH_4821 chỉ xuất hiện trong 3. Nếu query chứa cả hai thì exact error code có sức phân biệt lớn hơn hẳn. Đây là trực giác của Inverse Document Frequency: term xuất hiện ở mọi nơi không giúp ta phân biệt, term hiếm thì có thể rất informative.

Term frequency có bão hoà. Với query postgres authentication, document B chứa authentication 10 lần có thể relevant hơn document A chỉ chứa một lần. Nhưng 100 lần không có nghĩa là relevant gấp 100:

Đóng góp vào score
      ^
      |             ___________
      |          __/
      |        _/
      |      _/
      |    _/
      +----------------------------->  term frequency

Đóng góp tăng nhanh lúc đầu rồi dần bão hoà. Tham số k1 điều khiển mức bão hoà này; Elasticsearch và Lucene mặc định k1 = 1.2.

Độ dài document được chuẩn hoá. Một match trong passage 80 từ thường có signal mạnh hơn một match tình cờ trong tài liệu 8.000 từ. BM25 đưa tỉ lệ |D| / avgdl vào scoring, và tham số b điều khiển mức ảnh hưởng của length normalization; mặc định thường là b = 0.75.

BM25 rất mạnh với exact identifier, tên sản phẩm, tên người, mã lỗi, API endpoint, số hiệu version, thuật ngữ hiếm và điều khoản pháp lý. Query B ERR_AUTH_4821 có thể dễ dàng đưa D5 lên top.

Nhưng nó yếu ở chỗ khác. Với query "How do I change my database login secret?" và document "Procedure for rotating PostgreSQL production credentials", keyword overlap gần như bằng không:

change        <->  rotating
login secret  <->  credentials
database      <->  PostgreSQL

Con người thấy chúng gần nghĩa; BM25 không trực tiếp có representation của “meaning”. Đây là vocabulary mismatch problem - và cũng là lý do dense retrieval trở nên quan trọng.


07Dense retrieval và bi-encoder

Dense retrieval dùng learned representation. Paper Dense Passage Retrieval là một mốc quan trọng cho open-domain QA: query và passage được encode thành dense vector bằng dual-encoder, sau đó search dựa trên vector similarity.

~/diagrams/retrieval-trong-rag/dense-bi-encoder.htmltương tác
mở riêng ↗
HìnhHai nhánh chạy ở hai thời điểm khác nhau và chỉ gặp nhau ở phép so độ tương đồng. Nhánh dưới chạy offline lúc index: toàn bộ chunk được encode một lần rồi nằm sẵn trong index. Nhánh trên chỉ chạy khi có câu hỏi: encode đúng một chuỗi rồi đi tìm láng giềng gần nhất. Chính sự độc lập đó khiến bi-encoder scale được tới hàng triệu chunk, và cũng chính nó là lý do một reranker vẫn cần đứng phía sau.
nhánh onlinechỗ hai nhánh gặp nhaunhánh offline

Một embedding model cố gắng đặt những đoạn text có quan hệ phù hợp gần nhau trong vector space. "rotate postgres credentials" và "change the database password" có lexical overlap rất thấp nhưng embedding có thể nằm rất gần nhau.

Điểm quan trọng cần nhớ:

Dense retrieval không đơn giản là “BM25 nhưng thông minh hơn”. Nó sử dụng một signal khác hẳn.

Sparse  ->  token overlap / lexical evidence
Dense   ->  learned semantic representation

Hai signal khác nhau nên chúng có failure mode khác nhau.

Ưu điểm cực lớn của kiểu bi-encoder là document vector có thể tính offline. Nếu corpus có 10 triệu chunk, ta không cần chạy transformer trên 10 triệu chunk cho mỗi query; ta chỉ encode query một lần rồi search trên precomputed vector. Đổi lại, mọi tương tác giữa query và document bị nén vào phép so hai vector độc lập - và đó chính là lý do ta cần một reranker ở phía sau.

Sau khi có vector, ta cần một cách đo “gần”. Ba lựa chọn phổ biến là cosine similarity (đo góc giữa hai vector, quan tâm hướng hơn độ lớn), dot product (khi vector đã normalize thì liên hệ trực tiếp với cosine) và Euclidean distance (khoảng cách hình học). Không có metric nào tốt nhất một cách phổ quát, nhưng có một nguyên tắc production quan trọng:

Dùng distance metric mà embedding model hoặc retrieval stack của bạn được thiết kế cho.


08Exact KNN và Approximate Nearest Neighbor

Giả sử index có 100.000 vector. Ta hoàn toàn có thể tính similarity từ query tới mọi vector. Nhưng nếu có 100 triệu vector, exhaustive comparison trở nên rất đắt.

Exact KNN so query với mọi vector rồi sắp xếp, và cho ta true nearest neighbors theo đúng metric đã chọn - đổi lại chi phí tăng theo kích thước corpus. Approximate Nearest Neighbor chấp nhận một trade-off khác:

Không nhất thiết kiểm tra tất cả vector; tìm một tập gần nhất đủ tốt với latency thấp hơn.

Hierarchical Navigable Small World là một ANN structure phổ biến. Mental model của nó khá trực quan: layer trên cùng có ít node nên di chuyển nhanh qua không gian, layer dưới có nhiều node hơn nên tinh chỉnh được vùng láng giềng. Search đi vào từ layer thưa, hướng dần về vùng có triển vọng, đi xuống, rồi tìm kỹ tại chỗ.

HNSW có các trade-off điều chỉnh được giữa chi phí build index, bộ nhớ, độ trễ truy vấn và recall. Azure AI Search chẳng hạn hỗ trợ cả exhaustive KNN lẫn HNSW, và mô tả HNSW như một ANN option có đánh đổi giữa tốc độ và độ chính xác.

Đây là một distinction rất quan trọng khi benchmark retrieval:

embedding quality  !=  ANN search quality

Bạn hoàn toàn có thể có một embedding model tốt nhưng cấu hình ANN lại làm mất candidate. Vì vậy đừng chỉ benchmark embedding model - tham số ANN cũng thay đổi Recall@K.


09Hybrid search: chạy nhiều retriever rồi hợp nhất

Quay lại mini-corpus. Với Query A ("How do I change the postgres production credential?"), dense retrieval có lợi thế vì nó nối được change với rotating, credential với credentials, postgres với PostgreSQL. Với Query B (ERR_AUTH_4821), D5 chứa exact string nên BM25 có lợi thế rõ ràng.

Đặc điểm Sparse / BM25 Dense retrieval
Matching signal lexical learned semantic
Exact identifier rất mạnh có thể yếu
Từ khoá hiếm mạnh tuỳ embedding
Đồng nghĩa hạn chế thường tốt
Paraphrase hạn chế thường tốt
Viết tắt tuỳ tokenizer và corpus tuỳ model
Mã sản phẩm mới tốt dễ khó biểu diễn
Cần embedding model không có
Giải thích được vì sao khớp tương đối dễ khó hơn
Hạ tầng ANN không bắt buộc thường cần ở scale lớn

Sai lầm thường gặp là hỏi “BM25 hay vector search, cái nào tốt hơn?”. Câu hỏi thực tế nên là:

Corpus và query distribution của mình có những retrieval signal nào?

Nếu knowledge base chứa cả natural language lẫn mã lỗi, số hiệu version, danh từ riêng, mã sản phẩm và jargon chuyên ngành, thì cả lexical lẫn semantic signal đều có giá trị. Từ đây hybrid retrieval trở thành một lựa chọn tự nhiên: chạy cả hai retriever trên cùng một query, mỗi bên trả về một ranked list, rồi hợp nhất hai list đó lại. Azure AI Search hiện triển khai hybrid query đúng theo cách này - chạy text search và vector search song song rồi merge bằng Reciprocal Rank Fusion.

Giả sử với Query A ta có:

BM25                                     Dense
1. D4  PostgreSQL Auth Troubleshooting   1. D1  Password Rotation
2. D1  Password Rotation                 2. D3  Security Policy
3. D6  Replication Operations            3. D4  Auth Troubleshooting
4. D3  Security Policy                   4. D2  MySQL Credential Reset

Hai list chứa thông tin bổ sung cho nhau. Ta muốn D1 được thưởng vì nó đứng cao ở cả hai. Đó chính là việc của rank fusion.


10Reciprocal Rank Fusion

Câu hỏi đầu tiên: sao không cộng thẳng điểm lại?

BM25 score(D1)    =  12.7
Cosine score(D1)  =   0.84

12.7 + 0.84  =  13.54     <-  con số này có nghĩa gì?

Hai score space không tương thích. Điểm BM25 phụ thuộc query term, corpus statistics, độ dài document và term frequency. Vector similarity nằm trong một phân phối hoàn toàn khác, và hai embedding model khác nhau còn tạo ra hai phân phối similarity khác nhau. Phép cộng raw score vì vậy không có ý nghĩa rõ ràng nếu chưa calibrate.

~/diagrams/retrieval-trong-rag/rrf-fusion.htmltương tác
mở riêng ↗
HìnhTừ hai ranked list của cùng một query có ba đường đi. Cộng thẳng điểm thô là phép cộng vô nghĩa vì hai phân phối điểm khác hẳn nhau. Chuẩn hoá rồi cộng thì mạnh nhưng cần dữ liệu và bảo trì. Hợp nhất theo thứ hạng chỉ cần biết một tài liệu đang đứng hạng mấy, nên nó là baseline đơn giản mà chắc chắn khi ghép các retriever không cùng thang đo.
cách nên dùng làm baselinehệ quả tương ứngcách cần điều kiện

Reciprocal Rank Fusion được Cormack, Clarke và Büttcher giới thiệu như một phương pháp hợp nhất nhiều ranked list mà không cần raw score phải calibrate với nhau:

RRF(d)  =  tổng trên mọi ranked list r của       1
                                            -----------
                                            k + rank(d)

trong đó rank(d) là vị trí của document d trong list r, còn k là một hằng số làm dịu chênh lệch ở nhóm rất đầu bảng.

Với ví dụ ở trên, D1 có BM25 rank = 2 và dense rank = 1, nên:

score(D1)  =  1 / (k + 2)  +  1 / (k + 1)

Document đứng cao ở nhiều ranking sẽ có fused score mạnh. Cái hay là RRF không cần biết BM25 score 11.2 có tương đương cosine 0.8 hay không - nó chỉ cần biết document đang đứng hạng bao nhiêu. Điều đó làm RRF rất thực dụng khi kết hợp các retriever không đồng nhất, và Microsoft hiện dùng đúng cơ chế này để fuse hybrid cũng như nhiều vector query trong Azure AI Search.

Nhưng RRF không phải phép màu. Hợp nhất theo rank bỏ qua phần lớn thông tin nằm trong raw score. Nếu hệ thống có điểm relevance đã calibrate, có dữ liệu learning-to-rank hoặc có trọng số theo từng loại query, thì fusion học được hoặc fusion có trọng số có thể tốt hơn. Hãy xem RRF là một baseline mạnh và đơn giản, không phải điểm tối ưu phổ quát.


11Metadata filtering: relevance chưa đủ

Với query "Production password rotation policy", cả D3 (chính sách hiện tại) và D7 (chính sách 2024) đều có similarity cao. Nhưng user cần version = current.

Enterprise RAG còn có thể cần thêm tenant_id, department, language, product, region, updated_at >= ... và access_group. Một design principle quan trọng:

Đừng cố embed mọi loại constraint rồi hy vọng similarity tự xử lý.

Vector similarity phù hợp cho semantic relevance. Metadata field phù hợp cho bằng nhau, khoảng giá trị, phân loại, version, permission, tenant và thời gian.

Nhưng ngay khi kết hợp vector search với metadata filter, một câu hỏi implementation rất thực tế xuất hiện: filter xảy ra trước hay sau nearest-neighbor search?

~/diagrams/retrieval-trong-rag/filter-order.htmltương tác
mở riêng ↗
HìnhHàng trên là pre-filter: áp ràng buộc cứng trước để thu hẹp index, rồi mới tìm láng giềng trong tập hợp lệ, nên top-k trả về đã hợp lệ sẵn. Hàng dưới là post-filter: tìm trên toàn index trước rồi mới loại, và khi filter chọn lọc mạnh thì phần lớn top-k bị loại sạch trong khi những tài liệu hợp lệ ở hạng 11 đến 100 không ai xem tới. Cùng một filter, đặt trước hay sau cho hai kết quả recall khác nhau.
đường pre-filterchỗ áp ràng buộcđường post-filter

Ví dụ điển hình của post-filter: vector search trả về top 10, trong đó 9 tài liệu thuộc sai tenant hoặc sai version, post-filter loại chúng đi và ta còn đúng một kết quả - trong khi corpus vẫn còn rất nhiều tài liệu hợp lệ ở hạng 11 đến 100.

Azure AI Search mô tả rõ ba chế độ preFilter, postFilter và strictPostFilter; post-filtering với filter chọn lọc mạnh hoặc k nhỏ có thể tạo false negative, còn pre-filter thường ưu tiên recall nhưng có thể tăng chi phí duyệt và độ trễ trong một số trường hợp.

Điều cần rút ra:

Filter strategy là một retrieval parameter, không chỉ là cú pháp của database query.

Khi benchmark, phải đo cùng lúc Recall@K, độ trễ, độ chọn lọc của filter và giá trị k.


12Retriever tìm rộng, reranker chấm kỹ

Giả sử hybrid retrieval trả về 50 candidate. Một sai lầm là đưa cả 50 vào LLM.

First-stage retriever được thiết kế để lấy rộng, chạy nhanh và giữ recall. Nhưng generation context lại cần nhỏ, chính xác, độ liên quan cao và ít nhiễu. Hai mục tiêu đó khác nhau, nên chúng thuộc về hai chặng khác nhau:

Hàng triệu chunk
    ->  retriever nhanh      ->  top 50 candidate
    ->  reranker chính xác   ->  top 8 evidence
    ->  Context Builder

Đây là retrieve-and-rerank architecture. Sentence Transformers cũng mô tả đúng pattern này: bi-encoder hiệu quả dùng để lấy candidate set, sau đó cross-encoder chấm lại từng cặp (query, candidate) vì cross-encoder chính xác hơn nhưng không scale để chấm toàn corpus.

~/diagrams/retrieval-trong-rag/encoder-continuum.htmltương tác
mở riêng ↗
HìnhBa kiến trúc nằm trên cùng một trục đổi mức tương tác lấy khả năng mở rộng. Bi-encoder mã hoá query và document hoàn toàn độc lập nên tính sẵn được và quét được cả corpus. Cross-encoder đọc cả hai cùng lúc nên chính xác nhất nhưng phải chạy lại cho từng cặp. Late interaction kiểu ColBERT nằm ở giữa: vẫn mã hoá độc lập nhưng giữ biểu diễn ở mức token và hoãn phép tương tác tới cuối, đổi lại index phình to.
hai đầu của trụcđánh đổi tương ứngđiểm ở giữa

Bi-encoder và cross-encoder

Bi-encoder encode query và document độc lập, nên document vector precompute được và nearest-neighbor search rất nhanh. Nhược điểm là tương tác giữa query và document bị nén vào hai vector rời nhau.

Cross-encoder nhận cả cặp [query ; document] làm một đầu vào và self-attention có thể model tương tác chi tiết giữa query token và passage token. Sentence Transformers mô tả CrossEncoder là model nhận một text pair và trả về score, không tạo ra embedding tái sử dụng được cho từng text.

Nếu có 10 triệu chunk và mỗi query cần chạy cross_encoder(query, chunk_i) 10 triệu lần thì chi phí là không khả thi cho phần lớn use case online. Do đó chặng một dùng retriever rẻ và scale được để đi từ 10 triệu xuống 50, chặng hai dùng reranker đắt và chính xác để đi từ 50 xuống 8. Đây là một general information retrieval pattern, không riêng gì RAG.

Late interaction và ColBERT

Giữa bi-encoder và full cross-encoder có một design point thú vị. ColBERT encode query và document độc lập nhưng giữ token-level representation, sau đó thực hiện phép tương tác rẻ hơn ở cuối. Paper gốc nhấn mạnh khả năng precompute document representation trong khi vẫn giữ được matching mịn hơn so với một vector duy nhất.

Điều đó không có nghĩa ColBERT luôn tốt hơn. Nó là một trade-off kiến trúc khác cần evaluate theo chất lượng, kích thước index, độ trễ, compute và độ phức tạp khi triển khai.

Ba loại điểm không nằm trên cùng một thang

Retriever hỏi “candidate nào đáng để xem xét?”. Reranker hỏi “trong tập này, candidate nào thật sự phù hợp nhất với query?”. Với Query A, retriever có thể đẩy D4 lên cao vì nó chứa cả PostgreSQL lẫn authentication, trong khi reranker nhận ra D1 trả lời trực tiếp hơn vì nó chứa đúng thủ tục rotate credential.

Vì vậy đừng diễn giải các điểm số như cùng một probability scale:

dense_score    =  0.82
sparse_score   =  11.4
fusion_score   =  0.032
rerank_score   =  0.91

Trong tracing nên log rõ bốn trường riêng biệt thay vì một field mơ hồ tên score.


13top_k thực ra là ba tham số khác nhau

Trong code RAG ta thường thấy một dòng top_k = 5. Nhưng một retrieval pipeline production có ít nhất ba chữ k nằm ở ba chỗ khác nhau.

~/diagrams/retrieval-trong-rag/topk-budget.htmltương tác
mở riêng ↗
HìnhHàng trên là số ứng viên co dần qua từng chặng: mỗi retriever lấy 50, sau fusion và khử trùng còn khoảng 60, reranker chấm lại 50 cặp rồi giữ 8, và Context Builder cuối cùng chỉ đưa 5 đoạn vào prompt. Hàng dưới là rủi ro và cái giá tương ứng - k đầu quá nhỏ thì reranker không bao giờ được nhìn thấy evidence đúng, còn k cuối quá lớn thì evidence đúng chìm giữa nhiễu và ngân sách token.
số ứng viên co dầnhai lần cắtrủi ro và cái giá

retrieval_k quyết định first-stage candidate pool. Lớn hơn thì tăng khả năng tài liệu đúng sống sót, nhưng cũng tăng chi phí candidate và chi phí reranker. Nhỏ hơn thì giảm latency nhưng tăng rủi ro evidence đúng không bao giờ tới được reranker.

rerank_k thực ra là hai khái niệm cần tách: số candidate gửi vào reranker (rerank_input_k) và số document giữ lại sau khi rerank (rerank_output_k). Nếu chỉ rerank top 10 của retriever thì tài liệu đúng đang đứng hạng 20 không bao giờ được cứu.

context_k là số đoạn thật sự vào prompt. Sau reranking, Context Builder có thể chỉ dùng top 5 vì ngân sách token, vì evidence trùng lặp, vì cần mở rộng sang đoạn cha, hoặc vì muốn đa dạng nguồn.

candidate_k  !=  evidence_k  !=  context_k

Vì sao “lấy nhiều hơn” không luôn tốt hơn

Trực giác “thêm chunk thì thêm thông tin thì câu trả lời tốt hơn” không phải lúc nào cũng đúng. K lớn hơn có thể tăng recall, nhưng cũng tăng trùng lặp, nội dung lạc đề, version mâu thuẫn, số token, latency và chi phí generation.

Và long-context model không nhất thiết sử dụng mọi vị trí trong context hiệu quả như nhau. Paper Lost in the Middle cho thấy performance của nhiều LLM có thể phụ thuộc mạnh vào vị trí của thông tin liên quan trong một context dài; trong các thí nghiệm của họ, thông tin nằm ở giữa context thường khó được tận dụng hơn so với ở đầu hoặc cuối.

Điều này không có nghĩa “LLM không đọc được context dài”. Nó có nghĩa:

Context window capacity và effective information utilization không phải cùng một khái niệm.

Từ góc nhìn retrieval, mục tiêu không phải maximize số chunk, mà là maximize mật độ evidence hữu ích.


14Đo chất lượng retrieval bằng gì

Muốn tuning retrieval, ta cần một dataset có relevance judgment. Ví dụ với query “How to rotate PostgreSQL production password?”, tài liệu liên quan là D1 và D3, còn retriever trả về:

Top 5

1. D1   <-  liên quan
2. D4
3. D3   <-  liên quan
4. D6
5. D2

Từ đây có thể tính các classical IR metric.

~/diagrams/retrieval-trong-rag/retrieval-metrics.htmltương tác
mở riêng ↗
HìnhCùng một query có nhãn liên quan, nhưng mỗi chặng cần một thước đo khác nhau. Chặng tìm ứng viên hỏi evidence có sống sót không nên đo bằng Recall@K. Chặng xếp hạng hỏi thứ tự có hợp lý không nên đo bằng NDCG@K và MRR. Tập evidence cuối hỏi context có sạch không nên đo bằng Precision@K. Không có một con số duy nhất cho cả retrieval, vì ba chặng đang trả lời ba câu hỏi khác nhau.
chặng xếp hạngmetric tương ứngchặng đầu và chặng cuối

Precision@K là tỉ lệ kết quả liên quan trong top K. Với ví dụ trên, P@5 = 2/5 = 0.4. Nó trả lời câu hỏi “result list sạch đến đâu”, và đặc biệt quan trọng khi ngân sách context ở phía sau rất nhỏ.

Recall@K là tỉ lệ evidence liên quan đã tìm được trên tổng số evidence đáng lẽ phải tìm. Nếu ground truth là D1, D3, D5 và top 5 lấy được D1, D3 thì Recall@5 = 2/3. Nó trả lời câu hỏi “có bỏ sót evidence không”. Chặng candidate retrieval thường ưu tiên recall khá mạnh, vì reranker chỉ có thể làm việc trên những candidate đã sống sót.

Cuốn Introduction to Information Retrieval của Stanford mô tả precision và recall như hai góc nhìn bổ sung, và mô tả đúng trade-off theo K: recall không giảm khi retrieve nhiều hơn, trong khi precision thường có xu hướng giảm khi result set mở rộng. Vì vậy đừng tune k bằng cảm giác - hãy đo cả hai.

MRR quan tâm tới việc kết quả liên quan đầu tiên xuất hiện sớm đến đâu: RR = 1 / rank của kết quả liên quan đầu tiên, rồi lấy trung bình qua các query. MRR phù hợp khi ta muốn một tài liệu chứa câu trả lời xuất hiện thật cao. Nhưng nếu một query cần nhiều evidence, MRR một mình không đủ.

NDCG dùng khi relevance có nhiều mức chứ không chỉ có liên quan và không liên quan:

3 = trả lời trực tiếp
2 = rất hữu ích
1 = hơi liên quan
0 = không liên quan

Hai ranking [3, 3, 2, 0, 0] và [0, 1, 3, 3, 2] chứa gần như cùng một tập tài liệu, nhưng ranking thứ nhất tốt hơn vì evidence hữu ích nằm ở trên. NDCG thưởng cho mức liên quan cao ở hạng cao và chiết khấu các hạng dưới, nên đây là metric hữu ích nhất khi đánh giá riêng phần reranker.

Chặng Câu hỏi nó trả lời Metric hữu ích
Candidate retriever Evidence đúng có sống sót không? Recall@K
Hybrid và fusion Ranking hợp nhất có tốt hơn không? Recall@K, NDCG@K
Reranker Evidence liên quan có được đẩy lên top? MRR, NDCG@K, Precision@K
Tập evidence cuối Context có sạch không? Precision@K, Recall@K
RAG end-to-end Câu trả lời có đúng và có căn cứ không? sang bài đánh giá RAG

Đừng chỉ nhìn con số trung bình

Giả sử có hai cấu hình với Recall@10 lần lượt là 0.86 và 0.87. Không nên vội kết luận cấu hình thứ hai tốt hơn: nó có thể cải thiện 50 query dễ nhưng phá 10 query mã định danh quan trọng.

Cách đọc hữu ích hơn là chia query thành các slice - paraphrase ngữ nghĩa, mã định danh chính xác, câu hỏi nhiều chặng, viết tắt, nhạy cảm version, nhiều thực thể, query rất ngắn, query hội thoại - rồi đo riêng từng slice:

Loại query BM25 Dense Hybrid + Rerank
Paraphrase ngữ nghĩa 0.62 0.88 0.91 0.93
Mã định danh 0.96 0.61 0.97 0.97
Nhạy cảm version 0.74 0.76 0.82 0.88
Hội thoại 0.50 0.54 0.57 0.58
+ Query rewrite - - 0.83 0.85

Các số trên chỉ là ví dụ minh hoạ, không phải benchmark thật. Nhưng chúng cho thấy đúng một điều:

Metric trung bình cho biết hệ thống dịch chuyển theo hướng nào. Query slice cho biết vì sao.


15Debug theo triệu chứng và tuning theo giả thuyết

Một retrieval system tốt không chỉ cần metric. Nó cần một failure taxonomy và một cách đi từ triệu chứng tới nguyên nhân.

~/diagrams/retrieval-trong-rag/retrieval-debug.htmltương tác
mở riêng ↗
HìnhHàng trên là đường đi ngược: từ câu trả lời sai, hỏi context có chứa tài liệu đúng không, nếu vắng thì hỏi nó đứng hạng mấy, nếu thấp thì hỏi nó có trong top 50 không, nếu sót thì hỏi nó có trong index không. Hàng dưới là nghi phạm cần soi ở đúng chặng đó. Đi theo thứ tự này thì mỗi câu trả lời loại bỏ được một nửa không gian nguyên nhân, thay vì đoán mò bằng cách đổi embedding model.
đường truy ngượcchặng xếp hạngnghi phạm ở từng chặng

Correct document không có trong candidate set. Kiểm tra theo thứ tự: query có bị rewrite sai không, document có bị chunk hỏng không, BM25 có lấy được nó không, dense có lấy được không, metadata filter có loại nhầm không, ANN recall có quá thấp không, retrieval_k có quá nhỏ không. Không cần chỉnh reranker, vì reranker chưa từng nhìn thấy document đó.

Có trong candidate nhưng rank thấp. Đây là ranking problem: soi fusion method, trọng số và cách trộn query, reranker, mức trùng lặp trong candidate, độ cụ thể của query, và các version cũ đang cạnh tranh.

Mã định danh bị miss. Kiểm tra lexical retrieval có bật không, tokenizer và analyzer xử lý ra sao, có chuẩn hoá chữ hoa chữ thường không, ký tự đặc biệt bị làm gì, candidate pool có phải hybrid không. Dense-only retrieval thường là nghi phạm đầu tiên.

Paraphrase ngữ nghĩa bị miss. Kiểm tra embedding model, ngữ cảnh còn lại trong chunk, query rewrite, cấu hình ANN và từ vựng chuyên ngành.

Tài liệu liên quan bị filter loại mất. Kiểm tra tính đúng của metadata, cách trích ràng buộc từ query, chế độ pre hay post filter, độ chọn lọc của filter, giá trị k và cách ACL được truyền xuống. Một filter bug có thể trông y hệt một embedding failure.

Top results toàn trùng lặp. Khi top 4 là D1 chunk 10, 11, 12, 13 thì đây có thể là vấn đề overlap khi chunk, vấn đề xếp hạng ở mức chunk, thiếu đa dạng nguồn hoặc thiếu dedup. Các hướng xử lý gồm khử trùng, giới hạn số chunk trên mỗi document, gom theo tài liệu cha, dùng MMR, hoặc mở rộng sang đoạn lân cận sau khi đã chọn.

Version cũ rank cao hơn version hiện tại. Search relevance không biết business truth. Kiểm tra metadata version, cờ đánh dấu bản hiện hành, filter theo thời gian, độ tin cậy của nguồn, độ tươi của index và các feature mà reranker đang dùng.

Benchmark retrieval tốt nhưng câu trả lời vẫn sai. Đây là chỗ nên dừng sửa retriever. Nếu evidence đúng, ranking đúng và context đúng mà LLM vẫn trả lời sai, thì vấn đề đã chuyển sang faithfulness, answer relevancy, cách context được sử dụng và hành vi của prompt.

Tuning là chạy thí nghiệm, không phải đổi tham số theo cảm giác

Một grid đầy đủ - hai embedding model, ba chế độ retrieval, ba giá trị dense K, bật tắt RRF, hai reranker, hai mức rerank input, ba mức evidence cuối - sẽ bùng nổ tổ hợp. Cách thực dụng hơn là thí nghiệm theo giả thuyết:

Thí nghiệm 1  -  Dense và Hybrid
Giả thuyết:  mã định danh đang fail nên thêm BM25 sẽ tăng recall
             mà không kéo tụt nhóm query ngữ nghĩa
Đo:          Recall@20, NDCG@10, slice định danh, slice ngữ nghĩa, latency

Thí nghiệm 2  -  Candidate K
Giả thuyết:  reranker không có cơ hội sửa vì tài liệu đúng
             thường nằm ở hạng 20 đến 40
Đo:          candidate Recall@K, NDCG@10 sau rerank, latency, chi phí rerank

Thí nghiệm 3  -  Query rewriting
Giả thuyết:  query hội thoại retrieval kém vì thiếu chủ ngữ
Đo:          tách riêng slice standalone và slice follow-up

Thí nghiệm 4  -  Filtering
Giả thuyết:  post-filter đang làm mất recall khi lọc theo version
Đo:          relevance, tính đúng version, Recall@K, tỉ lệ zero-result, latency

Ở thí nghiệm 3, nếu rewrite chỉ cải thiện slice hội thoại mà gây regression ở query standalone, thì lời giải không phải bỏ rewrite mà là route: chỉ rewrite khi câu hỏi thật sự phụ thuộc ngữ cảnh.

Trace cần giữ đủ thông tin trung gian

{
  "request_id": "rag_82f1",
  "user_query": "What about prod?",
  "retrieval_query": "PostgreSQL production password rotation",
  "filters": { "tenant_id": "acme", "version": "current" },
  "dense":    { "k": 50, "latency_ms": 42 },
  "bm25":     { "k": 50, "latency_ms": 18 },
  "fusion":   { "method": "rrf" },
  "reranker": { "input_k": 50, "output_k": 8, "latency_ms": 175 }
}

và với từng candidate:

{
  "chunk_id": "postgres-runbook#auth#7",
  "dense_rank": 2,
  "dense_score": 0.81,
  "bm25_rank": 5,
  "bm25_score": 9.4,
  "fusion_rank": 1,
  "rerank_rank": 1,
  "rerank_score": 0.93
}

Với trace này, khi câu trả lời sai ta thấy được document rơi ở chặng nào. Nếu chỉ lưu query và final answer, retrieval trở thành một hộp đen.

Chất lượng luôn có giá

Một pipeline thực tế có thể có hình dạng như sau:

Query rewrite       180 ms
Embedding            25 ms
Dense search         45 ms
BM25                 20 ms
Fusion                3 ms
Reranker            190 ms
--------------------------
Retrieval total     463 ms

Thêm multi-query ba nhánh, thêm HyDE, hoặc nâng reranker lên 100 candidate đều làm con số này tăng đáng kể. Vì vậy optimization objective không phải đơn giản là maximize quality, mà gần hơn với maximize quality subject to latency, cost, reliability. Một retriever thắng trên benchmark nhưng cộng thêm hai giây latency có thể không phù hợp với sản phẩm.

Do đó retrieval benchmark nên log ít nhất: metric chất lượng, p50 và p95 latency, số lần gọi model, số candidate đưa vào reranker, và chi phí.

Một baseline để bắt đầu

Không có universal configuration, nhưng một baseline hợp lý có thể là:

Query
    ->  chỉ rewrite khi câu hỏi phụ thuộc hội thoại
    ->  BM25 top 30  +  Dense top 30
    ->  RRF
    ->  khử trùng
    ->  rerank top 40
    ->  giữ top 5 đến 8

Điểm quan trọng là baseline phải đơn giản và đo được. Đừng bắt đầu ngay bằng multi-query cộng HyDE cộng decomposition cộng ba vector index cộng BM25 cộng RRF cộng cross-encoder cộng LLM reranking. Nếu hệ thống cải thiện hoặc tụt lùi, bạn sẽ không biết stage nào tạo ra thay đổi đó.

Một progression tốt là cộng dồn từng lớp một - BM25, rồi dense, rồi hybrid với RRF, rồi reranker, rồi conditional query rewrite, rồi mới tới các biến đổi chuyên biệt - và mỗi lớp chỉ được giữ lại nếu benchmark chứng minh nó giải quyết một failure class có thật.


Retrieval là một ranking pipeline, không phải một phép similarity

Nếu chỉ giữ lại một câu sau bài này:

Retrieval trong RAG không phải semantic similarity search. Nó là một information-retrieval pipeline có nhiều ranking signal và nhiều trade-off.

Những gì nên mang theo:

  • BM25 không lỗi thời chỉ vì embedding xuất hiện, và dense retrieval không thay thế lexical retrieval trong mọi corpus;
  • hybrid search không tự nhiên tốt nếu fusion và candidate pool chưa được tune;
  • RRF hợp nhất theo thứ hạng nên nó không cần hai retriever cùng thang đo - đó là sức mạnh và cũng là giới hạn của nó;
  • reranking không cứu được document chưa lọt vào candidate set;
  • retrieval_k, rerank_k và context_k là ba tham số khác nhau, và top-k không phải càng lớn càng tốt;
  • metadata filtering thay đổi recall, và thứ tự lọc trước hay sau search là một retrieval parameter;
  • query rewriting có thể cứu query hội thoại nhưng cũng có thể làm lệch chính câu hỏi;
  • ANN là một nguồn lỗi độc lập với chất lượng embedding;
  • metric trung bình nói hệ thống dịch chuyển ra sao, query slice mới nói vì sao.

Và cuối cùng:

Good retrieval  !=  Good RAG answer

Retrieval tốt chỉ đảm bảo đúng một điều rất quan trọng: LLM có cơ hội nhìn thấy đúng evidence.

Bài tiếp theo bắt đầu chính tại điểm này. Khi evidence đã được retrieve và ghép thành context, ta cần trả lời một nhóm câu hỏi khác hẳn: context có đủ coverage không, ranking có hợp lý không, câu trả lời có grounded không, model có hallucinate ngoài context không, và câu trả lời có thực sự trả lời đúng câu hỏi không. Đó là lý do chúng ta cần đánh giá RAG với DeepEval.


Tài liệu tham khảo

Trương Đức Dũng

AI Engineer - Hà Nội

Tập trung vào LLM, RAG và các hệ thống AI cần chạy ổn định trong môi trường vận hành thật.