AI & GenAI

[Tech Blog #31 | AI & GenAI] Advanced RAG Thực Chiến: Nâng Cấp Hệ Thống Với Chunking Chiến Lược & Kỹ Thuật Reranking

0 views
[Tech Blog #31 | AI & GenAI] Advanced RAG Thực Chiến: Nâng Cấp Hệ Thống Với Chunking Chiến Lược & Kỹ Thuật Reranking

1. Đặt vấn đề: Tại sao 90% hệ thống RAG cơ bản đều "thất bại" ở Production?

Ở [Tech Blog #11], chúng ta đã tự tay xây dựng một ứng dụng RAG mini (Naive RAG): Cắt nhỏ tài liệu →\rightarrow Tạo Vector Embedding →\rightarrow Tìm kiếm bằng Cosine Similarity →\rightarrow Nạp vào LLM để trả lời.

Quy trình đó hoạt động rất hoàn hảo trong môi trường thử nghiệm với 5–10 trang tài liệu mẫu. Thế nhưng, khi đưa vào môi trường doanh nghiệp thực tế với hàng nghìn trang hợp đồng pháp lý, tài liệu kỹ thuật phức tạp, hệ thống bắt đầu bộc lộ những "căn bệnh" nan y:

  1. Mất đứt ngữ cảnh (Context Fragmentation): Đoạn văn bản bị cắt ngang xương ở giữa một câu điều kiện quan trọng (ví dụ: "Khách hàng được hoàn tiền 100%..." bị cắt riêng với vế sau "...chỉ khi sản phẩm còn nguyên tem mác trong 3 ngày").
  2. "Lạc trôi" tài liệu đúng (Retrieval Dilution): Khi tìm kiếm bằng Cosine Similarity, tài liệu thực sự chứa câu trả lời lại bị xếp ở vị trí thứ 8 hoặc thứ 10, trong khi các đoạn văn có nhiều từ khóa trùng lặp nhưng vô nghĩa lại chễm chệ ở Top 1, Top 2!
  3. Hiện tượng "Mất trí nhớ ở giữa" (Lost in the Middle): Khi bạn nhồi 5–10 đoạn văn bản vào Prompt, LLM chỉ tập trung đọc đoạn đầu tiên và đoạn cuối cùng, hoàn toàn phớt lờ đoạn văn quan trọng nhất nằm kẹp ở chính giữa.

Để giải quyết triệt để vấn đề này, các kỹ sư AI sử dụng kiến trúc Advanced RAG (RAG nâng cao) với hai vũ khí chủ lực: Chunking Chiến Lược và Reranking (Xếp hạng lại hai giai đoạn).


2. Chunking Chiến Lược: Đừng cắt văn bản một cách "mù quáng"

Trong Naive RAG, người ta thường cắt văn bản theo số lượng ký tự cố định (Fixed-size: ví dụ cứ 500 ký tự là cắt một nhát). Cách làm này rất cẩu thả vì máy tính có thể chém đôi một từ, một công thức, hoặc một bảng biểu.

[Fixed-size Chunking]:   Cắt cứng nhắc 500 ký tự ──► Dễ đứt gãy ý nghĩa câu
[Semantic Chunking]:     Cắt theo ranh giới câu, đoạn văn, hoặc tiêu đề Markdown ──► Giữ trọn vẹn tư tưởng

Hai nguyên tắc vàng khi Chunking:

  1. Cắt theo cấu trúc ngữ nghĩa (Semantic Boundary): Luôn ưu tiên cắt tại dấu chấm câu, dấu xuống dòng (\n\n), hoặc theo các thẻ tiêu đề Markdown (#, ##, ###). Mỗi chunk phải là một ý tưởng hoàn chỉnh.
  2. Chunk Overlap (Phần gối đầu 10–20%): Luôn để đoạn văn bản sau lặp lại khoảng 50–100 từ cuối cùng của đoạn văn bản trước. Phần gối đầu này đóng vai trò như "chất keo" liên kết ngữ cảnh, đảm bảo không có câu điều kiện nào bị cắt đứt lìa.

3. Kỹ thuật Reranking: Cuộc cách mạng Bi-Encoder vs. Cross-Encoder

Tại sao tìm kiếm bằng Vector Embedding (như đã học ở [Tech Blog #07]) lại hay lấy nhầm tài liệu?
Bởi vì các mô hình Embedding là Bi-Encoder: Chúng mã hóa câu hỏi và đoạn văn bản một cách độc lập và riêng biệt, không hề có sự tương tác ngữ nghĩa qua lại giữa từng từ của câu hỏi và từng từ của tài liệu.

Để khắc phục, chúng ta sử dụng quy trình Truy xuất hai giai đoạn (Two-stage Retrieval) kết hợp với Cross-Encoder (Reranker):

[Kho dữ liệu: 100.000 Chunks]
             │
             ▼ Giai đoạn 1: Bi-Encoder (Vector Search) ──> Cực nhanh (Vài mili-giây)
[Lấy ra Top 20 ứng viên tiềm năng]
             │
             ▼ Giai đoạn 2: Cross-Encoder (Reranker Model) ──> Đọc sâu & Chấm điểm tương quan
[Sắp xếp lại thứ tự: Kéo đoạn chứa đáp án thực sự từ Top 8 lên thẳng Top 1]
             │
             ▼ Lấy duy nhất Top 3 đoạn tinh hoa nhất nạp vào LLM
[Câu trả lời chuẩn xác 100%]
  • Bi-Encoder (Vector Search): Tốc độ siêu thanh, quét hàng triệu vector trong chớp mắt nhưng độ chính xác ở mức khá. Đóng vai trò là "Vòng sơ loại" nhặt ra Top 20 ứng viên.
  • Cross-Encoder (Reranker): Ghép đôi trực tiếp (Câu hỏi + Đoạn văn) để các lớp Attention của mạng nơ-ron phân tích sâu sự liên quan giữa từng chữ. Chậm hơn nhưng độ chính xác cực kỳ cao. Đóng vai trò là "Vòng chung kết" chấm điểm và xếp hạng lại Top 20 đó.

4. Bảng so sánh: Bi-Encoder (Vector Search) vs. Cross-Encoder (Reranker)

Tiêu chíBi-Encoder (Embedding Models)Cross-Encoder (Reranker Models)
Cơ chế hoạt độngMã hóa câu hỏi và tài liệu thành 2 vector riêng biệt rồi tính Cosine Similarity.Đưa cả câu hỏi và tài liệu vào chung 1 mạng nơ-ron để chấm điểm tương quan trực tiếp.
Tốc độ xử lý🚀 Siêu nhanh (Sub-second) trên hàng triệu tài liệu nhờ Vector Database.🐢 Chậm hơn nhiều, chỉ xử lý hiệu quả với số lượng nhỏ (10–30 tài liệu).
Độ hiểu ngữ nghĩa sâuKhá tốt (nhận diện được các chủ đề rộng).🟢 Xuất sắc, bắt trọn các câu phủ định, sắc thái chi tiết và logic phức tạp.
Vai trò trong RAGGiai đoạn 1 (Retrieval): Thu hẹp từ 100.000 tài liệu xuống Top 20.Giai đoạn 2 (Reranking): Sắp xếp lại Top 20 để chọn ra Top 3 chuẩn nhất.

5. Thực hành thực chiến: Xây dựng Pipeline Reranking bằng Python

Đoạn mã Python dưới đây mô phỏng trường hợp kinh điển: Vector Search lấy tài liệu chuẩn xếp ở vị trí áp chót, nhưng sau khi qua mô hình Reranker, tài liệu chính xác được kéo thẳng lên Top 1:

import numpy as np

# Giả lập hoặc sử dụng thư viện sentence-transformers thực tế
try:
    from sentence_transformers import CrossEncoder, SentenceTransformer
    
    # 1. Tải mô hình Embedding (Bi-Encoder) và mô hình Reranker (Cross-Encoder)
    embed_model = SentenceTransformer("all-MiniLM-L6-v2")
    reranker_model = CrossEncoder("cross-encoder/ms-marco-MiniLM-L-6-v2")
    USE_REAL = True
    print("Đã tải thành công các mô hình AI thực tế!")
except ImportError:
    USE_REAL = False
    print("Môi trường chưa cài sentence-transformers, sử dụng cơ chế chấm điểm minh họa.")

# Tập tài liệu chính sách bảo hiểm công ty
documents = [
    {"id": "DOC_1", "text": "Chính sách bảo hiểm y tế doanh nghiệp: Hỗ trợ 80% chi phí khám chữa bệnh nội trú tại các bệnh viện công lập."},
    {"id": "DOC_2", "text": "Quy định về thời gian nghỉ thai sản và chế độ thai sản dành cho lao động nữ theo luật hiện hành."},
    {"id": "DOC_3", "text": "Hạn mức bảo hiểm nha khoa: Nhân viên được thanh toán tối đa 3.000.000 VNĐ tiền khám răng và cạo vôi răng mỗi năm."},
    {"id": "DOC_4", "text": "Thủ tục thanh toán chi phí công tác: Yêu cầu nộp hóa đơn VAT điện tử hợp lệ trong vòng 7 ngày làm việc."}
]

user_query = "Tôi đi nhổ răng khôn thì công ty có chi trả tiền bảo hiểm không và hạn mức bao nhiêu?"

print(f"\n[CÂU HỎI]: '{user_query}'\n")

if USE_REAL:
    # ==============================================================================
    # GIAI ĐOẠN 1: VECTOR SEARCH (BI-ENCODER) - VÒNG SƠ LOẠI
    # ==============================================================================
    query_vec = embed_model.encode(user_query)
    doc_vecs = embed_model.encode([d["text"] for d in documents])
    
    # Tính Cosine Similarity
    scores = np.dot(doc_vecs, query_vec) / (np.linalg.norm(doc_vecs, axis=1) * np.linalg.norm(query_vec))
    initial_ranking = sorted(zip(documents, scores), key=lambda x: x[1], reverse=True)
    
    print("--- 1. KẾT QUẢ GIAI ĐOẠN 1 (VECTOR SEARCH THUẦN TÚY) ---")
    for rank, (doc, score) in enumerate(initial_ranking, 1):
        print(f"Top {rank} [Score: {score:.4f}]: {doc['text']}")

    # ==============================================================================
    # GIAI ĐOẠN 2: RERANKING (CROSS-ENCODER) - VÒNG CHUNG KẾT
    # ==============================================================================
    # Tạo các cặp (Query, Document) để Cross-Encoder chấm điểm tương quan thực sự
    pairs = [[user_query, doc["text"]] for doc, _ in initial_ranking]
    rerank_scores = reranker_model.predict(pairs)
    
    final_ranking = sorted(zip(initial_ranking, rerank_scores), key=lambda x: x[1], reverse=True)
    
    print("\n--- 2. KẾT QUẢ GIAI ĐOẠN 2 (SAU KHI RERANKING BẰNG CROSS-ENCODER) ---")
    for rank, ((doc, _), score) in enumerate(final_ranking, 1):
        print(f"Top {rank} [Rerank Score: {score:.4f}]: [{doc['id']}] {doc['text']}")
        
    print("\n===> Kết luận: Đoạn văn bản DOC_3 (Nha khoa/Răng) đã được Reranker đưa lên vị trí số 1 tuyệt đối để nạp vào LLM!")

6. Câu hỏi phỏng vấn thực tế (Interview Q&A)

Câu 1 (Mức độ: Entry / Junior)

Câu hỏi: "Chunk Overlap trong RAG là gì? Nếu đặt Chunk Overlap bằng 0 thì hệ thống sẽ gặp rủi ro gì?"

Gợi ý trả lời:
"Chunk Overlap là phần văn bản gối đầu lặp lại giữa hai đoạn văn liền kề (thường chiếm từ 10% đến 20% kích thước chunk).
Nếu đặt Chunk Overlap bằng 0, chúng ta sẽ gặp rủi ro đứt gãy ngữ cảnh nghiêm trọng (Context Severing) tại các điểm phân ranh giới.
Ví dụ: Câu văn gốc là: 'Khách hàng được đổi trả miễn phí, VỚI ĐIỀU KIỆN bao bì còn nguyên seal'. Nếu vết cắt vô tình rơi đúng vào chữ 'miễn phí', chunk 1 sẽ chỉ chứa câu 'được đổi trả miễn phí' (thiếu điều kiện ràng buộc), trong khi chunk 2 lại bắt đầu bằng 'với điều kiện bao bì còn nguyên seal' (không rõ chủ ngữ đang nói về việc gì). Khi đó, hệ thống RAG sẽ trả lời sai lệch hoàn toàn. Chunk Overlap đảm bảo ý nghĩa toàn vẹn của câu luôn được lưu giữ ở ít nhất một trong hai chunk."

Câu 2 (Mức độ: Junior / Mid-level)

Câu hỏi: "Tại sao chúng ta không dùng luôn mô hình Cross-Encoder để tìm kiếm trên toàn bộ hàng triệu tài liệu trong kho dữ liệu ngay từ đầu mà lại phải qua bước Vector Search (Bi-Encoder) làm gì cho tốn công?"

Gợi ý trả lời:
*"Lý do cốt lõi là sự đánh đổi về độ phức tạp tính toán và thời gian phản hồi (Computational Latency):

  • Với Bi-Encoder, chúng ta có thể tính toán sẵn (Pre-compute) vector cho hàng triệu tài liệu và lưu vào Vector Database. Khi người dùng đặt câu hỏi, hệ thống chỉ cần tính vector câu hỏi 1 lần và dùng các thuật toán chỉ mục xấp xỉ (như HNSW hay IVF) để so khớp cực nhanh trong vòng vài mili-giây.
  • Với Cross-Encoder, chúng ta không thể tính toán trước. Nó đòi hỏi phải ghép cặp câu hỏi của người dùng với TỪNG TÀI LIỆU một và cho chạy qua toàn bộ các tầng mạng nơ-ron sâu. Nếu có 1.000.000 tài liệu, mô hình sẽ phải tính toán 1.000.000 lần cho MỖI MỘT LƯỢT HỎI, khiến thời gian phản hồi có thể mất hàng chục phút và làm sập hoàn toàn hệ thống máy chủ.
    Do đó, kiến trúc Two-stage Retrieval là tối ưu nhất: Dùng Bi-Encoder để lọc nhanh 100.000 tài liệu lấy ra Top 20, sau đó chỉ dùng Cross-Encoder để đọc kỹ và chấm điểm lại 20 ứng viên đó."*

7. Key Takeaways (Ghi nhớ nhanh)

  1. Naive RAG chỉ là đồ chơi thử nghiệm; Advanced RAG mới là giải pháp cho Production.
  2. Luôn có Chunk Overlap (10–20%): Không bao giờ cắt văn bản với overlap = 0 để tránh đứt gãy câu điều kiện và ngữ cảnh.
  3. Quy trình truy xuất 2 giai đoạn (Two-stage Retrieval):
    • Vòng 1 (Bi-Encoder / Vector Search): Quét nhanh trên diện rộng để lấy ra Top 20–30 đoạn.
    • Vòng 2 (Cross-Encoder / Reranker): Đọc kỹ và xếp hạng lại để chọn ra Top 3 đoạn tinh hoa nhất nạp vào LLM.
  4. Giải quyết triệt để lỗi "Lost in the Middle": Reranker đảm bảo những thông tin sống còn nhất luôn nằm ở vị trí số 1 trong Prompt của mô hình.
0Claps