Technische deep-dive · 2026-05-25

AI observability: PII-redactie aan de bron, de drie harde invariants en wat we bewust niet doen

LLM-calls loggen op een manier die bruikbaar is voor debugging én compliant met GDPR, AI Act en NIS2. PII-redactie client-side, Ed25519-getekende evidence packs, en wat dit niet is.

Wanneer je backend een LLM aanroept — OpenAI, Claude, Mistral, Azure OpenAI — heb je een logging-probleem dat anders is dan bij traditionele API-calls. Een REST-call naar een payment provider logt je een request-ID en een status-code. Een LLM-call logt potentieel de volledige tekst die een eindgebruiker heeft ingevoerd, inclusief alles wat daarin zit.

GDPR noemt dit "persoonsgegevens". AI Act artikel 12 noemt dit "events die het AI-systeem genereert". NIS2 noemt dit "audit trail". Drie regelgevingen, één probleem: hoe log je LLM-calls op een manier die bruikbaar is voor debugging én compliant is met alle drie?

Dit is wat de monsys AI observability module doet — en wat ze bewust niet doet.

De drie harde invariants

Drie principes die niet onderhandelbaar zijn, ongeacht klant of configuratie:

1. Passief, nooit autonoom monsys voert geen prompts uit, blokkeert nooit inline, neemt nooit zelf beslissingen op basis van wat er gelogd wordt. Het is een observability-laag — bewijsmateriaal achteraf, geen control plane. Als je inline prompt-injection-filtering of guardrails nodig hebt, combineer ons met Lakera of Protect AI.

2. PII geredacteerd aan de bron Belgische IBAN, Rijksregister, BTW-BE, KBO, NL BSN, FR NIR, e-mails en telefoonnummers worden herkend met checksum-validatie vóór opslag. Raw inhoud bereikt de database nooit.

3. Evidence packs met Ed25519 Elke loggingsessie is offline verifieerbaar via een getekende tarball. Een DPA-inspecteur zonder monsys-account kan bewijzen dat de data niet is aangepast.

De wire envelope spec: wat er van jouw code naar de hub gaat

Wanneer je SDK een span sluit, wordt één HTTPS POST verstuurd:

{
  "schema_version": "1",
  "app_token": "aiv_...",
  "trace_id": "0e22f4a1-...",
  "span_id": "b3c1d2e4-...",
  "span_name": "openai.chat",
  "provider": "openai",
  "model": "gpt-4o",
  "prompt_hash": "sha256:a3f2c1...",
  "completion_hash": "sha256:b7e4d2...",
  "prompt_text": "Wat is het saldo van IBAN [REDACTED-IBAN-BE] per vandaag?",
  "completion_text": "Het saldo van uw rekening [REDACTED-IBAN-BE] is €1.847,32.",
  "input_tokens": 23,
  "output_tokens": 18,
  "cost_eur": 0.000041,
  "pii_hits": [
    {
      "type": "IBAN_BE",
      "offset_start": 28,
      "offset_end": 46,
      "token": "sha256:c1d2e3f4..."
    }
  ],
  "pii_hits_count": 1,
  "started_at": "2026-05-25T09:14:02.341Z",
  "ended_at": "2026-05-25T09:14:03.887Z"
}

Merk op: prompt_text en completion_text bevatten [REDACTED-IBAN-BE] — niet het originele IBAN. De redactie gebeurt in de SDK, client-side, voordat de POST verstuurd wordt. De hub ziet de raw waarde nooit.

prompt_hash en completion_hash zijn de SHA256-hashes van de originele tekst (vóór redactie). Die hashes kun je gebruiken om later te bewijzen dat twee spans dezelfde prompt hadden, zonder de promptinhoud zelf te bewaren.

PII-detectie: hoe de checksum-validatie werkt

Voor IBAN-detectie is een regex niet voldoende — BE68539007547034 ziet eruit als een IBAN, maar BE00123456789012 is er geen (checksum faalt). monsys gebruikt de ISO 13616 mod-97 checksum:

def validate_iban(iban: str) -> bool:
    # Verplaats de eerste 4 karakters naar achteren
    rearranged = iban[4:] + iban[:4]
    # Vervang letters door cijfers (A=10, B=11, ...)
    numeric = ''.join(
        str(ord(c) - ord('A') + 10) if c.isalpha() else c
        for c in rearranged
    )
    # Mod-97 check
    return int(numeric) % 97 == 1

Hetzelfde principe voor Rijksregisternummer (mod-97), BTW-BE (mod-97), KBO (mod-97), NL BSN (mod-11), FR NIR (mod-97).

Dit elimineert false positives: een nummer dat toevallig op een IBAN lijkt maar de checksum faalt, wordt niet geredacteerd.

