Ngày nay, RAG đôi khi bị mang tiếng xấu: hoặc vì mọi người cho rằng nó hoàn toàn đơn giản (bắt đầu thì đúng, nhưng mở rộng quy mô thì không), hoặc vì họ nghĩ nó đã bị “các hệ thống tác tử” vượt qua (mà trong nhiều trường hợp, chỉ cần xem xét kỹ là chúng nhanh chóng trông rất giống RAG…).
Bài viết này trình bày một vài ví dụ cụ thể để cho thấy cách chúng tôi xử lý một số thách thức thường gặp, cụ thể là:
Xử lý dữ liệu hỗn hợp gồm văn bản và số, cũng như lý do cách RAG đơn giản thất bại: từ khóa trùng lặp còn các con số không mang ý nghĩa ngữ nghĩa.
Vì sao nên thiết kế embedding ưu tiên bản tóm tắt: tạo một bản tóm tắt mô tả ngắn cho mỗi đoạn, sau đó nhúng và truy vấn bản tóm tắt đó.
Cách tạo bản tóm tắt có ngữ cảnh: đưa vào ngữ cảnh của tài liệu gốc để phân biệt các số liệu thống kê có cấu trúc tương tự.
Khi nào nên dựa vào mã và mô hình Pydantic: nếu cần giữ nguyên nội dung, hãy kết hợp mã tùy chỉnh và/hoặc mô hình Pydantic với các lệnh gọi LLM để tăng độ tin cậy.
Kiến thức cơ bản
Các hệ thống RAG hỗ trợ nhiều ứng dụng, từ bot chăm sóc khách hàng đến trợ lý tri thức nội bộ.
Về cơ bản, bạn thường sẽ:
Chia nhỏ tài liệu nguồn
Nhúng từng đoạn vào không gian vectơ
Truy xuất K đoạn phù hợp nhất tại thời điểm truy vấn
Tạo câu trả lời dựa trên các đoạn đó
Các bộ công cụ phổ biến như LangChain, LlamaIndex và Filestore của OpenAI khiến những bước này trở nên gần như đơn giản tuyệt đối. Nhưng trong các quy trình thực tế, bạn sẽ gặp dữ liệu không chỉ gồm văn bản dày đặc, và RAG cơ bản có thể gặp khó khăn. Trong các phần sau, chúng tôi sẽ đưa ra những ví dụ cụ thể về thách thức dữ liệu và từng bước xây dựng giải pháp khi độ phức tạp tăng lên.
Khi dữ liệu không chỉ là văn bản (thực ra rất thường gặp)
Hãy xem xét đoạn dữ liệu sau trong bối cảnh trò chơi:
JSON
Embedding hoạt động nhờ các mối quan hệ đã học giữa những từ dựa trên ý nghĩa ngữ nghĩa và ngữ pháp. Dữ liệu trên là sự kết hợp giữa văn bản và số; khi tách khỏi chính bối cảnh này, các con số không có mối liên hệ nào với những từ ngữ. Vì vậy, có thể nói đoạn dữ liệu này gần như chỉ là tổ hợp của một vài từ mang tính mô tả, theo sau là những con số ngẫu nhiên.
Điều này thực ra sẽ không thành vấn đề nếu đây là loại dữ liệu duy nhất, vì ta vẫn có thể truy xuất dựa trên embedding của một vài từ mô tả hiện có (hoặc đơn giản là dùng text-to-sql). Nhưng nếu đoạn này bị vùi trong rất nhiều đoạn văn bản dày đặc cũng chứa những từ đó thì sao? Ví dụ:
JSON
Giờ hãy tưởng tượng ta muốn truy xuất “What is the attack range with Draconic Ascension?” Rất có thể ta sẽ không truy xuất được đoạn liên quan cần tìm vì nó bị chìm trong nhiễu từ những đoạn khác chứa cùng từ khóa.
Vấn đề cốt lõi là ta không thể phân biệt rõ các đoạn dữ liệu này, dù chúng chứa những loại thông tin khác nhau về cùng một chủ đề. Liệu ta có thể làm phong phú hoặc cải thiện dữ liệu đó không? Tất nhiên là có thể:smile:
Làm phong phú dữ liệu bằng cách tóm tắt—đúng vậy, bạn không đọc nhầm đâu
Thay vì nhúng trực tiếp đoạn dữ liệu, trước tiên ta có thể tạo một bản tóm tắt mô tả nội dung dữ liệu, sau đó nhúng và truy xuất dựa trên bản tóm tắt ấy. Ở bước tạo câu trả lời, ta vẫn sử dụng dữ liệu gốc được liên kết với bản tóm tắt.
Với hai đoạn ví dụ ở trên, ta sẽ tạo những bản tóm tắt như sau:
Số liệu thống kê về tầm đánh, tốc độ và sát thương (mặc định và khi có Draconic Ascension).
Mô tả và thông tin chi tiết về Draconic Ascension, bao gồm điều kiện kích hoạt, hiệu ứng hình ảnh và cốt truyện.
Sau đó, ta cũng bổ sung cho truy vấn để nó “khớp” với bản tóm tắt. Chẳng hạn, ta sẽ biến “What is the attack range with Draconic Ascension?” thành “What is the statistics of attack range with Draconic Ascension?” Điều này đặc biệt quan trọng khi truy vấn đến từ người dùng ngoài lĩnh vực kỹ thuật, được diễn đạt bằng ngôn ngữ ~~“tự do”~~ tự nhiên của con người. Suy cho cùng, họ không cần biết hay quan tâm RAG hoạt động thế nào để tối đa hóa độ chính xác và độ bao phủ.


