Initial monorepo structure
This commit is contained in:
@@ -0,0 +1,293 @@
|
||||
# Wissensdatenbank ETL-Pipeline
|
||||
|
||||
## Worum geht es? (in einfach)
|
||||
|
||||
Dies ist die **zentrale Wissenssammlung** fuer den 1st-Level-Support und Chatbots rund um
|
||||
DB InfraGO. Wir **sammeln hier das Wissen** aus vielen Quellen (Confluence, Webseiten,
|
||||
PDFs), pruefen es und legen es **versioniert im Git-Repo** ab. Damit ist dieses Repo die
|
||||
**Single Source of Truth**: Wenn etwas hier steht und freigegeben ist, gilt es - und genau
|
||||
das lesen die Chatbots.
|
||||
|
||||
**Was ist „ETL"?** Drei einfache Schritte, die bei jeder Aufnahme passieren:
|
||||
|
||||
1. **Extract = Sammeln.** Wissen aus den Quellen holen (Confluence-Seiten, Webseiten, PDFs).
|
||||
2. **Transform = Aufbereiten.** In einheitliches Markdown umwandeln, mit Schlagworten/Scope
|
||||
versehen und vertrauliche Inhalte herausfiltern.
|
||||
3. **Load = Ablegen.** Das geprueefte Wissen versioniert nach `data/processed/` schreiben -
|
||||
die zentrale Wahrheit, aus der Chatbots/Vektor-DB lesen.
|
||||
|
||||
Kurz: **Quellen rein → aufbereiten/pruefen → als Single Source of Truth ablegen.**
|
||||
|
||||
## Ziel
|
||||
|
||||
Klar definieren, **welches Wissen in Chatbots darf und welches nicht.** Wissen wird bei
|
||||
der Aufnahme speziell verarbeitet und bereitgestellt, sodass Chatbots es nutzen koennen.
|
||||
Klassifikation nach Scope:
|
||||
|
||||
- **allgemein** – darf intern **und** extern genutzt werden; nicht toolspezifisch
|
||||
(z.B. Regulierung/INB, Kundeninformationen).
|
||||
- **intern** – nur DB InfraGO intern.
|
||||
- **extern** – public im Internet bzw. fuer EVUs/EIUs.
|
||||
|
||||
Technisch eine **ETL-Pipeline fuer LLM/RAG**: holt Wissen aus Confluence, Webseiten und
|
||||
PDFs, klassifiziert es nach Scope, filtert vertrauliche Inhalte und gibt nur geprueftes
|
||||
Wissen frei (`data/processed/<scope>/...` = Feed fuer die Vektor-DB).
|
||||
|
||||
> Architektur & vollstaendiger Wissensfluss inkl. Diagramme:
|
||||
> [`.kiro/steering/architecture.md`](.kiro/steering/architecture.md)
|
||||
> · Manuelle Einrichtungsschritte: [`docs/SETUP.md`](docs/SETUP.md)
|
||||
|
||||
## Denkmodell
|
||||
|
||||
```
|
||||
Domaene -> Scope (intern|extern|allgemein) -> Tool -> Dokument(e)
|
||||
```
|
||||
|
||||
- **Domaene**: fachlicher Bereich (jedes Tool ist eigene Domaene; `regulierung`, `kundeninfo` fuer allgemeines Wissen).
|
||||
- **Scope**: `intern` | `extern` | `allgemein` (allgemein gilt fuer intern UND extern).
|
||||
- **Tool**: eine Fachanwendung aus dem 1st-Level-Support (Katalog automatisch aus Seite 428909879).
|
||||
- **Strategie pro Quelle** – siehe Tabelle unten.
|
||||
|
||||
### Strategien (Verarbeitung pro Quelle)
|
||||
|
||||
| Strategie | Was sie tut | Wichtige Options |
|
||||
|-----------|-------------|------------------|
|
||||
| `confluence_page` | genau diese eine Seite | `scope` (auch `intern,extern`), `incremental`, `attachments` |
|
||||
| `confluence_tree` | Seite inkl. Unterseiten | `max_depth` (-1=alle, 0=nur Seite), `incremental`, `attachments` |
|
||||
| `confluence_faq` | FAQs aus Tabellen | `incremental` |
|
||||
| `crawler` | Webseite: Index → Detailseiten | `detail_pattern`, `selector`, `max_pages` |
|
||||
| `sitemap` | Detailseiten aus (gz-)Sitemap | `sitemap_url`, `url_pattern`, `limit`, `incremental` |
|
||||
| `gitlab_md` | Markdown-Dateien aus einem GitLab-Repo | `ref`, `path`, `max_depth` (Token: `GITLAB_TOKEN`) |
|
||||
| `file` | Dateien (md/pdf) aus dem Repo-Ordner `files/` | `max_depth` |
|
||||
| `pdf` | PDFs einer Seite → Markdown (1→n) | `split: headings\|pages`, `heading_pattern`, `keep_raw`, `redact` |
|
||||
|
||||
### Confluence-Anhaenge (eingebundene PDFs)
|
||||
|
||||
Bei `confluence_page`/`confluence_tree` werden **eingebundene PDF-Anhaenge**
|
||||
(`view-file`/`viewpdf`-Makros) automatisch geparst und als **eigene Dokumente** im
|
||||
selben Feed abgelegt (Frontmatter `kind: attachment`, `parent_url` zeigt auf die Seite).
|
||||
Scope/Domain/Owner werden von der Seite geerbt. Inkrementell ueber die Attachment-Version
|
||||
(unveraenderter Anhang -> kein Re-Parse). Default an, pro Quelle ueber
|
||||
`options: { attachments: false }` abschaltbar.
|
||||
|
||||
Bilder werden nicht binaer uebernommen; der **alt-Text/Dateiname** bleibt als
|
||||
`[Bild: ...]` im Markdown - gibt dem RAG Kontext ohne Volumen. Datei-Embeds ohne
|
||||
Parsing-Pfad (z.B. Office-Docs) tauchen als `[Anhang: ...]`-Marker im Seitentext auf.
|
||||
|
||||
### Mehrere Quellen pro Tool
|
||||
|
||||
Ein Tool kann beliebig viele, gemischte Quellen haben – einfach weitere Eintraege
|
||||
unter `sources:` anhaengen (z.B. mehrere Confluence-Quellen mit `max_depth`, eine
|
||||
interne und eine externe Seite, oder zusaetzlich eine FAQ-Seite). Siehe die Tools
|
||||
`pathos` (mehrere Quellen inkl. `confluence_faq` und `intern,extern`) und `nur`.
|
||||
|
||||
### intern vs. extern – wie wird klassifiziert?
|
||||
|
||||
Es gibt **keine Inhaltstrennung innerhalb einer Seite** mehr. Klassifiziert wird pro
|
||||
Tool und pro Quelle:
|
||||
|
||||
**Tool-`scope`** (in `config/tools.yaml`):
|
||||
|
||||
| Tool-`scope` | Bedeutung |
|
||||
|--------------|-----------|
|
||||
| `intern` | alles intern (Default, restriktiv – z.B. Salesforce, NuR) |
|
||||
| `extern` | alles extern |
|
||||
| `allgemein` | gilt fuer intern UND extern |
|
||||
| `mixed` | Tool hat Quellen mit **unterschiedlichem** Scope (z.B. eine interne und eine externe Seite) |
|
||||
|
||||
**Source-`scope`** (pro Quelle, gilt fuer die ganze Seite):
|
||||
- `intern` | `extern` | `allgemein`
|
||||
- `"intern,extern"` → die Seite wird fuer **beide** Scopes genutzt (dupliziert).
|
||||
|
||||
Quellen ohne eigenen `scope` erben den Tool-Scope (bei `mixed` => intern).
|
||||
**Wichtig:** Hat ein Tool gemischtes Wissen, braucht es im Zweifel **zwei Seiten**
|
||||
(eine interne, eine externe) als zwei Quellen – statt einer gemischten Seite.
|
||||
|
||||
Allgemeines, tool-uebergreifendes Wissen steht separat in **`config/general.yaml`**.
|
||||
|
||||
Der Tool-Katalog wird **initial** mit `scripts/bootstrap_tools.py` erzeugt und danach
|
||||
**manuell** in `config/tools.yaml` gepflegt. Uebersicht aller Quellen: GitLab-Pages-Seite
|
||||
**„Wissensquellen"** (`config.html`, geparst aus tools.yaml/general.yaml/approvals.yaml).
|
||||
|
||||
## Neues Wissen hinzufuegen (Workflow)
|
||||
|
||||
Jedes Wissen hat **owners** (intern Verantwortliche, Accountability) und einen
|
||||
**contact** (herausgebbare Kontaktadresse, Default `einfachbahn@deutschebahn.com`).
|
||||
Beide stehen im Frontmatter jedes Dokuments und auf der Pages-Seite, damit klar ist,
|
||||
wer verantwortlich ist und an wen man sich wenden darf.
|
||||
|
||||
1. **Issue anlegen** mit der Vorlage „Neues Wissen" (`.gitlab/issue_templates/`):
|
||||
Tool/Domaene, **Quellen je Zeile** (`URL | Strategie | Scope`), **Verantwortliche**
|
||||
(`owners`) und optional **Kontakt** (Default `einfachbahn@deutschebahn.com`).
|
||||
2. **Reviewer uebernimmt** die Angaben nach `config/tools.yaml` bzw. `config/general.yaml`
|
||||
(neuer Tool-/Quellen-Eintrag inkl. `owners:`) – per GitLab Web-IDE (Variante A) oder
|
||||
manuell (Variante B).
|
||||
3. **Merge Request** öffnen. Die **MR-Freigabe ist das Quality Gate** (4-Augen, via
|
||||
`CODEOWNERS` + Protected Branch).
|
||||
4. **Vorschau** im MR: den `preview`-Job manuell starten und `PREVIEW_ONLY` auf
|
||||
Tool-Id/Domaene/URL der neuen Quelle setzen. Er verarbeitet nur diese Quelle und
|
||||
stellt die erzeugten Markdowns als Job-Artefakt bereit. Lokal alternativ
|
||||
`python -m src.main --only "<tool|domaene|url>" --data preview`.
|
||||
5. **Merge** → der ETL-Schedule verarbeitet die Quelle (stuendlich Mo–Fr, inkrementell) und
|
||||
committet das Wissen; die Vektor-DB liest direkt aus `data/processed/<scope>/...`.
|
||||
|
||||
## Freigabe-Prozess
|
||||
|
||||
```
|
||||
MR mergen (Mensch) -> ETL: Extract -> Transform -> Auto-Filter -> data/processed/ -> Vektor-DB
|
||||
```
|
||||
|
||||
- **Freigabe = Merge Request (Mensch, das eigentliche Gate):** Wer eine Quelle
|
||||
(Link + Strategie + Scope) in `config/tools.yaml`/`general.yaml` eintraegt, stellt einen
|
||||
MR. Beim **Merge** (4-Augen via `CODEOWNERS` + Protected Branch) wird entschieden,
|
||||
welche Quelle mit welchem Scope aufgenommen wird. Danach laeuft der ETL automatisch.
|
||||
- **Auto-Filter im ETL (Sicherheitsnetz, kein Freigabe-Knopf):** setzt pro Dokument
|
||||
`review_status` – standardmaessig **approved**:
|
||||
- `approved` -> `data/processed/...` (**direkt live**, sobald gemergt + ETL gelaufen)
|
||||
- `pending` -> `data/staging/pending/` (Blacklist-Treffer, Scope-Warnung, oder
|
||||
Inhalt < 50 Zeichen – Grund im Frontmatter `review_notes`)
|
||||
- `trusted: true` (z.B. INB) bleibt trotz Treffer approved; `redact: false` schaltet
|
||||
die Redaction pro Quelle ab.
|
||||
- **`config/approvals.yaml` (nachtraegliche Korrektur):** hebt einzelne
|
||||
`pending`-Dokumente per URL oder `hash:<content_hash>` doch auf `approved`.
|
||||
Kein Schritt, den jedes Dokument durchlaeuft. Den Hash zeigt die Pages-Seite (Detail-Fenster).
|
||||
- **Vektor-DB liest direkt aus `data/processed/<scope>/<domaene>/`** (implementiert):
|
||||
Nur freigegebenes Wissen liegt dort, der Scope steckt im Ordnerpfad **und** im
|
||||
Frontmatter (inkl. `owners`/`contact`). Kein separater Export-/Ingestion-Schritt –
|
||||
`processed/` IST der Feed. Der externe Index nimmt nur `extern/` + `allgemein/`.
|
||||
- **`data/_meta.json`**: globale Statusdatei fuer nachgelagerte Systeme mit
|
||||
`last_run` (letzter ETL-Lauf), `last_change` (wann sich der Bestand zuletzt
|
||||
inhaltlich/metadatenseitig geaendert hat), `documents`, `by_scope` und einer
|
||||
`content_signature`. Ein Index-/RAG-Consumer kann daran erkennen, ob ein erneutes
|
||||
Einlesen ueberhaupt noetig ist.
|
||||
- **`data/_index.json`**: dokument-genauer Katalog des gesamten Bestands.
|
||||
Pro Voll-Dokument: `domain`/`tool`/`scope`, `url`, `path`, `content_hash`,
|
||||
`last_updated` und - falls vorhanden - die zugehoerigen `chunks` (Anzahl + Pfad)
|
||||
und `attachments` (Anhang-Dokumente: Name + Pfad). Anhang-Dokumente haben zudem
|
||||
`kind: "attachment"` und `parent_url`. Aggregate: `documents_total`,
|
||||
`chunks_total`, `attachments_total`, `by_domain` (mit `documents`/`chunks`/`attachments`),
|
||||
`by_scope`. So sieht ein Anschliesser auf einen Blick, was pro Domaene/Tool wo liegt
|
||||
und welche Dokumente zusaetzlich als Chunks oder Anhaenge vorliegen (z.B. INB =
|
||||
Voll-Dokument **und** Chunks; pathOS-Seite = Voll-Dokument **und** Anhang-PDF).
|
||||
Deterministisch (kein Zeitstempel) -> aendert sich nur bei echten Bestandsaenderungen.
|
||||
- **`data/run_log.jsonl`**: append-only **Lauf-Historie** (1 JSON-Zeile je ETL-Lauf):
|
||||
Zeitstempel, verarbeitete Dokumente, Status-Counts und **Fehler je Quelle**
|
||||
(z.B. fehlgeschlagene Confluence-Abrufe). Auf die letzten 500 Laeufe gekappt. So sieht
|
||||
man - auch historisch - ob ein Lauf sauber durchlief, ohne die fluechtigen CI-Job-Logs
|
||||
zu durchsuchen. Die Uebersichtsseite zeigt zusaetzlich eine kompakte Health-Zeile
|
||||
(„N Dok verarbeitet, M Quellen mit Fehler") in der Fusszeile.
|
||||
|
||||
### Wo liegt das final freigegebene Wissen?
|
||||
|
||||
Ausschliesslich unter **`data/processed/<scope>/<domaene>/[<tool>/]`** im Repo
|
||||
(versioniert, = Single Source of Truth). `pending`-Dokumente liegen in
|
||||
`data/staging/pending/` und werden zur **Transparenz ebenfalls committet** (auf der
|
||||
Pages-Seite einsehbar). Der Audit-Trail steht in `data/review_report.json` und in
|
||||
der Git-Historie.
|
||||
|
||||
## Lokal testen
|
||||
|
||||
```bash
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install -r requirements.txt
|
||||
|
||||
ruff check src tests # Lint
|
||||
python -m pytest -q # Offline-Tests
|
||||
python -m src.main --config config/tools.yaml --data data # Live-Lauf
|
||||
python -m src.chunk --data data # Chunks erzeugen (offline, optional)
|
||||
```
|
||||
|
||||
## Confluence aktivieren
|
||||
|
||||
```bash
|
||||
export CONFLUENCE_URL="https://arija-confluence.jaas.service.deutschebahn.com"
|
||||
export CONFLUENCE_TOKEN="<PAT>" # Bearer-PAT (Server/DC)
|
||||
pip install atlassian-python-api
|
||||
```
|
||||
|
||||
Ohne Credentials werden Confluence-Quellen sauber uebersprungen.
|
||||
|
||||
## Betrieb (GitLab CI)
|
||||
|
||||
- `quality`-Stage: `ruff` (Lint) + `gitleaks` (Secret-Scan) bei jedem Push/MR.
|
||||
- `etl`-Stage: **stuendlich Mo–Fr 8–17 Uhr** (Cron `0 8-17 * * 1-5`), baut Wissen,
|
||||
committet `data/` nach `main` (per `GIT_PUSH_TOKEN`); der Commit loest automatisch
|
||||
einen `pages`-Refresh aus.
|
||||
- `pages`-Stage: baut `public/` (Uebersicht, Hilfe, Chatbot-Anschluss, Wissensquellen)
|
||||
fuer GitLab Pages auf `main`.
|
||||
- `preview`-Job (MR, manuell): verarbeitet eine neue/geaenderte Quelle via
|
||||
`--only` und stellt die erzeugten Markdowns als Job-Artefakt bereit (zum Pruefen
|
||||
vor dem Merge). `PREVIEW_ONLY` beim Start auf Tool-Id/Domaene/URL setzen.
|
||||
- **Kundeninfos inkrementell**: `incremental: true` crawlt nur NEUE Detailseiten und
|
||||
ueberschreibt vorhandene nicht – Re-Runs bleiben schnell.
|
||||
- **Confluence inkrementell**: `options: { incremental: true }` prueft pro Seite die
|
||||
Confluence-Versionsnummer (`source_version` im Frontmatter). Unveraenderte Seiten
|
||||
werden uebersprungen, nur geaenderte/neue Seiten neu verarbeitet – spart API-Last.
|
||||
- **Re-Tagging bei Metadaten-Aenderung**: Aenderst du in `tools.yaml`/`general.yaml`
|
||||
nur `tags`/`owners`/`contact` einer Quelle, weicht deren `meta_fingerprint`
|
||||
(im Frontmatter) vom gespeicherten ab. Der naechste inkrementelle Lauf passt dann
|
||||
die bestehenden Dateien dieser Quelle in-place an (nur Frontmatter, kein erneuter
|
||||
Download/API-Call).
|
||||
- **Scope-Wechsel**: Aenderst du den `scope` einer Quelle, wird die Seite neu
|
||||
verarbeitet und an `data/processed/<neuer-scope>/...` geschrieben; die alte Ablage
|
||||
wird nach erfolgreichem Schreiben **automatisch geloescht** (domaenen-intern, damit
|
||||
eine URL, die legitim unter mehreren Domaenen liegt – z.B. eine pathOS-Seite auch
|
||||
unter `web` – nicht versehentlich entfernt wird).
|
||||
- **Domain-Umbenennung**: Die neue Ablage entsteht automatisch, die alte Domain-Ablage
|
||||
bleibt aber stehen (domaenenuebergreifendes Loeschen waere wegen geteilter URLs
|
||||
unsicher). Den alten `data/processed/<scope>/<alte-domain>/`-Ordner daher bei einer
|
||||
Domain-Umbenennung manuell loeschen.
|
||||
- CI/CD-Variablen: `GIT_PUSH_TOKEN`, `CONFLUENCE_URL`, `CONFLUENCE_TOKEN` (Masked).
|
||||
- `scm-info.yaml`: Pflichtdatei fuer DB-GitLab-Compliance.
|
||||
|
||||
## Konfiguration
|
||||
|
||||
- `config/tools.yaml` – Tools, Domaenen, Quellen, Strategien, Scopes, Tags
|
||||
- `config/filter_rules.json` – Blacklist-Keywords, Regex-Redaction
|
||||
- `config/chunking.yaml` – gemeinsame Chunk-Defaults (pro Quelle ueber `options.chunk` ueberschreibbar)
|
||||
|
||||
### Chunking (optional, Default aus)
|
||||
|
||||
Grosse, stark gegliederte Dokumente (v.a. **INB**) werden zusaetzlich zur Voll-Datei in
|
||||
**Chunks entlang der Ueberschriften** zerlegt – ein **abgeleitetes, jederzeit neu
|
||||
erzeugbares** Artefakt unter `data/chunks/<scope>/<domaene>/<docslug>/`. Das Voll-Dokument
|
||||
in `data/processed/` bleibt unangetastet (Parent-Document-Muster): ein Anschliesser waehlt
|
||||
Voll-Dokument, Chunks oder beides. Es werden bewusst nur **deterministische, embedding-freie**
|
||||
Strategien angeboten (`headings | faq | recursive`); semantisches Chunking gehoert in die
|
||||
Vektor-DB des Anschliessers. Jeder Chunk bekommt eine kurze **Contextual-Retrieval**-Zeile
|
||||
(`> Kontext: <Dokument> > <Abschnitt>`). Aktivierung pro Quelle via `options: { chunk: headings }`.
|
||||
|
||||
**Inkrementell (kein Voll-Rebuild):** Chunking laeuft am Ende jedes ETL-Laufs automatisch
|
||||
mit (`src.main`) und ist Teil von `data/`. Pro Dokument wird nur dann neu gechunkt, wenn
|
||||
sich der **Inhalt** (`parent_hash`) ODER die **wirksamen Optionen/Strategie**
|
||||
(`chunk_fingerprint`) geaendert haben - sonst wird das Dokument uebersprungen (keine
|
||||
Datei-Aenderung, kein Git-Churn). Aenderst du also bei einer Quelle z.B. `chunk_level`
|
||||
oder von `headings` auf `recursive`, werden **nur die Dokumente dieser Ablage** neu
|
||||
erzeugt. Wird `chunk` wieder auf `off` gestellt oder ein Voll-Dokument geloescht, werden
|
||||
die zugehoerigen Chunks automatisch entfernt.
|
||||
|
||||
**Feinsteuerung (Default aus, pro Quelle ueberschreibbar):**
|
||||
|
||||
- `chunk_min_doc_chars`: Dokumente unter dieser Zeichenzahl bleiben **ganz** (kleine
|
||||
FAQ-/How-to-Seiten muessen nicht zwingend gechunkt werden).
|
||||
- `chunk_kind`: chunkt nur Dokumente mit passendem `kind` (`""` = alle; `attachment` =
|
||||
nur Anhang-PDFs; `document` = nur Voll-Seiten).
|
||||
|
||||
Beispiel **pathOS** (an der Tool-Tree-Quelle in `config/tools.yaml`): grosse Anhang-PDFs
|
||||
(z.B. EVU-Schnittstellen-Doku, 2,8 MB) werden gechunkt, Seiten und FAQs bleiben ganz.
|
||||
|
||||
```yaml
|
||||
options:
|
||||
incremental: true
|
||||
chunk: headings
|
||||
chunk_kind: attachment # nur Anhang-PDFs chunken
|
||||
chunk_min_doc_chars: 3000 # < 3k Zeichen bleiben ganz (~750 Token)
|
||||
```
|
||||
|
||||
## Version & Changelog
|
||||
|
||||
Aktuelle Version steht in **`VERSION`** (SemVer), die Historie in **`CHANGELOG.md`**
|
||||
(Format „Keep a Changelog"). Beides wird auf der GitLab-Pages-Seite **„Changelog"**
|
||||
angezeigt (Version auch in der Navigationsleiste). Pflege-Regeln:
|
||||
`.kiro/steering/changelog-versioning.md`.
|
||||
Reference in New Issue
Block a user