Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

Β 

History

5 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

🧠 Veritas-RAG β€” Production-Grade Personal Knowledge Engine

Python LangChain Pinecone Groq Streamlit HuggingFace

A self-evaluating, hallucination-resistant RAG system featuring a 3-stage retrieval pipeline (Hybrid Search β†’ Cross-Encoder Reranking β†’ LLM Generation), dual-LLM judge architecture, multi-modal ingestion, and session-scoped conversational memory.

Architecture Β· 3-Stage Pipeline Β· Judge Architecture Β· Roadmap


Executive Summary

Neural KB is a production-grade personal knowledge engine that transforms unstructured documents β€” PDFs, YouTube videos, and Notion exports β€” into a queryable, conversational intelligence layer. The system goes far beyond a basic RAG implementation by stacking three architectural layers that most portfolio projects lack entirely.

Layer 1 β€” Retrieval Quality: A custom Hybrid Retriever fuses BM25 sparse keyword search with Pinecone dense vector search via Reciprocal Rank Fusion (RRF). This ensures both semantic meaning and exact technical terms (e.g., Ξ²β‚‚=0.98, BLEU 27.3) are reliably retrieved.

Layer 2 β€” Precision Filter: A Cross-Encoder reranker (BAAI/bge-reranker-base) re-scores the top 12 candidates by joint query-document relevance before any text reaches the LLM, eliminating topically-adjacent noise that embedding similarity alone cannot distinguish.

Layer 3 β€” Answer Verification: Every generated answer is independently evaluated by a second LLM pass β€” the Judge β€” which detects hallucinations, measures contextual grounding, and returns a calibrated confidence score rendered as a live UI card.

"Don't just retrieve and generate β€” retrieve precisely, rerank aggressively, generate, and verify."

Key Metrics from Production Testing

Benchmark Result
Direct factual retrieval (e.g., "How many attention heads?") 100% Confidence βœ…
Multi-step numerical retrieval (e.g., BLEU score = 27.3) 90% Confidence βœ…
Anaphora resolution ("What were its hyperparameters?" β†’ Adam) 100% β€” Memory resolved βœ…
Out-of-context hallucination guard ("FIFA World Cup 2026") 0% β€” Correctly refused βœ…
Ingestion throughput (15-page technical PDF) 79 chunks, ~12s βœ…
Reranking stage latency (12 candidates, CPU) ~0.8s (cached model) βœ…

Tech Stack

Layer Technology Purpose
Orchestration LangChain 0.3+ (LCEL) Chain composition, memory, retrieval
LLM Groq / Llama 3.1 8B Instant Generation + evaluation judge (250+ tok/s)
Embeddings HuggingFace all-MiniLM-L6-v2 384-dim semantic vectors (bi-encoder)
Reranker BAAI/bge-reranker-base Cross-encoder precision filter (stage 2)
Sparse Retrieval BM25Retriever (rank-bm25) Keyword-exact in-memory search
Dense Retrieval Pinecone Serverless (AWS us-east-1) Scalable ANN search + metadata filtering
RRF Fusion Custom HybridRetriever(BaseRetriever) Zero-dependency RRF implementation
PDF Ingestion LangChain PyPDFLoader Page-level document extraction
YouTube Ingestion youtube-transcript-api + yt-dlp Dual-plan transcript pipeline
Speech-to-Text Groq Whisper Large v3 Audio transcription fallback
Memory LangChain RunnableWithMessageHistory Session-scoped conversational state
UI Streamlit Premium dark-mode chat dashboard

System Architecture

