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:
- Extract = Sammeln. Wissen aus den Quellen holen (Confluence-Seiten, Webseiten, PDFs).
- Transform = Aufbereiten. In einheitliches Markdown umwandeln, mit Schlagworten/Scope versehen und vertrauliche Inhalte herausfiltern.
- 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· Manuelle Einrichtungsschritte:docs/SETUP.md
Denkmodell
Domaene -> Scope (intern|extern|allgemein) -> Tool -> Dokument(e)
- Domaene: fachlicher Bereich (jedes Tool ist eigene Domaene;
regulierung,kundeninfofuer 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.
- Issue anlegen mit der Vorlage „Neues Wissen" (
.gitlab/issue_templates/): Tool/Domaene, Quellen je Zeile (URL | Strategie | Scope), Verantwortliche (owners) und optional Kontakt (Defaulteinfachbahn@deutschebahn.com). - Reviewer uebernimmt die Angaben nach
config/tools.yamlbzw.config/general.yaml(neuer Tool-/Quellen-Eintrag inkl.owners:) – per GitLab Web-IDE (Variante A) oder manuell (Variante B). - Merge Request öffnen. Die MR-Freigabe ist das Quality Gate (4-Augen, via
CODEOWNERS+ Protected Branch). - Vorschau im MR: den
preview-Job manuell starten undPREVIEW_ONLYauf Tool-Id/Domaene/URL der neuen Quelle setzen. Er verarbeitet nur diese Quelle und stellt die erzeugten Markdowns als Job-Artefakt bereit. Lokal alternativpython -m src.main --only "<tool|domaene|url>" --data preview. - 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.yamleintraegt, stellt einen MR. Beim Merge (4-Augen viaCODEOWNERS+ 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 Frontmatterreview_notes)trusted: true(z.B. INB) bleibt trotz Treffer approved;redact: falseschaltet die Redaction pro Quelle ab.
config/approvals.yaml(nachtraegliche Korrektur): hebt einzelnepending-Dokumente per URL oderhash:<content_hash>doch aufapproved. 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 nurextern/+allgemein/. data/_meta.json: globale Statusdatei fuer nachgelagerte Systeme mitlast_run(letzter ETL-Lauf),last_change(wann sich der Bestand zuletzt inhaltlich/metadatenseitig geaendert hat),documents,by_scopeund einercontent_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_updatedund - falls vorhanden - die zugehoerigenchunks(Anzahl + Pfad) undattachments(Anhang-Dokumente: Name + Pfad). Anhang-Dokumente haben zudemkind: "attachment"undparent_url. Aggregate:documents_total,chunks_total,attachments_total,by_domain(mitdocuments/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
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
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 (Cron0 8-17 * * 1-5), baut Wissen, committetdata/nachmain(perGIT_PUSH_TOKEN); der Commit loest automatisch einenpages-Refresh aus.pages-Stage: bautpublic/(Uebersicht, Hilfe, Chatbot-Anschluss, Wissensquellen) fuer GitLab Pages aufmain.preview-Job (MR, manuell): verarbeitet eine neue/geaenderte Quelle via--onlyund stellt die erzeugten Markdowns als Job-Artefakt bereit (zum Pruefen vor dem Merge).PREVIEW_ONLYbeim Start auf Tool-Id/Domaene/URL setzen.- Kundeninfos inkrementell:
incremental: truecrawlt nur NEUE Detailseiten und ueberschreibt vorhandene nicht – Re-Runs bleiben schnell. - Confluence inkrementell:
options: { incremental: true }prueft pro Seite die Confluence-Versionsnummer (source_versionim 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.yamlnurtags/owners/contacteiner Quelle, weicht derenmeta_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
scopeeiner Quelle, wird die Seite neu verarbeitet und andata/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 unterweb– 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, Tagsconfig/filter_rules.json– Blacklist-Keywords, Regex-Redactionconfig/chunking.yaml– gemeinsame Chunk-Defaults (pro Quelle ueberoptions.chunkueberschreibbar)
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 passendemkind(""= 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.
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.