Đừng tách mọi thứ khỏi ngữ cảnh (một lời khuyên thường cũng đúng trong cuộc sống)
Tiếp theo là tình huống phải xử lý vô số đoạn dữ liệu trông giống hệt nhau, như dưới đây:
Plain Text
Nếu vẫn dùng cách tiếp cận này, hãy tưởng tượng ta hỏi “what is character X’s attack range?” Với những bản tóm tắt vừa tạo, ta sẽ phải chơi trò đoán mò dựa vào may rủi vì chúng cũng trông rất giống nhau. Vậy làm thế nào để phân biệt chúng?
Câu trả lời đơn giản là cung cấp ngữ cảnh. Ta có thể đơn giản đưa tham chiếu đến tài liệu gốc của đoạn vào chính đoạn dữ liệu, chẳng hạn {”character”: “X”} trong trường hợp này. Nhờ vậy, ta có thể truy xuất chính xác dữ liệu phù hợp cho Nhân vật X ngay cả khi cũng có cùng loại dữ liệu cho Nhân vật Y và Z.
Tuy nhiên, cách tiếp cận tốt hơn và có tính khái quát cao hơn là tạo bản tóm tắt có ngữ cảnh cho đoạn. Nói cách khác, thay vì chỉ tạo bản tóm tắt cho riêng đoạn dữ liệu, ta có thể đưa cả tài liệu gốc lẫn đoạn đó vào để tạo bản tóm tắt ngữ cảnh tổng quát, trong đó giải thích đoạn này liên quan thế nào đến tài liệu gốc, chẳng hạn:
Đoạn này cung cấp số liệu thống kê chi tiết về … của Nhân vật X. Đoạn này bổ sung cho toàn bộ tài liệu bằng cách thể hiện thế mạnh về tốc độ đánh của X…
Đoạn này cung cấp số liệu thống kê chi tiết về … của Nhân vật Y. Đoạn này bổ sung cho toàn bộ tài liệu bằng cách thể hiện các chỉ số được tăng cường của Y khi dùng năng lực đặc biệt…
Đoạn này cung cấp số liệu thống kê chi tiết về … của Nhân vật Z. Đoạn này bổ sung cho toàn bộ tài liệu bằng cách cho thấy các chỉ số của Z rất phù hợp với vai trò chống chịu trong các trận đấu đồng đội…
Phương pháp này (được truyền cảm hứng một phần từ Anthropic) có vẻ hơi quá mức cần thiết cho ví dụ trên. Tuy nhiên, nó rất hiệu quả với những đoạn có thể bị hiểu sai khi “tách khỏi ngữ cảnh”, đồng thời cung cấp một cách tiếp cận thống nhất cho mọi đoạn và giữ cho quy trình kỹ thuật gọn gàng.


