Vector Database thực chiến: chatbot, lọc tin trùng và Qdrant
Từ trải nghiệm dùng Qdrant cho chatbot và lọc tin trùng, bài viết hướng dẫn cách dùng vector search để tìm nội dung gần nghĩa, thiết kế dữ liệu, thử nghiệm local và kiểm soát chất lượng khi vận hành.
Một bài tin mới có thể viết “triển khai Qdrant trong container”, trong khi bài cũ nói “chạy Qdrant bằng Docker”. So URL, hash hoặc từ khóa đơn thuần chưa chắc nhận ra chúng đang nói về cùng một việc.
Đó là điểm vector database phát huy tác dụng: thay vì chỉ hỏi “có đúng chuỗi từ này không?”, hệ thống có thể tìm nội dung gần nhau về ý nghĩa.
Từ trải nghiệm dùng Qdrant cho chatbot và lọc tin trùng theo ngữ nghĩa, tôi nhận ra đây không chỉ là công cụ cho RAG. Nó còn hữu ích khi cần tìm ticket tương tự, gợi ý nội dung liên quan, truy xuất ghi chú cho agent hoặc nhóm các phản hồi gần nghĩa.
> Các quy trình và mã nguồn trong bài là ví dụ minh họa, không phải mô tả nguyên trạng một hệ thống đang vận hành.
Vector search giải quyết phần nào?
Embedding là quá trình chuyển văn bản thành một dãy số gọi là vector. Với một model phù hợp, các câu hoặc đoạn có ý nghĩa gần nhau có thể được biểu diễn gần nhau trong không gian vector.
Ví dụ:
“Làm sao chạy Qdrant bằng Docker?”
“Hướng dẫn triển khai cơ sở dữ liệu vector Qdrant trong container.”
Qdrant là database chuyên lưu trữ, lập chỉ mục và truy vấn các vector này. Khi có một câu hỏi hoặc nội dung mới, hệ thống tạo vector cho đầu vào rồi tìm những vector gần nhất theo thước đo đã chọn.
Tuy nhiên, vector search không thay thế database nghiệp vụ.
Nếu cần tìm hóa đơn HD-2026-001, tính tổng doanh thu, kiểm tra tồn kho hoặc xác minh trạng thái đơn hàng, bạn vẫn cần dữ liệu có cấu trúc và điều kiện nghiệp vụ chính xác. Vector search phù hợp nhất khi đầu vào là ngôn ngữ tự nhiên, nơi cách diễn đạt có thể khác nhau.
Model embedding quyết định cách biểu diễn ý nghĩa; vector database tổ chức và truy xuất các biểu diễn đó. Chọn database tốt không tự bảo đảm kết quả tìm kiếm tốt.
Những khái niệm cần biết trong Qdrant
| Khái niệm | Ý nghĩa |
|---|---|
| Collection | Tập các point có cấu hình vector tương ứng. |
| Point | Một bản ghi gồm ID, vector và payload. |
| Vector | Biểu diễn dùng để tính độ tương đồng. |
| Payload | Metadata hoặc nội dung đi kèm, như nguồn, ngôn ngữ, tenant, mã tài liệu. |
| Payload index | Index hỗ trợ lọc theo các trường payload thường được truy vấn. |
Trong cùng một cấu hình vector, số chiều và thước đo khoảng cách cần nhất quán. Qdrant cũng hỗ trợ named vectors, cho phép một point có nhiều biểu diễn với cấu hình riêng.
Ví dụ payload cho một bài tin:
json
{
"articleid": "news-001",
"tenantid": "ai-news",
"title": "Hướng dẫn chạy Qdrant bằng Docker",
"summary": "Các bước khởi động Qdrant và kết nối từ Python.",
"sourceurl": "https://example.com/news-001",
"language": "vi",
"embeddingmodel": "ten-model-dang-su-dung"
}
Nếu hệ thống đã dùng PostgreSQL, một cách tổ chức hợp lý là giữ bài gốc và trạng thái biên tập trong database nghiệp vụ; Qdrant giữ vector cùng metadata cần cho truy xuất. Khi đó, cần có cơ chế đồng bộ cả lúc tạo, sửa và xóa dữ liệu.
Hai bài toán thực tế: chatbot và lọc tin trùng
Chatbot hỏi đáp trên tài liệu
Một luồng RAG cơ bản gồm:
Đọc tài liệu, làm sạch và chia thành các đoạn.
Tạo embedding cho từng đoạn rồi lưu vào Qdrant.
Khi người dùng đặt câu hỏi, tạo embedding cho câu hỏi.
Tìm các đoạn liên quan trong phạm vi mà người dùng được phép truy cập.
Đưa các đoạn cùng thông tin nguồn cho LLM tạo câu trả lời.
Ví dụ, khi người dùng hỏi “Muốn chạy Tika bằng Docker thì làm thế nào?”, hệ thống có thể tìm các đoạn hướng dẫn liên quan thay vì đưa toàn bộ kho tài liệu vào prompt.
Nhưng tìm được đoạn gần nghĩa chưa có nghĩa chatbot sẽ trả lời đúng. Cần kiểm tra xem đoạn đó có thực sự chứa câu trả lời, còn hiệu lực và thuộc đúng sản phẩm hoặc phiên bản hay không.
Với mã lỗi, tên API, mã sản phẩm hoặc chuỗi cấu hình chính xác, chỉ dùng dense vector có thể chưa đủ. Có thể thử hybrid search: kết hợp tìm kiếm vector với truy xuất từ khóa hoặc sparse vector, sau đó hợp nhất và đánh giá lại kết quả.
Phân quyền phải được áp dụng trước khi nội dung được đưa cho LLM. Không nên truy xuất toàn bộ dữ liệu rồi chỉ dựa vào prompt để yêu cầu model không tiết lộ thông tin.
Lọc tin tức trùng nhau theo ngữ nghĩa
Đây là bài toán tôi đã dùng Qdrant. So URL hoặc tiêu đề nguyên văn thường chưa đủ vì cùng một sự kiện có thể được nhiều nguồn viết lại bằng cách diễn đạt khác nhau.
Một quy trình tham khảo:
Chuẩn hóa URL và kiểm tra hash nội dung để xử lý trùng chính xác.
Tạo embedding từ tiêu đề và phần mô tả đủ thông tin.
Dùng Qdrant tìm một nhóm tin cũ gần nghĩa.
So sánh các ứng viên theo chủ thể, sự kiện, phiên bản, thời gian và dữ kiện mới.
Gộp nguồn, chuyển duyệt hoặc giữ thành tin riêng tùy kết quả đánh giá.
| Quan hệ giữa hai bài | Hướng xử lý đề xuất |
|---|---|
| Cùng sự kiện, gần như không có thông tin mới | Gộp vào cùng nhóm tin và giữ các nguồn. |
| Cùng sự kiện nhưng có cập nhật đáng kể | Giữ cập nhật hoặc bổ sung vào bài tổng hợp. |
| Cùng chủ đề nhưng khác sự kiện | Giữ thành tin riêng. |
Ví dụ, “Công ty A giới thiệu model X” và “Model X chính thức ra mắt” có thể là cùng một sự kiện. Nhưng “Model X gặp sự cố sau khi ra mắt” là một cập nhật khác, dù embedding có thể vẫn rất gần.
Vì vậy, không nên đặt một ngưỡng cố định, chẳng hạn 0,85, rồi tự động loại mọi bài vượt ngưỡng. Hãy gán nhãn một tập cặp tin thật, gồm cả các trường hợp dễ nhầm, rồi đo precision và recall.
Với trang tin, loại nhầm một cập nhật quan trọng có thể gây hại hơn việc giữ lại một bài hơi giống.
Thử Qdrant local bằng Docker
Ví dụ dưới đây chạy Qdrant trên máy local. Named volume giúp dữ liệu vẫn tồn tại khi container được tạo lại.
bash
docker volume create qdrantdata
docker run -d \
--name qdrant-demo \
--restart unless-stopped \
-p 127.0.0.1:6333:6333 \
-v qdrantdata:/qdrant/storage \
qdrant/qdrant:latest
curl http://localhost:6333/collections
REST API sử dụng cổng 6333; dashboard có tại:
text
http://localhost:6333/dashboard
Cấu hình trên chỉ publish cổng ở loopback, phù hợp để thử nghiệm local. Tag latest có thể thuận tiện khi bắt đầu, nhưng khi triển khai thực tế nên cố định phiên bản đã kiểm tra.
Nếu cần mở Qdrant cho dịch vụ khác truy cập, cần xem xét cấu hình mạng, xác thực và TLS phù hợp. Bản self-hosted không bật sẵn xác thực theo mặc định.
Cài Python client và model embedding minh họa:
bash
python -m pip install qdrant-client sentence-transformers
Ví dụ Python: lưu và tìm ứng viên tin trùng
Ví dụ dùng model paraphrase-multilingual-MiniLM-L12-v2, tạo vector 384 chiều. Đây chỉ là lựa chọn minh họa cho cách tích hợp, không phải kết luận model tốt nhất cho dữ liệu tiếng Việt.
python
from uuid import NAMESPACEURL, uuid5
from qdrantclient import QdrantClient, models
from sentencetransformers import SentenceTransformer
MODEL = "sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2"
COLLECTION = "newsminilmdemov1"
TENANT = "ai-news"
encoder = SentenceTransformer(MODEL)
client = QdrantClient(url="http://localhost:6333")
if not client.collectionexists(COLLECTION):
client.createcollection(
collectionname=COLLECTION,
vectorsconfig=models.VectorParams(
size=encoder.getsentenceembeddingdimension(),
distance=models.Distance.COSINE,
),
)
client.createpayloadindex(
collectionname=COLLECTION,
fieldname="tenantid",
fieldschema=models.PayloadSchemaType.KEYWORD,
wait=True,
)
Dữ liệu giả định, dùng để minh họa.
def embed(text):
return encoder.encode(
text,
normalizeembeddings=True
).tolist()
points = []
for article in articles:
text = article["title"] + ". " + article["summary"]
points.append(
models.PointStruct(
id=str(uuid5(NAMESPACEURL, TENANT + ":" + article["url"])),
vector=embed(text),
payload={
article,
"tenantid": TENANT,
"embeddingmodel": MODEL,
},
)
)
client.upsert(
collectionname=COLLECTION,
points=points,
wait=True,
)
incoming = (
"Hướng dẫn triển khai Qdrant trong container "
"và truy cập bằng Python."
)
candidates = client.querypoints(
collectionname=COLLECTION,
query=embed(incoming),
queryfilter=models.Filter(
must=[
models.FieldCondition(
key="tenantid",
match=models.MatchValue(value=TENANT),
)
]
),
limit=5,
withpayload=True,
).points
for hit in candidates:
print(round(hit.score, 4), hit.payload["title"])
Chạy chương trình:
bash
python demoqdrant.py
UUID trong ví dụ được tạo ổn định từ tenant và URL. Vì vậy, khi chạy lại với cùng URL, ứng dụng có thể cập nhật cùng một point thay vì sinh thêm bản ghi mới.
Điều quan trọng: đoạn mã này chỉ trả về ứng viên cùng điểm tương đồng. Nó chưa kết luận bài trùng, chưa tự động gộp hay xóa tin, và không đi kèm số liệu benchmark.
Model minh họa cũng có giới hạn đầu vào ngắn. Với bài dài, cần chia đoạn hoặc tạo phần mô tả có kiểm soát thay vì âm thầm cắt mất nội dung.
Những ứng dụng khác có thể khảo sát
| Bài toán | Cách dùng vector search | Điều kiện cần kiểm tra |
|---|---|---|
| Tìm kiếm tài liệu nội bộ | Tìm tài liệu từ câu hỏi tự nhiên. | Phân quyền, phiên bản, thời hạn hiệu lực. |
| Gợi ý bài liên quan | Tìm nội dung gần với bài đang đọc. | Tránh đề xuất lại các bài gần như trùng. |
| Tìm ticket hỗ trợ tương tự | Tìm sự cố và hướng xử lý đã có. | Sản phẩm, phiên bản, môi trường, trạng thái xử lý. |
| Gom nhóm phản hồi khách hàng | Tìm phản hồi gần nghĩa để hỗ trợ phân cụm. | Cần thêm bước clustering hoặc quy tắc nhóm. |
| Tìm sản phẩm bằng mô tả | Ghép nhu cầu tự nhiên với mô tả sản phẩm. | Giá, tồn kho và thuộc tính bắt buộc phải lọc chính xác. |
| Tìm ảnh tương tự | Dùng embedding ảnh để truy xuất hình gần nhau. | Cần model ảnh hoặc đa phương thức phù hợp. |
| Semantic cache | Tìm câu hỏi gần nghĩa để cân nhắc dùng lại câu trả lời. | Quyền truy cập, ngữ cảnh và độ mới của đáp án. |
| Bộ nhớ truy xuất cho agent | Tìm lại ghi chú hoặc quyết định liên quan. | Nguồn, thời gian, quyền, cơ chế sửa và xóa. |
Đây là các hướng ứng dụng để khảo sát, không phải cam kết rằng chỉ cần thêm Qdrant là giải quyết được toàn bộ bài toán.
Những điểm cần kiểm soát khi vận hành
Vector gần không đồng nghĩa là đúng
Hai nội dung có thể có vector gần nhau nhưng khác nhau ở chi tiết nghiệp vụ quan trọng. Chẳng hạn, “hệ thống cho phép xuất dữ liệu” và “hệ thống không cho phép xuất dữ liệu” có thể chứa nhiều từ giống nhau, nhưng ý nghĩa quyết định lại trái ngược.
Điểm tương đồng cũng không phải xác suất đúng. Score 0,9 không có nghĩa hai bài có 90% khả năng trùng nhau.
Đánh giá bằng dữ liệu thật
Với chatbot, nên đo khả năng truy xuất đúng đoạn chứa câu trả lời. Với lọc tin trùng, nên đo tỷ lệ gộp đúng và tỷ lệ bỏ sót, đặc biệt với các bài cập nhật dễ bị loại nhầm.
Nếu hệ thống đa ngôn ngữ, cần kiểm tra riêng khả năng nhận diện các cặp tin Việt–Anh. Không phải model nào cũng biểu diễn các bản dịch tương đương đủ gần nhau.
Quản lý model như một phần của schema
Cùng số chiều không có nghĩa vector từ hai model tương thích. Khi đổi model embedding hoặc cách tiền xử lý, nên tạo biểu diễn mới, đánh giá lại chất lượng, rồi chuyển truy vấn theo cách có kiểm soát.
Đồng bộ cả sửa và xóa
Nếu tài liệu bị xóa trong database nghiệp vụ nhưng các chunk cũ vẫn còn trong Qdrant, chatbot có thể tiếp tục truy xuất nội dung đã hết hiệu lực.
Nên lưu các trường như documentid, chunkid, phiên bản và model embedding để quản lý vòng đời dữ liệu.