FAQ như RAG: Khi bạn được thiết kế kho ngữ liệu
Bài viết phân tích cách tiếp cận RAG (Retrieval-Augmented Generation) khi kho ngữ liệu là một bộ câu hỏi thường gặp (FAQ) — trường hợp mà thứ tự ưu tiên của toàn bộ pipeline bị đảo ngược. Thay vì xử lý tài liệu phức tạp, FAQ cho phép tối ưu hóa từng thành phần: phân tích cú pháp trở nên tầm thường, truy xuất hoạt động như một bộ nhớ đệm, và kỹ thuật few-shot trở thành một bài toán truy xuất. Tác giả đề xuất kiến trúc ba nhánh (khớp trực tiếp, khớp gần, và bỏ lỡ) giúp giảm chi phí, tăng độ chính xác và cải thiện vòng phản hồi từ chuyên gia.

FAQ như RAG: Khi bạn được thiết kế kho ngữ liệu
Một FAQ (câu hỏi thường gặp) vốn đã là câu trả lời, được viết sẵn và ghép cặp với câu hỏi của nó. Khi khách hàng hỏi "Mức khấu trừ của tôi là bao nhiêu?", câu trả lời đúng chỉ là một phép tra cứu đơn giản — đội hỗ trợ đã viết nó từ nhiều tháng trước, từng chữ một. Nếu bạn đưa FAQ qua cùng một pipeline embed-and-retrieve như một tài liệu PDF thô, bạn sẽ vứt bỏ cấu trúc có sẵn đó, thường trả về kết quả tệ hơn cả một phép tra cứu thông thường. Khi nguồn dữ liệu đã là dạng hỏi-đáp, RAG phải xử lý nó đúng cách như vậy.
Bài viết này là một phần bổ sung trong series Enterprise Document Intelligence, xây dựng một hệ thống RAG doanh nghiệp từ bốn viên gạch nền tảng. FAQ-as-RAG là trường hợp bạn được thiết kế kho ngữ liệu: mọi thành phần đều bị đảo ngược, phân tích cú pháp trở nên tầm thường, truy xuất đóng vai trò bộ nhớ đệm, và few-shot prompting cũng trở thành một bài toán truy xuất.
Vị trí bài viết trong series
Vì sao FAQ là một bài toán khác biệt
Trong phần còn lại của series, kho ngữ liệu là ràng buộc. Một chuyên gia viết hợp đồng từ một thập kỷ trước, PDF được scan ở độ phân giải 200 dpi, số trang không khớp với bản in — hệ thống phải phục hồi ý nghĩa từ tất cả những hỗn loạn đó. Phần lớn kỹ thuật tập trung vào việc khôi phục cấu trúc mà người khác đã đánh mất.
Trong trường hợp FAQ, cấu trúc nằm ở thượng nguồn. Đội ngũ biên tập FAQ chọn schema, độ chi tiết (một cặp hỏi-đáp cho mỗi khái niệm), cách diễn đạt chuẩn cho từng câu hỏi, từ ngữ cho từng câu trả lời, và các thẻ phân loại. Không có gì phải khôi phục vì không có gì bị mất. Hệ quả cho từng thành phần là rõ ràng.
RAG tiêu chuẩn thừa hưởng một kho ngữ liệu, còn FAQ-as-RAG biên soạn một kho ngữ liệu; mỗi viên gạch đều được đơn giản hóa theo một cách cụ thể.
Phân tích cú pháp trở nên tầm thường khi bạn chủ động thiết kế schema
Bước "phân tích cú pháp" trên một FAQ chỉ đơn giản là tải một tệp có cấu trúc. Không có PDF, không phục hồi bố cục, không OCR. Đội ngũ sở hữu FAQ định nghĩa schema một lần và sử dụng nó lâu dài.
class FAQEntry(BaseModel):
qid: str # định danh ổn định để tham chiếu chéo
tag: str # nhóm chủ đề thô (bảo hiểm, yêu cầu bồi thường, loại trừ...)
question: str # cách diễn đạt chuẩn
answer: str # câu trả lời đã biên tập, người dùng nhìn thấy
class FAQCorpus(BaseModel):
entries: list[FAQEntry]
last_updated: date
owner: str
Công sức bỏ ra cho phân tích cú pháp trong các bài viết về parsing của series chính không áp dụng ở đây. Điều thực sự cần thiết là quản lý phiên bản kho ngữ liệu — một mục FAQ thay đổi khi sản phẩm thay đổi, và team cần biết phiên bản nào đã được trả cho người dùng vào ngày nào.
Phân loại câu hỏi như tra cứu bộ nhớ đệm
Nhiệm vụ của bước phân loại câu hỏi trên một FAQ là xác định xem truy vấn của người dùng có khớp với bất kỳ câu hỏi chuẩn nào đã được biên tập hay không. Có ba kết quả có thể xảy ra:
- Khớp trực tiếp: Truy vấn và câu hỏi chuẩn cùng nghĩa. Trả về câu trả lời chuẩn nguyên văn, không cần sinh văn bản.
- Khớp gần: Câu hỏi chuẩn có liên quan nhưng không hoàn toàn giống. Câu trả lời chuẩn là điểm khởi đầu, có thể qua một bước viết lại mỏng bằng LLM.
- Bỏ lỡ: Không có câu hỏi chuẩn nào đủ gần. Truy vấn nằm ngoài FAQ, hoặc là câu hỏi mới mà team nên bổ sung.
def classify_query(
user_query: str,
faq_corpus: FAQCorpus,
*,
direct_threshold: float = 0.92,
adjacent_threshold: float = 0.78,
) -> tuple[str, float, str]:
q_vec = embed(user_query)
sims = cosine_against(q_vec, faq_corpus.canonical_vecs)
top_idx = int(np.argmax(sims))
top_sim = float(sims[top_idx])
if top_sim >= direct_threshold:
outcome = "direct"
elif top_sim >= adjacent_threshold:
outcome = "adjacent"
else:
outcome = "miss"
return faq_corpus.entries[top_idx].qid, top_sim, outcome
Ba nhánh này mang ba mức chi phí rất khác nhau. Một lần khớp trực tiếp chỉ mất vài mili-giây và không tiêu tốn token LLM nào. Một lần khớp gần tốn một lần gọi embedding cộng một lần hoàn thành LLM, với prompt được giới hạn (hệ thống + ba cặp Q-A + truy vấn người dùng, thường dưới 1000 token). Một lần bỏ lỡ là rẻ nhất ở thời điểm chạy nhưng đắt nhất trong vòng đời sản phẩm: mỗi lần bỏ lỡ được ghi log là một phần công việc biên tập mà team FAQ cần thực hiện.
Một lần gọi embedding với vector câu hỏi chuẩn
Truy xuất như bộ nhớ đệm
Một hệ RAG tổng quát truy xuất các đoạn văn bản. Một hệ FAQ truy xuất các cặp Q-A hoàn chỉnh: câu hỏi chuẩn, câu trả lời, và thẻ phân loại. Điều này quan trọng vì cặp Q-A là đơn vị ý nghĩa trong kho ngữ liệu này, đồng thời cũng là đơn vị mà bước sinh văn bản cần trong trường hợp khớp gần (cả câu hỏi lẫn câu trả lời đều được đưa vào prompt).
Vài điểm kỹ thuật cần làm rõ:
- Kho ngữ liệu tĩnh tại thời điểm truy vấn: Embedding trên các câu hỏi chuẩn được tính một lần khi xuất bản FAQ và được lưu vào bộ nhớ đệm. Một truy vấn người dùng chỉ cần một lần gọi embedding và một phép nhân ma trận-vector. Ngân sách độ trễ chỉ vài mili-giây, bất kể kích thước FAQ lên đến vài nghìn mục.
- Quản lý phiên bản bộ nhớ đệm embedding: Khi từ ngữ trong một mục FAQ thay đổi, embedding của nó cũng thay đổi. Khóa bộ nhớ đệm phải bao gồm văn bản câu hỏi chuẩn (hoặc một hash của nó) để không ai có thể để embedding cũ tồn tại sót sau khi sửa.
- Điểm số kết hợp (hybrid scoring) quan trọng hơn trên kho ngữ liệu nhỏ: Với 15 mục, cosine similarity dễ gây mơ hồ — "policy" và "premium" nằm gần nhau trong không gian embedding, nên truy vấn "How much do I pay?" có thể xếp hạng Q07 (pricing) và Q15 (billing) chỉ cách nhau 0.02. Thêm BM25 trên các token chính xác giúp phá vỡ thế bí.
Sinh văn bản và trường hợp cho dynamic few-shot
Few-shot prompting (cung cấp cho LLM vài ví dụ về cặp hỏi-đáp trước truy vấn trực tiếp) thường là một hiện vật kỹ thuật tĩnh: một kỹ sư cao cấp viết ba ví dụ Q-A vào system prompt, và prompt đó đi cùng bản build. Nó hoạt động, nhưng lão hóa tệ: khi FAQ phát triển, các ví dụ tĩnh bị lệch và prompt trở thành một nguồn hướng dẫn cũ kỹ tiềm ẩn.
Cấu hình FAQ-as-RAG làm cho một lựa chọn khác trở nên tự nhiên. Bước truy xuất đã tạo ra top-k cặp Q-A chuẩn cho truy vấn hiện tại. Thay vì các ví dụ được thiết kế tĩnh trong system prompt, user prompt được xây dựng tại thời điểm truy vấn với các cặp truy xuất đó làm ví dụ trong ngữ cảnh. Few-shot trở nên động, được truy xuất cho từng truy vấn, lấy từ FAQ hiện tại. Khi FAQ được cập nhật, các ví dụ tự động cập nhật theo.
def build_prompt(user_query: str, faq_corpus, k: int = 3) -> str:
similar = retrieve_top_k(user_query, faq_corpus, k=k)
examples = "\n\n".join(
f"Q: {row.question}\nA: {row.answer}"
for row in similar
)
return (
"You are a customer support assistant. Answer the user's question, "
"using the example Q-A pairs below as reference.\n\n"
f"--- Examples (retrieved from the live FAQ) ---\n{examples}\n\n"
f"--- User question ---\n{user_query}"
)
Điều này mang lại những lợi ích rõ rệt:
- Kỷ luật phạm vi: LLM không ví dụ sẽ trôi dạt về các câu trả lời kiểu internet chung chung. Ví dụ lấy từ FAQ cụ thể giữ cho giọng điệu, con số và thương hiệu nhất quán với câu trả lời của team.
- Rẻ hơn bạn nghĩ: Prompt tăng thêm vài trăm token mỗi truy vấn. Với hầu hết mô hình chat, chênh lệch chi phí giữa zero-shot và dynamic few-shot nhỏ so với chênh lệch chất lượng.
- Phát hiện mâu thuẫn miễn phí: Khi câu trả lời của LLM bất đồng với các ví dụ truy xuất được, sự khác biệt đó có thể quan sát trong log — một tín hiệu sạch rằng truy vấn đã trượt ra ngoài phạm vi FAQ, hoặc FAQ nội tại có mâu thuẫn cần team giải quyết.
FAQ phát triển từ dòng câu hỏi
Mọi thứ đến đây đều giả định kho FAQ đã sẵn sàng từ ngày đầu. Giả định đó sai. Viết một FAQ đầy đủ trước là công việc thực sự, và làm tốt điều đó nghĩa là dự đoán những câu hỏi chưa được hỏi, bằng từ vựng chưa được dùng. Thiết kế trung thực bắt đầu từ tiền đề ngược lại: FAQ không đầy đủ ngay từ đầu, và hệ thống được xây dựng để thu hẹp khoảng trống khi nó được quan sát.
Bỏ lỡ chuyển đến con người, không phải RAG tổng quát
Bản năng từ phần còn lại của series sẽ là: khi FAQ bỏ lỡ, chuyển sang RAG trên tài liệu sản phẩm. Cơ chế đó hoạt động, nhưng bỏ qua vấn đề thực sự. Ai đó phải quyết định câu trả lời chuẩn cho câu hỏi mà FAQ chưa bao phủ — và người đó là chuyên gia lĩnh vực, không phải LLM đọc tài liệu.
Kiến trúc đề xuất: kết quả bỏ lỡ từ bộ phân loại chuyển truy vấn vào hàng đợi chuyên gia. Một chuyên viên hỗ trợ (cùng người viết các mục hiện có) xem xét câu hỏi, viết câu trả lời chuẩn, và cặp Q-A mới được thêm vào kho FAQ. Lần sau, câu hỏi đó (hoặc câu tương tự) sẽ rơi vào khớp trực tiếp hoặc khớp gần.
Ý nghĩa thực sự của "thường gặp"
Hầu hết dự án FAQ đoán trước câu hỏi nào sẽ thường gặp và biên tập quanh những dự đoán đó. Sau ba tháng vận hành, các dự đoán thường sai: một nửa số mục biên tập chỉ nhận một hoặc hai lượt truy cập, và năm câu hỏi hàng đầu team nhận được chưa bao giờ có trong danh sách.
Một FAQ dẫn dắt bởi dòng truy vấn đảo ngược thứ tự: team bắt đầu với những gì họ có, quan sát mẫu bỏ lỡ nào lặp lại, xếp hạng theo tần suất, và nâng các mẫu tần suất cao lên thành mục chuẩn. Các mục cũ không bao giờ được truy cập sẽ bị loại bỏ.
Chuyên gia trong vòng lặp, không bị thay thế
Ba nơi con người làm công việc mà hệ thống không thể làm:
- Viết câu trả lời chuẩn cho câu hỏi mới: Chuyên gia quyết định lập trường công ty, từ ngữ, con số, ngoại lệ.
- Duyệt các khớp gần ở biên giới: Bộ phân loại trả câu trả lời LLM điều chỉnh cho người dùng, nhưng hàng đợi chuyên gia nhận mẫu để xem xét.
- Loại bỏ các mục đã lỗi thời: Sản phẩm thay đổi, chính sách được cập nhật, quy định dịch chuyển — ai đó phải phát hiện và kéo mục đó ra.
Đây là lập trường trung tâm của series áp dụng cho trường hợp FAQ: hệ thống tồn tại để khuếch đại công việc của chuyên gia, bằng cách tái sử dụng mỗi câu trả lời đã biên tập hàng nghìn lần, bằng cách hiển thị các câu hỏi cần đầu vào chuyên gia, bằng cách giữ các câu trả lời nhất quán giữa người dùng.
Điểm dừng và nơi series chính tiếp tục
Trường hợp FAQ trông đơn giản vì sự đảo ngược. Các vấn đề chuẩn vẫn còn đó, chỉ được đẩy vào một lớp khác.
Quản trị kho ngữ liệu giờ là vấn đề khó: ai có thể sửa một mục, phiên bản được theo dõi thế nào, câu trả lời cũ được loại bỏ ra sao. FAQ không xóa bỏ chi phí — nó di dời chi phí.
Các câu hỏi liệt kê và tổng hợp vẫn áp dụng: "Tất cả các loại trừ là gì?" cần quét toàn bộ kho, không chỉ top-k. Top-k sai về cấu trúc cho việc liệt kê vì nó dừng khi đã có đủ ứng viên, không phải khi tìm thấy mọi thứ.
Đánh giá vẫn theo từng chế độ lỗi: Khớp trực tiếp sai là lỗi kinh điển cho hệ FAQ và vô hình trước một chỉ số recall tổng hợp.
Kết luận
Trường hợp FAQ là hình ảnh của mọi viên gạch trong pipeline khi bạn được thiết kế kho ngữ liệu có chủ đích. Phân tích cú pháp là một thao tác tải Pydantic, phân loại câu hỏi là một ngưỡng tương đồng, truy xuất là một phép nhân ma trận-vector được tính trước, sinh văn bản là một lệnh gọi hàm. Công việc không biến mất; nó được đẩy lên phía trước — vào schema FAQ, kỷ luật biên tập, quản lý phiên bản câu trả lời, và tinh chỉnh ngưỡng giữa khớp trực tiếp và khớp gần.
Hai mẫu hình có thể mở rộng lại cho series chính: lưu vào bộ nhớ đệm những gì kho ngữ liệu đã trả lời (bất kỳ hệ thống nào phục vụ lặp lại cùng một câu hỏi), và dynamic few-shot (truy xuất áp dụng cho prompt). Khi ai đó mô tả trường hợp của họ là "chúng tôi có một danh sách câu hỏi người dùng liên tục hỏi", đó là một FAQ — và lợi thế cấu trúc đó không nên bị vứt bỏ bằng cách đưa mọi thứ qua RAG tổng quát.
Minh họa các chế độ khớp
Nguồn và đọc thêm
Độ tương đồng câu dùng ngưỡng cosine dựa trên Reimers và Gurevych (Sentence-BERT, EMNLP 2019). Việc chọn few-shot dựa trên truy xuất trong mẫu dynamic few-shot dựa trên Liu et al. (What Makes Good In-Context Examples for GPT-3?, ACL 2022). Bối cảnh rộng hơn (augmented language models) nằm trong Mialon et al. (Augmented Language Models, TMLR 2023).
Bài viết thuộc series Enterprise Document Intelligence, với sự đóng góp của Angela Shi và Kezhan Shi trên Towards Data Science. Notebook thực hành kèm theo có trên GitHub: doc-intel/notebooks-vol1.
Bài viết liên quan

Công nghệ
Nộp đơn xin việc lẽ ra nên khó hơn. Thật đấy
25 tháng 8, 2026

Công nghệ
Điện thoại công cộng miễn phí tại Burning Man: Gọi đi khắp thế giới qua Internet
31 tháng 8, 2026

Công nghệ
Apache Iggy - Nền tảng streaming tin nhắn viết bằng Rust chính thức trở thành dự án cấp cao nhất của Apache
31 tháng 8, 2026