Khi bạn cần ~~thích kiểm soát~~ thật nghiêm ngặt
Thông thường, chúng tôi nhận dữ liệu ở dạng hoàn chỉnh rồi chia thành các đoạn cho hệ thống RAG. Trong ví dụ này, tình huống hơi khác: dữ liệu đã được chia thành những đoạn không hợp lý—các phần ngẫu nhiên của một đoạn logic mà thực tế cần được ghép lại với nhau. Đoạn logic là phần nội dung vốn dĩ nên nằm cùng nhau, chẳng hạn một tiểu mục trong tài liệu hoặc một đoạn văn mạch lạc.


Ở lần thử đầu tiên, chúng tôi đưa toàn bộ dữ liệu vào một lệnh gọi LLM, yêu cầu nó tự nhóm các phần theo cách phù hợp rồi trả về nội dung đã nhóm. LLM hẳn phải làm việc này khá tốt, đúng không? Vừa đúng vừa không.
Qua nhiều trường hợp khác, chúng tôi nhận thấy LLM thường xử lý qua loa và thiếu tin cậy khi được yêu cầu giữ đầy đủ, chính xác nội dung, đặc biệt khi ngữ cảnh dài. Điều này hoàn toàn dễ hiểu. Nhưng đó lại là điểm bất khả thi với trường hợp sử dụng cụ thể này, vì chúng tôi thực sự cần nội dung chính xác đến từng từ—không tóm tắt, không bỏ sót bất kỳ phần nào của nội dung gốc. Chúng tôi không thể bỏ sót bất kỳ chi tiết nào.
Dĩ nhiên, phần “đúng” là nó đã làm rất tốt việc hiểu ngữ nghĩa và cấu trúc của các đoạn bị chia nhỏ. Miễn là nó không từ chối trích lại chính xác nội dung. Bực thật:/
Vậy làm sao tận dụng thế mạnh của LLM mà tránh những phần nó xử lý thiếu tin cậy? Chúng tôi tìm đến người bạn cũ đáng tin cậy: mã (cụ thể là một hàm Python tùy chỉnh). Cùng một mô hình Pydantic “không thể đơn giản hơn”. Đây là giải pháp:
Lặp qua các phần, đồng thời duy trì một đoạn logic hiện tại
Ở mỗi phần, hỏi LLM: phần này có thuộc đoạn logic hiện tại không? Trả lời có hoặc không (theo mô hình Pydantic).
Nếu có, thêm phần đó vào đoạn; nếu không, xuất nguyên đoạn logic hiện tại vì nó đã hoàn tất, rồi bắt đầu đoạn mới bằng phần này.


Dĩ nhiên, cách này dùng nhiều token hơn một chút so với xử lý toàn bộ nội dung trong một lượt. Nhưng với trường hợp cụ thể mà việc giữ nguyên nội dung là ưu tiên hàng đầu, chi phí bổ sung (không đáng kể) hoàn toàn xứng đáng.
Đây là một giải pháp rất đơn giản nhưng tuân theo nguyên tắc quan trọng: khi cần độ nghiêm ngặt, ta không nên chỉ dựa vào LLM vì xét cho cùng, chúng hoạt động theo xác suất.
Có thể dùng mã/hàm tùy chỉnh và mô hình Pydantic để đạt kết quả có thể dự đoán, đáng tin cậy mà vẫn phát huy năng lực của LLM.
Xây dựng một giải pháp AI tạo sinh vừa là thách thức kỹ thuật, vừa là thách thức về AI. Chúng tôi hy vọng những ví dụ này sẽ truyền cảm hứng để bạn giải quyết các thách thức riêng của mình. Để tìm hiểu thêm về các giải pháp AI tạo sinh ưu tiên kỹ thuật, hãy đọc bài viết của chúng tôi về thiết kế hệ thống tác tử dựa trên bộ định tuyến.