Hybrid-RAG for workplace safety with n8n: what works — and what doesn't

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

TL;DR. Hybrid retrieval — full-text search plus vector search plus a cross-encoder reranker — is what makes a workplace-safety assistant actually trustworthy in production. n8n handles ingestion and routing well, but it demands a different way of thinking than programming. Weaknesses are real: JavaScript-to-Python impedance at query time, debugging is painful, and version migrations bring surprises you couldn't see coming. An honest account.


A safety officer is up on the scaffolding and needs an answer right now. Which fall-protection equipment is mandatory for work at heights above three metres on a wet surface? He has his phone, he has Telegram. The regulation itself is somewhere on a SharePoint server, buried in a 340-page PDF that someone uploaded in 2021. The SharePoint search doesn't recognise the term he just typed.

Ten minutes later, he has the answer. Not because he found the right keyword — because he stopped searching and just asked.

That is what we built. And it was not as simple as that one sentence makes it sound.

The problem with documents nobody can find

The real issue with workplace-safety documentation is not that the documents are missing. Most industrial companies have their H&S regulations, their internal SOPs, their risk assessments — properly filed, regularly updated, stored as PDFs on a network drive or in SharePoint.

The problem is the gap between the document and the question.

Classic full-text search finds "fall protection" when someone types "fall protection". It does not find it when someone asks "securing scaffolding in the rain" — even though both phrases refer to the same thing. And a language model turned loose without grounding in the actual documents will invent regulation paragraphs that may plausibly exist but probably don't read quite like that. An assistant that cannot be trusted on safety questions will not be used. An assistant that goes unused saves nobody's time — and in the worst case, nobody at all.

What safety officers actually ask — real test cases

Before you understand why Hybrid-RAG, you need to understand what users actually type. Three examples from our golden evaluation set of 120 verified H&S queries, drawn from real production use:

PPE for painters: "What protective clothing and PPE is required for painting work?" — The system retrieves the relevant sections from the applicable safety regulation. Profession-specific queries are among the most common: painters, crane operators, electricians on site — each group has distinct requirements, and users expect a direct answer, not a pointer to a general chapter.

Working at height: "General requirements for workplaces involving work at height" — A fixture in every test strategy, because it exposes the gap between lexical and semantic retrieval cleanly. The regulatory term for this is often encoded as "elevated workstation" or buried under scaffolding provisions. Without hybrid retrieval, users regularly hit dead ends — even when the information is right there in the corpus.

Chapter query: "Requirements from Section IV" — No semantic context, just a direct chapter reference. These score 100 % in our test suite. The metadata-based chapter routing path fires before the vector search is even involved — a deliberate design decision in the query workflow, not a side effect.

What caught us off guard in practice: users regularly send photographs of documents — handwritten safety briefings, scanned pages from outdated print editions, screenshots forwarded from other chats. OCR is not a nice-to-have feature. It is a mandatory specification item.

Why pure vector RAG is not enough

When we first built the system, we started with dense-only retrieval: semantically similar chunks from the vector store, top-5 passed to the language model. That works surprisingly well for general questions — until the first on-site audit with the client.

There, questions come in like: "What does the construction safety regulation, Section IV, item 48 say?" Exact-match retrieval is the opposite of semantic similarity. Regulation codes, section numbers, specific clause references — these are keywords, not a semantic search problem. No embedding model helps here. Full-text search has to.

Hybrid-RAG solves exactly this tension. Postgres FTS with multilingual language-aware analyzers runs in parallel with pgvector cosine similarity. Results are merged using Reciprocal Rank Fusion (k=60, top-12), then a cross-encoder reranker (bge-reranker-base, running locally) produces the final top-5. On our golden evaluation set, recall@5 went from 0.71 to 0.88. The cross-encoder adds roughly 150 ms of latency. It is worth it.

Why n8n — and what way of thinking it demands

n8n is not a RAG framework. It is a workflow automation tool — and that is precisely the point.

When you are building a RAG system for an industrial client, the real engineering challenge is not the retrieval algorithm. The challenge is: how do the 340 documents sitting in three different systems get into the vector store regularly and automatically? Who triggers reprocessing when an SOP is updated? How do you treat a scanned image-PDF differently from a machine-readable text?

n8n answers these questions visually and in a maintainable way. The ingestion pipeline — Google Drive webhook, download document, deduplication, OCR if needed, chunking, categorisation, embedding, upsert into pgvector — is an n8n workflow that lives in a git repository, gets reviewed in pull requests and can be modified without a redeployment.

