π An enterprise knowledge assistant with authorization-aware retrieval, citation validation, and offline evaluation.
| Metric | Value |
|---|---|
| Documents | 275 (from 260 Markdown files) |
| Chunks | 450 |
| Body Tokens | 124,472 (MiniLM tokenizer) |
| Vector Store | ChromaDB β’ 450 vectors β’ 384 dims |
| Backend Tests | 211 passed |
| Quality Gates | β ruff β’ β mypy β’ β format (348 files) |
| Embedding Model | sentence-transformers/all-MiniLM-L6-v2 |
| Reranker | cross-encoder/ms-marco-MiniLM-L6-v2 |
| BM25 | k1=1.5, b=0.75 (library defaults) |
The offline evaluation harness measures retrieval and generation quality. Metrics are computed over the golden dev/holdout sets β never tuned on holdout data.
| Strategy | Recall@5 | Recall@10 | Hit@5 | MRR@10 | nDCG@10 |
|---|---|---|---|---|---|
| Dense-only | measured* | measured* | measured* | measured* | measured* |
| BM25-only | measured* | measured* | measured* | measured* | measured* |
| Hybrid (BM25+Dense RRF) | measured* | measured* | measured* | measured* | measured* |
| Hybrid + Cross-Encoder Rerank | measured* | measured* | measured* | measured* | measured* |
*Actual values computed by uv run run_retrieval_eval.py β see evaluation/ for per-category breakdowns.
| Metric | Description |
|---|---|
| Faithfulness | Answer supported by retrieved evidence |
| Answer Relevancy | LLM answer addresses the query |
| Context Precision | Retrieved chunks relevant to answers |
| Context Recall | Retrieved chunks cover answer scope |
| Metric | Value |
|---|---|
| Citation Validity Rate | validated server-side |
| Unsupported Citation Rate | rejected/invalid IDs removed |
| Citation Coverage | % of answers with β₯1 valid citation |
| Metric | Invariant |
|---|---|
| Unauthorized chunks reaching LLM | 0 (enforced) |
| Cross-user cache leakage | blocked by scope-hashed keys |
| Cross-role cache leakage | blocked by RBAC in cache keys |
Never tune on the holdout set. Development data (
development.jsonl: 283 items) is separate from final reporting data (holdout.jsonl: 71 items).
USER β AUTH β AUTHORIZATION β QUERY ROUTER
β
ββββββββββββββββββββββββββΌββββββββββββββββββββββββ
βΌ βΌ βΌ
DOCUMENT RAG STRUCTURED SQL REFUSE
(Hybrid: BM25+Dense, (PostgreSQL, authz) (safe refusal)
RRF, Reranker) (query results) response
β
βΌ
GENERATION (Groq LLM)
β
βΌ
CITATION VALIDATION
β
βΌ
FINAL RESPONSE
Key invariants: RRF on chunk_id not document_id; authorization before evidence; cache scope includes auth context; LLM never decides authorization.
- Heterogeneous ingestion β Markdown-aware hierarchical chunking (atoms + packing)
- Hybrid retrieval β BM25 sparse + dense vector β RRF fusion
- Cross-encoder reranking β Bounded candidate set (top 20β50), ms-marco-MiniLM-L6-v2
- RBAC + ABAC β Roles
employee|manager|hr|finance|it|admin+ attributes (tenant_id,department,owner_user_id) - Secure cache β L1 exact + L2 semantic, scope-hashed keys, RBAC-aware, TTL invalidation
- Structured SQL β PostgreSQL via psycopg[binary], template-based query path (not free-form SQL)
- Citation-validated generation β Server-side evidence validation; unknown citation IDs rejected
- Offline evaluation β RAGAS + deterministic retrieval metrics + ablation studies
- Vector-store adapter β Provider-agnostic (ChromaDB default, Pinecone migratable)
- Full CI readiness β formatting/lint/typecheck/tests on every PR
securecorp-ai-hybridrag/
βββ AGENTS.md CLAUDE.md README.md LICENSE
βββ pyproject.toml uv.lock .env.example .gitignore
βββ docs/company_bible.md
β
βββ evaluation/
β βββ golden_set/{qa_pairs,qa_pairs_hard}.json
β βββ retrieval_eval/{retrieval_queries,expected_chunks}.json
β βββ security_eval/{rbac_queries,forbidden_queries}.json
β
βββ data/
β βββ raw/ 275 documents in 260 Markdown files
β βββ processed/{registry,registry_issues,chunks}.jsonl
β βββ golden/ Phase 8 golden set (JSONL)
β β βββ development.jsonl 283 items, 9 categories
β β βββ holdout.jsonl 71 items, 9 categories
β βββ sweep/ chunking-sweep artifacts
β βββ chroma_db/ dense index (gitignored)
β
βββ scripts/
β βββ build_registry.py raw corpus β registry.jsonl
β βββ build_chunks.py registry β chunks.jsonl
β βββ build_index.py chunks.jsonl β ChromaDB
β βββ build_golden.py Phase 8: bootstrap+hand-fill
β βββ audit_golden.py Phase 8: validate golden set schema
β βββ seed_db.py PostgreSQL synthetic seeding
β βββ run_retrieval_eval.py overall 4-arm metrics
β βββ run_ablation.py Phase 8: 4-arm Γ per-category
β βββ run_ragas.py Phase 8: RAGAS adapter
β βββ run_chunking_sweep.py Phase 8: 4-cell sweep
β βββ run_cache_experiments.py Phase 8: hit rate + isolation
β βββ run_phase8_eval.py Phase 8: orchestrator + HTML report
β
βββ src/hybridrag/
β βββ config.py Settings (env prefix HYBRIDRAG_)
β βββ domain/models.py Document, Chunk, RankedChunk, enums
β βββ ingestion/ frontmatter, loaders, registry,
β β structure, tokenization, chunking
β βββ indexing/ embeddings, vector_store, chroma_store,
β β chunk_metadata, bm25_store, pipeline
β βββ retrieval/ fusion, reranker, hybrid
β βββ generation/ provider, formatter, generator
β βββ authorization/ models, engine
β βββ routing/ router
β βββ structured/ db, query_path
β βββ caching/ redis_cache, history
β βββ evaluation/ retrieval_eval, citation_metrics,
β β ragas_adapter, ragas_runner,
β β redis_cache_eval, html_report
β βββ assistant.py end-to-end orchestration
β βββ api/ FastAPI routes + schemas
β
βββ tests/
βββ unit/ test_config, test_domain_models,
β test_frontmatter, test_loaders,
β test_registry, test_structure,
β test_chunking, test_indexing, test_bm25
βββ integration/test_chroma_store.py real Chroma, fake vectors
βββ test_structured.py test_cache.py test_abstention.py
βββ security/ (stub)
βββ evaluation/ test_retrieval_eval_metrics,
test_citation_metrics,
test_ragas_adapter,
test_redis_cache_eval,
test_html_report,
test_phase8_smoke
# Install
uv sync
# Quality gates (all pass)
uv run pytest # 211 passed, 1 skipped (PHASE8_RUN smoke)
uv run ruff check . # clean
uv run ruff format --check . # clean (348 files formatted)
uv run mypy src # clean (44 source files, no issues)
# Run the backend
uv run uvicorn src.hybridrag.api.main:app --reload
# Run the frontend
cd web && npm install && npm run dev # localhost:3000Topology: Next.js frontend on Vercel, FastAPI backend on Railway,
with Railway managed Postgres + Redis and Chroma Cloud for the dense
index. The backend builds from the root Dockerfile (see railway.toml); the
frontend builds from web/Dockerfile / Vercel's native Next.js build.
-
Create a project and add two plugins: Postgres and Redis.
-
Add your repo as a service (Railway auto-detects the
Dockerfile). -
Set the service variables (Variables tab β RAW editor). The app reads Railway's native
DATABASE_URL/REDIS_URLdirectly, or the prefixed overrides β set whichever you prefer:# Data services β reference the plugins (names must match your services) HYBRIDRAG_DATABASE_URL=${{Postgres.DATABASE_URL}} HYBRIDRAG_REDIS_URL=${{Redis.REDIS_URL}} # Secrets / providers HYBRIDRAG_JWT_SECRET=<a long random secret> HYBRIDRAG_GROQ_API_KEY=<groq key> # Chroma Cloud (dense index) HYBRIDRAG_CHROMA_CLOUD=true HYBRIDRAG_CHROMA_API_KEY=<key> HYBRIDRAG_CHROMA_TENANT=<tenant-uuid> HYBRIDRAG_CHROMA_DATABASE=securecorp HYBRIDRAG_CHROMA_SERVER_URL=api.trychroma.com # Split-domain auth cookie (frontend and API are on different origins) HYBRIDRAG_CORS_ORIGINS=["https://<your-app>.vercel.app"] HYBRIDRAG_AUTH_COOKIE_DOMAIN= HYBRIDRAG_AUTH_COOKIE_SAMESITE=none HYBRIDRAG_AUTH_COOKIE_SECURE=true
The
preDeployCommand(python scripts/seed_db.py) creates the schema and seeds synthetic records on each deploy β idempotent (IF NOT EXISTS+ON CONFLICT DO NOTHING). If Postgres isn't wired yet it logs a warning and skips (exit 0) so the deploy still succeeds in document-RAG-only mode.
-
Import the repo, set the project root directory to
web/. -
Set the production env var so the browser calls your Railway API:
NEXT_PUBLIC_API_BASE=https://<your-backend>.up.railway.app
NEXT_PUBLIC_*is inlined at build time, so a change requires a redeploy β not just a restart.
curl https://<your-backend>.up.railway.app/api/health
# {"status":"ok","retriever_wired":true,"redis_ok":true,"database_ok":true}Then open the Vercel URL, log in with a demo user, and run a query β the
cross-site session cookie is sent because the API sets SameSite=None; Secure.
MIT β see LICENSE for details.
- Project: SecureCorp AI β HybridRAG + Reranking
- Repository:
securecorp-ai-hybridrag - Purpose: Enterprise GenAI/RAG portfolio project