Zum Inhalt springen

Presidio selbst hosten (Docker Compose, Deutsch + Englisch)

Admin

Die 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"
Terminal-Fenster
docker compose up -d
curl 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.yaml
# Presidio Text-Only: Analyzer (Stanza, en+de) + Anonymizer — Produktiv-Setup
services:
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:
# 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 anlegen
USER root
ENV STANZA_RESOURCES_DIR=/opt/stanza_resources
RUN 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 legen
COPY conf/stanza_multilingual.yaml /tmp/conf/stanza_multilingual.yaml
COPY conf/analyzer.yaml /tmp/conf/analyzer.yaml
COPY conf/recognizers.yaml /tmp/conf/recognizers.yaml
RUN 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 backen
USER presidio
RUN 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.yaml
ENV ANALYZER_CONF_FILE=presidio_analyzer/conf/analyzer.yaml
ENV RECOGNIZER_REGISTRY_CONF_FILE=presidio_analyzer/conf/recognizers.yaml
# 7) Gunicorn-Config mit Lifecycle-Hooks + faulthandler-Boot-Watchdog.
USER root
COPY --chown=presidio:presidio gunicorn.conf.py /app/gunicorn.conf.py
USER 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 root
COPY patch_stanza_mwt.py /tmp/patch_stanza_mwt.py
RUN cd /app && python3 /tmp/patch_stanza_mwt.py && rm /tmp/patch_stanza_mwt.py
USER presidio

analyzer/conf/stanza_multilingual.yaml — NLP-Engine

Section titled “analyzer/conf/stanza_multilingual.yaml — NLP-Engine”
nlp_engine_name: stanza
models:
- lang_code: en
model_name: en
- lang_code: de
model_name: de
ner_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:
- NRP
# '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: 0

analyzer/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: predefined

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 faulthandler
import os
import sys
import 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 = True
enable_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_exit und on_exit fü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 of
the first word, lemma = surface). Alignment then holds, doc.text == text, entities
keep their original offsets. Verified in-container: minimal pairs im/in dem, zum/zu
dem, beim/bei dem, Am ... zur all yield PERSON with correct offsets; lemmas of
ordinary tokens unchanged.
Fails the build loudly when the expected code is not found (a presidio bump must be
re-verified, not silently unpatched).
"""
import importlib
import os
import pathlib
import sys
# run from /app: presidio_analyzer lives there as a plain package, not in site-packages
sys.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 import
compile(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 []:

Terminal-Fenster
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"]}'
Terminal-Fenster
docker compose build # lädt Stanza en+de — dauert einige Minuten
docker compose up -d
docker compose logs -f analyzer # auf "post_worker_init ... READY" warten

Die unterstützten Sprachen stehen an drei Stellen und müssen identisch sein:

DateiSchlüssel
analyzer.yamlsupported_languages
recognizers.yamlsupported_languages
stanza_multilingual.yamlmodels[].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-TypBeispieleSprachabhängig?
NLP/NER (Stanza)PERSON, LOCATION, ORGANIZATION, NRP, DATE_TIMEJa — Umfang je Modell
Sprachsensitiv (regelbasiert)Kreditkarte, Datum, E-Mail, TelefonJa — eigene Kontextwörter je Sprache
Sprachagnostisch (reine Regex)IBAN, Krypto, IP, MAC, URL, Medical LicenseNein — gelten für alle Sprachen

Wichtige Folgen für Deutsch vs. Englisch:

  • Das englische Stanza-Modell (OntoNotes) erkennt zusätzlich DATE/TIME per NER und liefert damit DATE_TIME; das deutsche Modell (CoNLL03) kennt nur PER/LOC/ORG/MISC — Datumsangaben werden dort nur über den regelbasierten DateRecognizer (Kontextwörter) erfasst.
  • NRP (Nationalität/Religion/politische Gruppe) speist sich im Englischen aus NORP, im Deutschen aus MISC — die Treffergüte unterscheidet sich.
  • MedicalLicenseRecognizer ist 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.

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-Workerohne Indexlaufmit IndexlaufFaktor
48,3 s10,2 s1,2×
24,8 s11,7 s2,4×
1 (Standard)7,2 s30,8 s4,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_LIMIT entsprechend anheben (z. B. 12g) und einen Host mit ≥ 16 GB einplanen.
  • CPU-Threads: ANALYZER_OMP_THREADS auf 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).
VariableDefaultWirkung
ANALYZER_WORKERS1Parallele Worker. Jeder lädt die Modelle voll → RAM ≈ Worker × ~2–3 GB. Für Chat + Indexläufe gleichzeitig ≥ 4 → Wie viele Worker?
ANALYZER_OMP_THREADS2CPU-Threads je Inferenz. Auf Kerne ÷ Worker setzen; ab 2 Workern Pflicht, sonst drohen drastische Einbrüche unter Parallellast.
ANALYZER_MEMORY_LIMIT6gSpeicherlimit des Analyzer-Containers; bei mehr Workern anheben.
ANALYZER_MEMORY_RESERVATION3gReservierter Speicher.
ANALYZER_PORT / ANONYMIZER_PORT6001 / 6002Host-Ports.
ANALYZER_TIMEOUT300Hard-Timeout pro Request/Worker-Boot (Sekunden).
ANALYZER_MAX_REQUESTS0Worker-Rotation; 0 = aus (empfohlen bei 1 Worker).
ANALYZER_BOOT_WATCHDOG330Frist für den Stack-Dump beim Worker-Boot.
ANONYMIZER_WORKERS1Worker des Anonymizers (genügsam).

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.

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.

  • Boot dauert lang / Healthcheck rot: Stanza en+de lädt beim Erststart zig Sekunden. Der start_period: 180s deckt das ab; in den Logs auf post_worker_init ... READY warten.
  • „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_WORKERS prü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_THREADS auf 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 dem curl aus dem Patch-Abschnitt prüfen.