Context
Engineers in the industrial and energy sector need answers buried in thousands of pages of OEM manuals and IOMs. A generic chatbot isn't enough: every answer has to cite the exact page, and each business unit may only see its own documents.
Approach
- Agentic hybrid retrieval: multi-turn query rewriting, then a tool-using agent (vector search, keyword search, document reads; capped at 4 turns) over pgvector HNSW and Postgres full-text search fused with RRF, with parent/child context expansion.
- Citation grounding: answers cite page-level sources, structural verification drops any citation that doesn't match a retrieved chunk, and everything streams to the client over SSE.
- Ingestion: Docling with OCR, contextual chunk enrichment, SHA-256 dedup, embedding reuse, and leased workers with retries.
- Tenant isolation: 23 permissions across Global, Organization and Unit scopes, a unit filter on every retrieval query, and isolation tests in both services.
- Auth: JWT bearer tokens for every sign-in method, including Microsoft Entra ID single sign-on.
- Guardrails: retrieved text is treated as data, never instructions; answers come only from cited sources, and the system declines when nothing relevant is found. A rule-based intent check turns away off-topic requests before any retrieval runs.
- Defence in depth: unit-scoped authorization in the API, a unit filter in every SQL retrieval path, and an internal RAG service the browser can never reach directly.
- Re-ingest tooling: embedding model and version tracked on every chunk, with unchanged chunks reusing their stored vectors when documents are reprocessed.
- Provider-agnostic adapters for LLMs, embeddings, rerankers and parsers, running on Microsoft Foundry (chat and text-embedding-3-large).
Key decisions
- pgvector over a dedicated vector DB: one database holds metadata, vectors and tenant filters.
- Hybrid search plus RRF over a reranker alone: keyword search catches exact matches that embeddings miss.
- The browser never calls the RAG service directly; the API layer owns auth and persistence.
- PyMuPDF rejected over its AGPL licence.
Hardest problems
- Page drift: the parser invented page numbers, corrupting citations. Fixed with nullable page bounds end to end, embeddings preserved on re-ingest, and a backfill script.
- Concurrency crash: parallel vector and keyword searches on one async DB session failed, so they now run sequentially.
- Hedge text breaking answers: fixed with grounded decoding, with the verified final answer treated as canonical.
Leadership
- Team and tech lead for 4 engineers: wrote the architecture docs and ERD, set PR review rules and CI quality gates, and estimated work for management.
- Ran product demos for the client and interviewed engineering candidates.
Outcome
Delivered to the client with 91 API endpoints, ~680 tests and a retrieval eval harness with a CI recall gate. A 1,300+ page manual was ingested into 3,800+ searchable chunks.