# 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` ermöglicht 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..]`. 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 über `processImageAsync`. **Einschränkung:** Eigenständige 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 2–9):** Bei Neutralisierung loggen: welche Quelle (Feature-Instanz / Workflow / File-Flag), welche `fileId` falls vorhanden, Ergebnis (OK / Part entfernt / blockiert). **Kein Klartext** in Logs.