Product Management
Đăng nhập
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Bài 7 — Hybrid search: kết hợp vector và keyword cho memory

Đã đọc 0/8 mục · còn khoảng 3 phút
Cách chúng tôi ghi nhận

Một mục được tính là đã đọc khi nó nằm giữa màn hình đủ lâu so với độ dài của nó (bảng và sơ đồ tính một nửa), bạn vẫn đang cuộn hoặc bấm phím, và bạn đã thấy tới cuối mục. Lướt nhanh hoặc để tab mở không được tính. Chúng tôi chỉ lưu thời gian đọc từng mục — không lưu gì khác.

Vector search giỏi bắt ý nghĩa nhưng dở với từ khóa chính xác: mã sản phẩm SKU-88213, số hợp đồng, tên riêng hiếm. Keyword search (BM25) thì ngược lại. Hybrid search kết hợp cả hai — thường cho recall tốt nhất cho bộ nhớ agent.

Khi nào semantic một mình là không đủ

  • Khách hỏi "tình trạng đơn DH-2024-0091" — vector có thể bỏ sót vì mã số không mang "ý nghĩa".
  • Truy vấn có thuật ngữ chuyên ngành, tên viết tắt, ID.
  • Cần khớp cả đúng từ (keyword) lẫn đúng ý (semantic).

Cơ chế: hai điểm số, một danh sách

Ta chạy song song:

  • Dense (vector) → điểm semantic.
  • Sparse/BM25 (keyword) → điểm từ khóa.
Rồi hợp nhất hai bảng xếp hạng. Cách phổ biến nhất là RRF (Reciprocal Rank Fusion): mỗi kết quả nhận điểm 1/(k + rank) từ mỗi danh sách rồi cộng lại — không cần chuẩn hóa thang điểm giữa hai hệ.

def rrf(dense_ids, sparse_ids, k=60):
    score = {}
    for rank, _id in enumerate(dense_ids):
        score[_id] = score.get(_id, 0) + 1/(k + rank)
    for rank, _id in enumerate(sparse_ids):
        score[_id] = score.get(_id, 0) + 1/(k + rank)
    return sorted(score, key=score.get, reverse=True)

Làm hybrid ở từng công cụ

-- pgvector + full-text (tsvector) rồi RRF ở app, hoặc gộp bằng SQL
SELECT id, content,
       ts_rank(to_tsvector('simple', content), plainto_tsquery(%s)) AS kw,
       1 - (embedding <=> %s) AS sem
FROM agent_memory
WHERE user_id = %s
ORDER BY (0.5*kw + 0.5*sem) DESC   -- hoặc dùng RRF
LIMIT 10;
# Qdrant: vector dense + sparse vector (BM25) trong Query API, fusion RRF sẵn
# Weaviate: hybrid(query=..., alpha=0.5)  # alpha=1 thuần vector, 0 thuần BM25
# Pinecone: index hỗ trợ sparse-dense, truyền cả sparse_values + values

alpha (Weaviate) hay trọng số trong công thức cho bạn chỉnh nghiêng về semantic hay keyword.

Reranking — tầng tinh chỉnh cuối

Sau khi hybrid lấy ~20 ứng viên, đưa qua một cross-encoder reranker để xếp lại theo độ liên quan thật với câu hỏi, rồi giữ top-5. Reranker chậm hơn nhưng chỉ chạy trên ít ứng viên nên chấp nhận được, và cải thiện độ chính xác rõ rệt.

query + candidate -> reranker -> điểm liên quan -> sort -> top 5

Khung tư duy chọn chiến lược

  • Dữ liệu có nhiều ID/mã/tên riêng không? → cần hybrid (thêm keyword).
  • Truy vấn thuần ngôn ngữ tự nhiên, ít mã? → vector thuần có thể đủ.
  • Chất lượng top-k quan trọng (agent ra quyết định)? → thêm reranker.
  • Bắt đầu alpha=0.5, rồi đo trên tập đánh giá và chỉnh.

Các bước triển khai hybrid

  • Chuẩn bị cả biểu diễn dense (vector) và sparse (BM25/tsvector) cho mỗi mẩu.
  • Chạy hai truy vấn song song (hoặc dùng API hybrid tích hợp).
  • Hợp nhất bằng RRF hoặc trọng số.
  • (Tùy chọn) rerank top-N bằng cross-encoder.
  • Áp cùng filter metadata (user_id...) cho cả hai nhánh — đừng để keyword lách filter.

Sai lầm thường gặp

  • Chỉ dùng vector rồi than "không tìm ra mã đơn" — đó chính là điểm mù của semantic; thêm keyword.
  • Cộng thẳng hai điểm số khác thang (cosine 0–1 vs BM25 0–20) mà không chuẩn hóa → một bên lấn át. RRF tránh được lỗi này.
  • Quên áp filter cho nhánh keyword → hybrid làm rò rỉ ký ức user khác dù nhánh vector đã lọc.
  • Bỏ qua reranking khi agent ra quyết định quan trọng — top-k thô đôi khi sai thứ tự.
  • Đặt alpha cực đoan (0 hoặc 1) rồi kết luận hybrid vô dụng; hãy dò quanh 0.5 và đo.
Bài cuối: đưa tất cả lên production — chunking, cập nhật, chi phí, giám sát và độ tin cậy.

Học xong bài này rồi? Tạo tài khoản miễn phí để lưu lại — lần sau vào là biết ngay đang dở ở đâu, và học hết khóa thì có chứng chỉ. Lưu tiến độ của tôi