Как вы фильтруете документы по метаданным в векторной БД?
Краткий тезис
Фильтрация по метаданным (metadata filtering) — это возможность искать документы не только по семантической близости (поиск|векторный поиск), но и по атрибутам: дата, автор, тип документа, категория, источник. В векторных БД есть два подхода: pre-filtering (сначала фильтр, потом поиск) и post-filtering (сначала поиск, потом фильтр). Pre-filtering точнее, но может не найти ничего, если отфильтровать слишком много документов. Post-filtering быстрее, но может потерять релевантные документы, не попавшие в top-k. Лучшие практики: создавать индексы на часто фильтруемых полях и выбирать подход в зависимости от селективности фильтра.
Ключевая идея поиск|Векторный поиск сам по себе не учитывает метаданные. Фильтрация — это отдельный этап, который добавляется до или после ANN поиска.
1. Термин: Метаданные (Metadata)
Что это Данные о данных. Не сам текст документа, а информация о нём.
Примеры метаданных для документа
{
"text": "Содержимое документа...",
"metadata": {
"source": "s3://bucket/policy.pdf",
"doc_type": "pdf",
"author": "legal_department",
"created_date": "2025-01-01",
"category": "legal",
"department": "finance",
"is_confidential": true,
"version": 3
}
}
Термин «Селективность» (Selectivity Доля документов, проходящих фильтр. Если фильтр оставляет 1% документов — высокая селективность. Если 80% — низкая.
2. Pre-filtering (сначала фильтр, потом поиск)
Что это Сначала применяем фильтр по метаданным (оставляем только документы, удовлетворяющие условию), затем выполняем поиск|векторный поиск только по отфильтрованному подмножеству.
Запрос пользователя: "финансовые отчеты за 2025 год про прибыль"
Шаг 1 (фильтр по метаданным):
- doc_type = 'financial'
- year = 2025
→ Оставляем только 1000 документов из 1 млн
Шаг 2 (векторный поиск):
- Ищем по вектору "про прибыль" среди этих 1000 документов
→ Возвращаем top-10
Плюсы
- Точность: все найденные документы точно соответствуют фильтру
- Нет риска потерять документы из-за top-k (поиск идёт по всем отфильтрованным)
Минусы
- Если фильтр слишком строгий (оставил 0 документов) → пустой результат
- Медленнее при очень селективных фильтрах (фильтр оставляет мало документов — поиск всё равно должен просмотреть их все)
Термин «Фильтр высокой селективности» Фильтр, который оставляет очень мало документов (например, doc_type='rare_type'). Pre-filtering эффективен, так как поиск идёт по маленькому множеству.
Термин «Фильтр низкой селективности» Фильтр, который оставляет много документов (например, year=2025 — 50% всех документов). Pre-filtering не даёт большого выигрыша.
3. Post-filtering (сначала поиск, потом фильтр)
Что это Сначала выполняем векторный поиск по всей БД, находим top-k документов (например, 100), затем применяем фильтр к этим 100, оставляя только те, которые соответствуют условию.
Запрос пользователя: "финансовые отчеты за 2025 год про прибыль"
Шаг 1 (векторный поиск):
- Ищем по всей БД (1 млн документов) top-100 по семантической близости
→ Находим 100 документов (могут быть любых типов и годов)
Шаг 2 (фильтр):
- Оставляем только те, у которых doc_type='financial' AND year=2025
→ Может остаться 0, 5 или 50 документов
Плюсы
- Быстрый (векторный поиск по всей БД, фильтр только на top-k)
- Гарантированно возвращает top-k по семантике (но после фильтра их может стать меньше)
Минусы
- Риск если релевантные документы не попали в top-k по семантике, они будут отфильтрованы
- Пример: запрос про прибыль (семантически близок к финансовым отчётам), но в top-100 попали только технические документы (потому что в них слово «прибыль» встречается редко, но есть другие совпадения). Финансовые отчёты на позициях 101-200 — потеряны.
Когда использовать post-filtering
- Фильтр низкой селективности (большая часть документов подходит)
- Скорость критична
- Потери релевантных документов допустимы
4. Pre-filtering vs Post-filtering: сравнение
| Характеристика | Pre-filtering | Post-filtering |
|---|---|---|
| Порядок | Фильтр → векторный поиск | Векторный поиск → фильтр |
| Точность (recall | 100% (все подходящие под фильтр) | Риск потери (если релевантные не в top-k) |
| Скорость при селективном фильтре (<10%) | Быстро (мало документов для поиска) | Быстро (векторный поиск по всей БД) |
| Скорость при неселективном фильтре (>50% | Медленно (почти все документы) | Быстро (фильтр только top-k) |
| Риск пустого результата | Если фильтр слишком строгий | Если релевантные не в top-k |
| Когда использовать | Селективные фильтры (<10%) | Неселективные фильтры (>50%) |
Термин «Recall» (в контексте фильтрации Доля релевантных документов (подходящих под фильтр), которые вернулись в результате.
5. Реализация в популярных векторных БД
5.1 Qdrant (рекомендуется)
Qdrant поддерживает pre-filtering с богатым синтаксисом условий.
from qdrant_client import QdrantClient
from qdrant_client.http.models import Filter, FieldCondition, Range, Match
client = QdrantClient(host="localhost", port=6333)
# Фильтр: doc_type = 'financial' И year >= 2024 И year <= 2025
filter_condition = Filter(
must=[
FieldCondition(key="doc_type", match=Match(value="financial")),
FieldCondition(key="year", range=Range(gte=2024, lte=2025))
]
)
# Pre-filtering поиск
results = client.search(
collection_name="documents",
query_vector=query_embedding,
query_filter=filter_condition, # сначала фильтр, потом поиск
limit=10
)
Операторы Qdrant
| Оператор | Что делает | Пример |
|---|---|---|
must | Все условия должны выполняться (AND) | doc_type='financial' AND year=2025 |
should | Хотя бы одно условие (OR) | doc_type='financial' OR doc_type='legal' |
must_not | Условие не должно выполняться (NOT) | NOT doc_type='draft' |
5.2 Weaviate
Weaviate поддерживает pre-filtering через where фильтр.
import weaviate
client = weaviate.Client("http://localhost:8080")
# where фильтр: doc_type = 'financial'
where_filter = {
"path": ["doc_type"],
"operator": "Equal",
"valueString": "financial"
}
result = client.query.get(
"Document", ["text", "doc_type", "year"]
).with_near_vector({
"vector": query_embedding
}).with_where(where_filter).with_limit(10).do()
5.3 Pinecone
Pinecone поддерживает post-filtering (после поиска).
import pinecone
pinecone.init(api_key="...")
index = pinecone.Index("documents")
# Поиск без фильтра (векторный)
results = index.query(
vector=query_embedding,
top_k=100,
include_metadata=True
)
# Post-filtering вручную
filtered_results = [
r for r in results['matches']
if r['metadata']['doc_type'] == 'financial'
][:10] # оставляем топ-10 после фильтрации
Важно Pinecone не поддерживает pre-filtering. Если вам нужен pre-filtering, рассмотрите Qdrant или Weaviate.
6. Оптимизация: индексы для часто фильтруемых полей
Проблема Фильтрация по метаданным без индекса — это линейное сканирование всех документов (O(n)). Для 1 млн документов — медленно.
Решение Создавать индексы (как в обычных БД) для полей, по которым часто фильтруют.
Термин «Индекс» (Index) в контексте метаданных Структура данных (B-tree, hash), ускоряющая поиск по значениям поля.
Какие поля индексировать
| Поле | Тип индекса | Почему |
|---|---|---|
doc_type | Hash (enum) | Мало уникальных значений (pdf, docx, xlsx) |
year | B-tree (range) | Фильтрация по диапазонам (year >= 2024) |
author_id | Hash | Точное совпадение |
timestamp | B-tree | Сортировка и диапазоны |
category | Hash | Категоризация |
Qdrant: индексация метаданных
from qdrant_client.http.models import PayloadSchemaType
client.create_payload_index(
collection_name="documents",
field_name="doc_type",
field_schema=PayloadSchemaType.KEYWORD # hash-индекс
)
client.create_payload_index(
collection_name="documents",
field_name="year",
field_schema=PayloadSchemaType.INTEGER # B-tree индекс
)
Результат Время фильтрации падает с O(n) до O(log n).
7. Гибридный подход (лучшая практика)
Идея Использовать двухэтапную фильтрацию для баланса скорости и точности.
def hybrid_filter(query_embedding, strict_filter, top_k=10):
# Шаг 1: Pre-filtering с широкими условиями (нестрогий фильтр)
loose_filter = Filter(
should=[
FieldCondition(key="doc_type", match=Match(value="financial")),
FieldCondition(key="doc_type", match=Match(value="legal"))
]
)
candidates = client.search(
query_vector=query_embedding,
query_filter=loose_filter,
limit=100 # больше, чем нужно
)
# Шаг 2: Post-filtering с точными условиями
exact_filtered = [
r for r in candidates
if r.payload['year'] >= 2024 and r.payload['year'] <= 2025
]
return exact_filtered[:top_k]
Преимущества
- Шаг 1 (loose filter) отсекает явно нерелевантные типы документов
- Шаг 2 (exact filter) применяется только к top-100, быстро
- Риск потери релевантных документов минимален (мы взяли top-100, а не top-10)
8. Когда использовать фильтрацию (use cases)
| Use case | Фильтр по | Почему |
|---|---|---|
| Юридический RAG | doc_type='contract', year>=2020 | Клиент хочет договоры только за последние 5 лет |
| Корпоративный поиск | department='finance', is_confidential=false | Сотрудник из финансов видит только свои документы |
| Медицинский RAG | patient_id='...', doc_type='lab_result' | Врач видит только лабораторные результаты конкретного пациента |
| E-commerce | category='electronics', price <= 50000 | Поиск товаров только в категории электроника |
| Новостной поиск | source='reuters', date>='2025-01-01' | Новости только от Reuters за последний месяц |
Термин «Multi-tenant RAG» Система, обслуживающая多个 клиентов. Фильтрация по tenant_id обязательна для изоляции данных.
9. Пет-проект для закрепления
Задача Реализовать поиск с фильтрацией по метаданным в Qdrant и сравнить pre-filtering vs post-filtering.
Инструменты Python, Qdrant (Docker), Faker (для синтетических данных)
Шаги
- Сгенерировать 100 000 документов со случайными метаданными:
doc_type: "financial", "legal", "technical", "marketing"year: 2020-2025author: "alice", "bob", "charlie"
- Загрузить в Qdrant, создать индексы на
doc_typeиyear - Реализовать три подхода:
- Pre-filtering (Qdrant native)
- Post-filtering (поиск top-100, фильтр в Python)
- Гибридный (loose filter + exact filter)
- Для 100 запросов замерить:
- Проанализировать:
- При каком фильтре pre-filtering быстрее?
- При каком post-filtering теряет релевантные документы?
- Гибридный подход даёт лучший trade-off?
Ожидаемый результат Вы поймёте, когда какой подход выбирать.
Связь с другими вопросами
| Вопрос | Тема |
|---|---|
| 1 | RAG архитектура (retrieval с фильтрацией) |
| 4 | Выбор векторной БД (Qdrant vs Pinecone для фильтрации) |
| 5 | Оценка retrieval (recall может падать при post-filtering) |
| 6 | Гибридный поиск (можно комбинировать с фильтрацией) |
| 84 | Multi-tenant RAG (фильтрация по tenant_id обязательна) |
| 84 | Collection per tenant vs metadata filtering |
12. Как вы фильтруете документы по метаданным в векторной БД|12. Как вы фильтруете документы по метаданным в векторной БД|12. Как вы фильтруете документы по метаданным в векторной БД|12 полностью разобран. Переходим к вопросу 13, когда будете готовы|Вопрос 12 полностью разобран. Переходим к вопросу 13, когда будете готовы]]
Навигация
- Предыдущий: 11
- Следующий: 13
- Индекс: 00. Индекс разборов