Обновление RAG-базы в n8n: как переиндексировать документы без старых ответов ¶
Обновлено: 2026-05-29
Короткий ответ ¶
RAG refresh — это процесс обновления документов в vector store так, чтобы AI-ответы ссылались на актуальные источники, а старые chunks не оставались в индексе. В n8n это лучше делать отдельным workflow: загрузить источник, очистить текст, посчитать checksum, сравнить с registry, удалить старые chunks по source_id, записать новые chunks и embeddings, обновить metadata и запустить retrieval-тесты. Главный риск — не ошибка индексации, а тихое накопление старых версий: пользователь спрашивает по новой политике, а RAG уверенно отвечает по прошлому документу.
Почему RAG-база устаревает ¶
RAG не знает, что документ обновился. Vector store хранит то, что вы туда положили. Если страница изменилась на сайте, Google Doc обновился, PDF заменили, а индекс не пересобрали, RAG продолжит возвращать старые chunks. Если вы просто добавите новые chunks поверх старых, проблема станет хуже: в retrieval попадут обе версии, а модель выберет ту, которая случайно ближе к запросу.
Особенно опасны темы, где актуальность критична: тарифы, политика возвратов, OAuth настройки, production checklist, юридические формулировки, инструкции по безопасности, runbook-и для инцидентов. Устаревший RAG-ответ может быть хуже отсутствия ответа, потому что звучит уверенно.
Что нужно хранить в source registry ¶
Source registry — это таблица или файл, где хранится состояние индекса. Без registry вы не знаете, что уже проиндексировано и какой версии.
Минимальная запись:
{
"source_id": "ai_rag_refresh",
"source_url": "https://nodbot.ru/ai/rag-refresh/",
"title": "Обновление RAG-базы в n8n",
"content_checksum": "sha256:4d2...",
"metadata_checksum": "sha256:91a...",
"embedding_model": "text-embedding-model-name",
"chunking_strategy": "section_1200_chars_overlap_150_v2",
"chunk_count": 12,
"status": "indexed",
"indexed_at": "2026-05-29T09:00:00Z",
"last_seen_at": "2026-05-29T09:00:00Z"
}
Registry можно хранить в Postgres, Google Sheets, Airtable, Supabase или любом управляемом источнике. Для production лучше Postgres: проще делать locks, транзакции, history и audit.
Полный refresh workflow в n8n ¶
- Trigger: Schedule Trigger раз в ночь, Webhook из CMS или Manual Trigger для редактора.
- List sources: получить список страниц/документов.
- Fetch source: загрузить HTML/Markdown/документ.
- Normalize: очистить от навигации, CTA, повторов, HTML и PII.
- Checksum: посчитать hash нормализованного текста.
- Compare registry: если hash не изменился, пропустить.
- Lock source_id: не дать двум refresh одновременно обновлять одну страницу.
- Delete old chunks: удалить chunks с тем же
source_id. - Chunk + metadata: создать новую версию chunks.
- Embeddings + insert: записать новые embeddings.
- Update registry: записать версию, количество chunks, дату.
- Run retrieval tests: проверить, что expected questions находят правильный источник.
- Alert on failure: если тесты упали, не считать refresh успешным.
Такой workflow можно делать без сложного кода, но важно, чтобы delete old и insert new были контролируемыми.
Delete/insert vs upsert ¶
Есть два подхода.
Delete then insert проще: удалить всё по source_id, затем записать новую версию. Минус — если insert упал посередине, источник временно пропал.
Versioned upsert безопаснее: новые chunks записываются с version_id, проходят тесты, затем registry переключается на новую версию, а старая удаляется позже. Это сложнее, но подходит для критичной базы.
| Подход | Когда использовать | Риск |
|---|---|---|
| Delete then insert | маленькая база, не критичный downtime | источник может исчезнуть при ошибке |
| Upsert by chunk_id | стабильный chunking, точечные изменения | сложно при изменении структуры |
| Versioned index | production RAG, важная база знаний | больше инфраструктуры |
| Blue/green collection | крупные обновления, миграция embeddings | нужно переключение коллекций |
Для первого production-варианта достаточно delete/insert + lock + alert. Для важного публичного помощника лучше versioned refresh.
Как считать checksum ¶
Checksum нужно считать не по сырому HTML, а по нормализованному тексту и важной metadata. Иначе любое изменение меню будет запускать переиндексацию, а изменение access_level может остаться незамеченным.
const crypto = require('crypto');
const normalized = $json.normalized_text;
const relevantMetadata = {
title: $json.title,
language: $json.language,
access_level: $json.access_level,
status: $json.status,
updated_at: $json.updated_at
};
const contentChecksum = crypto
.createHash('sha256')
.update(normalized)
.digest('hex');
const metadataChecksum = crypto
.createHash('sha256')
.update(JSON.stringify(relevantMetadata))
.digest('hex');
return [{ json: { ...$json, contentChecksum, metadataChecksum } }];
Если изменился только updated_at, возможно, переиндексация не нужна. Если изменился access_level, обязательно обновите metadata или удалите документ из публичной коллекции.
Incremental refresh ¶
Не нужно переиндексировать всё каждый час. Лучше делить источники на типы:
- частые изменения: тарифы, политики, API-инструкции — refresh по webhook или каждые 1–6 часов;
- обычные статьи: раз в день;
- архив/история: раз в неделю или вручную;
- критичные runbook-и: после merge/publish + smoke test.
Incremental refresh должен уметь удалять документы, которые пропали из источника. Если страница снята с публикации, chunks должны стать status=archived или удалиться. Иначе RAG будет отвечать по несуществующей странице.
Как обрабатывать удалённые и переименованные страницы ¶
Переименование URL — частая причина дублей. Старый source_url изменился, но source_id может остаться тем же. Поэтому source_id должен быть стабильным бизнес-ключом, а не просто URL. Например, ai_rag_refresh, а не https://nodbot.ru/ai/rag-refresh/.
Если страница переехала:
- сохранить тот же
source_id; - обновить
source_url; - удалить старые chunks;
- вставить новые;
- проверить, что RAG цитирует новый URL;
- оставить 301 на сайте для пользователей и поисковиков.
Если документ удалён, registry должен зафиксировать status=deleted, а chunks должны исчезнуть из public retrieval.
Как тестировать актуальность ¶
Добавьте freshness tests. Это не просто “нашёл ли документ”, а “нашёл ли правильную версию”.
[
{
"question": "Как сейчас обновлять RAG базу без старых chunks?",
"expected_source_id": "ai_rag_refresh",
"expected_min_updated_at": "2026-05-29",
"forbidden_terms": ["старый pipeline v1", "deprecated"]
}
]
После refresh workflow должен запускать эти кейсы. Если expected source не найден, значит индекс сломан. Если найден deprecated chunk, значит удаление старых версий не работает.
Как логировать refresh ¶
{
"refresh_run_id": "rag_refresh_20260529_0100",
"source_id": "ai_rag_refresh",
"previous_checksum": "sha256:old",
"new_checksum": "sha256:new",
"old_chunk_count": 9,
"new_chunk_count": 13,
"deleted_chunks": 9,
"inserted_chunks": 13,
"embedding_model": "...",
"retrieval_tests_passed": true,
"duration_ms": 28400
}
Эти логи важны, когда пользователь жалуется: “бот отвечает старым текстом”. Вы сразу видите, обновлялся ли источник и какой результат дали тесты.
Rollback ¶
Rollback нужен не только для кода, но и для RAG. Новая версия документа может быть плохой: сломанный HTML, пустой текст после парсинга, неправильная metadata, слишком мелкие chunks. Если вы удалили старые chunks и вставили плохие, база стала хуже.
Минимальная защита:
- перед удалением сохранить old registry;
- не считать refresh успешным без retrieval tests;
- если новых chunks слишком мало, остановить публикацию;
- если новая версия пустая, не удалять старую;
- хранить последние N версий для критичных источников.
Частые ошибки ¶
- добавлять новые chunks поверх старых;
- использовать URL как единственный ID;
- не проверять metadata changes;
- не удалять документы после снятия с публикации;
- запускать два refresh одновременно для одного источника;
- не тестировать retrieval после обновления;
- не логировать embedding model и chunking strategy;
- обновлять production index без rollback.
FAQ ¶
Как часто обновлять RAG-базу?
Зависит от источника. Критичные документы обновляйте по событию публикации, обычные статьи — раз в день, архив — реже.
Можно ли просто переиндексировать всё каждую ночь?
Можно для маленькой базы, но это дороже и рискованнее. Для роста лучше incremental refresh по checksum.
Почему бот отвечает по старой версии?
Вероятно, старые chunks не удаляются, source_id нестабилен или новая версия не прошла refresh.
Нужно ли пересчитывать embeddings при изменении metadata?
Если изменился только фильтр доступа или статус, embedding можно не пересчитывать, но metadata в store/registry нужно обновить. Если изменился текст, embeddings нужны заново.
Что делать, если refresh упал посередине?
Использовать lock, registry status и rollback. Для критичной базы лучше versioned index, где старая версия остаётся активной до успешного теста новой.