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

Embeddings в n8n: как выбрать модель, индексировать текст и не сломать RAG

Обновлено: 2026-05-29

Открыть мой план

Короткий ответ

Embeddings — это числовое представление текста, по которому vector store ищет похожие фрагменты не по точному совпадению слов, а по смысловой близости. В n8n embeddings нужны для RAG, AI-поиска, семантической классификации, дедупликации обращений и похожих документов. Главная ошибка — подключить Embeddings node и сразу индексировать весь сайт или все документы без очистки, chunking, metadata и стратегии обновления. Production-подход такой: подготовить текст, нарезать его на осмысленные chunks, добавить metadata, посчитать hash, сгенерировать embeddings одной выбранной моделью, записать в vector store и отдельно тестировать retrieval на реальных вопросах.

Что такое embeddings простыми словами

Embeddings превращают текст в вектор — массив чисел, который отражает смысл. Если два фрагмента говорят об одном и том же разными словами, их векторы будут ближе друг к другу, чем у случайных текстов. Поэтому запрос “почему бот отвечает устаревшей инструкцией” может найти фрагмент “RAG возвращает старые chunks после обновления документа”, даже если точные слова не совпадают.

В n8n embeddings обычно находятся между двумя слоями: source documents и vector store. Документы нужно сначала привести к чистому тексту, затем разбить на chunks, затем для каждого chunk сгенерировать embedding и сохранить его вместе с metadata. При ответе на вопрос workflow снова генерирует embedding для запроса, ищет похожие chunks и передаёт найденные фрагменты в AI Agent, Question and Answer Chain или другой LLM-узел.

Embeddings не “понимают истину”. Они только помогают найти похожие тексты. Если в базе лежит старый документ, embedding честно найдёт старый документ. Если chunk слишком короткий, поиск вернёт обрывок. Если metadata не отделяет публичные документы от внутренних, RAG может дать агенту опасный источник. Поэтому embeddings — это не волшебный поиск, а инженерный слой, которому нужны правила.

Когда использовать embeddings

Используйте embeddings, когда нужен смысловой поиск по текстам: база знаний поддержки, документация, runbook-и, статьи, письма, тикеты, резюме, описания товаров, FAQ, заметки менеджеров. Это особенно полезно, если пользователь формулирует вопрос иначе, чем написан документ.

Не используйте embeddings как замену SQL, фильтрам и точному поиску. Если нужно найти заказ по order_id, embedding не нужен. Если нужно проверить статус платежа, лучше обратиться к API платежной системы. Если нужно отобрать документы только за конкретный регион, сначала примените metadata filter, а не надейтесь, что embedding “сам поймёт регион”.

Задача Подходит ли embedding Почему
Найти инструкцию по похожему вопросу Да важен смысл, а не точные слова
Найти клиента по email Нет нужен точный ключ
Найти похожий тикет Да обращения могут быть сформулированы по-разному
Проверить актуальный статус оплаты Нет нужен API/source of truth
Подобрать chunks для RAG-ответа Да retrieval основан на смысловой близости
Разграничить доступ к документам Только вместе с metadata embedding сам не знает права доступа

Архитектура workflow для indexing

Нормальная indexing-схема в n8n должна быть отдельным workflow, а не частью чата. Индексация и ответ пользователю — разные процессы с разными рисками.

  1. Source trigger: Manual Trigger, Schedule Trigger, Webhook из CMS, GitHub, Google Drive, Notion или внутренней админки.
  2. Load document: забираем HTML, Markdown, PDF-текст, страницу help center или строку базы.
  3. Normalize: удаляем навигацию, CTA, cookie banners, повторяющиеся блоки, HTML-разметку и мусор.
  4. Extract metadata: source_id, URL, title, language, audience, access level, updated_at, status.
  5. Chunk: режем не механически, а по секциям и смысловым блокам.
  6. Checksum: считаем hash нормализованного текста, чтобы не переиндексировать неизменённые документы.
  7. Embedding: генерируем вектор выбранной моделью.
  8. Upsert/Delete old: удаляем старые chunks того же source_id или обновляем их по chunk_id.
  9. Write registry: фиксируем, сколько chunks создано, какой моделью, когда и с каким checksum.
  10. Quality check: запускаем несколько тестовых вопросов и смотрим, какие chunks вернулись.

