$ cat ./blog/gen-ai/rag-du-lieu-ban.md

RAG không cứu được dữ liệu bẩn

Bài viết về lý do RAG không cứu được dữ liệu bẩn và cách chuẩn hoá tài liệu trước khi đưa vào production.

~/diagrams/internal-document-assistant/rag-dataflow.htmltương tác
mở riêng ↗
HìnhĐường đi của một tài liệu từ kho nội bộ đến câu trả lời có trích dẫn. Rê chuột vào từng khối để xem chi tiết, kéo để di chuyển, cuộn để phóng to.
tầng nhận & chuẩn hoátruy hồisinh trả lời & trích dẫn

Khi bắt đầu dự án, tôi nghĩ phần khó là chọn mô hình embedding và tinh chỉnh prompt. Sau ba tháng, nhật ký công việc cho thấy một bức tranh khác: gần như toàn bộ thời gian nằm ở việc làm cho 180.000 trang tài liệu trở nên đọc được bằng máy một cách nhất quán.

Bài viết này ghi lại những gì đã hỏng, cách phát hiện, và những thay đổi giữ lại sau cùng.

01Triệu chứng: câu trả lời đúng ngữ pháp, sai sự thật

Bản demo đầu tiên chạy tốt trên 40 câu hỏi mẫu. Khi mở cho phòng vận hành, tỷ lệ câu trả lời được đánh dấu sai tăng lên 31%. Điểm chung của các lỗi: hệ thống trích đúng tài liệu nhưng lấy nhầm phiên bản, hoặc lấy đúng phiên bản nhưng cắt mất bảng điều kiện ở trang kế tiếp.

  • 34% lỗi đến từ tài liệu trùng lặp nhiều phiên bản, không có trường ngày hiệu lực.
  • 28% từ bảng bị vỡ khi trích xuất PDF, biến thành chuỗi số không có tiêu đề cột.
  • 18% từ việc chia đoạn cắt ngang một điều khoản.
  • Phần còn lại là lỗi mô hình thật sự.

02Chuẩn hoá trước, chia đoạn sau

Thay vì chia đoạn theo số ký tự, chúng tôi dựng một bước trung gian: mỗi tài liệu được đưa về một cấu trúc chung gồm tiêu đề, ngày hiệu lực, phòng ban sở hữu và danh sách khối nội dung có kiểu. Bảng giữ nguyên là bảng, điều khoản giữ nguyên là một khối.

@dataclass
class Block:
    doc_id: str
    kind: Literal["heading", "clause", "table", "para"]
    text: str
    effective_from: date
    department: str

def normalize(doc: RawDoc) -> list[Block]:
    meta = extract_header(doc)          # ngày hiệu lực, phòng ban
    blocks = layout_parse(doc)          # giữ bảng nguyên khối
    return [b.with_meta(meta) for b in blocks if b.text.strip()]

Mỗi khối giữ lại metadata của tài liệu gốc, nên bộ lọc theo phòng ban và ngày hiệu lực chạy được ngay ở tầng truy hồi.

03Kết quả đo được

Cùng một mô hình, cùng một prompt, chỉ khác tầng dữ liệu. Đo trên bộ 320 câu hỏi do phòng vận hành viết.

chỉ sốtrướcsau
trả lời sai (người chấm)31%7%
recall@50,610,89
trích dẫn sai phiên bản22%1,5%
độ trễ p952,4s1,9s

“Mô hình chỉ tốt ngang với đoạn văn bản tệ nhất mà nó được đưa cho.”

04Những gì tôi sẽ làm khác lần sau

  1. Viết bộ eval trong tuần đầu. Không có nó, mọi tranh luận về chất lượng đều là cảm tính.
  2. Kiểm kê dữ liệu trước khi hứa tiến độ. Lấy mẫu 200 tài liệu và đọc bằng mắt.
  3. Đưa trích dẫn ra giao diện từ ngày đầu. Người dùng phát hiện lỗi dữ liệu nhanh hơn bất kỳ dashboard nà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.