Wat nog niet gedekt is:

Deze staan op de roadmap voor Q3-Q4 2026. We benoemen dit expliciet omdat "PII-redactie" een gevaarlijk vage belofte is als je niet zegt welke PII-types je bedoelt.

De SDK: ~150 regels per taal, geen dependencies

De Python SDK (en Node/Go equivalent) heeft één externe dependency: cryptography voor de Ed25519-signing van het span-payload. Dat is het.

# monsys_ai/tracer.py — vereenvoudigd
import hashlib, json, time
from contextlib import contextmanager
from dataclasses import dataclass, field
from typing import Optional
import httpx

@dataclass
class Span:
    name: str
    provider: str
    model: str
    prompt: Optional[str] = None
    completion: Optional[str] = None
    input_tokens: Optional[int] = None
    output_tokens: Optional[int] = None
    _started_at: float = field(default_factory=time.time)

    def _redact_pii(self, text: str) -> tuple[str, list]:
        """Redacteer PII client-side voor verzending."""
        hits = []
        result = text
        import re
        iban_pattern = re.compile(r'\bBE\d{2}[\s]?\d{4}[\s]?\d{4}[\s]?\d{4}\b')
        for match in iban_pattern.finditer(result):
            candidate = match.group().replace(' ', '')
            if self._validate_iban(candidate):
                hits.append({"type": "IBAN_BE", "token": hashlib.sha256(candidate.encode()).hexdigest()})
                result = result[:match.start()] + "[REDACTED-IBAN-BE]" + result[match.end():]
        return result, hits

De SDK faalt nooit: als de HTTP POST naar de hub mislukt (timeout, network error), wordt het gelogd maar de aanroepende code krijgt geen exception. De LLM-functionaliteit van je applicatie wordt nooit geblokkeerd door de observability-laag.

Alerts: wanneer het systeem je wekt

Vier alert-types voor AI observability:

Cost-spike: cost_per_minute > threshold_eur. Standaard: €1/minuut. Configureerbaar per app. Vuurt via ntfy/webhook, niet als e-mail (te traag voor een prompt-bug die €500/uur verbrandt).

Refusal-rate: refusal_count / total_spans > threshold over een sliding window van 15 minuten. De hub detecteert refusals door te kijken of de completion begint met patronen als "I cannot", "I'm sorry, I can't", of equivalenten in NL/FR. Niet perfect, maar goed genoeg om model-drift te signaleren.

PII-rate: pii_hits_count / total_spans > threshold. Als plots 40% van je spans PII-hits heeft waar dat normaal 2% is, is er iets veranderd in hoe eindgebruikers de applicatie gebruiken.

Z-score anomalie: dezelfde z-score methode als de server-monitoring (|z| > 2.5). Berekend over cost_per_span, input_tokens_per_span, duration_ms per app per model. Vangt prompt-regressions die de kosten opdrijven zonder een absolute drempel te overschrijden.

Evidence packs voor AI Act artikel 12

Artikel 12 van de AI Act vereist dat high-risk AI-systemen automatisch logs bijhouden van de werking, inclusief de invoer-data voor zover dat de identificatie van de oorzaak van problemen mogelijk maakt.

Een monsys evidence pack voor AI observability bevat per periode:

evidence_<tenant>_<period>.tar.gz
├── manifest.json              ← periode, tenant, inputs_hash
├── manifest.sig               ← Ed25519 signature
├── signing_key.pub
├── verify.py
└── data/
    └── ai_traces/
        ├── spans.ndjson       ← alle spans, PII-geredacteerd
        ├── pii_summary.json   ← aggregaat: X hits van type Y
        ├── cost_summary.json  ← kosten per app, per model, per dag
        └── alerts.ndjson      ← cost-spikes, refusals, anomalies

De spans.ndjson bevat de geredacteerde teksten én de SHA256-hashes van de originele teksten. Een toezichthouder kan bewijzen dat een specifieke span de IBAN van een klant heeft verwerkt (hash matcht), zonder de IBAN zelf te zien.

Voor het scenario waarbij je wél de originele inhoud nodig hebt (bijv. voor klachtbehandeling): de TOTP-gated "unlock content" functie geeft eenmalig toegang tot de unredacted span, gelogd in het audit trail.

Wat dit niet is

We benoemen dit expliciet omdat de markt vol is met producten die "AI compliance" beloven zonder te zeggen wat ze precies bieden:

De waarde is bewijsvoering, niet preventie. Voor preventie heb je andere tools.


AI observability is gedocumenteerd in docs.monsys.ai/nl/ai/quick-start. Eerste AI-app gratis per tenant: monsys.ai/nl/ai.

Terug naar blog