Hybrid-RAG для охраны труда с n8n: что работает, а что нет
Коротко о главном. Гибридный поиск (полнотекстовый плюс векторный плюс cross-encoder) делает ассистента по охране труда по-настоящему надёжным в боевых условиях. n8n хорошо справляется с конвейером загрузки документов и маршрутизацией запросов, но требует особого мышления: это не программирование в классическом смысле, а оркестрация. Слабые места тоже есть: на уровне запросов мешает стык JavaScript и Python, отладка сложных цепочек мучительна, а миграции между версиями преподносят сюрпризы, которых заранее не предвидишь. Это честный отчёт о том, что мы построили и что сделали бы иначе.
Специалист по охране труда стоит на лесах, и прямо сейчас ему нужен ответ. Какие средства защиты от падения обязательны при работе на высоте свыше трёх метров при мокром основании? Телефон есть, Telegram есть. Сам норматив лежит где-то на SharePoint-сервере, в 340-страничном PDF, который кто-то загрузил в 2021 году. Поиск по SharePoint не находит термин, который он только что ввёл.
Через десять минут ответ у него есть, и не потому, что он подобрал нужное ключевое слово, а потому, что перестал искать и просто спросил.
Именно это мы и построили. И это оказалось не так просто, как звучит в одном предложении.
Проблема документов, которые никто не находит
Настоящая проблема с документацией по охране труда не в том, что документов нет. У большинства промышленных предприятий есть свои нормативы, внутренние инструкции, оценки рисков, аккуратно разложенные и регулярно обновляемые, в виде PDF на сетевом диске или в SharePoint.
Проблема в расстоянии между документом и вопросом.
Классический полнотекстовый поиск найдёт «страховочный пояс», если человек напишет «страховочный пояс». Он не найдёт ничего, если человек спросит «защита при работе на высоте», хотя оба выражения означают одно и то же. А языковая модель, запущенная без привязки к реальным документам, изобретёт параграфы нормативов, которые, может, и существуют в похожем виде, но точно написаны иначе. Ассистентом, которому нельзя доверять в вопросах безопасности, пользоваться не станут. А ассистентом, которым не пользуются, никого не спасёшь.
Что специалисты по ОТ спрашивают на самом деле: реальные тест-кейсы
Чтобы понять, зачем нужен Hybrid-RAG, сначала стоит посмотреть, что пользователи реально пишут. Вот три примера из нашего golden-сета со 120 проверенными запросами, взятыми прямо из продуктивной эксплуатации:
СИЗ для маляра: «Какая спецодежда и СИЗ обязательны при малярных работах?» Система отвечает соответствующими разделами действующего норматива по охране труда. Запросы по профессиям приходят очень часто: маляры, крановщики, электрики на стройплощадке. У каждой профессии свои требования, и пользователи ждут конкретного ответа, а не отсылки к общей главе.
Работы на высоте: «Общие требования к организации рабочих мест для производства работ на высоте». Классика для любой тест-стратегии: этот случай наглядно показывает разрыв между лексическим и семантическим поиском. В языке нормативов «работы на высоте» часто закодированы как «работы на повышенном рабочем месте» или спрятаны в разделе о подмащивании. Без гибридного поиска пользователи регулярно уходят ни с чем, хотя информация в корпусе есть.
Запрос по главе: «Требования из раздела IV». Прямой запрос к конкретному разделу, без семантического контекста. Такие запросы дают 100% успех в нашем тест-сьюте. Маршрутизация по метаданным срабатывает раньше, чем вообще подключается векторный поиск. Это осознанное архитектурное решение, а не случайный эффект.
Что нас удивило на практике: пользователи регулярно отправляют сфотографированные документы, будь то рукописные инструктажи, отсканированные страницы из устаревших печатных изданий или скриншоты из других чатов. Поэтому OCR у нас не дополнительная опция, а обязательная часть спецификации.
Почему чистый векторный RAG не работает
Когда мы строили систему с нуля, то начали с dense-only поиска: семантически похожие чанки из векторного хранилища, top-5 отправляется в языковую модель. Для общих вопросов это работает неожиданно хорошо, но только до первого аудита на объекте клиента.
Там приходят такие вопросы: «Что написано в приказе, раздел IV, пункт 48?» Точное совпадение — это противоположность семантического сходства. Коды приказов, номера разделов и обозначения пунктов ищутся как ключевые слова, семантический поиск здесь ни при чём. Embeddings не помогут, нужен полнотекстовый поиск.
Hybrid-RAG снимает именно это противоречие. Postgres FTS с многоязычными анализаторами работает параллельно с pgvector. Результаты объединяются через Reciprocal Rank Fusion (k=60, top-12), затем cross-encoder-реранкер (bge-reranker-base, локально) выдаёт итоговый top-5. На нашем golden-сете recall@5 вырос с 0,71 до 0,88. Cross-encoder добавляет около 150 мс задержки, и оно того стоит.
Почему n8n и какого мышления он требует
n8n не RAG-фреймворк, а инструмент автоматизации процессов, и в этом весь смысл.
Когда строишь RAG-систему для промышленного предприятия, быстро понимаешь, что реальная инженерная задача не в алгоритме поиска, а в том, как 340 документов из трёх разных систем регулярно и автоматически попадают в векторное хранилище. Кто запускает повторную обработку, когда обновляется СОП? Как обработать PDF с картинкой иначе, чем машиночитаемый текст?
n8n отвечает на эти вопросы наглядно и так, что решение можно сопровождать. Конвейер загрузки (вебхук из Google Drive, скачивание документа, дедупликация, OCR при необходимости, чанкинг, категоризация, генерация эмбеддингов, upsert в pgvector) оформлен как n8n-workflow, который лежит в git-репозитории, меняется через pull request и обновляется без передеплоя.
Но владеть n8n и хорошо программировать — не одно и то же, и это постоянно недооценивают. Тот, кто начинал с Python или Java, думает функциями, возвращаемыми значениями и потоком управления. n8n думает потоками: данные движутся через узлы, каждый узел получает выход предыдущего. Вместо result = myFunction(input) ты не пишешь код, а конфигурируешь, соединяешь, параметризуешь.
Звучит проще, чем есть на самом деле. У выражений n8n вроде {{ $node["NodeName"].json.field }} собственная логика: это и не стандартный JavaScript, и не классический шаблонизатор. Обработка ошибок в визуальном редакторе устроена иначе, чем try/catch: «error»-выходы соединяются с отдельными узлами, которые логируют, оповещают или запускают логику повторных попыток. Кто этого не понимает, строит workflows, которые падают молча.
Конкретный пример из нашей системы: в конвейере загрузки понадобилось тематическое моделирование LDA, чтобы автоматически категоризировать новые документы до их попадания в pgvector. На Python это двадцать строк scikit-learn. В n8n это узел с Python task runner в отдельном Docker-контейнере, с морфологическим анализом текстов, gensim и NLTK, который общается с основным процессом n8n по собственному IPC-протоколу. Пришлось собрать кастомный Docker-образ со своим Dockerfile, своим окружением Python и своим runner-образом.
Другие случаи, потребовавшие глубокой компетенции в n8n:
Многоязычное определение языка запроса: Один JavaScript-узел определяет язык входящего запроса по написанию и ключевым словам и автоматически переключает нужный FTS-анализатор и шаблон промпта. Это не plug-and-play: нужно точно понимать, как n8n работает с бинарными данными, когда символы приходят в правильной кодировке, а когда нет.
Переключение домена по команде пользователя: Суперпользователи могут переключить активный корпус нормативов прямо во время разговора командой /set_domain строительство или /set_domain all, без перезапуска. Технически это отдельный n8n-workflow для распознавания команд, который встраивается в основной и пишет в таблицу сессий Postgres. Сложность в том, что вебхуки Telegram не хранят состояние, а чисто управлять им по conversation ID без race conditions непросто.
Управление кодами активации: Пользователи активируют бота одноразовым кодом. n8n берёт на себя всю логику: получить код, проверить его в Postgres, инвалидировать при первом использовании, создать сессию, отправить уведомление о конфиденциальности, дождаться подтверждения. Семь связанных действий в одном workflow, и каждое может упасть по-своему.
Маршрутизация ошибок в выделенный канал: Каждая неудачная загрузка документа автоматически попадает в отдельный Telegram-канал для команды ОТ. Звучит просто, но на деле у каждого узла workflow есть error-выход, ведущий к центральному обработчику ошибок. Это отдельный workflow, который нормализует контекст ошибки, структурирует его и пересылает. Он существует только ради ошибок и при этом больше большинства функциональных.
На стороне запроса n8n принимает вебхук от Telegram, анализирует запрос с помощью LLM (определение языка, извлечение ключевых слов, решение о маршруте) и направляет его по одному из путей поиска.
Четыре вещи, которые оправдали себя независимо от остального:
Самохостинг. n8n работает на сервере клиента. Документы не покидают сеть, и для данных по охране труда на промышленных предприятиях это условие не обсуждается.
Обработка ошибок через вебхук. Неудачные загрузки сразу уходят в выделенный канал. Команда ОТ видит пробелы в корпусе раньше, чем с ними столкнётся пользователь.
GitOps для workflows. JSON-описание workflow хранится в git, изменения проходят code review, никакого слепого кликания в UI, которого никто больше не видит.
Vibe coding с собственным API-ключом. Поскольку n8n работает на своей инфраструктуре, а API-ключи настраиваются прямо в узлах workflow, открывается рабочий режим, которого нет в классических IDE: описываешь языковой модели желаемый выход узла, получаешь JavaScript- или Python-блок и вставляешь его напрямую. Никакой компиляции, никакого деплоя, немедленный тест на следующем реальном документе. Это работает не потому, что n8n «AI-native», а потому, что у каждого узла чётко определены вход и ожидаемый выход, а это идеальные условия для итеративной генерации и мгновенной проверки. В сочетании с собственным тест-фреймворком (у нас это pytest-based golden-set harness со 120 проверенными запросами) и набором специализированных sub-workflows (собственные «скиллы» для классификации документов, определения языка, форматирования ошибок, логики кодов активации) получается среда разработки, где быстрая итерация сочетается с надёжностью. Новую логику узлов мы пишем за минуты, но без тест-фреймворка не знали бы, правильная ли она.
Что работает плохо: честная часть
n8n написан на JavaScript. Реранкер, конвейер эмбеддингов, диспетчер LLM — всё на Python. Каждое обращение из n8n к RAG-логике означает HTTP-вызов к сервису на FastAPI. Для конвейера загрузки это не проблема: он работает асинхронно. На уровне запроса задержки складываются иначе.
Пользователь в Telegram отправляет вопрос. n8n принимает его, строит HTTP-запрос, вызывает Python-оркестратор. Тот выполняет поиск и реранкинг, обращается к модели и возвращает ответ, а n8n пересылает его дальше. В этой цепочке на каждый запрос приходится два последовательных HTTP round-trip, обычно это 80–150 мс дополнительной задержки. Для ассистента по охране труда с умеренным числом пользователей это приемлемо. Для системы с сотнями параллельных запросов n8n в роли query router не подходит.
Вторая проблема — отладка. Когда сгенерированная цитата неверна (cross-encoder поставил неправильный чанк выше или FTS-анализатор не распознал термин), история выполнения n8n показывает сам факт, но не причину. В одном из тестовых прогонов все content-запросы вернули ошибку, и не потому, что RAG-система сломалась, а потому, что Windows curl передавал кириллические символы как ????????. n8n засчитал HTTP-вызов как успешный, ведь технически так оно и было. Система правильно ответила «информация не найдена», потому что буквально не получила вопроса. Тест-фреймворк (Python/pytest на golden-сете) живёт вне n8n и запускается отдельно, и история выполнения n8n его не заменяет.
Третья проблема, самая болезненная: миграции версий.
В феврале 2026 мы мигрировали на n8n 2.2.3, и пришлось полностью перестроить инфраструктуру Python task runner. В n8n 2.x внешний code runner общается по изменённому IPC-протоколу. Это означало новый кастомный Dockerfile, новый pipeline сборки runner-образа, пять фаз тестирования (unit, integration, end-to-end, performance, data quality) и три недели параллельной работы старой и новой среды.
Настоящая боль была не в самой перестройке, а в том, чего нельзя было предвидеть. В n8n 1.x JavaScript-узел для извлечения глав недокументированно прощал кривые UTF-8 последовательности, а в 2.x стал падать с жёстким исключением. Ни в changelog, ни в release notes об этом не было ни слова. Проявилось это при edge-case тесте с 5-мегабайтным PDF 2018 года с битым кодом символа. Не будь этого документа в тестовой коллекции, мы узнали бы о проблеме в продуктиве, при первом же обращении специалиста, чья инструкция оказалась именно в таком формате.
Поэтому миграция версий в n8n требует тщательного тестирования на реальном subset продуктивных данных, а не на синтетических тест-кейсах. Разрыв между тем, что написано в release notes, и тем, что изменилось на самом деле, вполне реален.
Что означают 94% точности цитирования и что стоит за оставшимися 6%
Наш golden-сет: 120 запросов по охране труда с зафиксированными ожидаемыми цитатами, которые раз в квартал проверяются вручную. 94% ответов ссылаются на чанк, который реально подтверждает утверждение.
6% — это случаи, когда вопрос был слишком размытым, документа не было в корпусе или cross-encoder поставил неверный фрагмент выше. Правильная реакция на них не в том, чтобы «оптимизировать эти 6%», а в том, чтобы ассистент возвращал «не найдено в источниках» вместо того, чтобы что-то придумать. Это не системная ошибка, а проектное решение.
Ассистент, который выдумывает нормативы, но при этом устраивает пользователей, опаснее того, кто честно говорит «не знаю». Звучит очевидно, и всё же объяснять это нам приходилось не раз, потому что «нет ответа» поначалу выглядит для некоторых клиентов как сбой.
Что взять с собой
Hybrid-RAG действительно окупает свою сложность в регулируемых доменах, но только если полнотекстовый и векторный поиск реально работают вместе. Приоритет лексического поиска нельзя откладывать как оптимизацию на потом: для кодов приказов и номеров пунктов это условие надёжности.
n8n мощнее, чем можно подумать по его репутации «no-code-инструмента». Сегодня это стабильная, готовая к продуктиву платформа оркестрации с настоящими узлами Python-кода, прямой интеграцией с Postgres, поддержкой GitOps и гибкостью, которой нет у классических workflow-движков. При правильном использовании n8n позволяет строить системы, которые одним Python-скриптом не сделаешь: визуально отслеживаемые, обслуживаемые без глубоких знаний программирования, расширяемые без передеплоя.
Но владеть n8n не значит «не программировать». Это другое программирование: потоковое вместо императивного, узловое вместо функционального, со своей логикой обработки ошибок, управления состоянием и дисциплиной версионирования. Кто работает с n8n как с Python, строит системы, которые ломаются по неожиданным причинам.
Для загрузочной части RAG-конвейера в регулируемом домене n8n вполне честный выбор. Для оркестрации запросов под нагрузкой, а тем более для потоковых ответов, прямой вызов Python чище.
И главный вопрос перед запуском в эксплуатацию звучит не «насколько хорош ассистент в среднем?», а «что происходит, когда он ошибается, и кто заметит это первым?» Система с 94% точностью звучит обнадёживающе. Но важно понять, распределены ли оставшиеся 6% равномерно по низкорисковым вопросам или сосредоточены именно там, где это важнее всего.
Выясните это до деплоя, а не после.
Подробнее об архитектуре Worksafety Superassistant: sintaris.net