Если всё это делать в одном AI Agent workflow, чат будет медленным, дорогим и нестабильным. Пользовательский запрос должен только искать в уже готовом индексе, а не индексировать документы на лету.

Как готовить текст перед embeddings

Самый важный этап — очистка. Плохой вход даёт плохой vector store. Для сайта Nodbot это особенно важно, потому что у многих страниц одинаковые блоки: “обновлено”, “сохранить в план”, повторяющиеся CTA, похожие вводные и навигационные элементы. Если индексировать это без фильтра, retrieval начнёт возвращать не уникальные фрагменты, а общий шаблон.

Перед embedding удаляйте:

  • меню, footer, breadcrumbs, кнопки, повторяющиеся CTA;
  • блоки “обновлено” и “сохранить в мой план”, если они не несут смысла;
  • одинаковые дисклеймеры;
  • пустые строки и лишние пробелы;
  • большие JSON/лог-фрагменты, если они не нужны для ответа;
  • персональные данные, если индекс будет использоваться публичным ботом.

Пример Code node для грубой нормализации:

const raw = $json.html || $json.text || '';
const text = String(raw)
  .replace(/<script[\s\S]*?<\/script>/gi, ' ')
  .replace(/<style[\s\S]*?<\/style>/gi, ' ')
  .replace(/<nav[\s\S]*?<\/nav>/gi, ' ')
  .replace(/<footer[\s\S]*?<\/footer>/gi, ' ')
  .replace(/<[^>]+>/g, ' ')
  .replace(/Сохранить в мой план/gi, ' ')
  .replace(/Открыть мой план/gi, ' ')
  .replace(/\s+/g, ' ')
  .trim();

return [{ json: { ...$json, normalized_text: text } }];

Это не финальный парсер, но уже лучше, чем отправлять HTML целиком.

Как выбирать embedding-модель

Главное правило: модель для indexing и модель для query должны быть совместимы. Если вы проиндексировали документы одной моделью, а запросы в рантайме считаете другой, качество поиска может резко просесть. Даже если размерность совпала, смысловое пространство может отличаться. Поэтому в registry нужно хранить embedding_provider, embedding_model, embedding_version и дату индексации.

Выбор модели зависит от языка, стоимости, latency, приватности и поддержки вашей инфраструктуры. Для русскоязычной базы знаний обязательно тестируйте русские запросы, транслит, смешанные запросы “webhook не работает after deploy” и реальные формулировки пользователей. Не выбирайте модель только по бенчмаркам: если ваши тексты короткие, шаблонные и технические, важнее проверить retrieval на собственном dataset.

Минимальный eval-набор:

[
  {
    "question": "Почему n8n webhook открывается в тесте, но не работает после активации?",
    "expected_source_id": "playbook_webhook_production_checklist",
    "must_not_return": ["diagnostics_telegram"]
  },
  {
    "question": "Как обновить RAG базу без старых ответов?",
    "expected_source_id": "ai_rag_refresh",
    "must_not_return": ["ai_rag_evaluation"]
  }
]

Если новая модель возвращает не те страницы, модель может быть хорошей вообще, но плохой для вашего корпуса.

Chunking и embeddings нельзя разделять

Embeddings зависят от chunking. Один огромный chunk содержит слишком много тем: модель найдёт его, но LLM получит лишний шум. Слишком маленький chunk теряет смысл: “проверьте ключ” непонятно без контекста, какой ключ и зачем. Для технических страниц обычно хорошо работают chunks по секциям: заголовок + 600–1500 символов текста + небольшой overlap.

Хороший chunk должен отвечать на один вопрос. Например, для страницы про WEBHOOK_URL один chunk может закрывать “почему production URL неправильный за reverse proxy”, а другой — “как проверить headers”. Не смешивайте их с общей вводной “что такое n8n”.

Добавляйте в текст chunk служебный контекст:

