Hầu hết các đội ngũ kỹ thuật đều vấp phải cùng một rào cản với retrieval-augmented generation. Họ làm theo các hướng dẫn cơ bản: chia nhỏ tài liệu thành các đoạn (chunk) cố định có độ dài 512 hoặc 1024 token, đẩy chúng qua một mô hình embedding duy nhất, và gọi cơ sở dữ liệu vector bằng phương pháp top-k lookup đơn giản. Trên một bản thuyết trình, điều này trông có vẻ ổn. Nhưng khi đưa vào thực tế (production), nó sẽ đổ vỡ.
Các đoạn cố định không quan tâm đến nội dung. Chúng sẽ sẵn lòng chia cắt một hợp đồng pháp lý ngay giữa câu, khiến các điều khoản trách nhiệm pháp lý bị treo lơ lửng giữa hai đoạn văn bản không liên quan. Chúng sẽ dồn toàn bộ mô tả một API endpoint vào một đoạn văn cồng kềnh đến mức tham số cụ thể mà người dùng hỏi bị chìm nghỉm trong mớ hỗn độn. Và khi việc truy xuất chậm, mỗi mili giây độ trễ (latency) sẽ ảnh hưởng trực tiếp đến trải nghiệm người dùng. Chúng tôi đã học được bài học này một cách cay đắng. Sau đó, chúng tôi đã dỡ bỏ lớp truy xuất của mình và xây dựng lại từ đầu. Chỉ số recall at ten của chúng tôi đã tăng từ 78% lên 95%. Độ trễ không hề tăng lên. Nó đã giảm mạnh.
Vấn đề với RAG kiểu "Copy-Paste"
Stack RAG tiêu chuẩn đã trở thành một kiểu thiết lập mặc định. Các đoạn nhỏ, một mô hình embedding, tìm kiếm vector, thế là xong. Cách tiếp cận đó có thể vượt qua được các buổi demo vì demo thường sử dụng các câu hỏi rõ ràng và tài liệu ngăn nắp. Dữ liệu thực tế (production) thì không bao giờ ngăn nắp như vậy.
Các tài liệu pháp lý có cấu trúc phân cấp. Các phần (sections) chứa các mục con (subsections). Các mục con chứa các điều khoản (clauses). Nếu bạn cắt chúng bằng một bộ đếm token thô sơ, bạn sẽ phá hủy chính những mối quan hệ mà mô hình cần để suy luận. Tài liệu API cũng có cấu trúc, nhưng theo cách khác. Một chữ ký hàm (function signature), các tham số, giá trị trả về và một ví dụ sử dụng tạo thành một đơn vị logic. Nếu ép chúng vào một cửa sổ token cố định, bạn sẽ phải cắt bớt ví dụ hoặc làm đoạn văn trở nên cồng kềnh với các hàm không liên quan. Các phiếu hỗ trợ (support tickets) thì lộn xộn, mang tính hội thoại và đầy rẫy những sự thay đổi chủ đề đột ngột. Các trang Wiki thì lan man và có nhiều tham chiếu chéo. Một chiến lược chia đoạn (chunking strategy) không thể phục vụ tất cả các loại này, vậy mà các đội ngũ vẫn thường xuyên triển khai đúng như vậy. Chúng tôi đã ngừng giả vờ rằng điều đó là khả thi.
Chia đoạn chiến lược: Phù hợp phương pháp với chất liệu
Chúng tôi đã chuyển sang chia đoạn nhận biết nội dung (content-aware chunking). Đối với tài liệu pháp lý, chúng tôi sử dụng chia đoạn đệ quy (recursive chunking) tuân thủ cấu trúc phân cấp của tài liệu. Nó giữ cho các điều khoản nguyên vẹn và bảo toàn mối quan hệ cha-con giữa các phần. Đối với tài liệu API, chúng tôi xây dựng cơ chế chia đoạn nhận biết hàm (function-aware chunking), coi mỗi hàm hoặc endpoint là một ranh giới. Nếu mô tả một tham số quá dài, đoạn văn sẽ mở rộng xung quanh hàm đó thay vì bị giới hạn bởi số lượng token. Đối với các phiếu hỗ trợ, chúng tôi sử dụng chia đoạn ngữ nghĩa (semantic chunking) để phát hiện các ranh giới chủ đề tự nhiên. Khi khách hàng đột ngột chuyển từ khiếu nại thanh toán sang lỗi kỹ thuật, việc chia đoạn sẽ diễn ra ngay tại điểm chuyển đổi đó. Đối với các trang Wiki và cơ sở kiến thức không cấu trúc, chúng tôi sử dụng chia đoạn dựa trên tác nhân (agentic chunking), trong đó một LLM nhẹ sẽ đánh giá văn bản và quyết định nơi nên đặt ranh giới có ý nghĩa. Cách này thiết lập chậm hơn so với chia theo ký tự, nhưng đó là sự khác biệt giữa việc truy xuất hiệu quả và việc truy xuất kiểu "đoán mò".
Truy xuất hỗn hợp: Tại sao chỉ tìm kiếm vector là không đủ
Tìm kiếm vector hiểu được ý nghĩa, nhưng nó có thể bỏ lỡ các kết quả khớp chính xác. Nếu người dùng dán một mã lỗi như ERR_CONNECTION_RESET_0x5F3, độ tương đồng ngữ nghĩa (semantic similarity) có thể xếp nó thấp hơn các đoạn văn chỉ thảo luận chung chung về lỗi mạng. Ngược lại, BM25 tìm thấy các chuỗi ký tự chính xác nhưng lại bỏ lỡ sự liên quan về mặt khái niệm. Bạn cần cả hai.
Chúng tôi chạy tìm kiếm vector và BM25 song song. Sau đó, chúng tôi kết hợp kết quả bằng Reciprocal Rank Fusion (RRF), giúp chuẩn hóa điểm số từ hai không gian tìm kiếm khác nhau mà không cần ép chúng vào cùng một thang đo. Sau khi hợp nhất, chúng tôi gửi các ứng viên hàng đầu qua một bộ xếp hạng lại (cross-encoder reranker). Việc này làm tăng thêm một chút độ trễ, nhưng hiệu quả về độ chính xác (precision) là rất đáng kể. Bộ xếp hạng lại sẽ đọc truy vấn và từng ứng viên cùng lúc, sau đó gán một điểm số liên quan chính xác hơn nhiều so với độ tương đồng cosine của embedding ban đầu. Trong thực tế, sự kết hợp này giúp bắt được các mã lỗi chính xác mà tìm kiếm vector thuần túy thường bỏ lỡ, đồng thời vẫn hiển thị được các bước khắc phục sự cố liên quan về mặt khái niệm mà tìm kiếm từ khóa sẽ bỏ qua.
Mở rộng truy vấn: Chỉnh sửa đầu vào của người dùng trước khi truy cập chỉ mục
Người dùng không viết các truy vấn tìm kiếm hoàn hảo. Họ đặt các câu hỏi đa bước (multi-hop questions) như "tại sao lần triển khai cuối cùng của tôi thất bại và làm thế nào để hoàn tác", điều này đòi hỏi phải tìm thấy hai khối kiến thức riêng biệt và kết nối chúng lại với nhau. Hoặc họ đặt những câu hỏi mơ hồ, khó khớp với chỉ mục (index).
Chúng tôi chuyển đổi các truy vấn trước khi tìm kiếm. Một câu hỏi đa bước (multi-hop) được chia nhỏ thành các câu hỏi phụ. Một ý định mơ hồ được mở rộng thành nhiều truy vấn tìm kiếm cụ thể. Chúng tôi nhận thấy rằng việc mở rộng một truy vấn của người dùng thành năm truy vấn tìm kiếm riêng biệt có thể nâng mức độ truy hồi (recall) từ 78% lên 96%. Điều này không phải là việc prompting LLM mạnh mẽ hơn. Mà là về việc tạo cho hệ thống truy xuất nhiều cơ hội hơn để tìm thấy ngữ cảnh phù hợp. Mỗi truy vấn được tạo ra sẽ nắm bắt một góc độ hoặc thuật ngữ khác nhau, và các kết quả hợp nhất sẽ tạo nên một bức tranh toàn diện.
Tối ưu hóa Bayesian: Ngừng việc đoán mò
Khi bạn đã có nhiều chiến lược phân đoạn (chunking), truy xuất hỗn hợp (hybrid retrieval) và mở rộng truy vấn, bạn sẽ đối mặt với một vấn đề mới. Có quá nhiều "nút vặn" (tham số). Kích thước chunk, tỷ lệ chồng lấp (overlap), trọng số vector so với trọng số BM25, ngưỡng xếp hạng lại (reranking thresholds) và các giá trị top-k đều tương tác với nhau theo những cách phi tuyến tính. Việc tinh chỉnh thủ công trở thành một trò chơi đoán mò.
Chúng tôi đã ngừng đoán mò. Chúng tôi xử lý...