flowchart TD
    subgraph INGEST["πŸ“₯ Ingestion Layer"]
        PDF["πŸ“„ PDF\n(PyPDFLoader)"]
        YT["πŸŽ₯ YouTube\n(Dual-Plan Pipeline)"]
        NOTION["πŸ“ Notion\n(Markdown Export)"]
    end

    subgraph PROCESS["βš™οΈ Processing Layer"]
        SPLIT["RecursiveCharacterTextSplitter\nchunk_size=1000 | overlap=200"]
        EMBED["HuggingFace Embeddings\nall-MiniLM-L6-v2 | 384 dims"]
        BM25CORP["BM25 Corpus Cache\n_session_chunks: List[Document]"]
    end

    subgraph STORE["πŸ—„οΈ Storage Layer"]
        PINE["Pinecone Serverless\nAWS us-east-1 | cosine\nMetadata: source, method, page, title"]
        BM25IDX["BM25 In-Memory Index\nrank-bm25 | term frequency"]
    end

    subgraph RETRIEVAL["πŸ” 3-Stage Retrieval Pipeline"]
        HYBRID["Stage 1: HybridRetriever\nRRF Fusion β†’ 12 candidates"]
        RERANK["Stage 2: Cross-Encoder\nBAAI/bge-reranker-base β†’ top 4"]
        GEN["Stage 3: Llama 3 Generation\n+ Conversation Memory"]
    end

    subgraph EVAL["βš–οΈ Judge Layer"]
        JUDGE["LLM-as-Judge\nConfidence Score 0-100\nHallucination Detection"]
    end

    PDF --> SPLIT
    YT --> SPLIT
    NOTION --> SPLIT
    SPLIT --> EMBED
    SPLIT --> BM25CORP
    EMBED --> PINE
    BM25CORP --> BM25IDX

    PINE --> HYBRID
    BM25IDX --> HYBRID
    HYBRID --> RERANK
    RERANK --> GEN
    GEN --> JUDGE

    style INGEST fill:#1a1a2e,stroke:#7c6fff,color:#f0f0ff
    style PROCESS fill:#1a1a2e,stroke:#ff6f9c,color:#f0f0ff
    style STORE fill:#1a1a2e,stroke:#4dffa6,color:#f0f0ff
    style RETRIEVAL fill:#1a1a2e,stroke:#ffb347,color:#f0f0ff
    style EVAL fill:#1a1a2e,stroke:#ff5f7e,color:#f0f0ff
Loading

3-Stage Retrieval Pipeline

This is the core architectural differentiator of Neural KB. Most RAG implementations stop at Stage 1. Production systems require all three.

Stage 1 β€” Hybrid Retrieval (BM25 + Dense RRF)

A custom HybridRetriever class (subclassing BaseRetriever) fuses two retrieval signals without any external ensemble dependency:

flowchart LR
    Q["πŸ” User Query"] --> D["Dense Retriever\nPinecone cosine similarity\nβ†’ ranked list A"]
    Q --> S["Sparse Retriever\nBM25 term frequency\n→ ranked list B"]

    D --> RRF["⚑ RRF Fusion\nscore = Σ weight / rank + 60\ndense_w=0.6 | bm25_w=0.4"]
    S --> RRF

    RRF --> OUT["12 Candidates\n(sorted by RRF score)"]

    style RRF fill:#1a1a2e,stroke:#ffb347,color:#f0f0ff
    style OUT fill:#1a1a2e,stroke:#4dffa6,color:#f0f0ff
Loading

RRF Formula:

score(doc) = Ξ£α΅’  weightα΅’ / (rankα΅’(doc) + 60)

The constant 60 is a smoothing factor preventing top-ranked documents from dominating. Documents appearing in both lists receive additive scores β€” consensus between retrieval methods naturally surfaces the best candidates.

Why hybrid matters: Dense embeddings miss exact technical terms. Ξ²β‚‚=0.98, BLEU 27.3, h=8 β€” these short numeric values have unpredictable embedding geometry. BM25 treats them as high-IDF tokens and ranks chunks containing them at the top regardless of semantic similarity.

Stage 2 β€” Cross-Encoder Reranking

