Startseite / Artikel / Selbst gehostetes RAG für Fragen an Dokumente: LangChain, pgvector und austauschbare LLMs

Selbst gehostetes RAG für Fragen an Dokumente: LangChain, pgvector und austauschbare LLMs

Wie eine Open-Source-RAG-App aus PDF- und DOCX-Dateien Antworten macht: Chunking, pgvector, ein Wechsel zwischen OpenAI und Claude per Einstellung und Lehren aus der Entwicklung mit kleinem Budget.

1235 Wörter

Dokumente beantworten ihre eigenen Fragen selten von allein. Richtlinien, Berichte und Verträge stapeln sich als PDFs, Word-Dateien und Scans, und kaum jemand hat Zeit, sie von vorn bis hinten zu lesen. Ein Retrieval-Augmented-Generation-System (RAG), das Fragen zu hochgeladenen Dateien beantwortet, ist ein praktischer Weg, das zu ändern, und das Open-Source-Projekt AI-DocumentIntelligence ist ein gutes Beispiel dafür, wie man ein solches System einfach und vertrauenswürdig hält.

Welche Lücke das schließt

In jeder Organisation mit viel Papierkram zeigt sich dieselbe Situation. Eine Sachbearbeiterin braucht eine Zeile aus einer 40-seitigen Richtlinie. Ein Analyst braucht eine einzige Zahl von Seite 22 eines Berichts. Die Datei ist zwar verfügbar, aber darin etwas zu finden dauert ewig.

Meist bleiben Teams zwei Optionen. Die eine ist ein proprietäres KI-Produkt für Dokumente mit undurchsichtigen Preisen und vagen Regeln dazu, was mit den Daten passiert. Die andere ist, weiter von Hand zu suchen.

Eine offene, selbst gehostete Alternative bietet einen dritten Weg. Das Ziel ist nicht noch ein dünner Wrapper um die API von irgendwem. Es soll etwas sein, das einfach genug ist, damit ein Team mit sensiblen Dokumenten es prüfen, Teile austauschen und ihm vertrauen kann.

So funktioniert es: hochladen, zerlegen, einbetten, fragen

Die Anwendung erledigt vier Aufgaben:

  1. Sie nimmt PDF-, DOCX- oder TXT-Dateien an.
  2. Sie zerlegt jede Datei in kleine, sinnvolle Chunks.
  3. Sie wandelt die Chunks in Embeddings um und speichert sie über die Erweiterung pgvector in PostgreSQL.
  4. Sie beantwortet Fragen in natürlicher Sprache zum Dokument, entweder mit OpenAI oder mit Anthropic Claude, und eine einzige Einstellung schaltet zwischen beiden um.

Der letzte Punkt ist wichtiger, als er aussieht. Viele RAG-Projekte aus Tutorials binden einen an einen einzigen Modellanbieter. Echte Projekte müssen den Anbieter oft wegen Kosten, Compliance oder Verfügbarkeit wechseln, und niemand will dann die App umschreiben. Hier ist LLM_PROVIDER ein schlichter Konfigurationswert statt etwas tief im Code Vergrabenes.

Der Stack

Die Technologieliste ist bewusst kurz:

  • Frontend: React 18 mit TypeScript
  • Backend: Node.js und Express in TypeScript
  • Orchestrierung: LangChain
  • Vektorspeicher: PostgreSQL mit pgvector
  • Modelle: OpenAI oder Anthropic Claude, je nach Wahl
  • Werkzeuge: Docker, ts-node und dotenv

Nichts davon verlangt von einem Team, eine neue Art der Softwareauslieferung zu lernen. Wer schon einen Node.js-Dienst neben einer PostgreSQL-Datenbank betreiben kann, kann auch das hier betreiben.

Architektur

Insgesamt spricht eine React-Oberfläche mit einer Express-API, die Uploads an einen Dokumentprozessor weiterreicht:

┌────────────┐      ┌──────────────────┐      ┌────────────────────┐
 │  React UI   │ ───► │  Express API      │ ───► │  Document Processor │
 │ (upload+chat)│      │ (TypeScript)       │      │ (chunking / split)   │
 └────────────┘      └──────────────────┘      └────────────────────┘
                                │                          │
                                ▼                          ▼
                      ┌──────────────────┐      ┌────────────────────┐
                      │  LangChain Layer   │ ◄──► │ PostgreSQL + pgvector│
                      │ (OpenAI / Claude)    │      │  (embeddings store)   │
                      └──────────────────┘      └────────────────────┘

Chunking und Embedding passieren einmal, beim Hochladen. Danach nimmt jede Frage denselben kurzen Weg. Die Frage wird in ein Embedding umgewandelt, pgvector liefert die nächstgelegenen Chunks, diese gehen zusammen mit der Frage an das gewählte Modell, und die Antwort kommt samt Chatverlauf zu diesem Dokument zurück.

An diesem Design ist nichts exotisch, und genau darum geht es. Der Wert liegt darin, die schlichten Teile richtig hinzubekommen: den Anbieter sauber auswählen, Text sinnvoll teilen und kontrolliert scheitern, wenn ein Schlüssel oder eine Einstellung fehlt.

