RAG có thể cho kết quả kém dù vector database, embedding model và LLM đều hoạt động đúng. Bài viết phân tích những lỗi kỹ thuật khó phát hiện nhất trong pipeline RAG, từ mất thông tin khi chunking đến retrieval nhiễu, context sai và evaluation đánh lừa, kèm các case study để xác định lỗi nằm ở đâu.
Một hệ thống Retrieval-Augmented Generation (RAG) có thể chạy ổn về mặt phần mềm nhưng vẫn trả lời sai một cách rất thuyết phục. Nguyên nhân thường không nằm ở một model duy nhất mà xuất hiện tại ranh giới giữa ingestion, chunking, retrieval, reranking, context construction và generation.
Điều nguy hiểm là nhiều lỗi RAG không tạo exception hay log đỏ. Pipeline vẫn trả về document, LLM vẫn sinh câu trả lời hợp ngữ pháp, latency vẫn bình thường. Vì vậy, kỹ sư cần chẩn đoán RAG như một hệ thống nhiều tầng thay vì nhìn output cuối rồi kết luận rằng “LLM hallucinate”.
Lỗi đầu tiên: dữ liệu đúng nhưng bị phá hỏng trước khi retrieval
Nhiều đội bắt đầu debug từ vector database trong khi lỗi đã xảy ra từ ingestion. Parser có thể làm mất heading, tách sai bảng, bỏ metadata, nối hai cột PDF thành một câu hoặc cắt mất quan hệ giữa tiêu đề và đoạn văn. Khi dữ liệu đầu vào đã mất cấu trúc, embedding model không thể khôi phục thông tin chưa từng được lưu.
Một ví dụ phổ biến là tài liệu chính sách có cấu trúc:
Section: Enterprise Plan
Data retention: 90 days
Section: Standard Plan
Data retention: 30 days
Nếu chunking chỉ giữ câu “Data retention: 90 days” mà bỏ heading, chunk đó mất điều kiện Enterprise Plan. Khi người dùng hỏi thời gian lưu dữ liệu của Standard Plan, retriever vẫn có thể lấy nhầm chunk 90 ngày vì hai đoạn rất giống nhau về ngữ nghĩa.
Đây không phải lỗi embedding. Đó là lỗi semantic boundary: ranh giới chunk không giữ đủ ngữ cảnh để một đoạn đứng độc lập vẫn có nghĩa.
Một sai lầm khác là chọn chunk size như một hằng số mang tính kinh nghiệm, chẳng hạn cứ 500 token thì cắt. Con số này có thể phù hợp với prose nhưng không phù hợp với API reference, bảng giá, hợp đồng hoặc tài liệu có cấu trúc phân cấp.
Chunk quá nhỏ làm mất quan hệ giữa các câu. Chunk quá lớn lại chứa nhiều chủ đề, khiến embedding trở thành một biểu diễn trung bình của nhiều ý khác nhau. Khi đó query có thể chỉ khớp với một phần nhỏ nhưng vẫn phải cạnh tranh với toàn bộ nội dung của chunk.
Case study: chatbot nội bộ trả sai chính sách nghỉ phép
Giả sử handbook có hai mục “Full-time Employees” và “Contractors”. Cả hai đều chứa câu “Annual leave is subject to manager approval”, nhưng số ngày nghỉ khác nhau. Parser loại heading và chunk từng paragraph riêng lẻ.
Query “Contractor được nghỉ phép bao nhiêu ngày?” retrieval được một chunk nói về full-time vì nội dung hai đoạn gần giống nhau. Reranker cũng không sửa được vì chunk không còn metadata để phân biệt loại nhân sự.
Cách chẩn đoán đúng: lấy chính document đã được index ra xem, không chỉ xem document gốc. Nếu người đọc không xác định được chunk đang nói về đối tượng nào thì retriever cũng không có đủ tín hiệu để làm việc đó.
Cách sửa: prepend heading vào chunk, lưu metadata như employee_type, policy_section và source_version, sau đó áp dụng metadata filtering khi query có điều kiện rõ ràng.
Retrieval trả về kết quả “có vẻ liên quan” nhưng không trả lời được câu hỏi
Đây là lỗi khó nhận ra nhất khi đánh giá bằng mắt. Một document có thể nói đúng chủ đề nhưng không chứa answer-bearing evidence, tức bằng chứng trực tiếp cần để trả lời câu hỏi.
Ví dụ query “Làm thế nào reset API key?” và document retrieved nói “API key dùng để xác thực request”. Đây là semantic match tốt về chủ đề API key nhưng không trả lời thao tác reset.
Dense retrieval đặc biệt dễ gặp trường hợp này vì embedding tối ưu similarity ngữ nghĩa, không đảm bảo quan hệ “document này chứa câu trả lời”. Lexical search cũng có vấn đề ngược lại: nó có thể ưu tiên document chứa nhiều keyword nhưng sai intent.
Vì vậy, đánh giá retrieval chỉ bằng cosine similarity hoặc xem top-1 “có liên quan không” là chưa đủ. Ít nhất cần phân biệt:
- Topical relevance: document có cùng chủ đề.
- Answer relevance: document thực sự chứa thông tin cần để trả lời.
- Coverage: tập retrieved documents có đủ mọi phần của câu hỏi.
Một query multi-hop có thể cần hai document khác nhau. Top-1 chính xác vẫn chưa đủ nếu câu trả lời cuối cần kết hợp nhiều bằng chứng.
Case study: semantic search bỏ sót mã lỗi chính xác
Một hệ thống hỗ trợ kỹ thuật dùng dense retrieval cho tài liệu troubleshooting. Người dùng hỏi: “Lỗi E042 khi đồng bộ dữ liệu là gì?”. Vector search trả về các đoạn mô tả lỗi đồng bộ nói chung, trong khi document chứa chính xác chuỗi “E042” nằm ở vị trí thấp.
Lý do là mã lỗi gần như không mang ý nghĩa ngữ nghĩa đối với embedding model. Với identifier, version, tên hàm, SKU hoặc error code, exact lexical overlap thường quan trọng hơn semantic similarity.
Nếu chuyển hoàn toàn sang BM25, hệ thống lại có thể yếu với query diễn đạt tự nhiên. Cách phù hợp hơn là hybrid retrieval: kết hợp lexical và dense search rồi rerank trên tập ứng viên hợp nhất.
Bài học ở đây không phải hybrid luôn tốt hơn dense. Điều quan trọng là xác định loại tín hiệu chứa thông tin: khái niệm ngữ nghĩa, từ khóa chính xác, metadata hay quan hệ cấu trúc.
Top-k lớn không chữa được retrieval kém
Một phản ứng thường gặp khi recall thấp là tăng top_k từ 5 lên 20 hoặc 50. Điều này đôi khi giúp tìm được document bị bỏ sót, nhưng đồng thời đưa nhiều nhiễu hơn vào context.
RAG không chỉ cần retrieval recall cao mà còn cần context precision: phần lớn token gửi cho generator phải hữu ích. Nếu relevant document đứng thứ 18 và 17 document trước đều nhiễu, retrieval thực tế vẫn có vấn đề.
Có thể hình dung hai pipeline:
| Pipeline | Retrieved | Context cuối |
|---|---|---|
| A | Top-5, 4 chunk liên quan | Ngắn, tập trung |
| B | Top-30, 5 chunk liên quan | 25 chunk nhiễu cạnh tranh attention |
Pipeline B có retrieval recall cao hơn nhưng chưa chắc tạo câu trả lời tốt hơn. Generator phải phân biệt bằng chứng với nội dung gần chủ đề nhưng không phù hợp.
Reranker tồn tại chính để giải quyết một phần vấn đề này. Retriever đầu tiên tối ưu recall trên tập lớn, sau đó reranker đánh giá cặp query-document kỹ hơn để chọn tập nhỏ hơn.
Tuy nhiên, reranking cũng có giới hạn. Nếu relevant document không lọt vào candidate set ban đầu, reranker không thể “cứu” nó. Ngược lại, nếu candidate set quá lớn, latency của reranking tăng đáng kể.
Case study: tăng top-k làm chatbot chính xác kém hơn
Một chatbot sản phẩm thường bỏ sót câu trả lời nên đội kỹ thuật tăng top-k từ 8 lên 40. Recall offline tăng vì relevant chunk xuất hiện thường xuyên hơn trong tập retrieved. Nhưng user-facing answer lại có thêm nhiều trường hợp trộn thông tin giữa các phiên bản sản phẩm.
Phân tích context cho thấy top-40 chứa tài liệu của nhiều version. LLM thấy cùng một cấu hình với ba giá trị khác nhau và chọn một giá trị không thuộc phiên bản mà người dùng hỏi.
Lỗi thực sự không phải top-k quá nhỏ mà là không áp dụng version filtering trước retrieval. Sau khi lọc metadata theo phiên bản và rerank tập ứng viên nhỏ hơn, việc tăng top-k không còn cần thiết.
Context đúng tài liệu nhưng sai cách đóng gói
Ngay cả khi retrieval đã đúng, bước xây context vẫn có thể làm hỏng bằng chứng. Một lỗi phổ biến là concatenate chunk theo score rồi gửi nguyên văn vào prompt mà không kiểm tra trùng lặp, thứ tự, nguồn hoặc xung đột.
Nếu chunk overlap lớn, ba kết quả đầu có thể thực chất là cùng một đoạn văn với vùng giao nhau. Khi đó context 3.000 token có vẻ chứa ba nguồn nhưng thực tế chỉ có một thông tin bị lặp ba lần.
Duplication không chỉ lãng phí token. Nó còn làm một bằng chứng có vẻ “mạnh” hơn vì xuất hiện nhiều lần, dù thực chất đến từ cùng document.
Một vấn đề khác là mất metadata khi xây prompt. Nếu context chỉ chứa text mà không có source, timestamp hoặc version, model không có cơ sở để ưu tiên tài liệu mới hơn khi hai nguồn mâu thuẫn.
Context nên được coi là một cấu trúc dữ liệu, không phải một chuỗi văn bản lớn. Ví dụ:
context = [
{
"text": "The API timeout is 30 seconds.",
"source": "api-guide-v3",
"version": "3.2",
"score": 0.92
}
]
Metadata này có thể được dùng để deduplicate, sort, group theo nguồn hoặc tạo citation. Nếu bỏ metadata quá sớm, nhiều bước xử lý sau đó trở nên khó hoặc không thể thực hiện.
Case study: hai tài liệu cùng đúng nhưng khác thời điểm
Kho dữ liệu chứa tài liệu năm trước ghi “maximum upload size: 50 MB” và tài liệu mới ghi “maximum upload size: 100 MB”. Cả hai đều được retrieval vì query rất rõ.
Nếu prompt không chứa timestamp hoặc version, LLM chỉ thấy hai phát biểu mâu thuẫn. Nó có thể chọn 50 MB, chọn 100 MB hoặc trả lời mơ hồ.
Giải pháp không phải prompt “hãy trả lời chính xác hơn”, mà là xử lý freshness tại retrieval hoặc context layer. Tùy domain, hệ thống có thể lọc document hết hiệu lực, ưu tiên version mới nhất hoặc truyền effective date vào prompt để model có tiêu chí giải quyết xung đột.
Grounding thất bại vì generator được phép suy diễn quá xa bằng chứng
RAG không tự động biến LLM thành một hệ thống grounded. Retrieval chỉ cung cấp dữ liệu; generator vẫn có thể bổ sung kiến thức từ pretraining, suy luận quá mức hoặc lấp khoảng trống bằng một chi tiết nghe hợp lý.
Ví dụ context nói:
“The service supports SSO for enterprise accounts.”
Nếu người dùng hỏi “SSO dùng SAML hay OIDC?”, câu trả lời đúng theo bằng chứng là không đủ thông tin. Một LLM có thể đoán SAML vì đây là lựa chọn phổ biến trong enterprise software. Câu trả lời nghe hợp lý nhưng không grounded.
Để giảm lỗi này, prompt cần phân biệt rõ ba trạng thái:
- Bằng chứng trực tiếp hỗ trợ câu trả lời.
- Bằng chứng chỉ hỗ trợ một phần.
- Không có đủ bằng chứng.
Nhưng instruction chỉ là một lớp bảo vệ. Với hệ thống quan trọng, nên kiểm tra câu trả lời sau generation bằng citation verification hoặc entailment-style checking: mỗi claim quan trọng phải ánh xạ được về một đoạn nguồn hỗ trợ nó.
Một lỗi kiến trúc thường gặp là yêu cầu model tạo citation sau khi đã viết xong câu trả lời nhưng không giữ mapping giữa chunk và source. Lúc này model có thể gán citation “có vẻ đúng” thay vì citation thực sự hỗ trợ claim.
Thiết kế tốt hơn là gán ID cho từng context item trước generation:
[D1] API timeout is 30 seconds.
[D2] Retry is enabled for HTTP 503 responses.
Sau đó yêu cầu mỗi claim có thể kiểm chứng phải tham chiếu ID tương ứng. Citation lúc này là một phần của generation protocol thay vì lớp trang trí thêm ở cuối.
Evaluation sai khiến đội ngũ tối ưu nhầm tầng
Một trong những lỗi tốn kém nhất là chỉ đo chất lượng câu trả lời cuối. Nếu accuracy giảm, bạn biết hệ thống có vấn đề nhưng không biết vấn đề ở retrieval hay generation.
RAG nên được đánh giá theo tầng. Ở retrieval layer, cần biết relevant evidence có được lấy ra không và đứng ở đâu. Ở context layer, cần kiểm tra lượng nhiễu, duplication và coverage. Ở generation layer, cần đo answer correctness và mức độ claim được hỗ trợ bởi context.
Một failure matrix đơn giản có thể giúp chẩn đoán:
| Retrieval | Answer | Khả năng lỗi |
|---|---|---|
| Sai | Sai | Ingestion, query, retrieval hoặc filtering |
| Đúng | Sai | Context construction hoặc generation |
| Sai | Đúng | Model trả lời từ parametric knowledge; grounding có vấn đề |
| Đúng | Đúng | Case thành công, nhưng vẫn cần kiểm tra citation |
Trường hợp thứ ba đặc biệt nguy hiểm. Nếu retrieval sai nhưng model vẫn trả lời đúng nhờ kiến thức đã học, metric answer accuracy vẫn đẹp. Nhưng khi dữ liệu nội bộ thay đổi hoặc câu hỏi nằm ngoài kiến thức pretraining, hệ thống sẽ thất bại.
Case study: benchmark tốt nhưng production thất bại
Một đội xây bộ test từ 100 câu hỏi phổ biến và đạt kết quả tốt. Tuy nhiên, phần lớn câu hỏi là kiến thức chung mà LLM có thể tự trả lời. Retrieval thực tế đóng góp rất ít.
Khi đưa vào production với tài liệu nội bộ, câu trả lời giảm chất lượng mạnh. Benchmark trước đó đã đo năng lực của LLM nhiều hơn năng lực của RAG.
Một evaluation set tốt nên chứa câu hỏi mà câu trả lời phụ thuộc vào corpus, bao gồm phiên bản, chính sách nội bộ, identifier, trường hợp multi-hop và câu hỏi không có câu trả lời. Nhóm cuối rất quan trọng vì một RAG production phải biết khi nào nên từ chối suy đoán.
Cách debug RAG theo thứ tự thay vì thử model ngẫu nhiên
Khi RAG trả lời sai, thay embedding model hoặc LLM ngay thường là cách debug kém hiệu quả. Một quy trình tốt hơn là lần theo đường đi của một query cụ thể.
- Kiểm tra source: corpus có thực sự chứa câu trả lời không?
- Kiểm tra indexed chunk: sau parsing và chunking, bằng chứng còn nguyên nghĩa không?
- Kiểm tra candidate retrieval: relevant chunk có xuất hiện trong top-k không?
- Kiểm tra reranking: nếu có, relevant chunk có bị đẩy xuống sai không?
- Kiểm tra context cuối: generator thực sự nhìn thấy những chunk nào?
- Kiểm tra answer-to-source: từng claim trong câu trả lời được hỗ trợ bởi đoạn nào?
Quy trình này giúp phân biệt một lỗi retrieval với một lỗi generation. Nếu bằng chứng chưa bao giờ vào context, thay prompt generation gần như vô nghĩa. Nếu bằng chứng đã ở vị trí rõ ràng nhưng LLM vẫn trả lời sai, lúc đó mới nên tập trung vào prompt, model hoặc structured generation.
Trong production, nên log tối thiểu query gốc, query sau transformation, document ID được retrieval, score, thứ tự sau reranking, context thực tế gửi cho model và citation của output. Không có trace này, việc debug thường biến thành đoán.
Nguyên tắc quan trọng: đừng tối ưu RAG dựa trên output cuối trước khi biết lỗi nằm ở tầng nào. Một câu trả lời sai có thể bắt nguồn từ parser, chunk boundary, metadata, retrieval, reranking, context packing hoặc generator; mỗi nguyên nhân cần một cách sửa khác nhau.
RAG đáng tin cậy không được tạo ra bằng cách ghép vector database với LLM rồi tăng top-k cho đến khi kết quả có vẻ ổn. Nó cần một pipeline mà mỗi tầng đều quan sát được, đánh giá được và có tiêu chí lỗi rõ ràng.
Khi xây RAG, câu hỏi quan trọng nhất không phải “model nào mạnh nhất?” mà là bằng chứng đã bị mất, biến dạng hay nhiễu ở bước nào?. Trả lời được câu hỏi đó giúp kỹ sư sửa đúng tầng, giảm chi phí thử nghiệm và xây hệ thống retrieval có khả năng kiểm chứng thay vì chỉ tạo ra câu trả lời nghe hợp lý.
Nội dung gốc thuộc tác giả. Vui lòng không đăng lại toàn văn khi chưa được phép, trừ trường hợp pháp luật hoặc giấy phép áp dụng cho phép. Nội dung của bên thứ ba tuân theo quyền và giấy phép tương ứng.
Nguồn gốc: Những lỗi kỹ thuật khi xây RAG