{
  "chunk_text": "Заголовок: RAG refresh. Раздел: Удаление старых chunks. При обновлении документа удалите chunks с тем же source_id...",
  "metadata": {
    "source_id": "ai_rag_refresh",
    "section_heading": "Удаление старых chunks",
    "topic": "rag_refresh",
    "language": "ru"
  }
}

Так retrieval лучше различает похожие страницы.

Что хранить рядом с embedding

Embedding без metadata почти бесполезен в production. Минимальный набор:

Поле Зачем нужно
source_id удалить/обновить все chunks документа
chunk_id точечно обновлять и цитировать фрагмент
source_url показывать ссылку на источник
title давать модели контекст
section_heading объяснять, откуда фрагмент
language не смешивать RU/EN без необходимости
access_level не отдавать внутренние документы публичному боту
status исключать draft/deprecated
updated_at учитывать свежесть
checksum понимать, изменился ли текст
embedding_model безопасно мигрировать индекс

Если metadata нет, потом невозможно объяснить, почему бот процитировал старый документ или почему пользователю показался внутренний runbook.

Production workflow для query

В пользовательском workflow не нужно индексировать документы. Он должен делать:

  1. принять вопрос;
  2. нормализовать вопрос;
  3. определить intent и язык;
  4. сгенерировать query embedding;
  5. применить metadata filters;
  6. получить top-k chunks;
  7. убрать дубли по source_id;
  8. передать chunks в AI node;
  9. потребовать ссылки на источники;
  10. проверить, что ответ основан на найденных chunks.

Если top-k пустой, честный ответ лучше галлюцинации: “В базе знаний нет достаточного источника, нужен ручной ответ”. Для support-бота это не провал, а нормальный guardrail.

Частые ошибки

  1. Индексировать всю страницу целиком. Retrieval возвращает длинный шумный документ, LLM выбирает случайные фрагменты.
  2. Не хранить source_id. Нельзя удалить старые chunks после обновления.
  3. Смешивать публичные и внутренние документы. Бот может раскрыть runbook или внутренний SLA.
  4. Менять embedding-модель без reindex. Поиск становится непредсказуемым.
  5. Не тестировать русские запросы. Корпус русский, а eval только английский.
  6. Индексировать boilerplate. Vector store возвращает одинаковые вводные вместо ответа.
  7. Считать similarity единственной метрикой. Высокий score не гарантирует правильный источник.

Как тестировать embeddings

Сделайте отдельный evaluation workflow: dataset вопросов → query embedding → vector store search → проверка expected source. Для каждого кейса храните expected source, unacceptable sources и комментарий. Смотрите не только top-1, но и top-3/top-5. Если правильный источник всегда на пятом месте, LLM может его проигнорировать.

Метрики:

  • top-1 accuracy;
  • top-3 recall;
  • доля устаревших источников;
  • доля внутренних источников в публичном режиме;
  • среднее число дублей одного source_id;
  • latency retrieval;
  • стоимость embedding на 1000 документов.

Что логировать

{
  "query": "как обновить rag базу",
  "embedding_model": "text-embedding-model-name",
  "top_k": 5,
  "filters": {"language": "ru", "access_level": "public"},
  "returned_sources": ["ai_rag_refresh", "ai_vector_store"],
  "deduped": true,
  "no_answer": false,
  "retrieval_latency_ms": 184
}

Без такого лога нельзя понять, виновата модель, chunking, metadata filter или устаревший индекс.

FAQ

Embeddings заменяют keyword search?
Нет. Лучше использовать оба подхода. Keyword search хорош для точных терминов, ID и названий ошибок. Embeddings хороши для смысловых вопросов.

Можно ли одной embedding-моделью индексировать русский и английский текст?
Можно, если модель хорошо работает с обоими языками, но это нужно проверять на собственном dataset. Добавляйте language в metadata.

Нужно ли пересчитывать embeddings после изменения текста?
Да, если изменился смысл chunk. Для этого нужен checksum и registry.

Можно ли хранить PII в vector store?
Только если это осознанное решение с доступами, retention и redaction. Для публичного помощника персональные данные лучше не индексировать.

Почему RAG возвращает похожий, но неправильный документ?
Чаще всего причина в плохом chunking, одинаковом boilerplate, слабой metadata schema или отсутствии eval-набора.