
Hybrid Search — Combining Dense + Sparse Retrieval
1. Dense Vector Search (tìm theo vector ngữ nghĩa)
Văn bản được embedding thành vector (ví dụ Titan Embeddings, Cohere Embed), rồi so sánh độ tương đồng cosine. Ví dụ nó "hiểu nghĩa" xe hơi khớp với automobile, ô tô. Điểm yếu: các token hiếm — mã sản phẩm, SKU, số hợp đồng — bị nén vào vector và mất tính đặc trưng, nên tìm mã chính xác thường trượt.
2. Sparse Search / BM25 (tìm theo từ khóa)
Đây là keyword search truyền thống, tính điểm dựa trên tần suất từ (TF-IDF cải tiến). Rất mạnh với chuỗi chính xác như AWS-19823. Điểm yếu: vocabulary gap — user gõ nghỉ phép mà tài liệu viết ngày phép hàng năm thì không match.
3. Hybrid + RRF (Reciprocal Rank Fusion)
Chạy song song cả hai, rồi hợp nhất kết quả theo thứ hạng (không dùng điểm số thô, vì cosine ~0.87 và BM25 ~14.2 không cùng thang đo):
RRF_score(doc) = Σ 1 / (k + rank_i(doc))
#với k thường = 60
Đánh đổi: thêm latency (2 truy vấn + hợp nhất) và phải tune trọng số dense/sparse.
Ví dụ cụ thể
Giả sử knowledge base là tài liệu nội bộ về hệ thống thanh toán OCB. User hỏi:
"Tại sao giao dịch bị lỗi mã ERR-4021 khi chuyển tiền liên ngân hàng?"
| Dense trả về | BM25 trả về | |
|---|---|---|
| Rank 1 | Doc B – "Xử lý sự cố chuyển khoản Napas" | Doc A – "Bảng mã lỗi ERR-40xx" |
| Rank 2 | Doc C – "Timeout khi gọi core banking" | Doc D – "Changelog: bổ sung ERR-4021" |
| Rank 3 | Doc A – "Bảng mã lỗi ERR-40xx" | Doc B – "Xử lý sự cố chuyển khoản Napas" |
Dense hiểu ý "chuyển tiền liên ngân hàng" ≈ "Napas" nhưng xếp bảng mã lỗi xuống hạng 3. BM25 bắt đúng ERR-4021 nhưng không hiểu ngữ cảnh nghiệp vụ.
Tính RRF với k=60:
- Doc A: 1/(60+3) + 1/(60+1) = 0.01587 + 0.01639 = 0.03226 → hạng 1
- Doc B: 1/(60+1) + 1/(60+3) = 0.03226 → hạng 1 (đồng điểm)
- Doc D: 0 + 1/(60+2) = 0.01613
- Doc C: 1/(60+2) + 0 = 0.01613
Kết quả: cả bảng mã lỗi (exact) và tài liệu nghiệp vụ Napas (semantic) đều lên top — đúng bộ context mà LLM cần để trả lời trọn vẹn.
Liên hệ với AWS
Trong Bedrock Knowledge Bases, đây chính là tham số overrideSearchType:
{
"retrievalConfiguration": {
"vectorSearchConfiguration": {
"numberOfResults": 5,
"overrideSearchType": "HYBRID"
}
}
}
Giá trị: HYBRID hoặc SEMANTIC. Lưu ý HYBRID chỉ khả dụng khi vector store hỗ trợ filterable text field — OpenSearch Serverless có, còn Pinecone/Redis thì không (chỉ SEMANTIC).
3. Amazon Bedrock Reranker
Vấn đề cốt lõi: similarity ≠ relevance
Vector search chấm điểm bằng cosine giữa hai vector được tính độc lập nhau. Đây gọi là bi-encoder:
embed(query) → [0.12, -0.44, ...] ← tính trước, không biết gì về query
embed(document) → [0.15, -0.41, ...] ← đã index sẵn từ lâu
cosine(a, b) → 0.91
Vì document được embed trước khi biết câu hỏi, vector của nó chỉ là bản nén "chủ đề tổng quát" của đoạn văn. Kết quả: tài liệu cùng chủ đề nhưng sai ý vẫn được điểm cao. Đó chính là 3 nhãn đỏ trên slide:
- Outdated doc — pricing 2022 vẫn nói về Bedrock, vẫn "giống" về mặt ngữ nghĩa
- Too generic — blog overview chạm mọi chủ đề nên gần với mọi query
- Wrong topic — FAQ giá cả, đúng vocabulary nhưng sai intent
Điều đáng chú ý ở slide: khoảng cách điểm 0.91 → 0.85 rất hẹp. Cosine trong vùng 0.80–0.95 gần như không phân biệt được tài liệu tốt và tài liệu tạm được. Doc thực sự cần thiết (API Reference) nằm hạng #3.
Cross-encoder giải quyết thế nào
Reranker nhét query và document vào cùng một lần forward pass:
cross_encoder("Làm sao cấu hình hybrid search?" + doc_text) → 0.96
Model nhìn thấy đồng thời cả hai, attention chạy chéo giữa từng token của query và từng token của doc. Nó "đọc hiểu" được rằng tài liệu này trả lời trực tiếp câu hỏi, không chỉ cùng chủ đề. Đổi lại, không thể index trước — mỗi cặp (query, doc) phải tính riêng, nên không dùng được để quét cả triệu document.
Vì vậy kiến trúc chuẩn là hai tầng:
| Tầng | Model | Nhiệm vụ | Số doc |
|---|---|---|---|
| Retrieval | Bi-encoder + BM25 | Recall cao, rẻ, nhanh | 1M → 25 |
| Rerank | Cross-encoder | Precision cao, đắt | 25 → 3 |
Tầng 1 lo đừng bỏ sót, tầng 2 lo đừng lẫn rác.
Đọc lại cột phải của slide
Sau rerank, điểm giãn ra rõ rệt: 0.96 / 0.72 / 0.31. Đây là điểm quan trọng nhất — cross-encoder cho điểm có ý nghĩa tuyệt đối, nên bạn có thể đặt ngưỡng cắt (ví dụ drop mọi doc < 0.5). Với cosine 0.91 vs 0.85 thì không thể đặt ngưỡng như vậy.
Doc pricing 2022 tuy vẫn hiện ở #3 trong hình, nhưng nhãn "Filtered out" nghĩa là nó bị loại khỏi context gửi vào LLM.
Ví dụ: Query: "Ngưỡng confidence score bao nhiêu thì cần human review?"
| Doc | Nội dung thật | Cosine | Rerank |
|---|---|---|---|
| A | SOP: "< 0.85 → đẩy sang queue kiểm tra thủ công" | 0.83 | 0.94 |
| B | Slide giới thiệu Textract features nói chung | 0.90 | 0.18 |
| C | Bảng benchmark accuracy các model OCR 2024 | 0.88 | 0.35 |
| D | Định nghĩa "confidence score là gì" | 0.87 | 0.41 |
Không có rerank, top-3 = B, C, D → LLM nhận toàn tài liệu nói về confidence score nhưng không có con số ngưỡng nào. Nó sẽ hallucinate một số tùy ý, hoặc trả lời "tùy trường hợp". Doc A — câu trả lời thật — bị loại ở hạng #4.
Có rerank: A lên #1 với 0.94, B/C/D bị cắt dưới ngưỡng. Đây là loại lỗi cực nguy hiểm trong domain ngân hàng, vì output nghe rất hợp lý nhưng sai quy trình.
Triển khai trên AWS
Bedrock có Rerank API độc lập (bedrock-agent-runtime):
import boto3
client = boto3.client("bedrock-agent-runtime", region_name="us-west-2")
resp = client.rerank(
queries=[{
"type": "TEXT",
"textQuery": {"text": "Ngưỡng confidence nào cần human review?"}
}],
sources=[
{"type": "INLINE",
"inlineDocumentSource": {"type": "TEXT", "textDocument": {"text": doc}}}
for doc in candidate_docs
],
rerankingConfiguration={
"type": "BEDROCK_RERANKING_MODEL",
"bedrockRerankingConfiguration": {
"numberOfResults": 3,
"modelConfiguration": {
"modelArn": "arn:aws:bedrock:us-west-2::foundation-model/amazon.rerank-v1:0"
}
}
}
)
# → [{"index": 0, "relevanceScore": 0.94}, ...]
Model có sẵn: amazon.rerank-v1:0 và cohere.rerank-v3-5:0.
Hoặc gắn trực tiếp vào Knowledge Base Retrieve:
{
"retrievalConfiguration": {
"vectorSearchConfiguration": {
"numberOfResults": 25,
"overrideSearchType": "HYBRID",
"rerankingConfiguration": {
"type": "BEDROCK_RERANKING_MODEL",
"bedrockRerankingConfiguration": {
"numberOfRerankedResults": 3,
"modelConfiguration": { "modelArn": "..." }
}
}
}
}
}
Chú ý pattern 25 → 3: phải tăng K ở tầng retrieval khi bật rerank. Nếu vẫn để numberOfResults: 3 thì reranker không có gì để chọn — nó chỉ sắp xếp lại 3 doc vốn đã sai. Đây là lỗi cấu hình phổ biến nhất.
Đánh đổi cần cân nhắc
| Yếu tố | Ảnh hưởng |
|---|---|
| Latency | +100–400ms mỗi query |
| Chi phí | Tính theo query × số doc rerank |
| Region | Rerank API chưa có ở mọi region — ap-southeast-1 cần kiểm tra lại, có thể phải cross-region gọi sang us-west-2 |
| Context window | Rerank giúp giảm số token gửi LLM → thường bù lại phần chi phí tăng thêm |