Hầu hết các hướng dẫn về RAG đều kết thúc ngay tại thời điểm bắt đầu triển khai thực tế (production). Bạn chia tài liệu thành các đoạn (chunk) 512 token, đẩy chúng qua một mô hình embedding duy nhất và gọi cơ sở dữ liệu vector với phương pháp truy xuất top-k đơn giản. Trong một bản demo, điều này trông có vẻ thuyết phục. Hỏi bot về chính sách nghỉ phép của công ty và nó sẽ trả về một đoạn văn mạch lạc. Mọi người đều gật đầu tán thưởng. Đáng tiếc thay, các bản demo thường lừa dối.

Môi trường production sẽ phơi bày mọi lối tắt. Các đoạn chunk cố định sẽ cắt ngang các hợp đồng pháp lý ngay giữa các điều khoản bồi thường. Tài liệu API trở thành những mớ nhiễu chồng chéo làm lu mờ những thông tin quan trọng mà bạn thực sự cần. Độ trễ (latency) tăng dần cho đến khi người dùng từ bỏ truy vấn trước khi câu trả lời kịp xuất hiện. Chúng tôi đã vấp phải rào cản này và buộc phải xây dựng lại từ đầu. Lớp truy xuất (retrieval layer) của chúng tôi đã tiến hóa từ kiểu "tìm kiếm ngữ nghĩa và hy vọng" thành một quy trình (pipeline) được đo lường và kiểm soát chặt chẽ. Kết quả là tỷ lệ recall đạt 95% và độ trễ giảm 40%. Dưới đây là những gì thực sự hiệu quả.

Điều chỉnh chiến lược chia đoạn (chunking) phù hợp với tài liệu

Mặc định 512 token vẫn tồn tại vì nó dễ thực hiện, chứ không phải vì nó chính xác. Các loại tài liệu khác nhau mang ý nghĩa khác nhau, và chiến lược chia đoạn của bạn cần phải phản ánh được điều đó.

Đối với hợp đồng pháp lý, hãy sử dụng phương pháp chia đoạn đệ quy (recursive chunking) để tôn trọng các ranh giới cấu trúc. Ngôn ngữ pháp lý có tính phân cấp. Một điều khoản phụ thuộc vào phần phía trên nó, và việc cắt ngang câu một cách cố định sẽ phá hủy tính logic của một nghĩa vụ. Chia đoạn đệ quy sẽ cố gắng thực hiện việc chia tách dựa trên các dấu phân cách tự nhiên trước—đầu tiên là đoạn văn, sau đó là câu—trước khi áp đặt giới hạn token. Điều này giúp giữ nguyên vẹn các điều khoản bồi thường hoặc trách nhiệm pháp lý.

Đối với tài liệu API, hãy sử dụng phương pháp chia đoạn nhận biết hàm (function-aware chunking). Các nhà phát triển không tìm kiếm các đoạn văn ngẫu nhiên; họ tìm kiếm các endpoint, tham số và chữ ký lỗi (error signatures). Một đoạn chunk nên chứa đầy đủ chữ ký hàm (function signature), mô tả và schema trả về như một đơn vị logic duy nhất. Nếu bạn chia đôi khối đó, hệ thống truy xuất sẽ trả về một nửa ngữ cảnh và mô hình tạo văn bản (generation model) sẽ "ảo giác" (hallucinate) phần còn lại.

Đối với phiếu hỗ trợ (support tickets), hãy dựa vào phương pháp chia đoạn ngữ nghĩa (semantic chunking) theo các lượt hội thoại. Các luồng hỗ trợ thường có tính tuyến tính và lặp lại. Khách hàng lặp lại vấn đề, nhân viên yêu cầu nhật ký (logs), khách hàng đính kèm chúng. Mỗi lượt hội thoại là một đơn vị ngữ nghĩa riêng biệt. Chia đoạn theo lượt hội thoại giúp bảo toàn thông tin ai đã nói gì và khi nào, điều này rất quan trọng khi người dùng hỏi: “Nhân viên đã đề xuất gì vào thứ Ba?”

Đối với wiki nội bộ, hãy thử phương pháp chia đoạn bằng tác nhân (agentic chunking). Hãy đưa một phần nội dung cho LLM và yêu cầu nó quyết định nơi một chủ đề kết thúc và chủ đề khác bắt đầu. Cách này tốn kém hơn ở giai đoạn nạp dữ liệu (ingest time), nhưng các trang wiki thường rất lộn xộn. Các trang chứa những cập nhật không liên quan từ các nhóm khác nhau, và ranh giới do con người định nghĩa hiếm khi mang lại hiệu quả. Việc để mô hình tự vạch ra ranh giới dựa trên sự thay đổi chủ đề sẽ giúp giảm nhiễu một cách đáng kể.