flowchart TD
    CANDS["12 Candidates\nfrom Stage 1"] --> CE

    subgraph CE["🎯 Cross-Encoder Scoring"]
        direction LR
        P1["query + chunk_1 β†’ score: 7.23 βœ…"]
        P2["query + chunk_2 β†’ score: 4.89 βœ…"]
        P3["query + chunk_3 β†’ score: 1.20 βœ…"]
        P4["query + chunk_4 β†’ score: -0.44 βœ…"]
        P5["query + chunk_5 β†’ score: -3.81 ❌"]
        P6["query + chunk_6..12 β†’ score: < -4 ❌"]
    end

    CE --> TOP4["Top 4 Chunks\n(noise eliminated)"]
    TOP4 --> LLM["Llama 3 Context\n~2,000 tokens | high precision"]

    style CE fill:#1a1a2e,stroke:#7c6fff,color:#f0f0ff
    style TOP4 fill:#1a1a2e,stroke:#4dffa6,color:#f0f0ff
Loading

Why cross-encoder beats bi-encoder for reranking:

Bi-encoders (like all-MiniLM-L6-v2) encode query and document independently, then compare via cosine similarity. The encoder never sees both together β€” semantic proximity in embedding space does not guarantee answer relevance.

A cross-encoder encodes [CLS] query [SEP] document [SEP] as a single input, producing one scalar relevance score. It captures exact term overlap, co-references, and direct answer relevance that cosine similarity structurally cannot.

Noise reduction example:

Chunk Bi-encoder score Cross-encoder score Passed to LLM?
"β₁=0.9, Ξ²β‚‚=0.98, Ξ΅=10⁻⁹ (Adam optimizer)" 0.71 +7.23 βœ…
"multi-head attention mechanism overview" 0.74 +1.20 βœ…
"beta testing software release cycles" 0.68 -3.81 ❌
"abstract: dominant sequence transduction..." 0.65 -4.60 ❌

The bi-encoder ranks "beta testing" higher than the optimizer chunk (0.68 vs 0.71) because "beta" shares semantic space. The cross-encoder drops it 11 points β€” clean context, zero noise.

Stage 3 β€” Generation with Memory

The top 4 reranked chunks are formatted with source provenance labels (including rerank scores for transparency) and passed to Llama 3 via a RunnableWithMessageHistory-wrapped LCEL chain that injects conversation history automatically.


The Judge Architecture

Dual-LLM Self-Evaluation

Every answer triggers a second, independent LLM call β€” the Judge β€” which receives the original question, retrieved context, and generated answer, then returns a structured evaluation.

sequenceDiagram
    participant U as πŸ‘€ User
    participant H as HybridRetriever
    participant CR as CrossEncoder
    participant G as Generator LLM
    participant J as Judge LLM
    participant UI as Streamlit UI

    U->>H: Submit query
    H->>H: Dense search (k=12) + BM25 (k=12)
    H->>H: RRF fusion β†’ 12 candidates
    H->>CR: Pass 12 candidates
    CR->>CR: Score all 12 jointly with query
    CR-->>G: Top 4 high-precision chunks
    G->>G: Generate answer (SYSTEM_PROMPT grounding)
    G-->>J: question + context + answer
    J->>J: Score contextual support (0-100)
    J->>J: Detect hallucination markers
    J-->>UI: JSON evaluation payload
    UI-->>U: Answer + Confidence Card + Mode Badge + Sources
Loading

Evaluation Schema

{
  "context_sufficient": true,
  "confidence_score": 92,
  "hallucination_detected": false,
  "hallucination_reason": "",
  "evaluation_summary": "Answer fully supported β€” all claims traceable to retrieved context."
}

Confidence Score Rubric

Score Interpretation UI Card
90–100 Every claim directly traceable to context 🟒 Context Verified
70–89 Mostly supported, minor inferential gaps 🟒 Context Verified
40–69 Partial support, some unverifiable claims 🟑 Moderate Confidence
0–39 Weak or no contextual grounding πŸ”΄ Low Confidence

Hallucination Guard β€” Two-Layer Defence

Layer 1 β€” Generator Self-Flagging: The SYSTEM_PROMPT instructs the LLM to prefix any outside-knowledge statement with [OUTSIDE KNOWLEDGE]. This creates an explicit, machine-readable signal in the answer text.

Layer 2 β€” Judge Independent Verification: The Judge LLM separately evaluates whether the answer contains claims absent from the retrieved context β€” regardless of any self-flagging. Two independent detection mechanisms means a hallucination must fool both the generator and judge simultaneously to pass through undetected.

