Перейти к содержанию

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 выглядит так:

  1. Trigger или Manual Run запускает ingestion.
  2. HTTP Request/GitHub/Notion/Google Drive получает документы.
  3. Code node нормализует поля metadata.
  4. Default Data Loader получает text и metadata.
  5. Text Splitter режет документ на chunks.
  6. Embeddings node создаёт vectors.
  7. 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 останутся без новых полей и будут выпадать из фильтров.