wiki/e-compliance/neutralisierung-detail.md

203 lines
14 KiB
Markdown
Raw 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.

# Neutralisierung
**Stand:** 2026-04-16
---
## 1. Grundidee
Neutralisierung passiert dort, wo **Content entsteht oder ins System einfliesst** — nicht als nachgelagerter Filter vor dem Modell-Call. Wenn Content einmal neutralisiert ist (z. B. im RAG), bleibt er neutralisiert. Kein doppeltes Verarbeiten.
---
## 2. Wer fordert Neutralisierung?
Drei Quellen, von breit nach spezifisch:
| Quelle | Flag im Code | Bedeutung |
|--------|-------------|-----------|
| **Feature-Instanz** | `DataNeutraliserConfig.enabled` (hat `featureInstanceId` + `mandateId`) | Alle Daten in dieser Feature-Instanz werden neutralisiert. Betrifft jeden Content-Einstieg innerhalb dieser Instanz. |
| **Chat-Workflow / Session** | `ServiceCenterContext.requireNeutralization` (gesetzt z. B. vom AI-Workspace oder Automation) | Dieser Workflow/Turn: jeder Content, der hier verarbeitet wird, wird neutralisiert. |
| **Dokument oder Quelle** | `FileItem.neutralize`, `FileFolder.neutralize`, `DataSource.neutralize`, `FeatureDataSource.neutralize`, `FeatureDataSource.neutralizeFields` | Dieses konkrete Objekt: Content daraus wird neutralisiert, egal ob die Feature-Instanz oder der Workflow es sonst fordern würden. `FileFolder.neutralize` propagiert auf alle enthaltenen Dateien. `neutralizeFields` ermoeglicht Feld-Level-Maskierung bei DB-Queries. |
**Auswertung:** Irgendeine Quelle sagt `True` → neutralisieren. Keine Quelle kann eine andere aufheben. Es gibt kein `False`-Override, das ein `True` von woanders aushebelt.
---
## 3. Wo passiert die Neutralisierung?
Am **Punkt der Content-Einspeisung** — dort wo Rohdaten zu verarbeitbarem Content werden:
| Content-Einstieg | Was passiert | Wer prüft das Flag |
|------------------|-------------|---------------------|
| **Datei-Indexierung** (RAG) | Text-Chunks werden über `processText` neutralisiert, bevor sie ins Embedding gehen. Medien: nur internes Modell oder nicht indexieren. Ergebnis: RAG enthält nur neutralisierten Content. | `mainServiceKnowledge.indexFile` liest `FileItem.neutralize` |
| **Content-Extraktion** (Dokumente für KI-Verarbeitung) | Extrahierter Text/Tabellen werden neutralisiert. PDF, DOCX, XLSX, PPTX über `processFile` (Extract → neutralisieren → zurückschreiben). | Caller kennt die Quelle und deren Flag |
| **User-Prompt** (Chat-Eingabe) | Prompt-Text wird durch `processText` neutralisiert, wenn Feature-Instanz oder Workflow es fordern. | `mainServiceAi` prüft Context-Flag / Config |
| **DataSource-Download** | Flag wird auf erstellte `FileItem`s vererbt → bei Extraktion/Indexierung greift das File-Flag automatisch. | `mainServiceAgent._resolveDataSource` / `_downloadFromDataSource` |
| **FeatureDataSource-Abfrage (Tabelle)** | Wenn eine `FeatureDataSource.neutralize=True` hat → Content aus diesem Sub-Agent-Call wird neutralisiert. | `mainServiceAgent._queryFeatureInstance` setzt `requireNeutralization=True` |
| **FeatureDataSource-Abfrage (Feld/Typ, seit 2026-06)** | Typ- und vererbungsbewusst: **Strings** werden substring-neutralisiert sobald effektiv (explizit ODER geerbt, Feldname als Typ-Hint); **Binary** wird gedroppt; **andere Skalare** (number/date/bool) nur bei **explizitem** Feld-Flag als Ganzwert-Platzhalter `[NEUT.<field>.<hash>]`. Identifikatoren/System-Spalten ausgenommen, Engine-Ausfall = `[REDACTED]`. RAG-Bootstrap nutzt dieselbe Policy (Query- und Index-Pfad konsistent). | `featureDataProvider.finalizeRowsAsync` / `_neutralizeAndSerializeRows`; Policy aus `coreTools/_featureSubAgentTools` via `resolveEffectiveForFds` |
| **De-Neutralisierung für Download (seit 2026-06)** | Read-only Agent-Tool `revealDocument` löst Platzhalter **nur** über das lokale Mapping (`resolveText`, kein externes LLM) auf und liefert Klartext als transienten Einmal-Download (SSE `revealDownload`) — kein Save/Index/Historie. | `coreTools/_mediaTools._revealDocument` |
| **Ordner-Neutralisierung** | `FileFolder.neutralize=True` propagiert auf alle enthaltenen Dateien (rekursiv). Neue/verschobene Dateien erben das Flag. Index-Purge + Re-Index wie bei einzelnen Dateien. | `routeDataFiles.updateFolderNeutralize` |
| **Workflow-Aktion `neutralizeData`** | Expliziter Neutralisierungs-Schritt auf bereits extrahierten ContentParts. | Workflow-Config + `NeutralizationConfig.enabled` |
**Was NICHT nochmal neutralisiert werden muss:**
- RAG-Chunks, die bereits neutralisiert indexiert sind — die sind schon sauber.
- Web-Suchergebnisse — enthalten keine Mandantendaten, brauchen keine Neutralisierung.
**Flag-Änderung:** Ändert sich `FileItem.neutralize` (Toggle) → Index löschen → Re-Indexierung triggern → neuer Index ist im richtigen Zustand.
---
## 4. Engine: `NeutralizationService`
Eine Engine, drei Einstiege, aufgerufen an den Content-Einstiegspunkten aus Abschnitt 3.
| API | Eingabe | Status |
|-----|---------|--------|
| `processText(text)` | Rohtext (Prompt, Message, Text-ContentPart, Text-Chunk) | **Ist** |
| `processFile(fileId)` | Datei aus DB — wird nach MIME geroutet | **Ist** |
| `processBinaryBytes` / `Async` | Bytes + Dateiname + MIME | **Ist** |
| **`processImage(imageBytes, fileName)`** / `processImageAsync` | Bild-Datei oder Bild-ContentPart (image/png, image/jpeg, …) | **Ist** (seit ~2026 Q1) |
**Ist-Zustand Bilder (aktualisiert 2026-04-05):** `processImageAsync` ist implementiert und nutzt `NEUTRALIZATION_IMAGE` (Private LLM Vision). `_processBinaryFile` verarbeitet jetzt Bild-Parts ueber `processImageAsync`. **Einschraenkung:** Eigenstaendige Bild-Dateien (`image/*` MIME direkt an `processFile`) werden in `_isBinaryMimeType` weiterhin als Skip behandelt.
**Ist (aktualisiert):** `processImageAsync` existiert als Einstieg. Ruft internes Vision-Modell (`NEUTRALIZATION_IMAGE` → Private-LLM) auf, um sensible Inhalte in Bildern zu erkennen/entfernen. Kein externer Provider. Modell nicht verfügbar → Bild blockieren (nicht unbehandelt weiterreichen).
**Ablauf nach MIME:**
| MIME | Pfad |
|------|------|
| PDF, DOCX, XLSX, PPTX | `runExtraction` → Text/Table-Parts durch `_neutralizeText`, Bild-Parts durch `processImage` → PDF in-place oder Office-Renderer |
| Text, JSON, CSV, XML | `_neutralizeText` direkt (TextProcessor, ListProcessor, BinaryProcessor je nach Inhalt) |
| Bilder (image/*) | **`processImage`** → internes Vision-Modell (`NEUTRALIZATION_IMAGE`). Nicht verfügbar → blockieren. |
| Video, Audio | Nur internes Modell oder blockieren (analog Bilder, aber niedrigere Priorität) |
| Anderes Binary | Skip (nicht unterstützt) |
**Mapping:** Platzhalter `[typ.uuid]` → Attribute in DB. `resolveText(text)` für Rückübersetzung.
---
## 5. Modellauswahl
| Operation Type | Modelle | Zweck |
|----------------|---------|-------|
| `NEUTRALIZATION_TEXT` | `poweron-text-general` (Rating 9) | Falls Engine ein LLM für Text braucht |
| `NEUTRALIZATION_IMAGE` | `poweron-vision-general` (9), `poweron-vision-deep` (9) | Medien bei Neutralisierungspflicht: nur intern |
Registriert in `aicorePluginPrivateLlm.py`. Externe Provider haben keine `NEUTRALIZATION_*`-Ratings → werden nie für Neutralisierung gewählt.
---
## 6. Fail-safe
| Situation | Verhalten |
|-----------|-----------|
| Neutralisierung gefordert, Engine nicht verfügbar | Content **nicht** weiterverarbeiten (blockieren) |
| Neutralisierung eines Teils schlägt fehl | Teil entfernen, nicht im Rohzustand weiterleiten |
| Kein internes Modell für Medien verfügbar | Medien-Part entfernen |
---
## 7. Code-Karte
| Bereich | Pfad |
|---------|------|
| Engine | `features/neutralization/serviceNeutralization/mainServiceNeutralization.py` + `subProcess*.py` |
| Config + Attribute DB | `features/neutralization/datamodelFeatureNeutralizer.py`, `interfaceFeatureNeutralizer.py` |
| Index (Data-at-rest) | `serviceCenter/services/serviceKnowledge/mainServiceKnowledge.py` |
| Prompt-Neutralisierung | `serviceCenter/services/serviceAi/mainServiceAi.py` (`_shouldNeutralize`, `_neutralizeRequest`) |
| Agent / DataSource / Feature | `serviceCenter/services/serviceAgent/mainServiceAgent.py` |
| Workflow-Aktion | `workflows/methods/methodContext/actions/neutralizeData.py` |
| Modelle / Enums | `datamodels/datamodelAi.py`, `aicore/aicorePluginPrivateLlm.py` |
| Flags | `datamodelFiles.py` (`FileItem.neutralize`), `datamodelDataSource.py`, `datamodelFeatureDataSource.py` |
| Context | `serviceCenter/context.py` (`ServiceCenterContext.requireNeutralization`) |
---
## 8. Umsetzungsplan
### Schritt 1: `processImage` in Engine bauen
**Datei:** `mainServiceNeutralization.py`
**Was:** Neue Methode `processImage(imageBytes: bytes, fileName: str, mimeType: str) -> Dict` und `processImageAsync`.
**Wie:** Internes Vision-Modell aufrufen (`NEUTRALIZATION_IMAGE` via Model Selector / `aiObjects`). Prompt: sensible Inhalte im Bild erkennen und beschreiben, damit Caller entscheiden kann ob Bild blockiert wird. Kein internes Modell verfügbar → `{'status': 'blocked'}` zurückgeben.
**Abhängigkeit:** `OperationTypeEnum.NEUTRALIZATION_IMAGE` + Modell-Ratings in `aicorePluginPrivateLlm.py`**bereits erledigt**.
### Schritt 2: `_shouldNeutralize` vereinfachen
**Datei:** `mainServiceAi.py` (Zeile ~560)
**Ist:** Prüft `request.requireNeutralization``context.requireNeutralization``NeutralizationConfig.enabled` (Mandant-Level Config). `request.requireNeutralization=False` bricht sofort ab.
**Soll:** Drei Quellen gemäss Abschnitt 2, kein `False`-Override:
1. Feature-Instanz: `NeutralizationConfig.enabled` (hat `featureInstanceId`)
2. Workflow/Session: `context.requireNeutralization`
3. Request: `request.requireNeutralization`
Irgendein `True` → neutralisieren. `False` in einer Quelle hebt `True` in einer anderen **nicht** auf.
**Änderung:** `if request.requireNeutralization is False: return False` **entfernen**. Stattdessen OR-Verknüpfung aller drei Quellen.
### Schritt 3: `_neutralizeRequest` auf Prompt/Messages beschränken
**Datei:** `mainServiceAi.py` (Zeile ~592)
**Ist:** Neutralisiert `prompt`, `context`, `messages` (String-Content).
**Soll:** Zusätzlich Text in `contentParts` und Text-Teile in multimodalen Messages (content-Liste mit `type==text`). Bild-Teile in Messages: bei Neutralisierungspflicht über `processImage` prüfen oder entfernen.
**Wichtig:** Hier geht es nur um Prompt-/Sessiondaten. File-Content aus RAG ist bereits neutralisiert (Schritt 5), muss hier nicht nochmal durch.
### Schritt 4: `indexFile` — Bild-Chunks behandeln
**Datei:** `mainServiceKnowledge.py` (Zeile ~146 ff.)
**Ist:** Nur `textObjects` (contentType == "text") werden bei `_shouldNeutralize` durch `processText` geschickt. Bild-Chunks (`contentType == "image"`) werden unverändert indexiert.
**Soll:** Bild-Chunks bei `_shouldNeutralize` über `processImage` (Schritt 1) prüfen. Ergebnis `blocked` → Bild-Chunk nicht indexieren. Ergebnis OK → Bild-Chunk indexieren (internes Modell hat es gesehen, kein externer Provider nötig).
**Abhängigkeit:** Schritt 1.
### Schritt 5: Agent-Tool `readFile` — Content bei Extraktion neutralisieren
**Datei:** `mainServiceAgent.py`, Tool `_readFile` (Zeile ~540 ff.)
**Ist:** Liest aus Knowledge Store (bereits indexiert) oder extrahiert on-demand. Kein Neutralisierungs-Check.
**Soll:**
- **Knowledge Store Pfad (Zeile ~548):** RAG-Chunks sind bereits neutralisiert wenn `FileItem.neutralize=True` war (Schritt 4) → nichts zu tun.
- **On-Demand Extraktion (Zeile ~630 ff.):** Nach `runExtraction` und vor Rückgabe der Text-Objekte: `FileItem.neutralize` laden. Wenn `True` → Text-Objekte durch `processText`, Bild-Objekte durch `processImage`.
### Schritt 6: Agent-Tool `summarizeContent` — File-Flag prüfen
**Datei:** `mainServiceAgent.py`, Tool `_summarizeContent` (Zeile ~1921 ff.)
**Ist:** Liest Content-Objects aus Knowledge Store, baut `combinedText` und ruft `callAi` auf. Kein Neutralisierungs-Check.
**Soll:** Content aus Knowledge Store ist bereits neutralisiert (Schritt 4) → nichts zu tun, **sofern** File indexiert war. Wenn nicht indexiert: File-Flag prüfen, Text vor `callAi` durch `processText`.
**Hinweis:** Durch Schritt 4 ist der Regelfall abgedeckt.
### Schritt 7: Agent-Tool `describeImage` — File-Flag prüfen
**Datei:** `mainServiceAgent.py`, Tool `_describeImage` (Zeile ~2015 ff.)
**Ist:** Lädt Bild-Chunk oder Rohdaten, sendet per `callAi` mit `IMAGE_ANALYSE` an **beliebigen** Provider (inkl. extern). Kein Neutralisierungs-Check.
**Soll:** `FileItem.neutralize` laden (fileId ist bekannt). Wenn `True`:
- `operationType` auf `NEUTRALIZATION_IMAGE` statt `IMAGE_ANALYSE` → Model Selector wählt nur internes Modell.
- Kein internes Modell → Tool gibt Fehler zurück statt Bild an externen Provider zu senden.
### Schritt 8: `_processBinaryFile` — Bild-Parts nicht mehr überspringen
**Datei:** `mainServiceNeutralization.py` (Zeile ~298)
**Ist:** `if type_group in ('binary', 'image'): neutralized_parts.append(part); continue` — Bild-Parts werden übersprungen.
**Soll:** `binary` weiterhin skip. `image` → durch `processImage` (Schritt 1). Ergebnis `blocked` → Part entfernen. Ergebnis OK → Part behalten.
**Abhängigkeit:** Schritt 1.
### Schritt 9: Workflow-Aktion `neutralizeData` — Bild-Parts
**Datei:** `neutralizeData.py` (Zeile ~130 ff.)
**Ist:** Iteriert über ContentParts, neutralisiert nur Parts mit `part.data` (Text). `typeGroup != text/table` → unverändert durchgereicht.
**Soll:** Bild-Parts durch `processImage`. Ergebnis `blocked` → Part entfernen.
**Abhängigkeit:** Schritt 1.
### Schritt 10: Tests
| Test | Was |
|------|-----|
| T1 | `FileItem.neutralize=True``indexFile` → Text-Chunks neutralisiert, Bild-Chunks geprüft/blockiert |
| T2 | `readFile` on-demand auf File mit `neutralize=True` → Text neutralisiert |
| T3 | `describeImage` auf File mit `neutralize=True` → nur internes Modell |
| T4 | Feature-Instanz Config `enabled=True` → Prompt in `callAi` neutralisiert |
| T5 | Workflow `requireNeutralization=True` → Prompt neutralisiert |
| T6 | `processFile` auf PDF mit Bildern → Text neutralisiert, Bild-Parts durch `processImage` |
| T7 | Flag-Toggle → Index gelöscht → Re-Index im richtigen Zustand |
### Schritt 11: Logging
**Alle Stellen (Schritte 29):** Bei Neutralisierung loggen: welche Quelle (Feature-Instanz / Workflow / File-Flag), welche `fileId` falls vorhanden, Ergebnis (OK / Part entfernt / blockiert). **Kein Klartext** in Logs.