Metadata design для Vector Store в n8n: как сделать RAG управляемым ¶
Обновлено: 2026-05-29
Короткий ответ ¶
Metadata в vector store — это не декоративные поля, а система управления retrieval. Без metadata RAG ищет “похожий текст” по всей базе и легко достаёт не тот язык, старую версию документа, чужого клиента, черновик или внутреннюю инструкцию. В n8n metadata задаётся на этапе загрузки документов через loader и используется при retrieval/filtering в vector store nodes. Хороший metadata design заранее отвечает на вопросы: кому можно видеть документ, к какой версии продукта он относится, на каком языке написан, актуален ли он, какой source URL цитировать и как удалить или переиндексировать его позже.
Почему metadata важнее, чем кажется ¶
Векторный поиск хорошо находит семантически похожие куски. Но он плохо понимает business constraints. Если пользователь спрашивает “как настроить webhook в production”, vector store может найти старую инструкцию, dev-заметку, английскую страницу, внутренний runbook или документ для другого тарифа. Семантически всё похоже. Бизнесово — нет.
Metadata превращает RAG из “поиска похожих фраз” в управляемую систему: сначала отфильтровать допустимые документы, потом искать смысловую близость. Это снижает hallucination, улучшает цитирование, ускоряет retrieval и помогает выполнять требования доступа.
Базовая модель metadata ¶
Минимальный набор полей:
{
"source_id": "docs_webhook_production_2026_05",
"source_url": "https://nodbot.ru/playbooks/webhook-production-checklist/",
"title": "Webhook production checklist",
"doc_type": "playbook",
"language": "ru",
"product": "n8n",
"topic": "webhook",
"audience": "ops",
"access_level": "public",
"version": "2026-05-29",
"updated_at": "2026-05-29",
"is_current": true
}
Этого достаточно для публичной базы знаний. Для SaaS, внутренних порталов и multi-tenant систем нужны дополнительные поля.
Поля для multi-tenant и доступа ¶
Если один vector store хранит документы разных клиентов, добавьте:
{
"tenant_id": "acme",
"workspace_id": "support_ru",
"visibility": "customer_visible",
"allowed_roles": ["support", "admin"],
"data_class": "internal",
"contains_pii": false
}
Но не полагайтесь только на prompt: “не показывай чужие документы”. Доступ должен ограничиваться до retrieval. Если документ не должен быть доступен пользователю, он не должен попадать в кандидаты.
Поля для актуальности ¶
RAG часто ошибается из-за старых документов. Добавьте:
{
"effective_from": "2026-05-01",
"effective_to": null,
"is_current": true,
"supersedes": "docs_webhook_production_2025_11",
"status": "published"
}
На retrieval-этапе фильтруйте status=published и is_current=true. Старые версии можно оставить для истории, но не давать их обычному support-боту.
Как задавать metadata в n8n ¶
Обычно pipeline выглядит так:
- Trigger или Manual Run запускает ingestion.
- HTTP Request/GitHub/Notion/Google Drive получает документы.
- Code node нормализует поля metadata.
- Default Data Loader получает text и metadata.
- Text Splitter режет документ на chunks.
- Embeddings node создаёт vectors.
- Vector Store node вставляет документы.
Ключевой момент: metadata нужно задать до вставки в vector store. Если chunk уже загружен без source_url или tenant_id, модель может дать ответ, но вы не сможете корректно процитировать или отфильтровать источник.
Пример нормализации metadata ¶
const source = $json;
const slug = source.url?.replace(/^https?:\/\/[^/]+\//, '').replace(/\/$/, '');
return [{
json: {
pageContent: source.content,
metadata: {
source_id: source.id || slug,
source_url: source.url,
title: source.title,
doc_type: source.section || 'article',
language: source.language || 'ru',
product: 'n8n',
topic: source.topic || 'automation',
audience: source.audience || 'general',
access_level: source.private ? 'internal' : 'public',
status: source.status || 'published',
is_current: source.is_current !== false,
updated_at: source.updated_at || new Date().toISOString().slice(0,10),
checksum: source.checksum
}
}
}];
pageContent идёт в loader, metadata должна сопровождать каждый chunk.
Не делайте metadata слишком свободной ¶
Плохой вариант:
{
"tags": "webhook, useful, maybe old, client stuff"
}
Хороший вариант:
{
"topic": "webhook",
"status": "published",
"is_current": true,
"audience": "ops",
"access_level": "public"
}
Свободные tags можно оставить, но для фильтров нужны стабильные поля с ограниченными значениями.
Какие поля использовать для фильтрации ¶
| Сценарий | Фильтр |
|---|---|
| Русский support bot | language=ru, status=published, access_level=public |
| Внутренний bot для ops | audience=ops, access_level in internal/public |
| Документы конкретного клиента | tenant_id=current_tenant |
| Только актуальная версия | is_current=true, effective_to=null |
| Только playbooks | doc_type=playbook |
| Исключить черновики | status!=draft или status=published |
| RAG для тарифов | product, plan, region |
Перед проектированием metadata выпишите вопросы, которые пользователи будут задавать, и ограничения, которые нельзя нарушать.
Разные vector store могут по-разному применять фильтры ¶
В n8n разные vector store nodes имеют свои особенности фильтрации. Например, один store может трактовать несколько metadata-полей как AND, другой — как OR. Поэтому нельзя проектировать RAG только “в голове”. Нужно открыть документацию выбранного vector store node, проверить семантику фильтра и добавить тесты. Если вам нужен строгий доступ, лучше делать предварительный фильтр или отдельные collections/indexes, а не надеяться на сложную комбинацию условий.
Metadata и цитирование ¶
Если AI-ответ должен ссылаться на источники, каждый chunk должен иметь минимум:
source_url;title;section_heading;updated_at;chunk_id.
Без section_heading ответ может сослаться на правильную страницу, но не объяснить, где именно найден факт. Без updated_at пользователь не поймёт, актуальна ли инструкция.
Metadata и переиндексация ¶
Добавьте checksum или content_hash. Тогда ingestion workflow сможет сравнить текущую версию документа с прошлой и не переиндексировать неизменившиеся страницы. Также храните source_id: если страница удалена, можно удалить все chunks с этим source_id.
Если vector store не поддерживает удобное удаление по metadata, заведите отдельный registry в Postgres/Google Sheets: source_id, chunk_ids, checksum, indexed_at, status. Это поможет делать cleanup.
Типовые ошибки ¶
Первая ошибка — хранить всё в одном vector store без tenant/access filters. Вторая — не сохранять URL источника. Третья — не различать draft/published. Четвёртая — не хранить язык и потом получать английские ответы на русские запросы. Пятая — не иметь версии документа, из-за чего RAG цитирует устаревшую инструкцию. Шестая — использовать tags вместо нормализованных полей. Седьмая — менять metadata schema без переиндексации.
Тесты metadata ¶
Проверьте минимум:
- пользователь tenant A не получает документы tenant B;
- русский запрос не достаёт английский документ, если есть русская версия;
- draft не попадает в ответ;
- старая версия не побеждает новую;
- source_url возвращается в финальном ответе;
- filter по audience работает;
- удалённый документ исчезает после cleanup;
- разные vector store не меняют смысл фильтров незаметно.
FAQ ¶
Нужно ли хранить metadata на каждом chunk? ¶
Да. Retrieval возвращает chunks, а не исходный документ целиком. Если chunk без metadata, вы теряете доступ, источник и версию именно для этого фрагмента.
Можно ли хранить PII в metadata? ¶
Обычно не стоит. Metadata часто логируется и используется в фильтрах. Для PII лучше хранить нейтральные идентификаторы, например customer_id, а не email или телефон.
Что лучше: один vector store или несколько? ¶
Для публичной базы можно один. Для строгих tenants, разных уровней доступа или сильно разных языков часто безопаснее отдельные collections/indexes.
Нужно ли добавлять updated_at? ¶
Да. Это помогает freshness-фильтрам, цитированию и отладке RAG. Пользователь должен понимать, свежий ли источник.
Что делать при изменении metadata schema? ¶
Запустить переиндексацию или миграцию. Иначе старые chunks останутся без новых полей и будут выпадать из фильтров.