n8n Ingestion Workflow: PROD2.0 - WorkSafety - 00-Import Knowledgebase. Documents from Google Drive are checked, split, categorised, and stored as vectors in Postgres. Fig. 1: The ingestion workflow in n8n — two pages, more than 30 nodes. Every step is visible: from the Google Drive source through OCR and chunking to the categorised upsert into pgvector.

But mastering n8n is not the same as being a good programmer. This is underestimated consistently. Someone who starts from Python or Java thinks in functions, return values and control flow. n8n thinks in flows: data moves through nodes, each node receives the output of the previous one. Instead of writing result = myFunction(input), you configure, connect and parameterise.

That sounds simpler. It isn't. n8n expressions — {{ $node["NodeName"].json.field }} — have their own logic, which is neither standard JavaScript nor a classical template engine. Error handling in the visual editor is different from try/catch: you connect "error" outputs to separate nodes that log, notify or trigger retries. Anyone who does not understand this builds workflows that fail silently.

A concrete example from our system: we needed LDA topic modelling inside the ingestion pipeline — to automatically categorise newly uploaded documents before they reach pgvector. In Python, that is a twenty-line scikit-learn block. In n8n, it is a Python task-runner node running in a separate Docker container, with pymorphy2 for Russian morphological analysis, gensim and NLTK — communicating with the main n8n process over a custom IPC protocol. Setting that up was not a standard n8n configuration. It was a custom Docker build with its own Dockerfile, its own Python environment and its own runner image.

Further cases that required deep n8n expertise:

Multilingual query detection: A single JavaScript node detects whether the incoming request is in Russian (Cyrillic), German (language-specific keywords), Slovenian or English — and switches the correct FTS analyzer and prompt template accordingly. That is not plug-and-play. It requires precise understanding of how n8n handles binary data, when characters arrive as proper UTF-8 and when they don't.

Domain switching via user command: Superusers can switch the active document corpus mid-conversation with /set_domain construction or /set_domain all — no restart required. Technically, this is a separate n8n workflow for command detection that hooks into the main query workflow and writes to a Postgres session table. The difficulty: Telegram webhooks are stateless. Managing session state cleanly across conversation IDs without creating race conditions was not trivial.

Activation code management: Users activate the bot with a one-time code. n8n handles the entire validation logic: receive code, check it in Postgres, invalidate on first use, create user session, send privacy notice, await acknowledgement. Seven chained actions in a single workflow — and each one can fail at a different point.

Error routing to dedicated channels: Every failed ingestion event lands automatically in a separate Telegram channel for the H&S team. It sounds simple. In practice it means: every workflow node has an error output connected to a central error-handler workflow that normalises the error context, structures it and forwards it. A dedicated workflow just for errors — and it is larger than most functional workflows.

On the query side, n8n handles the Telegram webhook, analyses the request with an LLM (language detection, keyword extraction, routing decision), and routes it to different retrieval paths depending on the strategy.

n8n Query Workflow: PROD2.0 - Worksafety - Hybrid RAG Query. From the user question through language detection and query analysis to three retrieval routes, cross-encoder, and answer generation. Fig. 2: The query workflow in n8n — webhook receipt, LLM-driven query analysis, three retrieval routes (Broad, Hybrid Search, Chapter-based), merge, context assembly, and answer dispatch.

Three things that proved their value regardless:

Self-hosted. n8n runs on the client's own server. Documents never leave the network — for workplace-safety data in industrial environments, this is not negotiable.

Error handling via webhook. Failed ingestions are immediately routed to a dedicated channel. The H&S team sees corpus gaps before any user hits them.

GitOps for workflows. Workflow JSON is version-controlled, changes go through code review, no blind clicking in a UI that nobody else can see.

Vibe coding with your own API key. Because n8n runs self-hosted and API keys are configured directly in workflow nodes, a working style opens up that has no equivalent in classical IDEs: you describe the desired output of a node to an LLM — Claude, a locally hosted model, whatever you have a key for — get the JavaScript or Python block back, and paste it straight in. No compile step, no deployment, immediate test against the next real document. This works not because n8n is somehow "AI-native", but because every node has a clearly defined input and an expected output — ideal conditions for generative iteration and instant verification. Combine this with a custom test framework — ours is a pytest-based golden-set harness covering 120 verified H&S queries — and a growing library of specialised sub-workflows (custom "skills" for document classification, language detection, error formatting, activation-code logic), and you get a development environment that pairs rapid iteration with reliability. We write new node logic in minutes — but without the test framework, we would not know whether it is correct.