Validated live result:

Query:  "Who won FIFA World Cup 2026?"
Answer: "[OUTSIDE KNOWLEDGE] I couldn't find this in your knowledge base."
Judge:  hallucination_detected: true | confidence_score: 0% πŸ”΄

Multi-Modal Ingestion Pipeline

YouTube: Two-Plan Architecture

flowchart TD
    URL["πŸ”— YouTube URL"] --> EXTRACT["Extract Video ID\n(v=, youtu.be, /shorts/, /embed/)"]
    EXTRACT --> PLANA

    subgraph PLANA["πŸ…°οΈ Plan A β€” Direct Transcript API"]
        API["youtube-transcript-api\nlist_transcripts()"]
        FETCH["find_transcript(en)\nfetch() β†’ Document list"]
        API --> FETCH
    end

    subgraph PLANB["πŸ…±οΈ Plan B β€” Whisper Transcription"]
        DL["yt-dlp audio download\nworstaudio | 32kbps MP3"]
        SIZE{"filesize\n> 24MB?"}
        FFMPEG["FFmpeg re-encode\n16kHz mono | 16kbps"]
        WHISPER["Groq Whisper Large v3\nTranscription API"]
        CLEAN["finally block\ndelete temp files"]
        DL --> SIZE
        SIZE -->|"Yes"| FFMPEG --> WHISPER
        SIZE -->|"No"| WHISPER
        WHISPER --> CLEAN
    end

    PLANA -->|"βœ… Success"| CHUNK
    PLANA -->|"❌ Bot detection / disabled captions"| PLANB
    PLANB --> CHUNK["RecursiveCharacterTextSplitter\nchunk_size=1000 | overlap=200"]
    CHUNK --> META["Attach metadata\nmethod: groq_whisper | title | source"]
    META --> PINE2["Upload to Pinecone\n+ register_chunks() for BM25"]

    style PLANA fill:#1a1a2e,stroke:#4dffa6,color:#f0f0ff
    style PLANB fill:#1a1a2e,stroke:#ffb347,color:#f0f0ff
Loading

Key engineering decisions:

  • Plan A fails silently on ~30% of videos (bot detection, disabled captions, region locks). Fallback is automatic and transparent.
  • The finally block in Plan B guarantees temp file deletion even if Whisper throws an exception β€” no disk leaks in long-running deployments.
  • FFmpeg re-encoding to 16kHz mono WAV keeps all audio under Groq's 24MB Whisper file size limit regardless of video length.
  • After ingestion, register_chunks() populates the in-memory BM25 corpus β€” both retrieval paths (dense + sparse) are fed simultaneously.

Conversation Design

Session-Scoped Memory

flowchart TD
    START(["πŸ”΅ User Query"]) --> CHECK{"session_id\nin store{}?"}

    CHECK -->|"No"| CREATE["ChatMessageHistory()\nstore[session_id] = history"]
    CHECK -->|"Yes"| LOAD["Load existing history"]

    LOAD --> TRIM{"len(messages)\n> 20?"}
    TRIM -->|"Yes"| EVICT["Evict oldest\nkeep messages[-20:]"]
    TRIM -->|"No"| INJECT
    EVICT --> INJECT

    CREATE --> INJECT["Build prompt\nSystemPrompt\n+ ChatHistory\n+ HumanMessage"]

    INJECT --> PIPELINE["3-Stage Retrieval Pipeline\nHybrid β†’ Rerank β†’ Generate"]
    PIPELINE --> SAVE["RunnableWithMessageHistory\nauto-saves HumanMessage + AIMessage"]

    SAVE --> NEXT{"Another\nquery?"}
    NEXT -->|"Yes"| CHECK
    NEXT -->|"Clear"| RESET["store[session_id] = ChatMessageHistory()"]
    RESET --> START

    style START fill:#7c6fff,color:#fff
    style RESET fill:#ff5f7e,color:#fff
    style PIPELINE fill:#1a1a2e,stroke:#ffb347,color:#f0f0ff
