Tầng 6b — Keyword extraction: từ câu hỏi hội thoại đến truy vấn BM25
Trang này trả lời một câu hỏi cụ thể: trong hệ RAG, có nên trích từ khóa từ câu hỏi của người dùng để làm truy vấn BM25 không, và nếu có thì bằng gì — đặc biệt khi corpus là tiếng Việt.
Phân biệt ba thứ hay bị gọi chung là “xử lý query” (chi tiết các món kia ở Tầng 6):
| Kỹ thuật | Vào → ra | Mục đích |
|---|---|---|
| Keyword extraction | câu hỏi dài → ít term đắt nhất | lọc nhiễu hội thoại cho lexical search |
| Query rewriting | câu hỏi mơ hồ → câu hỏi rõ, đủ ngữ cảnh | chữa “còn cái kia thì sao?” trong chat |
| Query expansion | truy vấn → truy vấn + thêm term đồng nghĩa/liên quan | chữa vocabulary mismatch cho lexical |
Extraction làm truy vấn hẹp lại, expansion làm truy vấn rộng ra — hai hướng ngược nhau, và câu trả lời đúng thường là làm cả hai trong cùng một lượt LLM (mục 4).
1. Khi nào không cần extract: BM25 đã tự lọc một nửa
Phần tiêu đề “1. Khi nào không cần extract: BM25 đã tự lọc một nửa”Trước khi thêm bất cứ bước nào, nhớ BM25 có sẵn hai cơ chế tự vệ:
idfđè từ phổ biến. “cho”, “mình”, “hỏi”, “là” xuất hiện ở gần mọi tài liệu →idf ≈ 0→ gần như không đóng góp điểm. Với truy vấn ngắn người dùng gõ thẳng (“lỗi E2003 khi thanh toán”), đưa nguyên văn vào BM25 là đủ — extraction không có gì để cải thiện, chỉ có thể làm hỏng (3.4b).- Term thừa không “phạt” tài liệu. BM25 cộng điểm theo term khớp; term trong truy vấn không xuất hiện trong tài liệu chỉ đơn giản là không cộng gì.
Extraction bắt đầu đáng tiền khi truy vấn không phải truy vấn, tức là ba tình huống rất phổ biến trong RAG:
- Câu hỏi hội thoại dài, nhu cầu thông tin bị chôn giữa xã giao: “Chào bạn,
cho mình hỏi chút là bên mình có hỗ trợ cái vụ đăng nhập bằng tài khoản Google
không nhỉ, tại mình thấy app khác có?” — nhu cầu thật là
đăng nhập tài khoản Google, phần còn lại là nhiễu cóidfkhông hề bằng 0 (“app”, “hỗ trợ”, “bên mình” đều là từ nội dung). - Câu lệnh trộn với nhu cầu thông tin: “Viết giúp mình email xin nghỉ phép
dựa trên quy định của công ty” — thứ cần retrieve là
quy định nghỉ phép, không phải “viết email”. - Chat nhiều lượt: truy vấn lượt này vô nghĩa nếu thiếu lượt trước — đây là việc của rewriting, nhưng trong thực tế một lượt LLM làm luôn cả rewrite + extract.
2. Bản đồ phương pháp
Phần tiêu đề “2. Bản đồ phương pháp”Xếp theo lượng “máy móc” cần mang theo:
| Phương pháp | Nguyên lý | Cần gì | Điểm mạnh | Điểm yếu |
|---|---|---|---|---|
| Raw query (không làm gì) | để idf tự lọc | — | 0 chi phí, 0 rủi ro | bất lực với nhiễu là từ nội dung |
| Stopword + tokenizer | bỏ từ dừng, giữ định danh | stopword list đúng ngôn ngữ | rẻ, dễ debug | không biết từ nào quan trọng, chỉ biết từ nào rác |
| TF-IDF | term hiếm trong corpus, dày trong văn bản | thống kê corpus | nền tảng, dễ hiểu | cần corpus; mù với cụm từ nếu không tự thêm n-gram |
| RAKE | cắt văn bản tại stopword/dấu câu → cụm ứng viên, chấm điểm bằng degree/frequency của từ trong cụm | stopword list | rất nhanh, bắt được cụm dài | chất lượng sống chết theo stopword list; cụm dài dễ thành rác |
| YAKE | thống kê một văn bản: vị trí, casing, tần suất, độ phân tán qua câu, quan hệ với ngữ cảnh | stopword list (có sẵn 34 thứ tiếng) | không cần corpus, không cần model | không có tiếng Việt (mục 5); yếu trên văn bản rất ngắn |
| TextRank | dựng đồ thị đồng xuất hiện của từ (lọc qua POS), chạy PageRank | POS tagger | không cần corpus, kết quả khá ổn định | cần văn bản đủ dài để đồ thị có nghĩa |
| KeyBERT | embed văn bản + embed các n-gram ứng viên, lấy n-gram có cosine cao nhất (kèm MMR để đa dạng) | model embedding | hiểu ngữ nghĩa, đa ngữ nếu chọn đúng model | chậm hơn hẳn nhóm thống kê; chất lượng = chất lượng model |
| LLM extraction | prompt: “trích các từ khóa tìm kiếm từ câu hỏi này”, ép JSON output | một lượt gọi LLM | duy nhất xử lý được cả 3 tình huống ở mục 1; kiêm luôn rewrite + expansion | thêm ~200–500 ms và tiền vào mọi truy vấn |
3. Sự thật khó chịu: các thuật toán trên sinh ra cho tài liệu, không phải truy vấn
Phần tiêu đề “3. Sự thật khó chịu: các thuật toán trên sinh ra cho tài liệu, không phải truy vấn”RAKE, YAKE, TextRank, KeyBERT đều được thiết kế và benchmark trên văn bản dài (abstract, bài báo, trang web). Chúng cần thống kê nội tại: tần suất, đồng xuất hiện, độ phân tán qua nhiều câu. Một câu hỏi 10–25 từ không có những thứ đó:
- TextRank trên một câu → đồ thị bé tí, PageRank không có gì để xếp hạng.
- YAKE dựa nặng vào tần suất và vị trí trong văn bản → trên một câu, mọi từ xuất hiện đúng 1 lần ở vị trí duy nhất của nó.
- RAKE thoái hóa thành “cắt câu tại stopword” — tức là chỉ còn tương đương dòng 2 của bảng trên, nhưng khó kiểm soát hơn.
Hệ quả thực dụng, và là khung quyết định của cả trang:
Phía query: chỉ có hai lựa chọn nghiêm túc — raw query (truy vấn ngắn, người dùng gõ thẳng) hoặc LLM (truy vấn hội thoại). Nhóm thuật toán cổ điển nằm lơ lửng ở giữa: quá yếu cho hội thoại, quá thừa cho truy vấn ngắn.
Phía index: đây mới là sân nhà của KeyBERT/YAKE/TextRank — trích từ khóa từ tài liệu lúc index để làm metadata, tag, hoặc field phụ có trọng số cao. Chạy offline, không ăn vào latency, văn bản đủ dài để thống kê có nghĩa.
Anthropic Contextual Retrieval là ví dụ cùng triết lý phía index: thay vì trích từ khóa, dùng LLM thêm ngữ cảnh vào chunk trước khi đánh BM25 index — giảm tỷ lệ retrieval thất bại từ 5,7% xuống 2,9% khi kết hợp contextual BM25 + contextual embedding (số của Anthropic, đo trên benchmark nội bộ của họ).
4. Pattern thực dụng: một lượt LLM cho hybrid search
Phần tiêu đề “4. Pattern thực dụng: một lượt LLM cho hybrid search”Trong RAG có hybrid search (Tầng 2), pattern phổ biến và đáng dùng nhất 2025–2026 là một lượt LLM sinh ra cả hai đầu vào:
Câu hỏi hội thoại (+ lịch sử chat) │ 1 lượt LLM, JSON output ▼{ "cau_hoi_viet_lai": "công ty có hỗ trợ đăng nhập bằng tài khoản Google không", "tu_khoa": ["đăng nhập", "tài khoản Google", "SSO", "OAuth"]} │ │ ▼ ▼ embed → dense search nối chuỗi → BM25 └──────────── RRF ────────────┘Chi tiết khiến pattern này thắng hoặc thua:
- Cho phép LLM thêm đồng nghĩa/viết tắt (“SSO”, “OAuth”) — đây là phần expansion, và là chỗ ăn điểm chính trên lexical: Query2doc đo được BM25 tăng 3–15% trên MS MARCO/TREC DL nhờ mở rộng truy vấn bằng LLM. Nhưng cùng dòng nghiên cứu đó cũng đo được expansion làm hại khi truy vấn mơ hồ hoặc nằm ngoài kiến thức của LLM — nó bịa từ khóa sai hướng và BM25 ngoan ngoãn đi tìm đúng cái sai đó. Đừng bật expansion mù; đo trên golden set trước.
- Ràng số lượng: 3–7 từ khóa. Danh sách 15 từ khóa là expansion trá hình và làm loãng điểm BM25.
- Từ khóa phải nguyên văn được khi có thể: mã lỗi, tên sản phẩm, số phiên bản giữ đúng dạng gốc — đó là những term BM25 ăn điểm nhất (Tầng 0).
- Latency: cảnh báo của Tầng 6 áp nguyên vào đây — thêm một lượt LLM vào p50 của mọi truy vấn phải đổi được cái gì đo được. Model nhỏ (Haiku-class, 8B) là đủ cho việc này; đừng dùng model to.
- Agentic RAG là biến thể miễn phí của pattern này: khi search là một tool và
agent tự viết tham số
query, thì chính LLM đó đã làm keyword extraction — không cần bước riêng, chỉ cần mô tả tool nói rõ “query là danh sách từ khóa cho máy tìm kiếm lexical, không phải câu hỏi tự nhiên” (Tầng 9).
5. Tiếng Anh vs tiếng Việt — khác nhau ở từng khâu
Phần tiêu đề “5. Tiếng Anh vs tiếng Việt — khác nhau ở từng khâu”Cột “hệ quả” là thứ đáng đọc: mỗi khác biệt đổ thẳng vào một quyết định kỹ thuật.
| Khâu | Tiếng Anh | Tiếng Việt | Hệ quả |
|---|---|---|---|
| Ranh giới từ | khoảng trắng ≈ ranh giới từ | khoảng trắng = ranh giới âm tiết; “học máy”, “khách sạn” là một từ viết bằng 2 âm tiết | mọi thuật toán đếm “word” đều đếm sai đơn vị nếu chưa tách từ |
| Word segmentation | không cần | cần segmenter (RDRSegmenter/VnCoreNLP, underthesea, pyvi) hoặc né hẳn bằng âm tiết + bigram (3.15 §5) | thêm một phụ thuộc và một nguồn lỗi với từ mới, tên riêng, thuật ngữ nội bộ |
| Stemming / lemmatization | cần (run/runs/running) | không tồn tại — tiếng Việt không biến hình | pipeline EN bê nguyên sang VI thì bước stemmer chỉ tạo nhiễu; bỏ |
| Stopword list | chuẩn hóa tốt (NLTK ~180 từ, mọi thư viện có sẵn) | chỉ có list cộng đồng (stopwords/vietnamese-stopwords, ~1900 mục) và list phải khớp segmentation — bản thường vs bản _ nối âm tiết | RAKE/YAKE sống bằng stopword list → chất lượng trên VI phụ thuộc thứ bạn tự mang theo |
| YAKE | hỗ trợ chính chủ | không có stopwords_vi.txt trong 34 ngôn ngữ của repo (kiểm tra trực tiếp, 09/2026) → âm thầm rơi về stopword khác, kết quả rác | “language-independent” trong paper nghĩa là cơ chế độc lập ngôn ngữ, không phải đóng gói sẵn cho ngôn ngữ của bạn |
| TextRank | POS tagger chuẩn có sẵn (spaCy, NLTK) | cần POS tagger tiếng Việt (underthesea, VnCoreNLP) chạy sau segmentation | chuỗi phụ thuộc dài gấp đôi: segment → POS → graph |
| KeyBERT | model mặc định all-MiniLM-L6-v2 dùng ngay | phải đổi sang model đa ngữ (paraphrase-multilingual-MiniLM-L12-v2) hoặc model tiếng Việt; mặc định tiếng Anh cho kết quả tệ âm thầm | chạy được ≠ chạy đúng — không có lỗi nào được ném khi dùng sai model |
| Dấu | không có vấn đề tương đương | truy vấn không dấu, teencode là chuyện thường (3.10) | từ khóa trích ra phải có dấu, đúng chính tả — LLM extraction tiện ở chỗ nó sửa luôn (“dang nhap gg” → “đăng nhập Google”) |
| LLM extraction | rẻ nhất | vẫn chạy tốt — đây là điểm mạnh nhất của LLM trên VI | chi phí token cao hơn ~1,3–2× (3.15 §4.1); model nhỏ cần kiểm tra chất lượng tiếng Việt trước khi tin |
Và cái bẫy lớn nhất, đủ quan trọng để tách khỏi bảng:
Từ khóa trích ra phải đi qua đúng tokenizer của BM25 index. Đây là quy tắc “cùng một hàm” của 3.15 §5 nhìn từ phía query. Tiếng Việt có thêm một tầng để sai: nếu bước extraction tách từ (
học_máy) nhưng index đánh theo âm tiết (học,máy) — hoặc ngược lại — thì từ khóa “đúng” không khớp một token nào trong index. Với tiếng Anh, lỗi tương đương (khác stemmer giữa query và index) chỉ làm giảm recall; với tiếng Việt, lệch segmentation làm recall về 0 cho chính những cụm từ đắt nhất.
6. Bảng quyết định
Phần tiêu đề “6. Bảng quyết định”| Tình huống | Làm gì |
|---|---|
| Truy vấn ngắn, người dùng gõ thẳng vào ô tìm kiếm | raw query — đừng extract, idf tự lo |
| RAG chat hội thoại (một hoặc nhiều lượt) | một lượt LLM: rewrite + extract + expansion có kiểm soát (mục 4), model nhỏ |
| Agentic RAG, search là tool | không cần bước riêng — mô tả tool cho rõ, agent tự viết keyword query |
| Cần tách ngày / mã / tên riêng thành filter | entity extraction (Tầng 6) — việc khác, đừng trộn |
| Làm giàu metadata phía index, tiếng Anh | KeyBERT hoặc YAKE, chạy offline |
| Làm giàu metadata phía index, tiếng Việt | KeyBERT + model đa ngữ (nhớ segment trước khi sinh ứng viên n-gram), hoặc LLM offline; YAKE loại vì không có stopword VI |
| Ngân sách bằng 0, vẫn muốn lọc nhiễu query VI | stopword list VI khớp segmentation + giữ nguyên định danh — chấp nhận đây là lọc rác, không phải chọn từ đắt |
7. Bẫy
Phần tiêu đề “7. Bẫy”- Extract xong rồi mới thấy tệ hơn raw query. Hoàn toàn có thể xảy ra, nhất là với truy vấn ngắn: extraction vứt mất term mang tín hiệu (“không”, “lỗi”, số phiên bản). So với baseline raw query trên golden set trước khi bật.
- Từ khóa phủ định. “đăng nhập không cần mật khẩu” — extractor ngây thơ trả
đăng nhập, mật khẩuvà BM25 tìm ra đúng tài liệu ngược ý. Lớp truy vấn D của golden set repo này tồn tại để bắt đúng lỗi này (C.0). - Dùng chung một extraction cho cả dense và lexical. Dense muốn câu đủ ngữ cảnh, lexical muốn term đắt — nén cả hai về một danh sách từ khóa làm hại dense (Tầng 1). Vì thế mục 4 sinh ra hai trường.
- YAKE/RAKE trên tiếng Việt không cấp stopword. Không có lỗi nào được ném — chỉ có kết quả rác trả về rất nhanh.
- Đo chất lượng extraction bằng mắt. “Từ khóa nhìn hợp lý” không phải metric. Metric là nDCG/recall của retrieval trước và sau khi bật extraction, trên golden set (Phần 2).