Hybrid-RAG для охраны труда с n8n: что работает — и что нет

2026-05-25 · Sintaris · rag, hybrid-rag, n8n, worksafety, automation, industrial, pgvector, llm

Коротко о главном. Гибридный поиск — полнотекстовый плюс векторный плюс cross-encoder — это то, что делает ассистента по охране труда по-настоящему надёжным в боевых условиях. n8n хорошо справляется с конвейером загрузки документов и маршрутизацией запросов, но требует особого мышления — не программирования в классическом смысле, а оркестрации. Слабые места есть: импеданс JavaScript–Python на уровне запросов, отладка сложных цепочек вызывает боль, а миграции между версиями преподносят сюрпризы, которых не предвидишь заранее. Честный отчёт о том, что мы построили и что бы мы сделали иначе.


Специалист по охране труда стоит на лесах и прямо сейчас ему нужен ответ. Какие средства защиты от падения обязательны при работе на высоте свыше трёх метров при мокром основании? Телефон есть, Telegram есть. Сам норматив лежит где-то на SharePoint-сервере, в 340-страничном PDF, который кто-то загрузил в 2021 году. Поиск по SharePoint не находит термин, который он только что написал.

Через десять минут у него есть ответ. Не потому что он нашёл нужное ключевое слово — потому что он перестал искать и просто спросил.

Именно это мы и построили. И это оказалось не так просто, как звучит в одном предложении.

Проблема документов, которые никто не находит

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

Проблема — в расстоянии между документом и вопросом.

Классический полнотекстовый поиск найдёт «страховочный пояс», если человек напишет «страховочный пояс». Он не найдёт ничего, если человек спросит «защита при работе на высоте» — хотя оба выражения означают одно и то же. А языковая модель, запущенная без привязки к реальным документам, изобретёт параграфы нормативов, которые, может, и существуют в похожем виде, но точно написаны иначе. Ассистенту, которому нельзя доверять в вопросах безопасности, пользоваться не станут. А ассистентом, которым не пользуются, никого не спасёшь.

Что специалисты по ОТ спрашивают на самом деле — реальные тест-кейсы

Прежде чем понять, зачем Hybrid-RAG, нужно понять, что пользователи реально пишут. Три примера из нашего golden-сета с 120 проверенными запросами, взятыми прямо из продуктивной эксплуатации:

СИЗ для маляра: «Какая спецодежда и СИЗ обязательны при малярных работах?» — Система отвечает соответствующими разделами действующего норматива по охране труда. Запросы по профессиям — одни из самых частых: маляры, крановщики, электрики на стройплощадке — у каждой профессии свои требования, и пользователи ждут конкретного ответа, а не отсылки к общей главе.

Работы на высоте: «Общие требования к организации рабочих мест для производства работ на высоте» — Классика для любой тест-стратегии: этот случай наглядно показывает разрыв между лексическим и семантическим поиском. В языке нормативов «работы на высоте» часто закодированы как «работы на повышенном рабочем месте» или в разделе о подмащивании. Без гибридного поиска пользователи регулярно уходят ни с чем — хотя информация в корпусе есть.

Запрос по главе: «Требования из приказа 883Н, раздел 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 Ingestion Workflow: PROD2.0 - WorkSafety - 00-Import Knowledgebase Рис. 1: Конвейер загрузки в n8n — две страницы, более 30 узлов. Каждый шаг виден: от источника в Google Drive через OCR и чанкинг до категоризированного upsert в pgvector.

Но владеть 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, который нормализует контекст ошибки, структурирует его и пересылает. Отдельный workflow только для ошибок — и он больше большинства функциональных.

На стороне запроса n8n принимает вебхук от Telegram, анализирует запрос с помощью LLM (определение языка, извлечение ключевых слов, решение о маршруте) и направляет его по одному из путей поиска.

n8n Query Workflow: PROD2.0 - Worksafety - Hybrid RAG Query Рис. 2: Workflow обработки запроса в n8n — приём вебхука, LLM-анализ запроса, три маршрута поиска (Broad, Hybrid Search, Chapter-based), объединение, сборка контекста и отправка ответа.

Три вещи, которые оправдали себя независимо от остального:

Самохостинг. 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) и три недели параллельной работы старой и новой среды.

Настоящая боль была не в перестройке — а в том, что нельзя было предвидеть. JavaScript-узел для извлечения глав имел недокументированную толерантность к кривым UTF-8 последовательностям в n8n 1.x. В 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/portfolio