- Vergleicht profile_documents (NAS) gegen anythingllm_vectors (apply4jobs.de) per filename/title - Embeddings werden für die Zielseite neu berechnet (multilingual-e5-small, 384 Dim), da OpenAI-1536-Dim-Vektoren aus job_matching inkompatibel sind - diff/sync Subcommands, --dry-run, --workspace-id, --force (Update/Re-Sync bestehender Titel) - JM_DB_HOST auf Tailscale-IP umgestellt (100.81.64.115) für Erreichbarkeit außerhalb des Heimnetzes - README um Tool 4 ergänzt und Hetzner->DO-Umzug dokumentiert
289 lines
12 KiB
Markdown
289 lines
12 KiB
Markdown
# RAG Ingestion Tools
|
||
|
||
Lokale Python-Tools zum Importieren von Dokumenten in pgvector-Vektordatenbanken.
|
||
Vier eigenständige Scripts für unterschiedliche Zielplattformen und Anwendungsfälle,
|
||
plus zwei gemeinsame Module für Extraktion und KI-Normalisierung.
|
||
|
||
---
|
||
|
||
## Übersicht & Zielplattformen
|
||
|
||
| Tool | Wofür? | Zielplattform | Verbindung | .env-Datei |
|
||
|---|---|---|---|---|
|
||
| `ingest_V2.py` | Fremde Dokumente, Scans, Bilder — mit automatischer Strukturierung | apply4jobs.de · anythingllm DB | SSH-Tunnel | `.env.ingest` |
|
||
| `ingest_anythingllm.py` | Eigene, bereits strukturierte Dokumente (Templates) | apply4jobs.de · anythingllm DB | SSH-Tunnel | `.env.ingest` |
|
||
| `ingest_job_matching.py` | Stellenanzeigen & Profil-Dokumente für Job-Matching | NAS · job_matching DB | Direkt (Port 5433) | `.env.job_matching` |
|
||
| `sync_missing_docs.py` | Dokumentenabgleich: fehlende/aktualisierte Profil-Dokumente von job_matching nach apply4jobs.de spiegeln | NAS → apply4jobs.de | Direkt + SSH-Tunnel | `.env.ingest` + `.env.job_matching` |
|
||
|
||
**Wann welches Tool?**
|
||
|
||
- Du hast ein **fremdes CV, einen Scan oder ein Bild** → `ingest_V2.py` (OCR + KI-Normalisierung)
|
||
- Du hast ein **eigenes, fertiges Markdown-Dokument** (z.B. ausgefülltes Template) → `ingest_anythingllm.py`
|
||
- Du willst **Stellenanzeigen oder Profil-Dokumente** für Job-Matching einpflegen → `ingest_job_matching.py`
|
||
- Du willst, dass Dokumente aus der **Job-Matching-Plattform auch in der apply4jobs.de-RAG-DB** verfügbar sind (Profil-Kern, Skill-Matrix, Projektbeschreibungen etc.) → `sync_missing_docs.py`
|
||
|
||
---
|
||
|
||
## Dateistruktur
|
||
|
||
```
|
||
rag-ingestion/
|
||
├── ingest_V2.py Vollpipeline v2: OCR + KI-Normalisierung (→ apply4jobs.de)
|
||
├── ingest_anythingllm.py Einfaches Script v1: nur MD/TXT/PDF (→ apply4jobs.de)
|
||
├── ingest_job_matching.py Job-Matching Script: direkt auf NAS (→ NAS Port 5433)
|
||
├── sync_missing_docs.py Abgleich job_matching → apply4jobs.de (fehlende/geänderte Dokumente spiegeln)
|
||
│
|
||
├── extractor.py Modul: Text-Extraktion + OCR (wird von ingest_V2 genutzt)
|
||
├── normalizer.py Modul: KI-Normalisierung via Claude (wird von ingest_V2 genutzt)
|
||
│
|
||
├── architektur/
|
||
│ └── ocr_rag_pipeline.svg Architektur-Diagramm der ingest_V2 Pipeline
|
||
│
|
||
├── templates/
|
||
│ ├── *_TEMPLATE.md Ziel-Strukturvorlagen (leer)
|
||
│ └── ausgefuellt/ Ausgefüllte Referenzbeispiele (Few-Shot für KI, nicht in Git)
|
||
│
|
||
├── .env.ingest Secrets für ingest_V2.py + ingest_anythingllm.py (nicht in Git)
|
||
├── .env.job_matching Secrets für ingest_job_matching.py (nicht in Git)
|
||
└── .env.example Vorlage für .env.ingest
|
||
```
|
||
|
||
---
|
||
|
||
## Tool 1: `ingest_V2.py` — Vollpipeline mit OCR + KI-Normalisierung
|
||
|
||
**Verwendungszweck:** Verarbeitung beliebiger Fremddokumente — gescannte PDFs, Bilder,
|
||
DOCX-Dateien, fremde CVs — die zunächst per OCR extrahiert und dann via Claude KI
|
||
automatisch in die einheitliche Template-Struktur transformiert werden.
|
||
|
||
**Zielplattform:** pgvector-Datenbank (`anythingllm`) für `ai.apply4jobs.de` via SSH-Tunnel.
|
||
|
||
> **Hinweis (Stand 2026-07):** Server + Domain wurden von Hetzner auf einen Digital-Ocean-
|
||
> Droplet umgezogen. `SSH_HOST` in `.env.ingest` muss auf die aktuelle DO-IP zeigen —
|
||
> die alte Hetzner-IP (`46.225.225.226`) ist nicht mehr gültig.
|
||
|
||
**Benötigt:** `extractor.py` und `normalizer.py` im gleichen Verzeichnis.
|
||
|
||
### Architektur
|
||
|
||

