Vì sao cần truy xuất theo ngữ nghĩa
Key-value tra chính xác theo khóa. Nhưng nếu người dùng hỏi "chính sách hoàn tiền thế nào?" còn tài liệu ghi "quy định trả lại sản phẩm và hoàn phí" — không có từ khóa trùng, tra khóa sẽ trượt. Ta cần tìm theo ý nghĩa, không theo chữ. Đó là việc của embeddings + vector store.
Embedding là gì
Embedding là hàm biến một đoạn text thành một vector số (ví dụ 1536 chiều). Điểm mấu chốt: hai đoạn text có ý nghĩa gần nhau sẽ cho hai vector gần nhau trong không gian. "Hoàn tiền" và "trả lại sản phẩm và hoàn phí" nằm sát nhau, dù không chung từ nào.
"chính sách hoàn tiền" -> [0.12, -0.03, 0.88, ...]
"quy định trả lại và hoàn phí" -> [0.11, -0.05, 0.85, ...] # rất gần
"cách nướng bánh mì" -> [-0.7, 0.4, 0.02, ...] # xa
Vector store là gì
Vector store (như FAISS, Chroma, pgvector, Pinecone) là cơ sở dữ liệu lưu các vector và cho phép truy vấn: "cho tôi K vector gần nhất với vector truy vấn này." Độ gần thường đo bằng cosine similarity. Đây là bước "tìm kiếm ngữ nghĩa" cốt lõi.
Quy trình chuẩn: từ tài liệu tới truy vấn
- Chunking: cắt tài liệu dài thành các đoạn nhỏ (chunk), ví dụ 300–800 token, có overlap ~10–15% để không đứt ngữ cảnh.
- Embedding: gọi model embedding cho từng chunk.
- Indexing: lưu (vector, text gốc, metadata) vào vector store.
- Query: khi có câu hỏi, embed câu hỏi → tìm top-K chunk gần nhất.
- Sử dụng: đưa các chunk đó vào prompt (đây là RAG, chi tiết ở Bài 5).
Code ngắn: dựng và truy vấn một vector store
import chromadb
client = chromadb.Client()
col = client.create_collection("kb")
# 1) Index tài liệu (giả sử embed_fn đã cấu hình)
docs = ["Chính sách hoàn tiền trong 7 ngày...",
"Hướng dẫn đổi địa chỉ giao hàng..."]
col.add(documents=docs,
ids=["d1", "d2"],
metadatas=[{"src": "policy"}, {"src": "shipping"}])
# 2) Truy vấn theo ngữ nghĩa
res = col.query(query_texts=["làm sao lấy lại tiền?"], n_results=2)
print(res["documents"]) # trả về chunk về hoàn tiền dù không trùng từ
Các quyết định thiết kế quan trọng
- Kích thước chunk: quá lớn → nhiễu và tốn token; quá nhỏ → mất ngữ cảnh. Bắt đầu ~500 token rồi tinh chỉnh.
- Overlap: giúp thông tin nằm ở ranh giới chunk không bị chặt đôi.
- Metadata: lưu nguồn, ngày, user_id để lọc trước khi tìm (ví dụ chỉ tìm trong tài liệu của user này).
- Chọn model embedding: dùng nhất quán một model cho cả index lẫn query — trộn hai model khác nhau sẽ làm khoảng cách vô nghĩa.
- top-K: lấy bao nhiêu chunk? Thường 3–8; nhiều quá gây nhiễu và tốn token.
Checklist trước khi index
- [ ] Đã chọn chiến lược chunking và test trên vài tài liệu thật?
- [ ] Query và index dùng CÙNG model embedding?
- [ ] Đã lưu text gốc + metadata kèm mỗi vector (để trích dẫn lại)?
- [ ] Có kế hoạch cập nhật/xóa vector khi tài liệu thay đổi?
- [ ] Đã thử vài câu hỏi thật và kiểm tra top-K có đúng đoạn cần không?
Sai lầm thường gặp
- Trộn model embedding. Index bằng model A, query bằng model B → similarity vô nghĩa, kết quả rác.
- Chunk quá to. Nhồi cả trang vào một vector khiến câu hỏi cụ thể match kém và tốn token khi đưa vào prompt.
- Quên metadata. Không lọc được theo user/nguồn → agent trả lời bằng tài liệu của người khác, rò rỉ dữ liệu.
- Coi vector search là "luôn đúng". Nó trả về đoạn gần nhất, không phải đúng nhất. Vẫn cần đánh giá và đôi khi kết hợp keyword search (hybrid).
- Index xong không bao giờ cập nhật. Tài liệu đổi mà vector cũ còn nguyên → agent trả lời theo chính sách lỗi thời.
Tổng kết
Embedding biến ý nghĩa thành tọa độ; vector store tìm các đoạn gần nghĩa nhất. Đây là hạ tầng truy xuất cho long-term memory phi cấu trúc và cho RAG. Bài sau ta ghép nó với LLM để tạo câu trả lời có căn cứ.