Christian Minich
Back to Projects

LLM Lingen

A modular, full-stack RAG research platform with swappable pipeline components, enabling systematic benchmarking of LLM configurations through an Abstract Factory architecture.

Tech Stack

pythonfastapihaystackreacttypescriptpytorchdockerragllmfaiss

LLM Lingen

A modular, full-stack RAG (Retrieval-Augmented Generation) platform designed for academic research, enabling systematic comparison of how different LLM pipeline components affect answer quality, all without writing a single line of code.

Motivation

When researching how different LLM components affect answer quality, you don't want to rewrite your entire pipeline every time you swap out an embedder, try a different chunking strategy, or switch LLM providers. LLM Lingen solves this by making every stage of the RAG pipeline a swappable module: configurable through a web dashboard with immediate evaluation against standardized benchmarks.

High-Level Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                        Frontend                              β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚  Legacy (HTML/JS)   β”‚    β”‚  React + TypeScript (Vite)  β”‚ β”‚
β”‚  β”‚  - Admin Dashboard  β”‚    β”‚  - Admin Dashboard          β”‚ β”‚
β”‚  β”‚  - User Chat UI     β”‚    β”‚  - Settings, Datasets,      β”‚ β”‚
β”‚  β”‚  - Jinja2 Templates β”‚    β”‚    Questions, Logs, Users   β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚           β”‚           REST API          β”‚                    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
            β”‚                             β”‚
            β–Ό                             β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    FastAPI Backend                            β”‚
β”‚                                                              β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚   Auth   β”‚ β”‚  Query   β”‚ β”‚   Chat   β”‚ β”‚  Evaluation   β”‚  β”‚
β”‚  β”‚  (JWT)   β”‚ β”‚  (RAG)   β”‚ β”‚ Sessions β”‚ β”‚  (Benchmark)  β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚ Datasets β”‚ β”‚ Q-Sets   β”‚ β”‚ Settings β”‚ β”‚    Logging    β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β”‚                                                              β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚
β”‚  β”‚              Service Layer (Factories)                β”‚   β”‚
β”‚  β”‚  Every component is instantiated via Abstract Factory β”‚   β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚
β”‚                                                              β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           β”‚
                           β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                   SQLAlchemy / SQLite                         β”‚
β”‚  Users, Pools, Settings, Datasets, QuestionSets, Logs, etc. β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

The RAG Pipeline

Each stage of the pipeline can be independently swapped at runtime through the admin dashboard:

PDF Upload β†’ PDF Parser β†’ Preprocessor β†’ Chunker β†’ Embedder β†’ Vector Store
                                                                    β”‚
User Query ──────────────────► Retrieval β†’ Reranker β†’ Prompt Builder β†’ LLM β†’ Response

Abstract Factory Pattern, The Core Design Decision

The defining architectural choice is the Abstract Factory Pattern applied consistently across every pipeline component. For academic research, you need to compare how different implementations of the same pipeline stage affect output quality.

The pattern is applied to 11 component families:

ComponentAvailable Implementations
LLMsOllama, OpenAI, Anthropic, Cohere, Mistral, vLLM, HuggingFace Local
EmbeddersOpenAI, Sentence-Transformers, Ollama
Vector StoresFAISS, Qdrant, Milvus, PGVector, Elasticsearch
ChunkersSimple (fixed-size), Recursive (structure-aware), Token-based
PDF ParsersPyPDF2, pdfplumber, pdfminer
PipelinesSimple Chat, Conversational RAG, Haystack-native
PreprocessorsText cleaner/normalizer
Prompt BuildersRegistry-based template system
RerankersSentence-Transformer Cross-Encoder
TokenizersWhitespace, Regex, Character
Token CountersHuggingFace tokenizer, tiktoken

The admin selects components through the settings UI. The backend resolves the correct implementation via the factory at runtime, no restart, no code change.

Question Generation & Evaluation

For any uploaded document, the system generates questions across 11 categories designed to test different aspects of LLM comprehension:

  • By format: Open questions, closed questions, multiple choice, fill-in-the-blank
  • By difficulty: Simple, medium, complex
  • By challenge type: Contextualized, ambiguous, erroneous (deliberately flawed), ethics/bias

Each question comes with a generated answer and the prompt used to produce it, creating a ground-truth dataset for benchmarking different pipeline configurations using metrics from scikit-learn and NLTK.

Haystack as the RAG Backbone

HaystackHaystack (farm-haystack)

The entire RAG pipeline is built on top of Haystack, the open-source NLP framework by deepset. Haystack is not just a utility, it is the central orchestration layer that ties the platform together:

  • Document dataclass: the universal data container used throughout the codebase to represent text chunks flowing through every pipeline stage.
  • Native pipeline abstractions: Haystack's composable pipeline API is used to wire retrieval, embedding, and generation stages together in a declarative way.
  • Built-in document store integrations: FAISS, Elasticsearch, and other vector stores are accessed through Haystack's document store interface, making it trivial to swap backends.
  • Retriever and Reader components: the core retrieval logic uses Haystack's retriever abstractions, which standardize how documents are fetched from vector stores.
  • Prompt node integration: LLM generation is routed through Haystack's prompt node, keeping prompt template management and model invocation consistent.

The rag_runner.py module implements the core RAG flow: given a user query and a set of context documents (as Haystack Document objects), it builds a prompt using the configured prompt template and sends it to the selected LLM. A second module, rag_runner_questionset.py, extends this for batch question generation: given a document, it generates questions across all 11 categories and their expected answers, which can then be used as ground truth for evaluation.

Multi-Tenant User System

The platform supports multiple user groups through a Pool system. Each Pool has a configurable user limit, an assigned LLM model, access to specific datasets, and a unique join token. Users authenticate via JWT and are assigned admin or user roles.

Logging & Observability

Every LLM generation is logged with full context: prompt template, rendered prompt, raw response, response time, token counts, and errors, essential for research reproducibility.

What This Enables

  1. Upload academic papers as PDFs
  2. Configure the RAG pipeline (chunker, embedder, vector store, LLM, etc.)
  3. Generate a standardized question set across 11 categories
  4. Run the question set and collect answers
  5. Evaluate answer quality using automated metrics
  6. Swap one component and re-run the same questions
  7. Compare results across configurations, all through the web UI