|
||
|
||
### Unterstützte Formate
|
||
|
||
`.md` `.txt` `.pdf` `.docx` `.png` `.jpg` `.jpeg` `.tif` `.tiff` `.bmp` `.webp`
|
||
|
||
### Voraussetzungen
|
||
|
||
```bash
|
||
# System-Tools (einmalig)
|
||
brew install tesseract tesseract-lang poppler
|
||
|
||
# Python-Pakete
|
||
pip install -r requirements.txt
|
||
|
||
# .env.ingest konfigurieren
|
||
cp .env.example .env.ingest
|
||
nano .env.ingest # ANTHROPIC_API_KEY eintragen
|
||
```
|
||
|
||
### Verwendung
|
||
|
||
```bash
|
||
# Einzelne Datei importieren (mit KI-Normalisierung)
|
||
python ingest_V2.py file ~/Downloads/fremdes_cv.pdf
|
||
|
||
# Dry-Run: erst testen, nichts in DB schreiben
|
||
python ingest_V2.py file ~/Desktop/scan.png --dry-run
|
||
|
||
# Dry-Run + normalisiertes Markdown automatisch speichern (<dateiname>_normalized.md)
|
||
python ingest_V2.py file ~/Downloads/cv.pdf --dry-run
|
||
|
||
# Dry-Run + normalisiertes Markdown in bestimmte Datei speichern
|
||
python ingest_V2.py file ~/Downloads/cv.pdf --dry-run --output ~/Desktop/cv_normalisiert.md
|
||
|
||
# Ohne KI-Normalisierung (für bereits strukturierte Dokumente)
|
||
python ingest_V2.py file ~/templates/ausgefuellt/mein_cv.md --no-normalize
|
||
|
||
# Ganzes Verzeichnis importieren
|
||
python ingest_V2.py dir ~/Dokumente/bewerbungsunterlagen/ --force
|
||
|
||
# Ordner live beobachten (neue Dateien automatisch verarbeiten)
|
||
python ingest_V2.py watch ~/Desktop/scan-eingang/
|
||
```
|
||
|
||
### Datenbank verwalten
|
||
|
||
```bash
|
||
# Alle importierten Dokumente anzeigen
|
||
# Zeigt: Titel, Dokumenttyp, Qualitäts-Score, Format, Anzahl Chunks, Datum
|
||
python ingest_V2.py list
|
||
|
||
# Ausgabe-Beispiel:
|
||
# Titel Typ Score Fmt Chunks Datum
|
||
# ──────────────────────────────────────────────────────────────────────────────────────────
|
||
# Arbeitszeugnis Sopra Financial Technology... arbeitszeugnis 95% .pdf 3 2026-04-28
|
||
|
||
# Einzelnes Dokument löschen (alle Chunks)
|
||
# Hinweis: Titel = exakter Dateiname wie beim Import
|
||
python ingest_V2.py delete "Arbeitszeugnis Sopra Financial Technology GmbH.pdf"
|
||
```
|
||
|
||
### Flags
|
||
|
||
| Flag | Beschreibung |
|
||
|---|---|
|
||
| `--force` | Bestehende Chunks überschreiben |
|
||
| `--no-normalize` | KI-Normalisierung überspringen |
|
||
| `--dry-run` | Nur extrahieren + normalisieren, nicht in DB speichern |
|
||
| `--output <pfad>` | Normalisiertes Markdown als Datei speichern (nur mit `--dry-run`) |
|
||
| `--quality-min 0.5` | Mindest-Qualitäts-Score (0.0–1.0, Standard: 0.0) |
|
||
|
||
### KI-Normalisierung (`normalizer.py`)
|
||
|
||
- **Typ-Erkennung:** Claude Haiku (günstig + schnell)
|
||
- **Transformation:** Claude Sonnet
|
||
- **Fehlende Felder** werden mit `[FEHLT]` markiert, nie halluziniert
|
||
- **Qualitäts-Score:** 0.0–1.0 (Anteil befüllter Pflichtfelder)
|
||
|
||
Erkannte Typen: `arbeitszeugnis`, `cv`, `lebenslauf`, `anschreiben`,
|
||
`projektbeschreibung`, `karriereziele`, `technologie`, `zertifikate`,
|
||
`referenzen`, `elevatorpitch`, `rahmenbedingungen`, `zielstellen`
|
||
|
||
### OCR-Pipeline (`extractor.py`)
|
||
|
||
1. `pypdf` versucht Text zu extrahieren
|
||
2. Weniger als 150 Zeichen → OCR-Fallback via Tesseract (300 DPI)
|
||
3. Bilder (PNG, JPG etc.) → direkt OCR
|
||
|
||
---
|
||
|
||
## Tool 2: `ingest_anythingllm.py` — Einfaches Ingestion-Script (v1)
|
||
|
||
**Verwendungszweck:** Schnelles Einpflegen von eigenen, bereits fertig strukturierten
|
||
Dokumenten — z.B. ausgefüllte Templates, eigene Markdown-Dateien oder einfache PDFs
|
||
die keiner OCR oder KI-Normalisierung bedürfen.
|
||
|
||
**Zielplattform:** pgvector-Datenbank (`anythingllm`) für `ai.apply4jobs.de` via SSH-Tunnel.
|
||
Gleiche Zieldatenbank wie `ingest_V2.py` — unterschiedlicher Verarbeitungsweg.
|
||
|
||
### Unterstützte Formate
|
||
|
||
`.md` `.txt` `.pdf`
|
||
|
||
### Verwendung
|
||
|
||
```bash
|
||
python ingest_anythingllm.py file ~/Dokumente/Lebenslauf.md
|
||
python ingest_anythingllm.py file ~/Dokumente/Lebenslauf.md --force
|
||
python ingest_anythingllm.py dir ~/Dokumente/bewerbung/
|
||
python ingest_anythingllm.py list
|
||
```
|
||
|
||
---
|
||
|
||
## Tool 3: `ingest_job_matching.py` — Job-Matching-Datenbank (NAS)
|
||
|
||
**Verwendungszweck:** Einpflegen von Stellenanzeigen und Profil-Dokumenten in die
|
||
dedizierte Job-Matching-Datenbank. Diese Datenbank wird von einem separaten
|
||
Job-Matching-Workflow genutzt um Stellenanzeigen mit dem eigenen Profil abzugleichen.
|
||
|
||
**Zielplattform:** pgvector-Datenbank (`job_matching`) direkt auf dem NAS
|
||
(Port 5433) — kein SSH-Tunnel erforderlich.
|
||
|
||
> `JM_DB_HOST` steht auf die Tailscale-IP `100.81.64.115`, damit die Verbindung auch
|
||
> außerhalb des Heimnetzes funktioniert. Im lokalen WLAN geht alternativ auch die
|
||
> LAN-IP `192.168.178.128` (kann sich per DHCP ändern).
|
||
|
||
Embeddings via OpenAI `text-embedding-3-small` (1536 Dimensionen, kostenpflichtig).
|
||
|
||
### Verwendung
|
||
|
||
```bash
|
||
python ingest_job_matching.py file ~/Dokumente/stellenanzeige.pdf
|
||
python ingest_job_matching.py dir ~/Dokumente/stellen/
|
||
python ingest_job_matching.py list
|
||
python ingest_job_matching.py clear "stellenanzeige.pdf"
|
||
```
|
||
|
||
---
|
||
|
||
## Tool 4: `sync_missing_docs.py` — Dokumentenabgleich job_matching → apply4jobs.de
|
||
|
||
**Verwendungszweck:** Job-Matching-Plattform und `ai.apply4jobs.de`/`apply4jobs.de` nutzen
|
||
zwei getrennte pgvector-Datenbanken (NAS vs. DO-Droplet). Dieses Script gleicht sie ab, damit
|
||
Profil-Dokumente, die in der Job-Matching-Plattform erstellt/hochgeladen wurden (Skill-Matrix,
|
||
Rahmenbedingungen, Zielstellen-Profil, Muster-Anschreiben, Elevator-Pitch, Projektbeschreibungen,
|
||
CV, Arbeitszeugnisse etc.), auch für die Webseiten-RAG verfügbar sind.
|
||
|
||
**Matching-Kriterium:** `filename` (job_matching.profile_documents) ↔ `metadata->>'title'`
|
||
(anythingllm_vectors).
|
||
|
||
**Embeddings:** werden für die Zielseite immer **neu berechnet** (lokal, `multilingual-e5-small`,
|
||
384 Dim) — die OpenAI-1536-Dim-Vektoren aus job_matching sind mit der 384-Dim-Spalte in
|
||
`anythingllm_vectors` nicht kompatibel. Nur der Text wird übernommen.
|
||
|
||
**Voraussetzung:** benötigt beide `.env`-Dateien (`.env.ingest` für die SSH-Tunnel-Verbindung
|
||
zum Ziel, `.env.job_matching` für die Quelle) sowie `sentence-transformers` (aus `requirements.txt`).
|
||
|
||
### Verwendung
|
||
|
||
```bash
|
||
# Nur anzeigen, welche Dokumente in apply4jobs.de fehlen (kein Schreibzugriff)
|
||
python sync_missing_docs.py diff
|
||
|
||
# Optional auf einen Workspace eingrenzen
|
||
python sync_missing_docs.py diff --workspace-id 1
|
||
|
||
# Fehlende Dokumente übertragen
|
||
python sync_missing_docs.py sync --dry-run # Simulation, nichts wird geschrieben
|
||
python sync_missing_docs.py sync # tatsächlich schreiben
|
||
|
||
# Auch bereits vorhandene Titel neu einlesen (Update/Re-Sync):
|
||
# löscht die alten Chunks in anythingllm_vectors und ersetzt sie mit dem
|
||
# aktuellen Stand aus job_matching
|
||
python sync_missing_docs.py sync --force
|
||
python sync_missing_docs.py sync --force --dry-run
|
||
```
|
||
|
||
### Flags
|
||
|
||
| Flag | Beschreibung |
|
||
|---|---|
|
||
| `--workspace-id <id>` | Nur Dokumente eines bestimmten Workspace aus job_matching berücksichtigen |
|
||
| `--dry-run` | Nur anzeigen, nichts in die Zieldatenbank schreiben |
|
||
| `--force` | Auch bereits vorhandene Titel neu einlesen (löscht + ersetzt statt nur zu ergänzen) |
|
||
|
||
---
|
||
|
||
## Gemeinsame Infrastruktur
|
||
|
||
### Embedding-Modelle
|
||
|
||
| Tool | Modell | Dimensionen | Kosten |
|
||
|---|---|---|---|
|
||
| `ingest_V2.py` | multilingual-e5-small (lokal) | 384 | kostenlos |
|
||
| `ingest_anythingllm.py` | multilingual-e5-small (lokal) | 384 | kostenlos |
|
||
| `ingest_job_matching.py` | OpenAI text-embedding-3-small | 1536 | kostenpflichtig |
|
||
| `sync_missing_docs.py` | multilingual-e5-small (lokal, für Ziel-DB neu berechnet) | 384 | kostenlos |
|
||
|
||
### Umgebungsvariablen
|
||
|
||
| Datei | Verwendet von | Inhalt |
|
||
|---|---|---|
|
||
| `.env.ingest` | `ingest_V2.py`, `ingest_anythingllm.py`, `sync_missing_docs.py` | ANTHROPIC_API_KEY, SSH, DB |
|
||
| `.env.job_matching` | `ingest_job_matching.py`, `sync_missing_docs.py` | OPENAI_API_KEY, NAS-DB |
|
||
|
||
---
|
||
|
||
## Setup (komplett, einmalig)
|
||
|
||
```bash
|
||
cd ~/Projekte/rag-ingestion
|
||
python3 -m venv venv
|
||
source venv/bin/activate
|
||
pip install -r requirements.txt
|
||
brew install tesseract tesseract-lang poppler # nur für ingest_V2.py
|
||
cp .env.example .env.ingest
|
||
nano .env.ingest # ANTHROPIC_API_KEY + SSH + DB-Zugangsdaten eintragen
|
||
```
|