Files
cbazza 4bf034b2a5 Add sync_missing_docs.py: Dokumentenabgleich job_matching -> apply4jobs.de
- 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
2026-07-28 17:25:40 +02:00

289 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
![OCR → RAG Ingestion Pipeline](architektur/ocr_rag_pipeline.svg)
### 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.01.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.01.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
```