Bỏ qua để đến nội dung
Search & RAG

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ậtVào → raMục đích
Keyword extractioncâu hỏi dài → ít term đắt nhấtlọc nhiễu hội thoại cho lexical search
Query rewritingcâu hỏi mơ hồ → câu hỏi rõ, đủ ngữ cảnhchữa “còn cái kia thì sao?” trong chat
Query expansiontruy vấn → truy vấn + thêm term đồng nghĩa/liên quanchữ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:

  1. 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ó idf không hề bằng 0 (“app”, “hỗ trợ”, “bên mình” đều là từ nội dung).
  2. 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”.
  3. 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.

Xếp theo lượng “máy móc” cần mang theo:

Phương phápNguyên lýCần gìĐiểm mạnhĐiểm yếu
Raw query (không làm gì)để idf tự lọc0 chi phí, 0 rủi robất lực với nhiễu là từ nội dung
Stopword + tokenizerbỏ từ dừng, giữ định danhstopword list đúng ngôn ngữrẻ, dễ debugkhông biết từ nào quan trọng, chỉ biết từ nào rác
TF-IDFterm hiếm trong corpus, dày trong văn bảnthống kê corpusnền tảng, dễ hiểucần corpus; mù với cụm từ nếu không tự thêm n-gram
RAKEcắ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ụmstopword listrất nhanh, bắt được cụm dàichất lượng sống chết theo stopword list; cụm dài dễ thành rác
YAKEthố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ảnhstopword list (có sẵn 34 thứ tiếng)không cần corpus, không cần modelkhông có tiếng Việt (mục 5); yếu trên văn bản rất ngắn
TextRankdựng đồ thị đồng xuất hiện của từ (lọc qua POS), chạy PageRankPOS taggerkhông cần corpus, kết quả khá ổn địnhcần văn bản đủ dài để đồ thị có nghĩa
KeyBERTembed 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 embeddinghiểu ngữ nghĩa, đa ngữ nếu chọn đúng modelchậm hơn hẳn nhóm thống kê; chất lượng = chất lượng model
LLM extractionprompt: “trích các từ khóa tìm kiếm từ câu hỏi này”, ép JSON outputmột lượt gọi LLMduy nhất xử lý được cả 3 tình huống ở mục 1; kiêm luôn rewrite + expansionthê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ọ).

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âuTiếng AnhTiếng ViệtHệ 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ếtmọi thuật toán đếm “word” đều đếm sai đơn vị nếu chưa tách từ
Word segmentationkhông cầncầ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 / lemmatizationcần (run/runs/running)không tồn tại — tiếng Việt không biến hìnhpipeline EN bê nguyên sang VI thì bước stemmer chỉ tạo nhiễu; bỏ
Stopword listchuẩ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ếtRAKE/YAKE sống bằng stopword list → chất lượng trên VI phụ thuộc thứ bạn tự mang theo
YAKEhỗ 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
TextRankPOS tagger chuẩn có sẵn (spaCy, NLTK)cần POS tagger tiếng Việt (underthesea, VnCoreNLP) chạy sau segmentationchuỗi phụ thuộc dài gấp đôi: segment → POS → graph
KeyBERTmodel mặc định all-MiniLM-L6-v2 dùng ngayphả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ầmchạy được ≠ chạy đúng — không có lỗi nào được ném khi dùng sai model
Dấukhông có vấn đề tương đươngtruy 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 extractionrẻ nhấtvẫn chạy tốt — đây là điểm mạnh nhất của LLM trên VIchi 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.

Tình huốngLàm gì
Truy vấn ngắn, người dùng gõ thẳng vào ô tìm kiếmraw 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à toolkhô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 filterentity extraction (Tầng 6) — việc khác, đừng trộn
Làm giàu metadata phía index, tiếng AnhKeyBERT hoặc YAKE, chạy offline
Làm giàu metadata phía index, tiếng ViệtKeyBERT + 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 VIstopword 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
  • 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ẩu và 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).
Phần 4 — Lý thuyết