Chỉ tìm kiếm vector thôi thì chưa phải một hệ thống truy xuất

Bản mẫu RAG nào cũng demo rất đẹp. Bạn nhúng vài trăm tài liệu, hỏi một câu mà tài liệu hiển nhiên trả lời được, và nhận về một phản hồi tốt. Rồi nó ra mắt người dùng thật, có ai đó tìm INV-2024-8871, và hệ thống tự tin trả về bốn đoạn văn nói về chính sách xuất hóa đơn.

Thất bại đó không phải lỗi của mô hình. Mô hình sinh văn bản chưa từng có cơ hội nào — nó bị đưa cho ngữ cảnh sai. Gần như mọi thứ phân biệt một bản mẫu với một hệ thống người ta tin dùng đều nằm ở tầng truy xuất, trước khi LLM tham gia vào.

Vì sao tìm kiếm vector thuần túy thất bại

Embedding nắm bắt ngữ nghĩa. Đó là toàn bộ mục đích của nó, và cũng chính là vấn đề: về bản chất thiết kế, chúng làm mất mát thông tin về token chính xác.

INV-2024-8871INV-2024-8872 được nhúng thành hai vector gần như y hệt nhau. Hai điều khoản hợp đồng chỉ khác nhau ở một con số ngưỡng cũng vậy. Độ tương đồng ngữ nghĩa không có cách nào diễn đạt được ý “đúng chuỗi ký tự này, chứ không phải chuỗi lệch một chữ số”.

Các thất bại tập trung ở những chỗ có thể đoán trước:

  • Mã định danh — số hóa đơn, số hồ sơ, mã linh kiện, mã bệnh nhân
  • Danh từ riêng hiếm gặp — tên một khách hàng chỉ xuất hiện hai lần trong toàn bộ kho tài liệu
  • Thuật ngữ và từ viết tắt mà mô hình embedding chưa từng thấy khi huấn luyện
  • Phủ định và ngưỡng số — “không được chi trả”, “dưới 30 ngày”

Đây đúng là những truy vấn người ta gõ vào khi đang cố hoàn thành công việc thật. Không ai đặt câu hỏi chủ đề mở cho một hệ thống tài liệu; họ hỏi một thứ cụ thể nằm ở đâu.

Vì sao chỉ tìm kiếm từ khóa cũng thất bại

BM25 xử lý hoàn hảo mọi trường hợp bên trên. Nó cũng bỏ lỡ hoàn toàn câu “làm sao thanh toán bữa tối với khách hàng?” khi chính sách viết là “hoàn tiền chi phí tiếp khách kinh doanh”, bởi hai bên không có từ nào chung.

Cả hai phương pháp đều thất bại. Nhưng chúng thất bại trên những truy vấn khác nhau. Đó mới là phần hữu ích.

Truy xuất kết hợp, rồi xếp hạng lại

Kiến trúc đã đứng vững với tôi:

1. Chạy cả hai bộ truy xuất. Tìm kiếm vector dense và BM25, độc lập với nhau, trên cùng tập đoạn văn. Lấy khoảng 50 kết quả đầu từ mỗi bên.

2. Hợp nhất bằng Reciprocal Rank Fusion. RRF chấm điểm mỗi tài liệu theo công thức Σ 1/(k + thứ_hạng) trên cả hai danh sách kết quả. Ưu điểm của nó là chỉ dùng thứ hạng, không dùng điểm số — nhờ vậy bạn không bao giờ phải chuẩn hóa độ tương đồng cosine so với điểm BM25, một phép so sánh vốn không có lời giải hợp lý nào. Tài liệu được cả hai bộ truy xuất xếp cao sẽ vươn lên đầu; tài liệu chỉ một bên xếp hạng nhất vẫn xuất hiện.

3. Xếp hạng lại bằng cross-encoder. Hai giai đoạn đầu tối ưu cho độ bao phủ — đưa được đoạn văn đúng vào đâu đó trong top 50. Cross-encoder đọc truy vấn và từng ứng viên cùng nhau thay vì so sánh các vector đã tính sẵn, điều này khiến nó chính xác hơn hẳn và cũng chậm đến mức không thể chạy trên toàn bộ kho tài liệu. Trên 50 ứng viên thì nó đủ nhanh, và đó là thứ cho phép bạn chỉ chuyển 5–8 đoạn văn cho mô hình thay vì 20.

4. Rồi mới sinh câu trả lời. Với ít đoạn văn hơn nhưng tốt hơn, bước sinh văn bản trở nên dễ hơn, còn câu trả lời thì ngắn hơn và bám sát nguồn hơn.

Giai đoạn 3 là giai đoạn người ta hay bỏ qua, và cũng là giai đoạn mang lại nhiều chất lượng nhất trên mỗi đơn vị công sức trong toàn bộ quy trình.

Cách chia đoạn quan trọng hơn mô hình embedding

Các đội dành hàng tuần để so sánh mô hình embedding và mười phút cho việc chia đoạn. Tỉ lệ đó nên được đảo ngược.

Một đoạn văn vừa là thứ được truy xuất, vừa là thứ mô hình nhìn thấy. Cắt sai thì không bộ truy xuất nào cứu bạn được: tách một bảng khỏi dòng tiêu đề của nó thì các con số trở nên vô nghĩa; tách một điều khoản khỏi phần định nghĩa thuật ngữ thì mô hình buộc phải đoán. Hãy tôn trọng cấu trúc tài liệu — mục, tiêu đề, ranh giới bảng — và giữ đủ độ chồng lấn để một khái niệm nằm vắt qua ranh giới vẫn còn nguyên vẹn trong ít nhất một đoạn.

Hãy gắn tiêu đề tài liệu và tiêu đề mục vào phần văn bản của mỗi đoạn. Đó là thay đổi một dòng giúp cải thiện cả hai bộ truy xuất, vì nó cho BM25 thêm từ khóa và cho mô hình embedding thêm ngữ cảnh mà riêng phần thân đoạn không mang theo.

Hãy xây bộ đánh giá trước

Bạn không thể tinh chỉnh bất kỳ phần nào ở trên bằng cảm tính. Hai mươi đến năm mươi câu hỏi thật kèm tài liệu nguồn đã biết là đúng thì đủ để bắt đầu, và thu thập chúng chỉ mất một buổi chiều.

Hãy đo truy xuất tách biệt với sinh văn bản — recall@k cho bộ truy xuất, mức bám nguồn cho câu trả lời. Khi chất lượng đi xuống, bạn cần biết nửa nào đã thay đổi. Không có sự tách bạch đó, bạn sẽ loay hoay sửa prompt để chữa một lỗi thật ra nằm ở khâu chia đoạn.

Bộ đánh giá cũng là cách trung thực duy nhất để trả lời “thay đổi vừa rồi có giúp ích không?” Mọi cải tiến ở trên nghe đều hiển nhiên là đúng. Một số trong đó sẽ không giúp gì trên kho tài liệu của bạn.

Bản rút gọn

Hãy chạy truy xuất dense và sparse cùng lúc, hợp nhất theo thứ hạng, xếp hạng lại những ứng viên sống sót bằng cross-encoder, và dành thời gian tinh chỉnh cho ranh giới đoạn văn cùng bộ đánh giá thay vì cho bảng xếp hạng mô hình embedding.

Chẳng có gì trong số này là kỳ lạ cả. Nó chỉ là phần không nhét vừa vào một bản demo.

Bài viết khác