Product Management
Đăng nhập
ESC

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

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

Bài 6 — Metadata filtering: lọc đúng ký ức của đúng người

Đã đọ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.

Tìm vector gần nhất chưa đủ. Trong thực tế agent luôn phải lọc: chỉ ký ức của user này, chỉ tài liệu còn hiệu lực, chỉ nguồn được phép. Đây là nơi nhiều hệ thống rò rỉ dữ liệu hoặc trả sai kết quả. Bài này dạy làm filter đúng và nhanh.

Vì sao filter quan trọng hơn bạn nghĩ

Agent chăm sóc khách hàng mà không filter user_id sẽ trả ký ức của khách A cho khách B — vừa sai vừa vi phạm quyền riêng tư. Filter metadata là hàng rào bảo mật, không chỉ là tiện ích.

Filter ở từng công cụ

# Qdrant — filter payload kết hợp với vector search
from qdrant_client.models import Filter, FieldCondition, MatchValue
c.search(
  "mem",
  query_vector=qvec,
  query_filter=Filter(must=[
      FieldCondition(key="user_id", match=MatchValue(value=42)),
      FieldCondition(key="kind", match=MatchValue(value="episodic")),
  ]),
  limit=5,
)
# Pinecone — filter kiểu Mongo
index.query(vector=qvec, top_k=5,
  filter={"user_id": 42, "status": {"$eq": "active"}})
-- pgvector — filter là mệnh đề WHERE bình thường
SELECT content FROM agent_memory
WHERE user_id = 42 AND kind = 'episodic'
ORDER BY embedding <=> %s LIMIT 5;

Pre-filter vs Post-filter — điểm mấu chốt

  • Post-filter: tìm top-k theo vector trước, rồi bỏ mẩu không khớp filter. Nguy hiểm: nếu top-k toàn của user khác, sau khi lọc còn 0 kết quả dù DB có mẩu phù hợp.
  • Pre-filter: thu hẹp theo filter trước, rồi tìm vector trong tập đã lọc. An toàn hơn cho agent memory.
Qdrant/Weaviate/Pinecone có cơ chế filter tích hợp với index để tránh bẫy post-filter. pgvector filter ngay trong WHERE (pre-filter tự nhiên), nhưng cần index phụ để nhanh.

Payload index — đừng để filter quét toàn bảng

Filter không có index = quét tuần tự, giết hiệu năng khi dữ liệu lớn.

# Qdrant: tạo payload index cho field hay filter
c.create_payload_index("mem", field_name="user_id", field_schema="integer")
-- pgvector: B-tree index cho field filter
CREATE INDEX ON agent_memory (user_id, kind);

Thiết kế schema metadata (khung tư duy)

Trước khi ghi vector, liệt kê mọi chiều bạn sẽ filter:

  • Quyền sở hữu: user_id, tenant_id — bắt buộc cho multi-tenant.
  • Thời gian: created_at — lọc ký ức cũ, hoặc ưu tiên gần đây.
  • Loại/nguồn: kind, source, doc_id.
  • Trạng thái: status (active/archived), version.
  • Phân quyền: visibility, role — ai được xem.
Ghi các field này vào payload/cột ngay từ đầu; thêm sau rất tốn công re-index.

Các bước làm filter đúng

  • Xác định các field filter bắt buộc (nhất là tenant_id/user_id).
  • Lưu chúng vào payload/cột khi ghi vector.
  • Tạo payload index / B-tree index cho field filter nóng.
  • Dùng pre-filter (filter tích hợp) thay vì lọc sau ở tầng app.
  • Test cạnh biên: user không có ký ức nào → phải trả rỗng an toàn, không rơi sang user khác.

Sai lầm thường gặp

  • Post-filter ở tầng app: lấy top-50 rồi tự lọc user_id bằng Python → dễ ra rỗng dù DB có dữ liệu, lại tốn băng thông.
  • Quên payload index → filter chạy nhưng chậm kinh khủng ở quy mô lớn.
  • Không có tenant_id trong hệ multi-tenant → rò rỉ dữ liệu chéo khách hàng.
  • Filter trên field không tồn tại trong payload → kết quả rỗng khó hiểu; hãy chuẩn hóa schema payload.
  • Lọc thời gian sai kiểu: lưu created_at dạng string thay vì timestamp/epoch khiến so sánh khoảng thời gian sai.
Bài sau: kết hợp filter với hybrid search (vector + keyword) để bắt cả những trường hợp semantic bỏ sót.

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