Việc chạy nhiều chiến lược trong cùng một pipeline đòi hỏi phải gắn thẻ (tagging) tài liệu theo loại ngay khi nạp dữ liệu. Một chút kỷ luật về schema nhỏ nhoi đó sẽ mang lại hiệu quả ngay lập tức.

Kết hợp các phương pháp tìm kiếm, đừng chỉ chọn một

Tìm kiếm vector hiểu được ý định, nhưng nó thường thất bại trong việc khớp chính xác (exact matches). Nếu bạn yêu cầu mã lỗi ERR_CONNECTION_REFUSED hoặc một mã SKU cụ thể, các dense embeddings thường trả về các kết quả tương đồng về mặt khái niệm nhưng sai về mặt thực tế. BM25, phương pháp truy xuất từ khóa thưa (sparse retrieval) cổ điển, xử lý các chuỗi chính xác rất tốt nhưng lại bỏ lỡ các sắc thái ngữ nghĩa. Bạn cần cả hai.

Hãy sử dụng truy xuất hỗn hợp (hybrid retrieval). Chạy tìm kiếm vector và BM25 song song. Sau đó kết hợp chúng bằng Reciprocal Rank Fusion (RRF). RRF ưu tiên các tài liệu mà cả hai phương pháp đều đồng ý là có liên quan, đồng thời vẫn làm nổi bật các ứng viên tiềm năng từ cả hai cách tiếp cận. Công thức toán học rất đơn giản và kết quả mang tính ổn định: không có phương pháp truy xuất đơn lẻ nào chiếm ưu thế hoàn toàn trong xếp hạng cuối cùng.

Sau khi hợp nhất (fusion), hãy thêm một bộ xếp hạng lại (reranker) sử dụng cross-encoder. Giai đoạn đầu — kết hợp vector và truy xuất thưa — diễn ra nhanh chóng và bao quát. Sau đó, cross-encoder sẽ chấm điểm từng cặp truy vấn-tài liệu với sự chú ý toàn diện (full attention), nghĩa là nó thực sự đọc ứng viên so với câu hỏi gốc. Đúng vậy, việc này làm tăng độ trễ. Trong trường hợp của chúng tôi, khoảng từ 50 đến 100 mili giây. Nhưng sự gia tăng về độ chính xác (precision) là đủ lớn để sự đánh đổi này trở nên rõ ràng. Bạn không thể bỏ qua bước này nếu quan tâm đến tỷ lệ recall.

Chỉnh sửa truy vấn trước khi chỉnh sửa chỉ mục (index)

Người dùng không viết truy vấn cho công cụ tìm kiếm của bạn. Họ viết cho con người. “Nó không hoạt động” là một truy vấn hỗ trợ phổ biến. Một mô tả tính năng mơ hồ là một tìm kiếm wiki nội bộ thường gặp. Nếu bạn tìm kiếm trong chỉ mục với đầu vào thô đó, bạn sẽ nhận lại kết quả rác.

Hãy biến đổi truy vấn trước khi nó chạm đến bộ truy xuất (retriever).

Sử dụng mở rộng truy vấn (query expansion) để tạo ra nhiều phiên bản khác nhau cho câu hỏi của người dùng. Nếu ai đó nhập “server down,” hệ thống của bạn cũng nên tìm kiếm “service unavailable,” “502 error,” và “connection timeout.” Việc bao quát các biến thể ý định này đã giúp nâng mức recall của chúng tôi từ 78% lên 96%. Đây chỉ là một bước duy nhất và chi phí gần như bằng không so với lợi ích thu được.

Sử dụng phân rã truy vấn (query decomposition) cho các câu hỏi phức tạp. Khi người dùng hỏi một câu như “Làm thế nào để tôi di chuyển từ legacy billing API sang API mới và những thay đổi gây lỗi (breaking changes) nào ảnh hưởng đến các tài khoản doanh nghiệp?,” hãy chia nó thành các câu hỏi phụ. Một câu hỏi phụ nhắm vào các bước di chuyển. Một câu hỏi khác nhắm vào các thay đổi gây lỗi dành riêng cho doanh nghiệp. Mỗi câu hỏi sẽ tác động đến một phần khác nhau của chỉ mục (index). Mô hình ngôn ngữ hạ nguồn sẽ tổng hợp câu trả lời cuối cùng từ các đoạn (chunks) được truy xuất tốt thay vì phải đoán mò trong một cửa sổ ngữ cảnh nhiễu.

Ngừng đoán các siêu tham số (Hyperparameters)