What does not work well — the honest part

n8n is JavaScript. The reranker, the embedding pipeline, the LLM dispatcher — all Python. Every call from n8n into RAG logic is an HTTP call to a FastAPI service. For the ingestion pipeline, this is fine — it runs asynchronously. At query time, latency compounds differently.

A Telegram user sends a question. n8n picks it up, builds an HTTP request, calls the Python orchestrator. The orchestrator runs retrieval, reranking, model dispatch, returns the answer. n8n forwards it. In that chain there are two serialised HTTP round-trips per request — typically 80–150 ms extra overhead. For a workplace-safety assistant with a manageable number of users, that is acceptable. For a system with hundreds of concurrent requests, n8n as query router would be the wrong choice.

The second problem is debugging. When a generated citation is wrong — the cross-encoder ranked the wrong chunk, or the FTS analyzer did not recognise a term — you can see the failure in n8n's execution history, but you cannot see why. In one test run, every content query came back as a failure. Not because the RAG system was broken — because Windows curl was transmitting Cyrillic characters as ????????. n8n reported the HTTP call as successful, because technically it was. The system then correctly returned "information not found in sources" — because it had literally received no question. The evaluation harness (Python/pytest over the 120-question golden set) lives outside n8n and has to be run separately. n8n's execution history does not replace it.

Third, and most consequential: version migrations.

We migrated to n8n 2.2.3 in February 2026 — Docker image 1.113.3+. What we encountered was a complete rebuild of the Python task-runner infrastructure. In n8n 2.x, the external code runner communicates over a changed IPC protocol. That meant a new custom Dockerfile, a new runner image build pipeline, five testing phases (unit, integration, end-to-end, performance, data quality), and three weeks of running old and new environments in parallel.

The real pain was not the rebuild — it was the things we could not see in advance. A JavaScript node for chapter extraction had an undocumented tolerance for malformed UTF-8 sequences in n8n 1.x. In 2.x, it threw a hard exception. Not in the changelog. Not in the release notes. It surfaced in an edge-case test with a 5 MB PDF from 2018 that contained a broken character code. If that document had not been in the test collection, we would have found out in production — when a safety officer's operating instruction happened to have that format.

Version migration in n8n means: thorough testing with a real production data subset, not synthetic test cases. The gap between what the release notes say changed and what actually changed is real.

What 94 % citation accuracy means — and what the other 6 % mean

Our golden set covers 120 H&S questions with pre-defined expected citations, manually verified quarterly. 94 % of answers link to a chunk that actually supports the statement.

The 6 % are cases where the question was too vague, the document was not in the corpus, or the cross-encoder ranked the wrong passage. The answer to these cases is not to optimise the 6 % away — it is that the assistant returns "not found in my sources" rather than inventing something. That is not a system failure. That is a design decision.

An assistant that hallucinates safety regulations but keeps users happy is more dangerous than one that says it does not know. That sentence sounds obvious. We have explained it more than once, because "no answer" initially looks like failure to some clients.

What to take away from this

Hybrid-RAG is genuinely worth the complexity for regulated domains — but only when full-text and vector search actually work together. Lexical-first is not an optimisation to schedule for later: for regulation codes and paragraph numbers, it is a prerequisite for reliability.

n8n is more powerful than its "no-code tool" reputation suggests. It is now a stable, production-ready orchestration platform — with real Python code nodes, direct Postgres integration, GitOps support and a degree of flexibility that classical workflow engines do not offer. Used properly, n8n enables systems that a standalone Python script cannot: visually traceable, maintainable without deep programming knowledge, extensible without redeployment.

But mastering n8n does not mean "no programming". It means a different kind of programming: flow-based rather than imperative, node-based rather than functional, with its own logic for error handling, state management and version discipline. Treating n8n like Python produces systems that break for unexpected reasons.

For the ingestion side of a RAG pipeline in a regulated domain, n8n is an honest choice. For query-time orchestration under load — and especially for streaming responses — calling Python directly is the cleaner path.

And the question worth asking before going live is not "How good is the assistant on average?" It is "What happens in the cases where it is wrong, and who notices first?" A system with 94 % accuracy sounds reassuring. The question is whether the remaining 6 % are distributed evenly across low-stakes questions, or concentrated in exactly the queries that matter most.

Figure that out before you deploy. Not after.


More on the Worksafety Superassistant architecture: sintaris.net/portfolio