AI observability : redaction PII à la source, les trois invariants stricts et ce que nous ne faisons pas délibérément
Journaliser les appels LLM d'une manière utile pour le debugging ET conforme au RGPD, à l'AI Act et à NIS2. Redaction PII côté client, evidence packs signés Ed25519, et ce que ce n'est pas.
Quand votre backend appelle un LLM — OpenAI, Claude, Mistral, Azure OpenAI — vous avez un problème de logging différent de celui des appels API traditionnels. Un appel REST vers un fournisseur de paiement vous log un request-ID et un status-code. Un appel LLM log potentiellement le texte intégral qu'un utilisateur final a saisi, y compris tout ce qu'il y a dedans.
Le RGPD appelle ça « données personnelles ». L'AI Act article 12 appelle ça « événements générés par le système IA ». NIS2 appelle ça « audit trail ». Trois régulations, un problème : comment loguer les appels LLM d'une manière utile pour le debugging ET conforme aux trois ?
C'est ce que fait le module AI observability monsys — et ce qu'il ne fait pas délibérément.
Les trois invariants stricts
Trois principes non négociables, quel que soit le client ou la configuration :
1. Passif, jamais autonome monsys n'exécute aucun prompt, ne bloque jamais inline, ne prend jamais de décisions de lui-même sur la base de ce qui est logué. C'est une couche d'observability — de la preuve a posteriori, pas un control plane. Si vous avez besoin de filtrage inline contre les prompt-injections ou de guardrails, combinez-nous avec Lakera ou Protect AI.
2. PII redactée à la source IBAN belge, registre national, BTW-BE, KBO, NL BSN, FR NIR, e-mails et numéros de téléphone sont reconnus avec validation par checksum avant stockage. Le contenu brut n'atteint jamais la base de données.
3. Evidence packs avec Ed25519 Chaque session de logging est vérifiable offline via une tarball signée. Un inspecteur DPA sans compte monsys peut prouver que les données n'ont pas été modifiées.
La spec wire envelope : ce qui va de votre code vers le hub
Quand votre SDK ferme un span, un seul HTTPS POST est envoyé :
{
"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": "Quel est le solde de l'IBAN [REDACTED-IBAN-BE] à ce jour ?",
"completion_text": "Le solde de votre compte [REDACTED-IBAN-BE] est de 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"
}
Remarquez : prompt_text et completion_text contiennent [REDACTED-IBAN-BE] — pas l'IBAN original. La redaction se fait dans le SDK, côté client, avant l'envoi du POST. Le hub ne voit jamais la valeur brute.
prompt_hash et completion_hash sont les hashes SHA256 du texte original (avant redaction). Vous pouvez utiliser ces hashes plus tard pour prouver que deux spans avaient le même prompt, sans conserver le contenu du prompt lui-même.
Détection PII : comment fonctionne la validation par checksum
Pour la détection IBAN, un regex ne suffit pas — BE68539007547034 ressemble à un IBAN, mais BE00123456789012 n'en est pas un (échec checksum). monsys utilise la checksum mod-97 ISO 13616 :
def validate_iban(iban: str) -> bool:
# Déplacer les 4 premiers caractères vers la fin
rearranged = iban[4:] + iban[:4]
# Remplacer les lettres par des chiffres (A=10, B=11, ...)
numeric = ''.join(
str(ord(c) - ord('A') + 10) if c.isalpha() else c
for c in rearranged
)
# Check mod-97
return int(numeric) % 97 == 1
Même principe pour le numéro de registre national (mod-97), BTW-BE (mod-97), KBO (mod-97), NL BSN (mod-11), FR NIR (mod-97).
Cela élimine les faux positifs : un numéro qui ressemble par hasard à un IBAN mais échoue la checksum n'est pas redacté.
Ce qui n'est pas encore couvert :
- DE Steuer-ID
- IT Codice Fiscale
- ES NIF
- US SSN
- Numéros de carte bancaire (check Luhn disponible mais pas encore implémenté)
Ces éléments sont sur la roadmap Q3-Q4 2026. Nous le nommons explicitement parce que « redaction PII » est une promesse dangereusement vague si vous ne dites pas quels types de PII vous couvrez.
Le SDK : ~150 lignes par langage, sans dépendances
Le SDK Python (et les équivalents Node/Go) a une seule dépendance externe : cryptography pour la signature Ed25519 du payload de span. C'est tout.
# monsys_ai/tracer.py — simplifié
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]:
"""Redacte PII côté client avant transmission."""
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
Le SDK n'échoue jamais : si le POST HTTP vers le hub échoue (timeout, erreur réseau), c'est logué mais le code appelant ne reçoit aucune exception. La fonctionnalité LLM de votre application n'est jamais bloquée par la couche observability.
Alertes : quand le système vous réveille
Quatre types d'alertes pour l'AI observability :
Cost spike : cost_per_minute > threshold_eur. Défaut : 1 €/minute. Configurable par app. Tire via ntfy/webhook, pas par e-mail (trop lent pour un bug de prompt qui brûle 500 €/heure).
Refusal rate : refusal_count / total_spans > threshold sur une fenêtre glissante de 15 minutes. Le hub détecte les refus en regardant si la completion commence par des motifs comme « I cannot », « I'm sorry, I can't », ou des équivalents NL/FR. Pas parfait, mais suffisant pour signaler la dérive de modèle.
PII rate : pii_hits_count / total_spans > threshold. Si tout à coup 40 % de vos spans ont des hits PII alors que c'est normalement 2 %, quelque chose a changé dans la manière dont les utilisateurs finaux utilisent l'application.
Anomalie Z-score : même méthode z-score que la surveillance serveur (|z| > 2.5). Calculée sur cost_per_span, input_tokens_per_span, duration_ms par app par modèle. Capte les régressions de prompt qui font monter les coûts sans dépasser un seuil absolu.
Evidence packs pour l'AI Act article 12
L'article 12 de l'AI Act exige que les systèmes IA à haut risque conservent automatiquement des logs de leur fonctionnement, y compris les données d'entrée dans la mesure où cela permet d'identifier la cause des problèmes.
Un evidence pack monsys pour l'AI observability contient par période :
evidence_<tenant>_<period>.tar.gz
├── manifest.json ← période, tenant, inputs_hash
├── manifest.sig ← signature Ed25519
├── signing_key.pub
├── verify.py
└── data/
└── ai_traces/
├── spans.ndjson ← tous les spans, PII redactée
├── pii_summary.json ← agrégat : X hits de type Y
├── cost_summary.json ← coût par app, par modèle, par jour
└── alerts.ndjson ← cost spikes, refusals, anomalies
Le spans.ndjson contient les textes redactés ET les hashes SHA256 des textes originaux. Un régulateur peut prouver qu'un span spécifique a traité l'IBAN d'un client (le hash correspond), sans voir l'IBAN lui-même.
Pour le scénario où vous avez bien besoin du contenu original (ex : traitement de plainte) : la fonction TOTP-gated « unlock content » donne un accès unique au span non redacté, journalisé dans l'audit trail.
Ce que ce n'est pas
Nous le nommons explicitement parce que le marché est plein de produits qui promettent « la conformité IA » sans dire ce qu'ils offrent réellement :
- Pas de certification NIS2 — ça n'existe pas pour les fournisseurs. Nous aidons sur la preuve pour l'article 21.
- Pas un remplaçant de DPIA — nous sommes un input pour votre impact assessment.
- Pas un guardrail ou filtre anti prompt-injection — couche d'audit passive, jamais bloquant inline.
- Pas de détection de jailbreaks — nous loguons les refus quand le modèle refuse ; la détection de pattern d'abus est hors scope.
- Pas de garantie sur ce qu'OpenAI/Anthropic fait de vos données — cela relève de votre contrat avec eux.
La valeur est la preuve, pas la prévention. Pour la prévention, vous avez besoin d'autres outils.
L'AI observability est documentée dans docs.monsys.ai/fr/ai/quick-start. Première app IA gratuite par tenant : monsys.ai/fr/ai.