Loading

Anaphora Resolution

MessagesPlaceholder(variable_name="chat_history") injects the full conversation window into every prompt, enabling the LLM to resolve pronoun references across turns.

Validated 2-turn memory test:

Turn Query Resolved Reference Confidence
1 "What optimizer did the authors use?" β€” 100% βœ…
2 "What were its hyperparameters?" its β†’ Adam optimizer 100% βœ…
Result β₁=0.9, Ξ²β‚‚=0.98, Ξ΅=10⁻⁹ returned correctly Anaphora fully resolved βœ…

Memory architecture notes:

  • Window: 20 messages (10 exchanges). Older turns evicted to cap token spend.
  • Store: Global dict keyed by session_id. Multi-user isolated in dev. Production upgrade: Redis (RedisChatMessageHistory, drop-in).
  • Auto-persistence: RunnableWithMessageHistory handles read-before and write-after. No manual .add_user_message() calls.

Project Structure

rag-knowledge-base/
β”œβ”€β”€ app.py              # Streamlit UI β€” chat, sidebar, eval cards, mode badge
β”œβ”€β”€ ingestor.py         # Multi-source loader β€” PDF, YouTube (Plan A/B), Notion
β”œβ”€β”€ vector_store.py     # HybridRetriever, BM25, Pinecone upload/load, RRF
β”œβ”€β”€ retriever.py        # 3-stage pipeline β€” hybrid β†’ rerank β†’ generate β†’ evaluate
β”œβ”€β”€ reranker.py         # CrossEncoder module β€” BAAI/bge-reranker-base, score caching
β”œβ”€β”€ .env                # API keys (gitignored)
β”œβ”€β”€ requirements.txt    # Frozen dependencies
└── data/               # Local document storage

Installation & Quickstart

# 1. Clone and create virtual environment
git clone https://github.com/yourusername/neural-kb.git
cd neural-kb
python -m venv venv
source venv/bin/activate      # Windows: venv\Scripts\activate

# 2. Install dependencies
pip install -r requirements.txt

# 3. Configure environment
cp .env.example .env
# Add: GROQ_API_KEY, PINECONE_API_KEY, PINECONE_INDEX_NAME

# 4. Launch
streamlit run app.py

Note: On first query after ingestion, BAAI/bge-reranker-base (~278MB) downloads automatically and caches. Subsequent queries use the cached model with ~0.8s reranking latency on CPU.

Environment Variables

Variable Required Description
GROQ_API_KEY βœ… Groq API β€” LLM generation + Whisper transcription
PINECONE_API_KEY βœ… Pinecone serverless vector store
PINECONE_INDEX_NAME βœ… Index name (e.g. rag-knowledge-base)
HUGGINGFACEHUB_API_TOKEN Optional Only for private HF model access

Requirements

langchain>=0.3
langchain-groq
langchain-pinecone
langchain-huggingface
langchain-community
pinecone-client
sentence-transformers      # CrossEncoder reranker
rank-bm25                  # BM25 sparse retrieval
streamlit
python-dotenv
youtube-transcript-api
yt-dlp

Architecture Decisions β€” Engineering Rationale

Why a custom HybridRetriever instead of EnsembleRetriever?

LangChain's EnsembleRetriever lives in langchain.retrievers β€” a package not always present in constrained environments. The custom HybridRetriever(BaseRetriever) implements identical RRF fusion logic natively with zero additional dependencies. It also exposes rrf_score directly in document metadata, enabling full retrieval transparency in the UI.

Why BAAI/bge-reranker-base over ms-marco-MiniLM?

cross-encoder/ms-marco-MiniLM-L-6-v2 is 67MB and fast, but trained on web search passages. BAAI/bge-reranker-base (278MB) is trained on a broader multilingual corpus including technical and scientific text β€” substantially better score separation on documents like research papers. Score delta between relevant and irrelevant chunks averages ~11 points vs ~6 points for ms-marco.

Why all-MiniLM-L6-v2 over larger embedding models?

