Trước khi retrieval, bạn cần hai thành phần lõi: một embedding model (biến text thành vector) và một vector store (lưu và tìm vector theo độ tương đồng). Bài này hướng dẫn thiết lập cả hai để có index chạy được.
Chọn embedding model
Embedding model quyết định chất lượng retrieval nhiều hơn bạn tưởng. Vài tiêu chí:
- Chiều vector (dimension): 384, 768, 1024, 1536 — cao hơn thường tốt hơn nhưng tốn bộ nhớ.
- Ngôn ngữ: với tiếng Việt, chọn model đa ngôn ngữ (multilingual) hoặc model chuyên tiếng Việt.
- Độ dài context tối đa của model: quyết định chunk lớn tối đa.
- Chi phí & độ trễ: API trả phí vs. model chạy local.
text-embedding-3-small (1536 chiều, đa ngôn ngữ, rẻ) cho MVP, hoặc bge-m3 chạy local nếu cần riêng tư.Chọn vector store
| Loại | Khi nào dùng |
|---|---|
| FAISS (in-memory) | Prototype, dữ liệu < 1 triệu chunk |
| Chroma / LanceDB | Local, dễ dùng, nhúng vào app |
| Qdrant / Weaviate | Production, cần filter metadata, scale |
| pgvector | Đã có Postgres, muốn 1 database duy nhất |
Các bước thiết lập
- Cài thư viện:
pip install sentence-transformers qdrant-client
- Khởi tạo embedding model:
from sentence_transformers import SentenceTransformer
model = SentenceTransformer("BAAI/bge-m3")
def embed(texts):
return model.encode(texts, normalize_embeddings=True)
normalize_embeddings=True rất quan trọng: chuẩn hóa vector để dùng cosine similarity chính xác.- Tạo collection trong vector store:
from qdrant_client import QdrantClient
from qdrant_client.models import VectorParams, Distance
client = QdrantClient(":memory:")
client.create_collection(
collection_name="docs",
vectors_config=VectorParams(size=1024, distance=Distance.COSINE),
)
Chú ý: size phải khớp đúng chiều vector của model (bge-m3 = 1024). Sai chiều là lỗi phổ biến nhất.- Nạp thử một vài chunk:
from qdrant_client.models import PointStruct
chunks = ["Phép năm được quy đổi tối đa 5 ngày.", "Giờ làm việc từ 8h đến 17h."]
vecs = embed(chunks)
points = [PointStruct(id=i, vector=v.tolist(), payload={"text": c})
for i, (c, v) in enumerate(zip(chunks, vecs))]
client.upsert(collection_name="docs", points=points)
- Kiểm tra truy vấn:
q = embed(["quy đổi phép năm"])[0]
hits = client.search("docs", query_vector=q.tolist(), limit=2)
for h in hits:
print(h.score, h.payload["text"])
Lưu metadata cùng vector
Đừng chỉ lưu text. Luôn kèm metadata để filter và trích dẫn:
payload = {
"text": chunk,
"source": "quy-che-2026.pdf",
"page": 4,
"section": "4.2",
"updated_at": "2026-06-01"
}
Metadata updated_at sẽ dùng ở Bài 8 để xử lý index freshness.Checklist thiết lập môi trường
- [ ] Chiều vector của model == size của collection.
- [ ] Bật normalize embeddings + dùng distance COSINE (hoặc dot khi đã normalize).
- [ ] Mỗi point có metadata: source, vị trí, thời gian cập nhật.
- [ ] Test embed một câu và search được kết quả hợp lý.
- [ ] Ghi lại tên model + version để tái lập.
Sai lầm thường gặp
- Trộn model embedding khác nhau giữa lúc index và lúc query: vector không cùng không gian → kết quả rác. Query PHẢI dùng đúng model đã index.
- Quên normalize rồi lại dùng cosine: điểm số méo.
- Không lưu metadata: sau này không trích dẫn được, không lọc theo phòng ban được.
- Chọn vector DB nặng ngay từ đầu: prototype nên bắt đầu bằng FAISS/Chroma cho nhanh, đổi sau khi cần scale.
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