Presidio selbst hosten (Docker Compose, Deutsch + Englisch)
AdminDie PII-Erkennung von CompanyWizard nutzt Microsoft Presidio als externen Dienst. Wer die kostenlose bzw. Evaluierungs-Version betreibt, kann ihn per Docker selbst bereitstellen. Diese Seite liefert zwei fertige Setups: einen Minimal-Quickstart (Englisch, schnell) und den mehrsprachigen Produktivbuild (Deutsch + Englisch, Stanza). Was der PII-Schild fachlich tut, erklärt Der PII-Schild; die App-seitige Anbindung beschreibt Presidio einrichten.
Variante A — Minimal-Quickstart (nur Englisch)
Section titled “Variante A — Minimal-Quickstart (nur Englisch)”Zum schnellen Ausprobieren reichen die beiden offiziellen Images ohne eigenen Build. Der Standard-Analyzer bringt ein englisches spaCy-Modell mit.
services: analyzer: image: mcr.microsoft.com/presidio-analyzer:2.2.362 restart: unless-stopped ports: - "127.0.0.1:6001:5001" anonymizer: image: mcr.microsoft.com/presidio-anonymizer:2.2.362 restart: unless-stopped ports: - "127.0.0.1:6002:5001"docker compose up -dcurl http://localhost:6001/health # -> "Presidio Analyzer service is up"Grenze: Dieses Setup erkennt nur englische Namen/Orte/Organisationen (NER). Für deutsche Texte brauchen Sie Variante B.
Variante B — Produktivbuild (Deutsch + Englisch, Stanza)
Section titled “Variante B — Produktivbuild (Deutsch + Englisch, Stanza)”Dieser Build ersetzt das spaCy-Standardmodell durch Stanza mit kuratierten Recognizern für en + de und einer robusten Gunicorn-Konfiguration. Legen Sie folgende Verzeichnisstruktur an:
presidio/├── docker-compose.yaml└── analyzer/ ├── Dockerfile ├── gunicorn.conf.py ├── patch_stanza_mwt.py └── conf/ ├── stanza_multilingual.yaml ├── analyzer.yaml └── recognizers.yamldocker-compose.yaml
Section titled “docker-compose.yaml”# Presidio Text-Only: Analyzer (Stanza, en+de) + Anonymizer — Produktiv-Setupservices: analyzer: build: context: ./analyzer restart: unless-stopped ports: # Sicherer (falls Reverse-Proxy auf demselben Host): "127.0.0.1:${ANALYZER_PORT:-6001}:5001" - "${ANALYZER_PORT:-6001}:5001" environment: - PORT=5001 - WORKERS=${ANALYZER_WORKERS:-1} # wird vom Image-entrypoint.sh ausgewertet: gunicorn -w $WORKERS # Threads pro Inferenz begrenzen: auf Kerne ÷ Worker setzen. Ohne Limit nimmt sich jede # Stanza/PyTorch-Inferenz ALLE Kerne; parallele Anfragen können dann um ein Vielfaches # einbrechen. KMP_BLOCKTIME=0 stoppt das Spin-Waiting der Threads nach der Inferenz. - OMP_NUM_THREADS=${ANALYZER_OMP_THREADS:-2} - KMP_BLOCKTIME=0 # Drei zueinander passende Configs (en + de). Müssen konsistent sein, # sonst: "Misconfigured engine, supported languages have to be consistent". - NLP_CONF_FILE=presidio_analyzer/conf/stanza_multilingual.yaml - ANALYZER_CONF_FILE=presidio_analyzer/conf/analyzer.yaml - RECOGNIZER_REGISTRY_CONF_FILE=presidio_analyzer/conf/recognizers.yaml # Gunicorn-Config-File mit Lifecycle-Hooks + faulthandler-Boot-Watchdog. - GUNICORN_CMD_ARGS=--config=/app/gunicorn.conf.py - ANALYZER_TIMEOUT=${ANALYZER_TIMEOUT:-300} - ANALYZER_GRACEFUL_TIMEOUT=${ANALYZER_GRACEFUL_TIMEOUT:-30} - ANALYZER_MAX_REQUESTS=${ANALYZER_MAX_REQUESTS:-0} - ANALYZER_MAX_REQUESTS_JITTER=${ANALYZER_MAX_REQUESTS_JITTER:-0} - ANALYZER_LOG_LEVEL=${ANALYZER_LOG_LEVEL:-info} - ANALYZER_BOOT_WATCHDOG=${ANALYZER_BOOT_WATCHDOG:-330} healthcheck: # curl ist in den slim-Images nicht garantiert -> python3 nutzen test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:5001/health', timeout=5)"] interval: 30s timeout: 10s retries: 5 start_period: 180s # Stanza en+de braucht beim Erststart deutlich länger deploy: resources: limits: memory: ${ANALYZER_MEMORY_LIMIT:-6g} # 8GB Host: 6g Analyzer + 1g Anonymizer + ~1g OS/Buffer reservations: memory: ${ANALYZER_MEMORY_RESERVATION:-3g} networks: - presidio
anonymizer: image: mcr.microsoft.com/presidio-anonymizer:2.2.362 restart: unless-stopped ports: - "${ANONYMIZER_PORT:-6002}:5001" environment: - PORT=5001 - GUNICORN_CMD_ARGS=--workers=${ANONYMIZER_WORKERS:-1} # zuverlässig im offiziellen Image healthcheck: test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:5001/health', timeout=5)"] interval: 30s timeout: 10s retries: 5 start_period: 30s depends_on: - analyzer # nur Start-Reihenfolge deploy: resources: limits: memory: 1g networks: - presidio
networks: presidio:analyzer/Dockerfile
Section titled “analyzer/Dockerfile”# 1) Basis-Image PINNEN (Tag vorher gegen MCR verifizieren!)FROM mcr.microsoft.com/presidio-analyzer:2.2.362
# 2) Feste, für 'presidio' nutzbare Modell-Location anlegenUSER rootENV STANZA_RESOURCES_DIR=/opt/stanza_resourcesRUN mkdir -p "$STANZA_RESOURCES_DIR" && chown -R presidio:presidio "$STANZA_RESOURCES_DIR"
# 3) stanza SYSTEMWEIT mit dem App-Interpreter installieren (kein user-site)RUN python3 -m pip install --no-cache-dir stanza
# 4) Conf-Dateien an den TATSÄCHLICHEN presidio_analyzer-Pfad legenCOPY conf/stanza_multilingual.yaml /tmp/conf/stanza_multilingual.yamlCOPY conf/analyzer.yaml /tmp/conf/analyzer.yamlCOPY conf/recognizers.yaml /tmp/conf/recognizers.yamlRUN CONF_DIR="$(python3 -c 'import os,presidio_analyzer as p; print(os.path.join(os.path.dirname(p.__file__),"conf"))')" \ && cp /tmp/conf/stanza_multilingual.yaml "$CONF_DIR/stanza_multilingual.yaml" \ && cp /tmp/conf/analyzer.yaml "$CONF_DIR/analyzer.yaml" \ && cp /tmp/conf/recognizers.yaml "$CONF_DIR/recognizers.yaml" \ && rm -rf /tmp/conf
# 5) Modelle als 'presidio' nach STANZA_RESOURCES_DIR backenUSER presidioRUN python3 -c "import stanza; stanza.download('en'); stanza.download('de')"
# 6) Default-ENVs auf unsere Configs umbiegen (von docker-compose ggf. überschrieben).ENV NLP_CONF_FILE=presidio_analyzer/conf/stanza_multilingual.yamlENV ANALYZER_CONF_FILE=presidio_analyzer/conf/analyzer.yamlENV RECOGNIZER_REGISTRY_CONF_FILE=presidio_analyzer/conf/recognizers.yaml
# 7) Gunicorn-Config mit Lifecycle-Hooks + faulthandler-Boot-Watchdog.USER rootCOPY --chown=presidio:presidio gunicorn.conf.py /app/gunicorn.conf.pyUSER presidio
# 8) Pflicht für Deutsch: Mehrwort-Token (im/am/zum/zur/beim/vom) nicht expandieren,# sonst verliert der de-Leg jede NER-Spanne (keine PERSON) und eingerückte Texte# enden im HTTP 500 "Did not find word ...". Siehe Abschnitt patch_stanza_mwt.py.USER rootCOPY patch_stanza_mwt.py /tmp/patch_stanza_mwt.pyRUN cd /app && python3 /tmp/patch_stanza_mwt.py && rm /tmp/patch_stanza_mwt.pyUSER presidioanalyzer/conf/stanza_multilingual.yaml — NLP-Engine
Section titled “analyzer/conf/stanza_multilingual.yaml — NLP-Engine”nlp_engine_name: stanzamodels: - lang_code: en model_name: en - lang_code: de model_name: dener_model_configuration: labels_to_ignore: - O model_to_presidio_entity_mapping: # Deutsch – Stanza CoNLL03: PER, LOC, ORG, MISC PER: PERSON LOC: LOCATION ORG: ORGANIZATION MISC: NRP # Englisch – Stanza OntoNotes: PERSON, GPE, ORG, NORP, DATE, TIME … PERSON: PERSON GPE: LOCATION LOCATION: LOCATION ORGANIZATION: ORGANIZATION NORP: NRP DATE: DATE_TIME TIME: DATE_TIME low_confidence_score_multiplier: 0.4 low_score_entity_names: - NRPanalyzer/conf/analyzer.yaml — Sprachen
Section titled “analyzer/conf/analyzer.yaml — Sprachen”# 'supported_languages' MUSS identisch sein zu recognizers.yaml und zu den# lang_code in stanza_multilingual.yaml. Sonst: "Misconfigured engine,# supported languages have to be consistent".supported_languages: - en - de
default_score_threshold: 0analyzer/conf/recognizers.yaml — Recognizer-Registry
Section titled “analyzer/conf/recognizers.yaml — Recognizer-Registry”# Custom registry für deutsche (de) + englische (en) PII-Erkennung mit Stanza.supported_languages: - en - de
global_regex_flags: 26 # re.DOTALL | re.MULTILINE | re.IGNORECASE
recognizers:
# NLP-Recognizer (Stanza) - explizit pro Sprache. - name: StanzaRecognizer type: predefined supported_languages: - en - de
# Sprachsensitive Recognizer mit deutschen + englischen Kontextwörtern - name: CreditCardRecognizer type: predefined supported_languages: - language: en context: [credit, card, visa, mastercard, cc, amex, discover, jcb, diners, maestro, instapayment] - language: de context: [kreditkarte, karte, kartennummer, visa, mastercard, amex, cc]
- name: DateRecognizer type: predefined supported_languages: - language: en context: [date, time, birthday, birthdate, dob] - language: de context: [datum, zeit, uhrzeit, geburtstag, geburtsdatum]
- name: EmailRecognizer type: predefined supported_languages: - language: en context: [email, mail, address] - language: de context: [email, "e-mail", mail, mailadresse, emailadresse]
- name: PhoneRecognizer type: predefined supported_languages: - language: en context: [phone, number, telephone, fax, mobile, cell] - language: de context: [telefon, telefonnummer, rufnummer, handy, mobil, mobiltelefon, fax, durchwahl]
# Sprachagnostische Recognizer (reine Regex) -> gelten für jede Sprache. - name: CryptoRecognizer type: predefined - name: IbanRecognizer type: predefined - name: IpRecognizer type: predefined - name: MacAddressRecognizer type: predefined - name: UrlRecognizer type: predefined - name: MedicalLicenseRecognizer type: predefinedanalyzer/gunicorn.conf.py
Section titled “analyzer/gunicorn.conf.py”Diese Datei macht den Worker-Boot beobachtbar und gibt Stanza beim Kaltstart genügend Zeit (Default-Timeout 30 s ist zu knapp). Übernehmen Sie sie unverändert:
from __future__ import annotations
import faulthandlerimport osimport sysimport threading
def _env_int(name: str, default: int) -> int: raw = os.environ.get(name) if raw is None or raw.strip() == "": return default try: return int(raw) except ValueError: return default
bind = f"0.0.0.0:{os.environ.get('PORT', '5001')}"workers = _env_int("WORKERS", 1)worker_class = "sync"
timeout = _env_int("ANALYZER_TIMEOUT", 300)graceful_timeout = _env_int("ANALYZER_GRACEFUL_TIMEOUT", 30)
# Worker-Rotation standardmäßig AUS: bei WORKERS=1 erzwingt sie einen ~90s# Stanza-Reload und bricht den Healthcheck. Per ANALYZER_MAX_REQUESTS aktivieren.max_requests = _env_int("ANALYZER_MAX_REQUESTS", 0)max_requests_jitter = _env_int("ANALYZER_MAX_REQUESTS_JITTER", 0)
loglevel = os.environ.get("ANALYZER_LOG_LEVEL", "info")accesslog = "-"errorlog = "-"capture_output = Trueenable_stdio_inheritance = True
# Boot-Watchdog: dumpt nach so vielen Sekunden einen Python-Stack, falls der# Worker zwischen post_fork und post_worker_init hängt._BOOT_WATCHDOG_SECONDS = _env_int("ANALYZER_BOOT_WATCHDOG", timeout + 30)
def on_starting(server) -> None: server.log.info("[gunicorn-hook] on_starting master_pid=%s timeout=%ss", os.getpid(), timeout)
def when_ready(server) -> None: server.log.info("[gunicorn-hook] when_ready master_pid=%s workers=%s bind=%s", os.getpid(), workers, bind)
def post_fork(server, worker) -> None: server.log.info("[gunicorn-hook] post_fork worker_pid=%s -- arming boot watchdog (%ss)", os.getpid(), _BOOT_WATCHDOG_SECONDS) faulthandler.enable(file=sys.stderr, all_threads=True) faulthandler.dump_traceback_later(_BOOT_WATCHDOG_SECONDS, repeat=False, file=sys.stderr, exit=False)
def post_worker_init(worker) -> None: try: faulthandler.cancel_dump_traceback_later() except Exception: pass worker.log.info("[gunicorn-hook] post_worker_init worker_pid=%s -> READY", os.getpid())
def worker_abort(worker) -> None: worker.log.error("[gunicorn-hook] worker_abort (timeout) worker_pid=%s -- dumping stack:", os.getpid()) faulthandler.dump_traceback(file=sys.stderr, all_threads=True)Die Originaldatei enthält zusätzlich
pre_fork,worker_int,worker_exit,child_exitundon_exitfür lückenlose Boot-Logs — diese Hooks sind optional und nur für die Diagnose nützlich.
analyzer/patch_stanza_mwt.py — Pflicht für Deutsch
Section titled “analyzer/patch_stanza_mwt.py — Pflicht für Deutsch”#!/usr/bin/env python3"""Build-time patch for presidio_analyzer/nlp_engine/stanza_nlp_engine.py (cw-app #953).
Problem (measured 2026-09-12 against presidio-analyzer 2.2.362 + stanza 1.14): The vendored spacy-stanza tokenizer flattens Stanza multi-word tokens into their expanded words ("im" -> "in", "dem"). The expanded words no longer match the original text, the alignment check in _convert_doc raises, and the whole document text is REPLACED by " ".join(tokens). Consequences: Stanza NER spans (original offsets) hit no token boundary in the replaced doc and are dropped -> `de` returns NO PERSON for any German text containing am/im/zum/zur/beim/vom; and in indented text the token list ends before the text does, so LemmaContextAwareEnhancer raises "Did not find word ... in the list of tokens" -> HTTP 500.
Fix: keep multi-word tokens as ONE surface token (text = token.text, annotations ofthe first word, lemma = surface). Alignment then holds, doc.text == text, entitieskeep their original offsets. Verified in-container: minimal pairs im/in dem, zum/zudem, beim/bei dem, Am ... zur all yield PERSON with correct offsets; lemmas ofordinary tokens unchanged.
Fails the build loudly when the expected code is not found (a presidio bump must bere-verified, not silently unpatched)."""import importlibimport osimport pathlibimport sys
# run from /app: presidio_analyzer lives there as a plain package, not in site-packagessys.path.insert(0, os.getcwd())
module = importlib.import_module("presidio_analyzer.nlp_engine.stanza_nlp_engine")path = pathlib.Path(module.__file__)source = path.read_text(encoding="utf-8")
OLD = """ for token in sentence.tokens: for word in token.words: # Here, we're calculating the absolute token index in the doc, # then the *relative* index of the head, -1 for zero-indexed # and if the governor is 0 (root), we leave it at 0 if word.head: head = word.head + offset - len(tokens) - 1 else: head = 0 heads.append(head) tokens.append(word) offset += sum(len(token.words) for token in sentence.tokens)"""
NEW = """ for token in sentence.tokens: # cw-app #953: a multi-word token (German "im" -> "in" + "dem") is kept # as ONE surface token. Expanding it breaks the text alignment below, # which replaces the whole doc text and drops every NER span. if len(token.words) > 1: heads.append(0) tokens.append(_SurfaceToken(token)) continue for word in token.words: # Here, we're calculating the absolute token index in the doc, # then the *relative* index of the head, -1 for zero-indexed # and if the governor is 0 (root), we leave it at 0 if word.head: head = word.head + offset - len(tokens) - 1 else: head = 0 heads.append(head) tokens.append(word) offset += sum(1 if len(token.words) > 1 else len(token.words) for token in sentence.tokens)"""
HELPER = '''
class _SurfaceToken: """cw-app #953: stand-in for a Stanza multi-word token, unexpanded.
Carries the surface text and offsets of the token and borrows every other annotation (pos, feats, deprel, ...) from its first word. """
def __init__(self, token): self._word = token.words[0] self.text = token.text self.lemma = token.text self.head = 0 self.start_char = token.start_char self.end_char = token.end_char self.ner = token.ner
def __getattr__(self, name): return getattr(self._word, name)'''
MARKER = "# cw-app #953"if MARKER in source: print(f"already patched: {path}") sys.exit(0)
if source.count(OLD) != 1: print(f"ERROR: expected code block not found exactly once in {path} " f"(count={source.count(OLD)}) — presidio version changed, re-verify the patch.") sys.exit(1)
anchor = "\nclass StanzaTokenizer(object):"if source.count(anchor) != 1: print(f"ERROR: StanzaTokenizer class anchor not found exactly once in {path}") sys.exit(1)
patched = source.replace(OLD, NEW).replace(anchor, HELPER + anchor)path.write_text(patched, encoding="utf-8")
# compile check + smoke importcompile(patched, str(path), "exec")importlib.reload(module)print(f"patched: {path}")Prüfen nach dem Start — erwartet ist genau eine PERSON „Thomas Bergmann“ mit Offset 28–43; ohne Patch antwortet dieselbe Anfrage mit []:
curl -s http://localhost:6001/analyze -H 'Content-Type: application/json' \ -d '{"text":"Wir treffen uns im Büro mit Thomas Bergmann.","language":"de","score_threshold":0.4,"entities":["PERSON"]}'Bauen & starten
Section titled “Bauen & starten”docker compose build # lädt Stanza en+de — dauert einige Minutendocker compose up -ddocker compose logs -f analyzer # auf "post_worker_init ... READY" wartenSprachkonfiguration ändern
Section titled “Sprachkonfiguration ändern”Die unterstützten Sprachen stehen an drei Stellen und müssen identisch sein:
| Datei | Schlüssel |
|---|---|
analyzer.yaml | supported_languages |
recognizers.yaml | supported_languages |
stanza_multilingual.yaml | models[].lang_code |
Weichen sie voneinander ab, bricht Presidio beim Start ab mit „Misconfigured engine, supported languages have to be consistent”. Eine Sprache hinzufügen heißt also: in allen drei Dateien eintragen, das passende Stanza-Modell im Dockerfile mit stanza.download(...) herunterladen und neu bauen.
Nicht jede Erkennung steht in jeder Sprache zur Verfügung
Section titled “Nicht jede Erkennung steht in jeder Sprache zur Verfügung”| Recognizer-Typ | Beispiele | Sprachabhängig? |
|---|---|---|
| NLP/NER (Stanza) | PERSON, LOCATION, ORGANIZATION, NRP, DATE_TIME | Ja — Umfang je Modell |
| Sprachsensitiv (regelbasiert) | Kreditkarte, Datum, E-Mail, Telefon | Ja — eigene Kontextwörter je Sprache |
| Sprachagnostisch (reine Regex) | IBAN, Krypto, IP, MAC, URL, Medical License | Nein — gelten für alle Sprachen |
Wichtige Folgen für Deutsch vs. Englisch:
- Das englische Stanza-Modell (OntoNotes) erkennt zusätzlich
DATE/TIMEper NER und liefert damitDATE_TIME; das deutsche Modell (CoNLL03) kennt nurPER/LOC/ORG/MISC— Datumsangaben werden dort nur über den regelbasiertenDateRecognizer(Kontextwörter) erfasst. NRP(Nationalität/Religion/politische Gruppe) speist sich im Englischen ausNORP, im Deutschen ausMISC— die Treffergüte unterscheidet sich.MedicalLicenseRecognizerist auf US-Formate ausgelegt und für deutsche Texte wenig aussagekräftig.
Welche Entitätstypen die App tatsächlich maskiert, stellen Sie zusätzlich in den Systemeinstellungen ein → Presidio einrichten.
Wie viele Worker?
Section titled “Wie viele Worker?”Der Analyzer arbeitet mit gunicorn-Sync-Workern: Jeder Worker bearbeitet eine Anfrage, alle weiteren warten. Ein laufender Indexlauf (Vollreindex, Import, Neuaufbau nach geändertem Embedding) scannt Abschnitt für Abschnitt und hält bei zwei Sprachen (de + en) dauerhaft 2 Worker belegt. Die Scans eines Chat-Zugs — Eingabe, Suchanfrage, Wissenskontext — reihen sich dahinter ein, sobald keine Worker mehr frei sind.
Faustregel: Bedient die Instanz Chat und Indexläufe gleichzeitig, planen Sie mindestens 4 Analyzer-Worker ein. Der Standard von 1 Worker reicht zum Ausprobieren und solange Chat und Indexläufe selten zusammenfallen.
Gemessen mit dem Produktivbuild (Variante B, de + en) — Dauer der PII-Scans eines Chat-Zugs, ohne und mit einem laufenden Indexlauf:
| Analyzer-Worker | ohne Indexlauf | mit Indexlauf | Faktor |
|---|---|---|---|
| 4 | 8,3 s | 10,2 s | 1,2× |
| 2 | 4,8 s | 11,7 s | 2,4× |
| 1 (Standard) | 7,2 s | 30,8 s | 4,3× |
Bei 1 Worker braucht zusätzlich der Scan eines großen Werkzeugergebnisses (12 000 Zeichen) bis zu ~30 s. Messung: p95 je Messblock, jeweils der höhere von zwei Blöcken; bei 1 Worker ist ein Block ohne Last mit einem einzelnen Ausreißer nicht gewertet. Die Instanzen hatten unterschiedliche Hardware — maßgeblich ist der Faktor, nicht die absolute Zeit.
Was 4 Worker kosten:
- Arbeitsspeicher: jeder Worker lädt die Modelle voll — Richtwert ~8–12 GB für den Analyzer.
ANALYZER_MEMORY_LIMITentsprechend anheben (z. B.12g) und einen Host mit ≥ 16 GB einplanen. - CPU-Threads:
ANALYZER_OMP_THREADSauf Kerne ÷ Worker setzen (8 Kerne, 4 Worker →2). Ohne diese Begrenzung nimmt sich jede Inferenz alle Kerne; mehrere gleichzeitige Anfragen können dann drastisch einbrechen — gemessen bis zum 30-Fachen (2 Worker auf 8 Kernen).
Tuning (Umgebungsvariablen)
Section titled “Tuning (Umgebungsvariablen)”| Variable | Default | Wirkung |
|---|---|---|
ANALYZER_WORKERS | 1 | Parallele Worker. Jeder lädt die Modelle voll → RAM ≈ Worker × ~2–3 GB. Für Chat + Indexläufe gleichzeitig ≥ 4 → Wie viele Worker? |
ANALYZER_OMP_THREADS | 2 | CPU-Threads je Inferenz. Auf Kerne ÷ Worker setzen; ab 2 Workern Pflicht, sonst drohen drastische Einbrüche unter Parallellast. |
ANALYZER_MEMORY_LIMIT | 6g | Speicherlimit des Analyzer-Containers; bei mehr Workern anheben. |
ANALYZER_MEMORY_RESERVATION | 3g | Reservierter Speicher. |
ANALYZER_PORT / ANONYMIZER_PORT | 6001 / 6002 | Host-Ports. |
ANALYZER_TIMEOUT | 300 | Hard-Timeout pro Request/Worker-Boot (Sekunden). |
ANALYZER_MAX_REQUESTS | 0 | Worker-Rotation; 0 = aus (empfohlen bei 1 Worker). |
ANALYZER_BOOT_WATCHDOG | 330 | Frist für den Stack-Dump beim Worker-Boot. |
ANONYMIZER_WORKERS | 1 | Worker des Anonymizers (genügsam). |
Absicherung
Section titled “Absicherung”Binden Sie die Ports an 127.0.0.1, wenn ein Reverse-Proxy auf demselben Host terminiert (im Compose auskommentiert vorbereitet): "127.0.0.1:${ANALYZER_PORT:-6001}:5001". Presidio selbst bringt keine Authentifizierung mit — exponieren Sie es nie ungeschützt ins Netz.
Mit CompanyWizard verbinden
Section titled “Mit CompanyWizard verbinden”Tragen Sie den Analyzer-Endpoint (http://localhost:6001) als PiiScanner:PresidioEndpoint oder in Administration → Systemeinstellungen ein; dort lassen sich auch Schwellwert, Entitätstypen und Sprachen anpassen und die Verbindung testen. Vollständige Anbindung und Ausfallverhalten: Presidio einrichten.
Troubleshooting
Section titled “Troubleshooting”- Boot dauert lang / Healthcheck rot: Stanza en+de lädt beim Erststart zig Sekunden. Der
start_period: 180sdeckt das ab; in den Logs aufpost_worker_init ... READYwarten. - „Misconfigured engine …”: Die drei Sprachlisten stimmen nicht überein (siehe oben).
- Chat wird während eines Indexlaufs langsam: Die Scans des Chats warten hinter dem Indexlauf auf freie Worker.
ANALYZER_WORKERSprüfen — für Chat + Indexläufe gleichzeitig ≥ 4 → Wie viele Worker? - Anfragen dauern plötzlich 30 s und mehr, sobald mehrere gleichzeitig laufen: Die Inferenzen konkurrieren um alle Kerne.
ANALYZER_OMP_THREADSauf Kerne ÷ Worker setzen und den Analyzer neu starten. - Worker hängt nach Fork: Der Boot-Watchdog dumpt einen Python-Stack auf
stderr(docker compose logs analyzer). - Deutsch erkennt keine Personen / HTTP 500 „Did not find word …“: Das Image wurde ohne Schritt 8 (
patch_stanza_mwt.py) gebaut. Schritt ergänzen,docker compose build analyzer && docker compose up -d analyzer, dann mit demcurlaus dem Patch-Abschnitt prüfen.