Der Code, auf den es ankommt

Schon wenige Ausschnitte zeigen die Kernideen.

Der Anbieterwechsel läuft auf das Lesen einer Umgebungsvariablen hinaus:

// backend/src/llm/provider.ts (simplified)
const provider = process.env.LLM_PROVIDER; // "openai" | "anthropic"

Eine kleine Factory-Funktion gibt dann je nach Wert das passende Chatmodell zurück:

export function getChatModel() {
  if (provider === "anthropic") {
    return new ChatAnthropic({
      apiKey: process.env.ANTHROPIC_API_KEY,
      model: process.env.ANTHROPIC_MODEL,
    });
  }
  return new ChatOpenAI({
    apiKey: process.env.OPENAI_API_KEY,
  });
}

Das Chunking nutzt den rekursiven Splitter von LangChain, eingestellt eher auf Fragen und Antworten als auf reines Speichern:

const splitter = new RecursiveCharacterTextSplitter({
  chunkSize: 1000,
  chunkOverlap: 150,
});
const chunks = await splitter.splitDocuments(rawDocs);

Die Überlappung verdient mehr Aufmerksamkeit, als sie meist bekommt. Ist sie zu klein, kann eine Antwort mittendrin auf zwei Chunks verteilt werden. Ist sie zu groß, bezahlt man dafür, denselben Satz dreimal zu speichern und zu durchsuchen. Eine Überlappung von 150 Zeichen bei Chunks von 1000 Zeichen brachte über eine Mischung aus PDF- und DOCX-Testdateien die besten Ergebnisse.

Auch Retrieval und Generierung bleiben getrennt. Jede Stufe lässt sich einzeln testen, sodass man prüfen kann, ob das Retrieval die richtigen Chunks findet, ohne API-Guthaben für die Generierung zu verbrennen. Mit einem kostenlosen OpenAI-Konto ist diese Ersparnis beträchtlich.

Was funktioniert hat und was wehgetan hat

Entscheidungen, die sich ausgezahlt haben

  • Ein austauschbarer Anbieter vom ersten Tag an. Der Großteil der Retrieval-Logik wurde mit kostenlosen lokalen Embeddings gebaut und getestet, bevor überhaupt eine kostenpflichtige API ins Spiel kam, sodass die Kosten während der Entwicklung nahe null blieben.
  • Chunking und Embedding getrennt vom Beantworten der Fragen zu halten. Wenn eine Antwort falsch aussieht, merkt man schnell, ob das Retrieval oder die Generierung schuld ist.
  • Docker Compose für den ganzen Stack (Backend, Frontend und Postgres). Jeder kann das Repo klonen und hat es in wenigen Minuten am Laufen, statt einen Nachmittag mit Abhängigkeiten zu kämpfen.

Was schwieriger war als erwartet

  • Ohne bezahltes API-Guthaben zu arbeiten. Kein Zugang zu einem kostenpflichtigen OpenAI- oder Claude-Konto ist eine echte Einschränkung. Der Ausweg war, das Retrieval zuerst mit kostenlosen lokalen Embedding-Modellen zu validieren und den bezahlten Generierungsschritt später hinzuzufügen. Ein vernünftiger Rat für jedes RAG-Projekt mit knappem Budget.
  • Die Chunk-Größe zu wählen. Unspektakulär und doch entscheidend. Kein Prompt-Tuning rettet eine schlechte Aufteilung, und deren Korrektur brachte die größte einzelne Verbesserung im ganzen Projekt.
  • Es auf fremden Rechnern zum Laufen zu bringen. Ein Laptop, auf dem alles funktioniert, und ein sauberes docker compose up auf dem Rechner eines Fremden sind zwei verschiedene Dinge. Eine klare .env.example, explizite Einstellungen für den Datenbanknamen und eine dokumentierte Lösung für Portkonflikte haben länger gedauert als die RAG-Logik selbst, aber sie entscheiden, ob irgendjemand sonst das Projekt nutzen kann.

Selbst ausprobieren

Das Repo ist Open Source und startet lokal mit wenigen Befehlen:

git clone https://github.com/Srameshgitnow/AI-DocumentIntelligence.git
cd AI-DocumentIntelligence
docker compose up --build

Umgebungsvariablen, die vollständige Einrichtung und Hinweise zur Fehlersuche stehen im README des Projekts. Fehlerberichte, Ideen zum Ansatz für den Anbieterwechsel und Pull Requests sind im Issue-Tracker gut aufgehoben.

Die übergeordnete Idee hinter solchen Projekten ist, LLM-Orchestrierung mit LangChain oder LangGraph auf echte Arbeitsabläufe anzuwenden, etwa Fragen zu Richtlinien im öffentlichen Dienst, Agenten für Code-Reviews oder das Umwandeln von Sprachnotizen in agile User Stories, und auf die polierte Demo zu verzichten, die nie auf echte Dokumente trifft. Einfach, austauschbar und leicht nachprüfbar schlägt meist clever.