Một khi bạn đã có nhiều chiến lược chia nhỏ (chunking), truy xuất hỗn hợp (hybrid retrieval) và biến đổi truy vấn, bạn sẽ đối mặt với một bài toán tổ hợp. Kích thước chunk, độ chồng lấp (overlap), trọng số hợp nhất (fusion weights), độ sâu của bộ xếp hạng lại (reranker depth) và số lượng mở rộng đều tương tác với nhau. Việc tinh chỉnh riêng lẻ một yếu tố sẽ làm hỏng yếu tố khác. Tìm kiếm lưới (Grid search) trên không gian này là một sự lãng phí và chậm chạp.

Thay vào đó, hãy sử dụng tối ưu hóa Bayesian (Bayesian optimization). Hãy coi đây như một công việc tinh chỉnh máy học. Xác định mục tiêu của bạn một cách rõ ràng: tối đa hóa recall trong khi vẫn giữ độ trễ (latency) dưới một ngưỡng nhất định. Xây dựng một tập dữ liệu vàng (golden dataset) — vài trăm câu hỏi đại diện mà bạn biết chính xác những đoạn (chunks) nào cần được truy xuất. Sau đó, hãy để tìm kiếm Bayesian khám phá không gian cấu hình một cách hiệu quả. Nó xây dựng một mô hình xác suất về những gì hiệu quả và thử nghiệm các vùng đầy triển vọng tiếp theo.

Mọi cấu hình ứng viên đều phải vượt qua tập dữ liệu vàng trước khi đưa lên môi trường staging. Nếu một kích thước chunk mới làm giảm recall hoặc một bộ xếp hạng lại (reranker) nặng hơn khiến bạn vượt quá ngân sách độ trễ, quá trình tối ưu hóa sẽ tự động phát hiện ra. Điều này loại bỏ các ý kiến chủ quan. Bạn sẽ ngừng tranh luận xem 256 hay 512 token là “tốt hơn” và bắt đầu đọc các kết quả thực tế.

Kết quả

Các thay đổi trong pipeline đã cộng hưởng chính xác như chúng tôi kỳ vọng.

  • Recall@10 tăng từ 78% lên 95%.
  • Độ trễ P95 giảm từ 850 ms xuống 320 ms.
  • Tỷ lệ ảo giác (hallucination rate) giảm từ 12% xuống 3%.
  • Chi phí mỗi truy vấn giảm 38%, phần lớn là nhờ việc truy xuất tốt hơn cho phép chúng tôi sử dụng mô hình tạo (generation model) nhỏ hơn và ít token câu lệnh (prompt tokens) hơn.

Việc giảm độ trễ đã khiến một số người trong nhóm ngạc nhiên. Việc thêm các bộ xếp hạng lại (rerankers) và mở rộng truy vấn nghe có vẻ như sẽ làm chậm mọi thứ lại. Nhưng vì chất lượng truy xuất đã được cải thiện, mô hình tạo cần ít câu lệnh hơn, ít sự suy đoán hơn và ít lần thử lại hơn. Truy xuất tốt giúp mọi thứ ở hạ nguồn trở nên rẻ hơn.

Hãy coi Truy xuất như Cơ sở hạ tầng

Truy xuất không phải là một notebook mà bạn chạy một lần rồi quên. Nó là cơ sở hạ tầng, và nó nên được quản lý như mã nguồn. Hãy đánh phiên bản (version) cho các chiến lược chia nhỏ của bạn. Khi đội ngũ pháp lý phát hành một mẫu hợp đồng mới, hãy kiểm tra bộ chia đệ quy (recursive splitter) của bạn trước khi đưa vào production. Duy trì tập dữ liệu vàng của bạn như những tài liệu sống, chứ không phải một tệp CSV tĩnh từ quý trước. Tự động hóa các đánh giá của bạn trong CI để một pull request sửa đổi mô hình nhúng (embedding model) hoặc trọng số hợp nhất sẽ nhận được nhận xét về các con số recall và độ trễ trước khi có con người xem xét.

Người dùng của bạn sẽ không bao giờ hỏi bạn đang chạy mô hình nhúng nào. Họ sẽ không quan tâm đến phương pháp heuristic chia nhỏ hay kiến trúc bộ xếp hạng lại của bạn. Họ chỉ quan tâm liệu câu trả lời có chính xác không, nó có đến nhanh không và liệu họ có thể tin tưởng nó không. Hãy xây dựng một pipeline tạo dựng được niềm tin đó, đo lường nó một cách trung thực và ngừng coi truy xuất như một việc làm thêm.

Nguồn: Optimizing RAG At Scale
Tham gia thảo luận: GyaanSetu AI Community