384-dimensional vectors β€” half of text-embedding-ada-002 (1536 dims) and all-mpnet-base-v2 (768 dims). At scale this is a 4x reduction in Pinecone storage and query cost with negligible quality degradation on factual retrieval tasks where the reranking stage compensates for any embedding imprecision.

Why Groq over OpenAI?

Groq's LPU hardware runs Llama 3 at 250+ tokens/second vs ~40 tok/s for GPT-4o. Neural KB makes two LLM calls per query (generation + judge). At 10 queries/minute this difference is architecturally significant β€” the full pipeline completes in under 3 seconds end-to-end on Groq's free tier.

Why temperature=0 for RAG?

Factual retrieval requires deterministic, reproducible outputs. temperature=0 forces the LLM to select the maximum-likelihood token at every decoding step β€” eliminating stochastic variation that would produce inconsistent answers to identical queries. The evaluation judge maintains temperature=0 for the same reason: confidence scores must be stable across re-evaluation.


Scalability & Roadmap

Implemented βœ…

Feature Status
Hybrid BM25 + Dense Retrieval (RRF) βœ… Live
Cross-Encoder Reranking (bge-reranker-base) βœ… Live
Dual-LLM Judge + Hallucination Detection βœ… Live
Multi-Modal Ingestion (PDF, YouTube, Notion) βœ… Live
Session-Scoped Conversational Memory βœ… Live
Metadata Filtering (PDF / YouTube scoped) βœ… Live
Retrieval Mode Badge in UI βœ… Live

Roadmap

Enhancement Description Impact
Redis Session Store Replace in-memory store{} with RedisChatMessageHistory Persistent memory across server restarts, horizontal scaling
Streaming Responses chain.astream() + st.write_stream() Eliminate perceived latency β€” tokens appear in real-time
Async Ingestion Celery/RQ job queue for PDF processing Non-blocking UI β€” large documents process in background
Namespace Isolation Pinecone namespace-per-user True multi-tenant isolation without multiple indexes
Document Versioning Differential re-ingestion via content hash Re-embed only changed pages, not entire documents
Score Threshold Tuning Expose score_threshold in UI slider User-controlled precision vs recall tradeoff

Live Test Results

Validated against "Attention Is All You Need" (Vaswani et al., 2017) β€” 15 pages, 79 chunks:

Pipeline: πŸ”€ Hybrid (BM25 + Dense) β†’ 🎯 Reranked (top 4) β†’ Llama 3

─────────────────────────────────────────────────────────────────
Query:   "How many attention heads does the base transformer use?"
Answer:  "The base model employs h = 8 parallel attention heads."
Mode:    πŸ”€ Hybrid + 🎯 Reranked
Score:   100% βœ… Context Verified

─────────────────────────────────────────────────────────────────
Query:   "What is the BLEU score reported for the base model?"
Answer:  "The base model achieves 27.3 BLEU on WMT 2014 EN-DE."
Score:   90% βœ… Context Verified

─────────────────────────────────────────────────────────────────
Query:   "What optimizer did the authors use?"
Answer:  "The authors used the Adam optimizer."
Score:   100% βœ… Context Verified

Query:   "What were its hyperparameters?"   ← anaphora: its = Adam
Answer:  "β₁=0.9, Ξ²β‚‚=0.98, Ξ΅=10⁻⁹"
Score:   100% βœ… Memory resolved anaphora correctly

─────────────────────────────────────────────────────────────────
Query:   "Who won FIFA World Cup 2026?"
Answer:  "[OUTSIDE KNOWLEDGE] I couldn't find this in your KB."
Judge:   hallucination_detected: true | 0% πŸ”΄ Correctly refused
─────────────────────────────────────────────────────────────────

License

MIT License β€” see LICENSE for details.


Built with LangChain Β· Pinecone Β· Groq Β· Streamlit Β· HuggingFace Β· rank-bm25 Β· sentence-transformers

Showcasing production RAG patterns: Hybrid Search Β· Cross-Encoder Reranking Β· LLM-as-Judge Β· Conversational Memory

⭐ Star this repo if it helped you build something better.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages