fahmek
Dans ce guide

fahmek — Guide d'installation et de gestion

fahmek masque les numéros bancaires et d'identité avant qu'un message de chat ne quitte l'entreprise, et auto-gad corrige l'orthographe et la grammaire simple en français et en anglais. Ce guide installe les deux sur une machine en 30 minutes environ, les branche sur une passerelle LiteLLM que le client a déjà, et prépare la production.

La première partie suffit pour installer et gérer. La seconde, Référence technique, détaille les contrats et les limites pour l'équipe technique. Version anglaise : docs/en/installation.md.

Pourquoi fahmek#

Le problème. Dans les banques, les assurances et les opérateurs, les employés collent de vraies données clients dans un chat d'IA : un RIB à vérifier, une CIN pour remplir un formulaire, un numéro de téléphone à rappeler. Chaque message part alors chez un fournisseur d'IA externe, souvent à l'étranger, et l'entreprise perd le contrôle de ces données.

Ce que fait fahmek. Il s'installe dans la passerelle d'IA de l'entreprise (LiteLLM) et remplace chaque identifiant par une étiquette avant le départ du message. Le modèle voit «MA_RIB_1» au lieu du numéro de compte. Au retour, la passerelle remet les vraies valeurs : l'employé lit une réponse normale.

  • 7 identifiants marocains (RIB, IBAN, téléphone, CIN, CNSS, affiliation CNSS, ICE), plus les adresses e-mail et les numéros de carte bancaire. Les numéros ICE sont signalés mais laissés visibles.
  • Arabe standard, darija en lettres arabes ou latines, français et messages mélangés, chiffres arabes-indiens compris.
  • Clé RIB et numéros de carte vérifiés par leurs chiffres de contrôle : un nombre au hasard n'est pas masqué.
  • Si fahmek est arrêté, les politiques strictes bloquent le message au lieu de le laisser partir sans contrôle.

Ce qu'ajoute auto-gad. Un correcteur pour les questions tapées. Il corrige les fautes en français et en anglais (je veu → je veux, a la banque → à la banque) pour que le modèle comprenne mieux. Il ne touche jamais à la darija ni à l'arabe, et ne bloque jamais un message.

Pourquoi un client peut dire oui.

  • Tout tourne sur ses propres serveurs : aucun service cloud, aucun appel vers l'éditeur, aucun accès Internet nécessaire après l'installation.
  • Se branche sur la passerelle LiteLLM qu'il utilise déjà, même gérée seulement depuis son interface web : les applications de chat et les habitudes des employés ne changent pas.
  • Quelques millisecondes par message (moins de 15 ms pour le masquage, 20 à 50 ms environ pour la correction).
  • Aide à respecter la loi 09-08 pour les données personnelles envoyées à l'étranger. C'est une mesure technique, pas une certification ni un avis juridique.

Ce qu'il ne fait pas. fahmek ne détecte ni les noms, ni les adresses, ni les dates de naissance. Il n'inspecte ni les images, ni l'audio, ni les nouvelles données personnelles inventées par le modèle dans ses réponses. La précision sur de vrais messages clients n'est pas encore mesurée : voir Limites connues.

fahmek ne fournit pas de modèle d'IA. Le client garde son fournisseur (Anthropic, OpenAI, un serveur interne…). La pile de démonstration contient seulement un backend de test qui renvoie le message reçu, pour voir le masquage fonctionner.

Comment circule un message#

  1. L'employé écrit dans le chatbot (Open WebUI) et appuie sur Entrée.
  2. LiteLLM, la passerelle, demande à fahmek de trouver les identifiants. fahmek remplace chacun par une étiquette comme «MA_PHONE_1» et garde la vraie valeur dans Redis, en mémoire seulement, quelques minutes.
  3. LiteLLM demande à auto-gad de corriger l'orthographe et la grammaire. auto-gad interroge LanguageTool, un moteur d'orthographe sur la même machine.
  4. LiteLLM envoie le message corrigé et masqué au modèle d'IA.
  5. La réponse revient. LiteLLM demande à fahmek les vraies valeurs et les remet à la place des étiquettes.
  6. L'employé lit une réponse normale, avec le vrai numéro.

Choisir son parcours#

Vous voulezSuivre
Montrer fahmek, ou faire un pilote sur une machine avec un chatbot de testÉtapes 1 à 7 (30 minutes environ)
Ajouter fahmek au LiteLLM existant d'un clientÉtapes 1 à 4, puis Client qui a déjà LiteLLM
Installer sur le cluster Kubernetes du clientClient sur Kubernetes (chart Helm ; pas d'étapes 1 à 7)
Servir beaucoup d'utilisateursUn pilote d'abord, puis Passer en production
Installer sur une machine sans InternetÉtape 2 option A, puis Installation sans Internet

Avant de commencer#

  • Un ordinateur ou une machine virtuelle Linux, Ubuntu 22.04 ou 24.04, avec au moins 4 cœurs, 4 Go de mémoire (8 Go c'est mieux) et 10 Go de disque libre (20 Go c'est mieux). Les images de l'archive sont pour processeurs Intel ou AMD (x86-64) ; pour une machine ARM, demander à l'éditeur l'archive ARM (voir plus bas).
  • Une connexion Internet pour la première installation (plusieurs Go à télécharger). Ensuite, fahmek n'a plus besoin d'Internet.
  • Un compte utilisateur qui peut utiliser sudo.
  • fahmek lui-même, fourni par l'éditeur : l'archive fahmek-0.1.0.tar.gz, ou un jeton d'accès temporaire pour le télécharger avec Git (étape 2).
  • Le fichier de licence fahmek.license, fourni par l'éditeur.
  • Facultatif : une clé de fournisseur d'IA pour essayer un vrai modèle (étape 3).

Comment taper les commandes. Ouvrir un Terminal (sur Ubuntu : Ctrl+Alt+T). Chaque bloc gris est une commande : la copier, cliquer dans le terminal, coller avec Ctrl+Shift+V, appuyer sur Entrée, et attendre le retour de l'invite (la ligne qui finit par $) avant la suivante. Les lignes qui commencent par # sont des notes : l'ordinateur les ignore.

Vérifier que la machine est assez grande :

nproc     # nombre de cœurs : 4 ou plus
free -h   # ligne "Mem", total : 4G ou plus
df -h ~   # colonne "Avail" (ou "Disp.") : 10G ou plus
uname -m  # x86_64 (aarch64 = ARM : demander l'archive ARM)

Intel/AMD ou ARM. fahmek tourne en conteneurs Linux, et Docker fait tourner des conteneurs Linux sur tout système : directement sous Linux, sous Windows avec Docker Desktop et WSL2, sur un Mac avec Docker Desktop. Ce qui compte, c'est le processeur : une image de conteneur est construite pour une famille de processeurs et ne démarre pas sur l'autre (exec format error).

Famille de processeursuname -mNom pour DockerMachines typiques
Intel ou AMDx86_64amd64Presque tous les serveurs de banque ou d'entreprise, les machines virtuelles du cloud, la plupart des PC Windows
ARMaarch64arm64Mac Apple Silicon, certains serveurs cloud, machines virtuelles ARM

Les images de l'archive sont amd64, sauf si l'éditeur a construit une archive ARM (son nom finit alors par -arm64). Avec l'option B de l'étape 2, les images sont construites sur la machine elle-même et lui correspondent toujours.

Étape 1 — Installer Docker et les petits outils#

Docker fait tourner chaque partie de fahmek dans sa propre boîte isolée (un conteneur). Sauter cette étape si docker compose version affiche déjà une version.

  1. Installer les petits outils (git télécharge le code, python3 fabrique les mots de passe, make donne des raccourcis) :

    sudo apt update && sudo apt install -y git python3 make curl
  2. Installer Docker avec son installateur officiel :

    curl -fsSL https://get.docker.com | sudo sh
  3. Autoriser votre utilisateur à lancer Docker sans sudo :

    sudo usermod -aG docker $USER
  4. Se déconnecter puis se reconnecter (ou redémarrer la machine). Sans cela, la commande suivante répond permission denied.

  5. Vérifier que Docker fonctionne :

    docker run --rm hello-world
    docker compose version

    Vous devez voir Hello from Docker! puis une ligne comme Docker Compose version v2.x.

Étape 2 — Obtenir fahmek#

L'éditeur vous donne l'une de ces deux choses. Les deux finissent avec fahmek dans un dossier fahmek de votre dossier personnel.

Option A — l'archive (fahmek-0.1.0.tar.gz, 600 Mo environ, par lien de téléchargement ou clé USB). Elle contient les quatre parties toutes prêtes de fahmek, les fichiers de réglages et ces guides. Supposons qu'elle soit dans Téléchargements (Downloads sur un système en anglais) :

mkdir -p ~/fahmek
tar xzf ~/Téléchargements/fahmek-0.1.0.tar.gz --strip-components=1 -C ~/fahmek
cd ~/fahmek
for f in fahmek-image.tar fahmek-litellm-image.tar fahmek-demo-stub-image.tar fahmek-autogad-image.tar; do docker load -i "$f"; done
ls docker-compose.*.yml

Chaque docker load se termine par une ligne comme Loaded image: fahmek:0.1.0.

Option B — Git, avec un jeton temporaire de l'éditeur.

cd ~
git clone https://github.com/Anas10x/fahmek.git
cd fahmek
ls docker-compose.*.yml

Git demande un nom d'utilisateur et un mot de passe : taper le nom d'utilisateur indiqué dans le message de l'éditeur, puis coller le jeton d'accès comme mot de passe (rien ne s'affiche pendant le collage ; appuyer sur Entrée). Le jeton expire après quelques jours : il ne sert qu'au téléchargement.

Dans les deux cas, la dernière commande liste cinq fichiers : autogad, chat, demo, litellm-ui et services.

Toutes les commandes suivantes se lancent dans ce dossier. Dans un nouveau terminal, taper d'abord cd ~/fahmek.

Étape 3 — Écrire le fichier de réglages#

Tous les réglages tiennent dans un petit fichier nommé .env, dans le dossier fahmek. Coller ce bloc en entier : il crée le fichier, fabrique deux mots de passe aléatoires et protège le fichier.

cat > .env <<EOF
# Secret partagé entre fahmek, auto-gad et la passerelle (obligatoire, pas de valeur par défaut)
FAHMEK_API_KEY=$(python3 -c 'import secrets; print(secrets.token_hex(32))')
# Mot de passe des applications qui appellent LiteLLM (le chatbot l'utilise aussi)
LITELLM_MASTER_KEY=sk-$(python3 -c 'import secrets; print(secrets.token_hex(16))')
# Politique : bank-pilot masque le plus et bloque l'appel si fahmek est arrêté
FAHMEK_POLICY_ID=bank-pilot
# 1 = trace lisible de chaque message dans les journaux de la passerelle (démos seulement)
FAHMEK_TRACE=1
EOF
chmod 600 .env

Le relire avec cat .env : quatre réglages, avec de longues valeurs aléatoires.

LigneCe qu'elle veut direLa changer ?
FAHMEK_API_KEYLe secret entre les partiesJamais à la main ; le garder secret
LITELLM_MASTER_KEYLe mot de passe des applications qui appellent la passerelleNe le donner qu'aux applications qui en ont besoin
FAHMEK_POLICY_IDLe niveau de sévérité du masquage (voir Changer les réglages)Garder bank-pilot pour commencer
FAHMEK_TRACE1 affiche chaque message, masqué, pour le voir fonctionner0 avant l'usage par de vrais employés

Facultatif : brancher un vrai modèle d'IA. Sans cela, le backend de test renvoie chaque message comme réponse, ce qui suffit pour toutes les vérifications de ce guide. Pour un vrai modèle, ajouter trois lignes. Pour Claude (Anthropic) :

cat >> .env <<'EOF'
DEMO_MODEL=anthropic/claude-opus-5-5
DEMO_API_BASE=https://api.anthropic.com
DEMO_API_KEY=collez-ici-votre-cle-api-anthropic
EOF

La clé vient de la console Anthropic (platform.claude.com) et demande des crédits API ; un abonnement au chat claude.ai ne donne pas accès à l'API. Pour OpenAI ou tout serveur compatible OpenAI (un serveur de modèles interne, par exemple) : DEMO_MODEL=openai/<nom du modèle>, DEMO_API_BASE=https://<serveur>/v1 et la clé de ce serveur. Si fahmek tourne déjà, appliquer avec $C up -d (étape 5).

Garder .env privé : quiconque le lit peut appeler la passerelle et dépenser vos crédits chez le fournisseur. Ne jamais l'envoyer par e-mail ni le mettre dans Git.

Étape 4 — Mettre la licence en place#

La licence est un petit fichier texte qui dit qui peut utiliser fahmek et jusqu'à quand. Sans elle, fahmek refuse de masquer, et la passerelle bloque alors chaque message plutôt que de l'envoyer sans contrôle.

  1. Copier le fichier reçu sur la machine. Supposons qu'il soit dans Téléchargements.

  2. Le placer dans un dossier license, sous le nom exact fahmek.license :

    mkdir -p license
    cp ~/Téléchargements/*.license license/fahmek.license
    ls license

    La dernière commande doit afficher fahmek.license, et rien d'autre.

Pas besoin de redémarrer pour changer de licence plus tard : fahmek relit ce fichier chaque minute. L'étape 6 vérifie qu'il est accepté.

Étape 5 — Tout démarrer#

fahmek se compose de trois morceaux qui s'empilent : le cœur (fahmek, Redis, LiteLLM, backend de test), le chatbot, et auto-gad avec LanguageTool. Enregistrer une fois les trois noms de fichiers dans un raccourci $C :

echo 'C="docker compose -f docker-compose.demo.yml -f docker-compose.chat.yml -f docker-compose.autogad.yml"' >> ~/.bashrc
source ~/.bashrc
echo $C

La dernière ligne doit afficher la longue commande docker compose .... Désormais, $C veut dire « tout fahmek », dans chaque terminal.

Télécharger les parties externes (Redis, LanguageTool, le chatbot). Le bloc recommence tout seul si la connexion coupe :

for i in 1 2 3 4 5; do $C pull --ignore-buildable && break; echo "nouvel essai $i"; sleep 10; done

Puis démarrer. Avec l'archive (option A), les parties de fahmek sont déjà chargées :

$C up -d --remove-orphans

Avec Git (option B), les construire d'abord, dans la même commande :

$C up --build -d --remove-orphans

Patience. La première fois prend 10 à 60 minutes selon le débit. Les démarrages suivants prennent quelques secondes. Au retour de l'invite, attendre encore une minute : LanguageTool charge ses dictionnaires.

Vérifier que tout tourne :

$C ps

Vous devez voir sept lignes (fahmek, redis, litellm, stub-provider, open-webui, autogad, languagetool), chacune Up ou running. Exited ou Restarting signale un problème : voir Dépannage.

Étape 6 — Vérifier que chaque partie répond#

D'abord charger les mots de passe de .env dans ce terminal (à refaire dans chaque nouveau terminal) :

set -a; . ./.env; set +a

Puis coller chaque vérification et comparer avec la bonne réponse.

#VérificationCommandeBonne réponse
1fahmek est vivantcurl -s localhost:8100/healthz{"status":"ok"}
2La licence est acceptéecurl -s -H "X-Fahmek-Key: $FAHMEK_API_KEY" localhost:8100/v1/license"state":"valid", le nom de votre entreprise et la date d'échéance
3Redis est prêt et n'écrit rien sur disquecurl -s -H "X-Fahmek-Key: $FAHMEK_API_KEY" localhost:8100/readyz{"status":"ready"}
4auto-gad joint LanguageToolcurl -s -H "X-Fahmek-Key: $FAHMEK_API_KEY" localhost:8200/readyz{"ready":true}

Enfin, demander à auto-gad de corriger une phrase avec une faute de grammaire (veu) et deux fautes d'orthographe (numero, telefone) :

curl -s -H "X-Fahmek-Key: $FAHMEK_API_KEY" -H 'Content-Type: application/json' \
  localhost:8200/v1/correct -d '{"text":"je veu changer mon numero de telefone"}'

La réponse commence par {"text":"je veux changer mon numéro de téléphone".

Si la vérification 2 affiche missing, invalid ou expired, son champ reason dit pourquoi : revenir à l'étape 4. Si la vérification 4 affiche false, ou si la phrase revient sans correction, attendre 30 secondes et réessayer : LanguageTool charge encore.

Étape 7 — Essayer le chatbot#

  1. Ouvrir un second terminal et suivre la trace. Elle montre, pour chaque message, ce que fahmek a caché, ce qu'auto-gad a corrigé et ce que reçoit exactement le modèle. La laisser ouverte :

    cd ~/fahmek && make trace

    Si fahmek est dans un autre dossier, partir de ce dossier (par exemple cd ~/airs-lab/fahmek && make trace). Ctrl+C arrête l'affichage ; la pile continue de tourner.

  2. Ouvrir le chatbot. Dans un navigateur sur la même machine, aller sur http://localhost:3000. Depuis un autre ordinateur, remplacer localhost par l'adresse de la machine (hostname -I).

  3. Créer le compte administrateur. Cliquer sur Sign up et saisir un nom, un e-mail et un mot de passe. Le premier compte devient administrateur : le créer vous-même, tout de suite.

  4. Couper les requêtes de fond. Le chatbot envoie des requêtes cachées pour produire titres et étiquettes, qui encombrent la trace. Cliquer sur votre nom (en bas à gauche), puis Admin Panel → Settings → Interface, désactiver Title Generation, Tags Generation et Follow Up Generation, et cliquer sur Save.

  5. Choisir le modèle demo-model en haut d'une nouvelle conversation.

  6. Envoyer les messages de test ci-dessous, un par un. Ils ne contiennent que des numéros fictifs. Ne jamais tester avec les données d'une vraie personne, y compris les vôtres.

Message à envoyerLa trace doit montrer
Bonjour, je veu changer mon numero de telefone, c'est le 06 61 23 45 67.masqué MA_PHONE x1 ; corrigé veu → veux, numero → numéro, telefone → téléphone
Salam, bghit nbdl RIB dyali 999001123456789012345691, wach momkin?masqué MA_RIB x1 ; darija laissée telle quelle
Hello. Their is a problem, I want to chnage my adress. My email is karim.test@example.com.masqué EMAIL x1 ; corrigé Their → There, chnage → change, adress → address
Merci, il a envoyer le virement a la banque hier. bghit n3ref chhal 3andi. My CIN is AB123456.masqué MA_CIN x1 ; corrigé envoyer → envoyé, a → à ; darija laissée telle quelle

Chaque message donne quatre lignes dans la trace :

[trace 48b77b] user wrote (identifiers hidden): Bonjour, je veu changer mon numero de telefone, c'est le «MA_PHONE_1».
[trace 48b77b] fahmek masked: MA_PHONE x1 (policy bank-pilot)
[trace 48b77b] auto-gad fixed: fr: veu → veux, numero → numéro, telefone → téléphone
[trace 48b77b] model receives: Bonjour, je veux changer mon numéro de téléphone, c'est le «MA_PHONE_1».

Le masquage doit correspondre exactement ; les corrections peuvent varier légèrement selon la version de LanguageTool. Avec le backend de test, la réponse répète votre message avec le vrai numéro remis en place : la preuve que l'aller-retour fonctionne, et http://localhost:9200/last_request montre ce que le backend a reçu, avec des étiquettes à la place des numéros. Avec un vrai modèle, les réponses sont de vraies réponses, et la ligne model receives de la trace en est la preuve.

Ce que corrige auto-gad, et ce qu'il laisse#

auto-gad ne corrige un mot que s'il est sûr ; dans le doute, il le laisse tel quel, car le modèle comprend de toute façon les petites fautes.

  • Orthographe : un mot mal écrit reçoit le mot du dictionnaire le plus proche, s'il est nettement le plus proche.
  • Grammaire : certaines règles lisent les mots voisins, par exemple le verbe après je ou il a. Une correction de grammaire n'est appliquée que si la règle propose exactement un seul mot de remplacement. Le style et la ponctuation ne changent jamais.

Résultats mesurés avec le LanguageTool installé :

Vous tapezLe modèle reçoitPourquoi
je veu changerje veux changerGrammaire : le verbe après je
il a envoyer le virementil a envoyé le virementGrammaire : participe passé après a
a la banqueà la banqueGrammaire : a ou à
Ces mon compteC'est mon compteGrammaire : homophones
Their is a problemThere is a problemGrammaire : homophones
numero de telefonenuméro de téléphoneOrthographe
les numeroles numéroAccent corrigé ; accord laissé, la grammaire propose deux solutions
fonefone (inchangé)Mot court : sous 5 lettres, seulement les accents
bghit nbdl RIB dyaliinchangéLa darija n'est jamais corrigée
أريد تغيير رقميinchangéL'écriture arabe n'est jamais corrigée
«MA_PHONE_1», Karim, wifi2inchangéÉtiquettes, noms avec majuscule et mots avec chiffres protégés

auto-gad travaille phrase par phrase : dans un message qui mélange darija et français, seule la phrase française est corrigée. Les règles de grammaire n'ajoutent pas de délai mesurable. Si LanguageTool est lent ou arrêté, le message part sans correction après 3 secondes ; auto-gad ne bloque jamais un message.

Gérer fahmek sur site#

Une fois installé, fahmek demande environ une heure de suivi par an : renouveler la licence, appliquer les mises à jour, garder les secrets secrets. Tout se fait depuis le dossier ~/fahmek.

TâcheQuiCommentQuand
Renouveler la licenceL'éditeur envoie le fichier, l'admin du site le copieRemplacer license/fahmek.license (ci-dessous)Une fois par an
Vérifier la licenceAdmin du siteVérification 2 de l'étape 6Chaque mois, ou quand les journaux avertissent
Changer la sévérité du masquageAdmin du site, avec l'équipe sécuritéModifier FAHMEK_POLICY_ID dans .env, puis $C up -dRarement
Installer une mise à jourAdmin du siteMettre à jourQuand l'éditeur en annonce une
Relire la liste de mots darijaUne personne de langue maternelle darijaauto-gad/config.yamlAvant le pilote, puis chaque année
Lire les journauxAdmin du siteJournauxQuand quelque chose semble anormal

La licence, mois après mois#

fahmek relit license/fahmek.license chaque minute et passe tout seul par ces états :

ÉtatQuandCe que voient les employésQue faire
validJusqu'à 30 jours avant l'échéanceRienRien
expiringLes 30 derniers joursRien ; un avertissement par jour dans les journaux de fahmekDemander le renouvellement à l'éditeur
graceJusqu'à 14 jours après l'échéanceRien ; un avertissement par jourRenouveler maintenant
expiredAprès la période de grâceChaque message échoue avec fahmek license not valid; request not forwardedRenouveler ; le chat refonctionne en moins d'une minute
missing / invalidPas de fichier, fichier modifié, ou signé par une clé inconnueComme expiredRecopier le fichier d'origine (étape 4)

Pour renouveler, copier le nouveau fichier par-dessus l'ancien. Rien à redémarrer :

cp ~/Téléchargements/*.license license/fahmek.license

Le fichier ne peut pas être modifié : changer un seul caractère (la date, par exemple) casse sa signature, et fahmek le traite comme invalid. Une licence expirée ne laisse jamais passer de message sans contrôle : elle les bloque. La restitution des vraies valeurs dans les réponses continue de fonctionner.

Changer les réglages#

Modifier .env avec un éditeur de texte (nano .env ; enregistrer avec Ctrl+O puis Entrée, quitter avec Ctrl+X), puis appliquer avec $C up -d. Le réglage le plus utile est la politique de masquage :

FAHMEK_POLICY_IDSi fahmek est arrêtéCIN seule (sans le mot « CIN » à côté)RIB d'une banque non listée, sans le mot « RIB » à côté
bank-pilot (recommandé)Message bloquéMasquéeMasqué
bank-ma-strictMessage bloquéNon masquéeMasqué ; tout le message est bloqué si un RIB est trouvé avec une confiance élevée
pilot-high-recallMessage envoyé sans contrôleMasquéeNon masqué
defaultMessage envoyé sans contrôleNon masquéeNon masqué

Le comportement d'auto-gad se règle dans auto-gad/config.yaml, avec un commentaire au-dessus de chaque réglage : les mots darija à ne jamais toucher, les mots qui servent à reconnaître chaque langue, les noms de marques à ne jamais corriger (never_correct), les groupes de règles de grammaire (grammar_categories ; une liste vide = orthographe seulement) et la longueur minimale d'un mot pour changer une lettre (min_word_length, 5). Après modification, redémarrer auto-gad seulement :

$C restart autogad

Mettre à jour#

Quand l'éditeur annonce une nouvelle version. Les réglages (.env), la licence et l'historique du chat sont conservés ; refaire ensuite les vérifications de l'étape 6.

Avec l'archive (option A), décompresser la nouvelle archive par-dessus le dossier, charger ses parties et redémarrer. Une liste de mots modifiée sur place (auto-gad/config.yaml) est remplacée : la copier de côté d'abord et comparer.

tar xzf ~/Téléchargements/fahmek-0.2.0.tar.gz --strip-components=1 -C ~/fahmek
cd ~/fahmek
for f in fahmek-image.tar fahmek-litellm-image.tar fahmek-demo-stub-image.tar fahmek-autogad-image.tar; do docker load -i "$f"; done
$C up -d --remove-orphans

Avec Git (option B ; l'éditeur envoie un nouveau jeton si l'ancien a expiré) :

git pull
$C up --build -d --remove-orphans

Journaux#

CommandeMontre
$C logs --tail 50 fahmekService de masquage, avertissements de licence
$C logs --tail 50 litellmPasserelle, et la trace quand FAHMEK_TRACE=1
$C logs --tail 50 autogadService de correction
$C logs --tail 50 languagetoolMoteur d'orthographe
make traceLa trace seule, en direct (étape 7)

Les journaux ne contiennent jamais les numéros détectés ni les mots de passe. La trace montre les messages avec les identifiants cachés, mais les noms, les adresses et tout ce que fahmek ne détecte pas apparaissent tels quels : la couper (FAHMEK_TRACE=0, puis $C up -d) avant l'usage par de vrais employés. La trace ne couvre que la passerelle de cette machine (port 4000), pas le LiteLLM d'un client.

Règles de sécurité#

  • .env lisible par l'administrateur seulement (chmod 600 .env, fait à l'étape 3).
  • Ne jamais ouvrir les ports 3000, 4000, 4001, 8100, 8200 ou 9200 sur Internet. Garder la machine sur le réseau interne.
  • Pour changer le secret partagé, mettre un nouveau FAHMEK_API_KEY dans .env et lancer $C up -d : toutes les parties redémarrent ensemble avec la nouvelle valeur.
  • Redis garde les vraies valeurs en mémoire seulement, quelques minutes par message, jamais sur disque. Sous une politique stricte, fahmek refuse de démarrer si Redis est configuré pour écrire sur disque.
  • Le chatbot conserve son historique non masqué sur le disque de cette machine. Tester uniquement avec des valeurs fictives, et traiter la machine comme contenant des données personnelles dès que les employés l'utilisent.

Client qui a déjà LiteLLM#

Si le client exploite déjà sa propre passerelle LiteLLM, on n'installe pas le chatbot de démonstration : on lance fahmek à côté de sa passerelle, puis on l'ajoute depuis l'interface web de LiteLLM, sans fichier à copier ni modification sur son serveur.

Depuis l'interface web de LiteLLMKit d'intégration
Pourun administrateur qui gère LiteLLM seulement depuis sa page webune passerelle gérée par son config.yaml
Sur le serveur LiteLLMrien ne changetrois fichiers Python et quelques lignes de configuration
Réponses en streamingvraies valeurs remises à partir de LiteLLM v1.96vraies valeurs remises
Identifiant dans les arguments d'un appel de fonctionrequête refuséemasqué puis remis
Guideci-dessousintegrations/litellm/README.md

1. Lancer fahmek à côté de la passerelle. Sur une machine que la passerelle peut joindre, faire les étapes 1 à 4 (Docker, fahmek, .env, licence). La politique de masquage est la ligne FAHMEK_POLICY_ID de .env : elle est choisie côté fahmek, aucune application ne peut en demander une plus faible. Avec Git (option B), construire une fois les parties de fahmek : docker compose -f docker-compose.demo.yml -f docker-compose.autogad.yml build fahmek autogad. Puis lancer fahmek, Redis, auto-gad et LanguageTool, sans chatbot ni backend de test :

docker compose -f docker-compose.services.yml --profile autogad up -d

fahmek répond alors sur le port 8100 et auto-gad sur le port 8200 de cette machine.

Sur Kubernetes, remplacer ce point par Client sur Kubernetes.

2. Ajouter les garde-fous dans l'interface web de LiteLLM. Ouvrir http://<passerelle>:4000/ui et se connecter en administrateur. Utiliser l'onglet Submitted Guardrails, pas Add New Guardrail → Add Provider Guardrail : ce formulaire n'a pas de champ pour l'adresse ni la clé de fahmek.

  1. Teams → Create Team, nom fahmek, Create Team. L'équipe sert seulement d'étiquette : les garde-fous protègent toutes les clés et toutes les requêtes de la passerelle.

  2. Guardrails → Submitted Guardrails → Add Guardrail. Remplir une ligne du tableau, choisir l'équipe fahmek, cliquer sur Submit for Review. Trois fois :

    Guardrail NameModeAPI Base URLAdditional litellm_params
    fahmek-maskPre Callhttp://<machine fahmek>:8100{"api_key": "<FAHMEK_API_KEY>", "default_on": true, "unreachable_fallback": "fail_closed"}
    fahmek-restorePost Callhttp://<machine fahmek>:8100{"api_key": "<FAHMEK_API_KEY>", "default_on": true, "streaming_transform_mode": "incremental_diff"}
    auto-gad (facultatif)Pre Callhttp://<machine fahmek>:8200{"api_key": "<FAHMEK_API_KEY>", "default_on": true}
  3. Toujours dans Submitted Guardrails, cliquer sur Approve pour chacun, puis encore sur Approve pour confirmer. Ils sont actifs aussitôt.

Remplacer <FAHMEK_API_KEY> par la valeur de .env (grep FAHMEK_API_KEY .env). L'interface n'accepte l'adresse que si la machine est une adresse IP, localhost ou un nom avec un point (fahmek.banque.local marche, fahmek non).

3. Ajouter des modèles et tester dans le Playground. Un modèle ajouté plus tard est protégé sans rien faire : les garde-fous sont default_on, donc valables pour tous les modèles de la passerelle, anciens et nouveaux. Vérifié sur LiteLLM v1.105.1.

  1. Models + Endpoints → Add Model. Provider : par exemple Anthropic, ou OpenAI-Compatible Endpoints pour un autre serveur compatible (avec son API Base). LiteLLM Model Name(s) : un modèle de la liste (par exemple claude-opus-5-5), ou Custom Model Name (Enter below) puis le nom exact chez le fournisseur. Public Model Name : le nom qu'utiliseront les applications. API Key : la clé du fournisseur. Cliquer sur Test Connect, puis Add Model.
  2. Playground → Chat. Virtual Key Source Current UI Session, Endpoint Type /v1/chat/completions, Select Model : le nouveau modèle. Laisser Guardrails vide : les garde-fous de fahmek s'appliquent quand même.
  3. Envoyer un message avec des valeurs fictives, par exemple Bonjour, mon RIB est 999001123456789012345691 et ma CIN AB123456. Merci. La réponse montre le RIB et la CIN tels que tapés : cachés au modèle, puis remis. Le Playground utilise le streaming : il faut incremental_diff (LiteLLM v1.96 ou plus récent), sinon la réponse garde des étiquettes «MA_RIB_…» (aucune fuite).
  4. Voir ce que le modèle a reçu. Le Playground n'affiche que la réponse restituée. Dans Logs, cliquer sur la requête : le badge vert guardrails evaluated liste fahmek-mask, auto-gad et fahmek-restore (ce dernier compte une fois par tour de streaming). Les journaux du fournisseur, s'il en garde, montrent les étiquettes.

Par défaut, les journaux de LiteLLM ne gardent ni le texte des messages ni les valeurs. Laisser store prompts in spend logs désactivé (Admin Settings → Logging Settings) : activé, LiteLLM garde le texte des conversations dans sa propre base, hors de la protection de fahmek.

4. Ce qu'il faut dire au client sur ce mode.

  • Si fahmek est injoignable, c'est le réglage unreachable_fallback de LiteLLM qui décide, pas la politique : garder fail_closed avec bank-pilot ou bank-ma-strict. Tous les refus de fahmek (licence, taille, limite de débit, identifiant bloqué, images) sont des blocages que LiteLLM applique toujours.
  • Appels de fonction : une requête dont l'historique d'appels de fonction contient un identifiant est refusée, car LiteLLM ne reprend pas d'arguments masqués de ce type de garde-fou. Les applications qui utilisent les appels de fonction ont besoin du kit d'intégration.
  • Streaming : les vraies valeurs ne sont remises qu'avec streaming_transform_mode: incremental_diff, sur LiteLLM v1.96 ou plus récent ; avant, les étiquettes restent (aucune fuite).
  • Pas de trace dans ce mode : vérifier avec le Playground et Logs.

Testé sur LiteLLM v1.89.7 à v1.105.1, sans modification (integrations/litellm/COMPATIBILITY.md, colonne « web UI »).

Répéter d'abord sur la machine de démonstration#

docker-compose.litellm-ui.yml lance un second LiteLLM à côté de la démonstration, exactement comme chez un client : l'image officielle, non modifiée, avec la base Postgres dont son interface web a besoin, sur le port 4001 et sans garde-fou. Le lancer (la première fois télécharge LiteLLM et Postgres) :

cd ~/fahmek
$C -f docker-compose.litellm-ui.yml up -d
  1. Ouvrir http://<adresse de la machine>:4001/ui. Se connecter avec l'utilisateur admin et la valeur de LITELLM_MASTER_KEY de .env comme mot de passe. Un bandeau rouge sur « environment-credential login » est normal sur une démonstration.
  2. Faire le point 2 ci-dessus avec ces adresses (noms des conteneurs dans Docker) : http://fahmek.internal:8000 pour les deux garde-fous fahmek et http://autogad.internal:8200 pour auto-gad.
  3. Faire le point 3 ci-dessus : le modèle demo-model (celui de .env) est déjà là, et vous pouvez en ajouter d'autres.

Le chatbot de démonstration (port 3000) utilise toujours la première passerelle (port 4000). Retirer la seconde avec $C -f docker-compose.litellm-ui.yml rm -sf litellm-ui litellm-ui-db.

Client sur Kubernetes#

Sur Kubernetes, rien des étapes 3 à 7 ne s'applique : pas de .env, pas de $C, pas de chatbot de démonstration. Le chart Helm de l'archive (deploy/helm/fahmek) installe fahmek, son Redis en mémoire et, en option, auto-gad avec LanguageTool. Le LiteLLM du client reste tel quel et ajoute fahmek comme garde-fous, exactement comme dans Client qui a déjà LiteLLM.

Docker Compose (étapes 1 à 7)Kubernetes
docker load sur la machineImages poussées dans le registre privé du client
.envLes deux Secrets ci-dessous, plus un petit fichier de valeurs
license/fahmek.licenseUn Secret ; le renouveler ne demande pas de redémarrage
$C up -d, $C logshelm upgrade, kubectl logs
Vérifications de l'étape 6helm test
Un seul fahmekDeux pods ou plus, règles réseau, budget d'interruption

Il faut : les droits kubectl et Helm 3 sur un namespace (Kubernetes 1.25 ou plus récent), un registre privé que le cluster peut lire, une machine avec Docker pour pousser les images, l'archive et le fichier de licence.

1. Pousser les images dans le registre. Sur la machine avec Docker, dans l'archive décompressée (étape 2, option A), avec l'adresse de votre registre :

REG=registry.banque.local/fahmek
for f in fahmek-image.tar fahmek-autogad-image.tar; do docker load -i "$f"; done
docker pull redis:7-alpine && docker pull erikvl87/languagetool:6.8
for img in fahmek:0.1.0 fahmek-autogad:0.1.0 redis:7-alpine erikvl87/languagetool:6.8; do
  docker tag "$img" "$REG/$img" && docker push "$REG/$img"
done

Sans Internet, charger Redis et LanguageTool depuis les fichiers d'Installation sans Internet au lieu de docker pull.

2. Créer le namespace et les deux Secrets. Le chart ne contient jamais de clé : il lit ces deux Secrets.

kubectl create namespace fahmek
kubectl -n fahmek create secret generic fahmek-api-key \
  --from-literal=FAHMEK_API_KEY="$(openssl rand -hex 32)"
kubectl -n fahmek create secret generic fahmek-license --from-file=fahmek.license=./fahmek.license

Garder la clé : les garde-fous de LiteLLM ont besoin de la même valeur (kubectl -n fahmek get secret fahmek-api-key -o jsonpath='{.data.FAHMEK_API_KEY}' | base64 -d).

3. Écrire le fichier de valeurs. L'enregistrer sous fahmek-values.yaml ; tous les autres réglages ont une valeur par défaut sûre (voir values.yaml dans le chart) :

imageRegistry: registry.banque.local/fahmek
# imagePullSecrets: [{name: regcred}]   # si le registre demande une connexion
fahmek:
  replicas: 2
  workers: 2          # un par cœur demandé (resources.requests.cpu)
  policyId: bank-pilot
networkPolicy:
  clients:            # seuls les pods LiteLLM peuvent appeler fahmek et auto-gad
    - namespaceSelector:
        matchLabels: {kubernetes.io/metadata.name: litellm}
# autogad:
#   enabled: false    # sans le correcteur (économise environ 2 Go de mémoire)

Remplacer litellm par le namespace où tourne LiteLLM. Les règles réseau ne s'appliquent que si le plugin réseau du cluster les fait respecter (Calico, Cilium et la plupart des clusters managés le font).

4. Installer et vérifier.

helm install fahmek ./deploy/helm/fahmek -n fahmek -f fahmek-values.yaml
kubectl -n fahmek get pods          # tous Running et READY 1/1 après une ou deux minutes
helm test fahmek -n fahmek --logs

Le test se termine par ok, après readyz: ready, license: valid, inspect decision: redact et la ligne d'auto-gad. LanguageTool met une ou deux minutes à charger ; d'ici là, auto-gad n'est pas prêt.

5. Ajouter les garde-fous dans LiteLLM. Faire le point 2 de Client qui a déjà LiteLLM, avec les adresses du cluster qu'affiche helm install :

Garde-fouAPI Base URL
fahmek-mask, fahmek-restorehttp://fahmek.fahmek.svc.cluster.local:8000
auto-gadhttp://fahmek-autogad.fahmek.svc.cluster.local:8200

(Release fahmek dans le namespace fahmek ; sinon http://<release>.<namespace>.svc.cluster.local:8000.) Avec le kit d'intégration, mettre plutôt FAHMEK_URL et AUTOGAD_URL sur le déploiement LiteLLM, avec les mêmes adresses. Puis ajouter des modèles et tester dans le Playground comme au point 3.

Au quotidien sur Kubernetes

TâcheCommande
Renouveler la licencekubectl -n fahmek create secret generic fahmek-license --from-file=fahmek.license=./nouvelle.license --dry-run=client -o yaml | kubectl apply -f - (pris en compte en quelques minutes, sans redémarrage)
Changer un réglageModifier fahmek-values.yaml, puis helm upgrade fahmek ./deploy/helm/fahmek -n fahmek -f fahmek-values.yaml
Changer la liste de mots d'auto-gadMettre tout le fichier sous autogad.config dans les valeurs, puis helm upgrade (auto-gad redémarre tout seul)
Installer une mise à jourPousser les nouvelles images (point 1, nouveau tag), décompresser la nouvelle archive, puis helm upgrade avec le nouveau chart
Lire les journauxkubectl -n fahmek logs deploy/fahmek (et deploy/fahmek-autogad)
Vérifierhelm test fahmek -n fahmek --logs
Monter en chargefahmek.replicas, ou fahmek.autoscaling.enabled: true (2 à 10 pods selon le CPU)
Test de chargekubectl -n fahmek exec deploy/fahmek -- python /app/scripts/load_test.py --mode ui --url http://fahmek:8000 --history 10 --rates 50,100 --duration 60

Lancé depuis un pod fahmek, le test de charge partage le CPU de ce pod : il montre si un débit tient, mais pour mesurer la capacité, le lancer depuis un autre pod ou une autre machine (Passer en production).

Répéter sur une machine avec kind

kind fait tourner un cluster Kubernetes à un nœud dans Docker, pratique pour répéter avant le cluster du client. Installer kind, kubectl et helm depuis leurs sites officiels (choisir la version de la machine : amd64 ou arm64). Dans le dossier fahmek, avec les images fahmek chargées (étape 2 ; sur une machine ARM, construites avec l'option B, car l'archive contient des images x86-64) :

kind create cluster --name fahmek
docker pull redis:7-alpine && docker pull erikvl87/languagetool:6.8
ARCH=$(docker version -f '{{.Server.Arch}}')
for img in fahmek:0.1.0 fahmek-autogad:0.1.0 redis:7-alpine erikvl87/languagetool:6.8; do
  docker save --platform "linux/$ARCH" "$img" \
    | docker exec -i fahmek-control-plane ctr --namespace=k8s.io images import --digests --snapshotter=overlayfs -
done

(La boucle remplace kind load docker-image, qui échoue avec content digest ... not found sur un Docker récent. Les lignes mismatched image rootfs and manifest layers peuvent être ignorées.) Puis faire le point 2 avec ./license/fahmek.license, et installer avec moins de CPU :

helm install fahmek ./deploy/helm/fahmek -n fahmek --set fahmek.workers=1 --set fahmek.resources.requests.cpu=250m --set fahmek.resources.limits.cpu=1 --set autogad.languagetool.resources.requests.cpu=250m

Vérifier avec le point 4, puis tout supprimer avec kind delete cluster --name fahmek.

Bon à savoir

  • Redis ne doit pas écrire sur disque. Le Redis du chart tourne avec --save "" --appendonly no et sans volume. Ne pas le remplacer par un chart Redis qui garde les données sur disque (celui de Bitnami le fait par défaut) : sous bank-pilot, fahmek refuse alors de démarrer. Pour un Redis existant, mettre redis.enabled: false et redis.externalUrl.
  • Durcissement : fahmek, auto-gad et Redis tournent sans root, avec un système de fichiers en lecture seule et sans capacité Linux. La vérification de disponibilité passe par une petite commande dans le conteneur : la clé n'apparaît jamais dans les manifestes.
  • Testé le 2026-10-19 sur un cluster kind à un nœud (Kubernetes 1.31, ARM64) : les cinq pods sont devenus prêts et helm test a réussi (disponibilité, licence, un CIN fictif masqué, auto-gad). Ce premier essai sur un vrai cluster a montré des pods refusés en CreateContainerConfigError (corrigé depuis dans le chart). kind ne fait pas respecter les règles réseau et n'a qu'un nœud : faire aussi la première installation sur le cluster de test du client.

Passer en production#

La machine de démonstration est une seule boîte : un processus LiteLLM, un fahmek, pas de TLS. Une installation de production pour beaucoup d'utilisateurs fait tourner plusieurs instances de fahmek, une limite de débit plus haute, et passe un test de charge sur le matériel du client. Le guide complet, avec les mesures : dimensionnement-production.md.

Dimensionner avec les appels par seconde, pas avec les utilisateurs.

tours par seconde      = utilisateurs en conversation au pic × messages envoyés par minute ÷ 60
appels fahmek par sec. = tours par seconde × appels par tour

Un tour coûte 2 appels par l'interface web (plus en streaming : un par tour de streaming), mais un appel par message de la conversation avec le kit d'intégration. Dans les mesures en bac à sable, un worker fahmek (un cœur) a traité environ 150 appels par seconde. Exemple pour 10 000 utilisateurs en même temps, un message toutes les 30 secondes (333 tours par seconde), avec 50 % de marge :

IntégrationAppels fahmek/sWorkersExemple de répartition
Interface web, sans streaming66772 instances × 4 workers
Interface web, streaming en 5 tours environ2 000205 instances × 4 workers
Kit, conversations de 10 messages4 7004712 instances × 4 workers

fahmek est rarement la limite : LiteLLM lui-même, les quotas du fournisseur de modèle et auto-gad (environ 30 cœurs LanguageTool à ce débit ; le couper ou en lancer plusieurs) doivent aussi être dimensionnés.

Réglages (dans le .env à côté du fichier compose) :

RéglagePar défautProduction
FAHMEK_WORKERS2un par cœur (1 à 32)
FAHMEK_INSPECT_RPS50 par workerenviron deux fois les inspections que reçoit chaque worker au pic
FAHMEK_INSPECT_BURST100 par workerdeux fois FAHMEK_INSPECT_RPS
Instances fahmek1au moins 2 derrière un répartiteur de charge, partageant un Redis sans persistance
FAHMEK_TRACE1 dans ce guide0

Sur Kubernetes, les mêmes réglages sont des valeurs du chart : fahmek.workers, fahmek.inspectRps, fahmek.inspectBurst, fahmek.replicas (ou fahmek.autoscaling) ; le Redis du chart est déjà en mémoire.

Test de charge. scripts/load_test.py envoie des conversations fictives à débit fixe et affiche les tours par seconde tenus, la part réussie et la latence. Il tourne avec le Python de l'image fahmek : rien à installer. Le lancer contre une instance de test réglée comme la production, jamais contre la production pendant les heures de travail :

cd ~/fahmek && set -a; . ./.env; set +a
docker run --rm --network host -e FAHMEK_API_KEY \
  -v "$PWD/scripts:/scripts:ro" --entrypoint python fahmek:0.1.0 \
  /scripts/load_test.py --mode ui --url http://localhost:8100 \
  --history 10 --stream-rounds 5 --rates 100,200,400 --duration 60 --max-p99-ms 100

--mode ui reproduit les garde-fous de l'interface web, --mode kit le kit d'intégration ; --history est la longueur habituelle des conversations du client. La capacité est le débit le plus haut où ok/s égale la cible et ok share vaut 1.0 ; prévoir le pic à deux tiers de ce débit. rate_limited : relever FAHMEK_INSPECT_RPS ; budget_exceeded ou des latences en secondes : ajouter des workers ou des instances.

Avant la mise en service :

  • Pic estimé avec la vraie longueur des conversations du client.
  • Test de charge réussi à 1,5 × ce pic, sur le matériel de production.
  • Au moins deux instances fahmek ; workers et limite de débit réglés.
  • Redis sans persistance ; /readyz prêt sur chaque instance.
  • FAHMEK_TRACE=0 ; stockage des prompts de LiteLLM désactivé.
  • bank-pilot (ou plus strict) et unreachable_fallback: fail_closed.
  • Expiration de la licence surveillée (GET /v1/license, days_left).
  • LiteLLM, les quotas du fournisseur et auto-gad dimensionnés pour le même pic.

Adresses et ports#

Depuis un autre ordinateur, remplacer localhost par l'adresse de la machine (hostname -I). Aucune de ces adresses ne doit être joignable depuis Internet.

AdresseCe que c'estConnexion
http://localhost:3000Chatbot (Open WebUI)Le premier compte créé est administrateur
http://localhost:4000/v1Passerelle LiteLLM : là où les applications envoient leurs requêtes d'IAAuthorization: Bearer <LITELLM_MASTER_KEY>
http://localhost:4001/uiSecond LiteLLM avec son interface web (seulement après la répétition)admin / LITELLM_MASTER_KEY
http://localhost:8100/docsAPI de fahmek, cliquableAuthorize avec FAHMEK_API_KEY
http://localhost:8200/v1/correctAPI d'auto-gadEn-tête X-Fahmek-Key: <FAHMEK_API_KEY>
http://localhost:9200/last_requestCe que le backend de test a reçu en dernierAucune (test seulement)

Redis et LanguageTool n'ont pas d'adresse propre : seules les autres parties les joignent. Les applications existantes de l'entreprise se branchent sur le port 4000 avec LITELLM_MASTER_KEY, comme n'importe quel service compatible OpenAI.

Installation sans Internet#

Pour une machine qui ne peut jamais joindre Internet, tout transporter sur un disque USB. La machine isolée doit déjà avoir Docker (votre équipe informatique peut l'installer depuis des paquets hors ligne).

Sur une machine connectée, enregistrer les trois parties externes à côté de l'archive :

for img in redis:7-alpine erikvl87/languagetool:6.8 ghcr.io/open-webui/open-webui:v0.7.2; do
  docker pull "$img" && docker save -o "$(echo $img | tr '/:' '__').tar" "$img"
done

Copier l'archive, les trois fichiers .tar et le fichier de licence sur le disque USB.

Sur la machine isolée, mettre les fichiers dans Téléchargements, faire l'étape 2 option A, puis charger les parties externes :

for f in ~/Téléchargements/*.tar; do docker load -i "$f"; done

Continuer avec les étapes 3 et 4, enregistrer le raccourci $C de l'étape 5 et démarrer avec $C up -d --remove-orphans, sans le bloc de téléchargement. Puis les étapes 6 et 7. Un vrai modèle d'IA doit être joignable depuis le réseau isolé (un serveur de modèles interne) ; sinon, garder le backend de test.

Dépannage#

La plupart des problèmes viennent de trois choses : un terminal dans le mauvais dossier, une partie qui démarre encore, ou une étape sautée.

Vous voyezPourquoiQue faire
permission denied avec docker.sockPas de déconnexion après l'étape 1.3Se déconnecter et se reconnecter, ou redémarrer
git clone répond Authentication failedJeton faux ou expiréDemander un nouveau jeton à l'éditeur, ou l'archive
up: command not foundLe raccourci $C n'est pas chargésource ~/.bashrc, ou refaire le premier bloc de l'étape 5
docker-compose.autogad.yml: no such file or directoryMauvais dossier, ou ancienne copie de fahmekcd ~/fahmek, puis mettre à jour (Mettre à jour)
pull access denied for fahmek, ou une erreur sur un Dockerfile absentParties de l'archive non chargées (option A)Refaire la ligne docker load de l'étape 2
Set FAHMEK_API_KEY for fahmek and its guardrailPas de .env dans ce dossiercd ~/fahmek ; si ls -a ne montre pas .env, refaire l'étape 3
Les vérifications répondent 401 ou UnauthorizedMots de passe non chargés dans ce terminalset -a; . ./.env; set +a
Chaque message échoue : fahmek license not valid; request not forwardedLicence absente, modifiée, ou expirée après la période de grâceLa vérification 2 de l'étape 6 dit pourquoi ; refaire l'étape 4 ou demander un renouvellement
Avec un vrai modèle, chaque réponse est une erreur du fournisseurClé fausse, pas de crédits API, ou nom de modèle fauxVérifier les trois lignes DEMO_ de .env, puis $C up -d ; $C logs --tail 50 litellm montre le message du fournisseur
connection reset by peer pendant un téléchargementConnexion instable sur un gros fichierRelancer le bloc de téléchargement ; les parties finies sont gardées. Si cela se répète : echo '{ "max-concurrent-downloads": 1 }' | sudo tee /etc/docker/daemon.json && sudo systemctl restart docker
429 Too Many Requests pendant un téléchargementLimite de Docker Hub pour les téléchargements anonymesAttendre une heure, ou docker login avec un compte Docker gratuit
La vérification 4 répond {"ready":false}, ou la trace dit auto-gad fixed: unavailableLanguageTool charge encoreAttendre une minute et réessayer
Le masquage marche mais aucun mot n'est jamais corrigéauto-gad non relié à la passerelle$C exec litellm printenv AUTOGAD_URL doit afficher http://autogad:8200 ; lire $C logs autogad languagetool
Un mot n'a pas été corrigéEn général voulu : mot court, deux corrections possibles, darija ou nom propreVoir Ce que corrige auto-gad
Rien n'apparaît dans la fenêtre de traceTrace désactivée, ou mauvais dossierFAHMEK_TRACE=1 dans .env, puis $C up -d ; lancer make trace depuis le dossier fahmek
Trace pleine de messages commençant par ### Task:Requêtes de fond du chatbotLes couper (étape 7.4)
La réponse affiche MA_PHONE_…_1 au lieu du numéroLe modèle a modifié le code de l'étiquetteAucune fuite ; sans les « », en gras ou avec des \_, elle est restituée
L'interface web de LiteLLM répond Must be a valid URLL'adresse de fahmek n'a pas de pointUtiliser une adresse IP ou un nom avec un point
Les réponses du Playground gardent des étiquettes «MA_…»fahmek-restore sans incremental_diff, ou LiteLLM avant v1.96Corriger les paramètres du garde-fou ou mettre LiteLLM à jour ; aucune fuite
Les messages échouent avec fahmek busy (inspection rate limit)Plus d'inspections que la limite de débitRelever FAHMEK_INSPECT_RPS (Passer en production)
La ligne fahmek de $C ps indique ExitedRedis configuré pour écrire sur disque sous une politique stricteUtiliser le service Redis fourni dans le fichier compose, sans le modifier
Le chatbot ne liste aucun modèlePasserelle encore en démarrage, ou LITELLM_MASTER_KEY changé après le premier démarrage$C logs litellm ; puis $C up -d open-webui
http://localhost:4000/ui affiche Not connected to DBLa passerelle de démonstration n'a pas de base de donnéesNormal ; pour l'interface web, utiliser la répétition sur le port 4001
Kubernetes : les pods restent en CreateContainerConfigErrorUn Secret manque ou porte un autre nom, ou le chart date d'avant le 2026-10-19kubectl -n fahmek describe pod <pod> donne la raison ; créer les Secrets du point 2, ou installer le chart actuel
Kubernetes : les pods restent en ImagePullBackOffimageRegistry faux, image non poussée, ou registre qui demande une connexionkubectl -n fahmek describe pod <pod> ; pousser l'image ou régler imagePullSecrets
Kubernetes : les pods fahmek tournent mais restent 0/1 (pas prêts)Redis injoignable ou qui écrit sur disque, ou Secret de licence fauxkubectl -n fahmek logs deploy/fahmek ; helm test fahmek -n fahmek --logs dit lequel
Kubernetes : helm test échoue avec license not usableLe Secret de licence contient le mauvais fichier, ou la licence a expiréRecréer le Secret depuis le fichier d'origine (Au quotidien sur Kubernetes)
Kubernetes : LiteLLM ne joint pas fahmekRègles réseau : LiteLLM n'est pas dans networkPolicy.clientsCorriger le sélecteur dans le fichier de valeurs, puis helm upgrade

Arrêter, redémarrer, réinitialiser#

PourCommandeCe qui est gardé
Tout arrêter$C downTout, historique du chat compris
Redémarrer$C up -dTout
Installer une mise à jourMettre à jourRéglages, licence, historique du chat
Effacer l'historique du chat et repartir à zéro$C down -vRéglages et licence seulement
Libérer de l'espace disquedocker image pruneTout ce qui est utilisé

Redis n'écrit jamais sur disque : après un redémarrage, les étiquettes des réponses en cours ne peuvent plus être remplacées. Seules les conversations en pleine réponse au moment du redémarrage sont concernées.

Limites connues#

À dire au client avant le pilote ; elles déterminent ce qu'il faut mesurer.

  • La précision sur de vrais messages n'est pas encore mesurée. Les chiffres actuels viennent de 65 messages inventés. Le pilote doit mesurer un échantillon relu des messages du client, par registre.
  • Identifiants seulement. Noms, adresses et dates de naissance ne sont pas détectés : ils arrivent au modèle et apparaissent dans la trace.
  • auto-gad ne corrige que le certain. Beaucoup de fautes restent telles quelles, par choix.
  • La liste de mots darija (une centaine dans auto-gad/config.yaml) doit être relue par une personne de langue maternelle darija avant le pilote.
  • Étiquettes réécrites par le modèle : sans les « », en gras ou avec des tirets bas échappés, elles sont restituées. Si le modèle modifie le code lui-même, la réponse affiche l'étiquette au lieu du numéro : aucune fuite, mais la réponse se lit mal.
  • La machine de démonstration est une installation de pilote, pas de production : pas de HTTPS entre les parties, un seul mot de passe partagé, un seul Redis, et le chatbot garde son historique non masqué sur disque. Voir Passer en production.
  • L'image LanguageTool est maintenue par la communauté (erikvl87/languagetool) ; LanguageTool ne publie pas d'image officielle.
  • La première vérification en français après un démarrage prend quelques secondes ; ensuite, 20 à 50 ms environ.

Référence technique#

Cette partie s'adresse à l'équipe technique : variables, contrats, limites d'exploitation et supervision.

Configuration détaillée#

VariableDéfautRôle
FAHMEK_RECOGNIZERS_DIRrecognizersRépertoire des packs YAML
FAHMEK_POLICIES_DIRpoliciesRépertoire des politiques YAML
FAHMEK_REDIS_URLredis://localhost:6379/0Carte de ré-identification
FAHMEK_API_KEY (service et garde-fou)aucun, obligatoireSecret partagé envoyé dans X-Fahmek-Key
FAHMEK_URL (garde-fou)http://fahmek:8000Adresse de fahmek vue du proxy
FAHMEK_POLICY_ID (garde-fou)defaultPolitique appliquée par la passerelle
FAHMEK_TENANT_ID (garde-fou)litellmLocataire pour la réversibilité
FAHMEK_INSPECTION_PROFILEinteractivedocument pour /v1/inspect-document (voir plus bas)
FAHMEK_WORKERS2Processus Uvicorn (1 à 32), un par cœur
FAHMEK_INSPECT_RPS / FAHMEK_INSPECT_BURST50 / 100Admission des inspections par worker (1 à 10 000)
FAHMEK_TRACE (garde-fou)01 = trace lisible pour les démonstrations seulement

Toute la configuration métier est en YAML : ajouter un identifiant = déposer un pack dans recognizers/ ; changer une action ou un seuil = éditer une politique. Aucun fichier Python n'est à modifier.

Une politique peut surcharger les seuils d'une seule entité sans toucher aux autres ni au pack, par exemple pour masquer un numéro de CIN même sans mot de contexte (plus de rappel, plus de faux positifs) :

entities:
  MA_CIN:
    thresholds:
      redact: 0.20

policies/pilot-high-recall.yaml illustre ce réglage ; default conserve le compromis documenté dans docs/formats/ma_cin.md.

Authentification du service#

Tous les chemins /v1/* exigent l'en-tête X-Fahmek-Key. /healthz reste public. Une clé absente, incorrecte ou répétée reçoit 401, avec uniquement {"detail":"Unauthorized"}, avant toute validation du corps de requête. La comparaison utilise hmac.compare_digest sur deux empreintes SHA-256 de taille fixe ; elle ne compare pas les préfixes ni la longueur de la clé secrète.

Générer une clé aléatoire de 32 octets ou plus et la conserver dans le gestionnaire de secrets du déploiement. Exemple pour une session Bash :

export FAHMEK_API_KEY="$(python -c 'import secrets; print(secrets.token_hex(32))')"

Ou PowerShell :

$env:FAHMEK_API_KEY = python -c "import secrets; print(secrets.token_hex(32))"

Ne pas afficher la variable. Utiliser exactement la même valeur dans les environnements du service et du garde-fou. Le code accepte un secret ASCII imprimable sans espace ni caractère de contrôle ; une configuration absente, vide ou invalide bloque le démarrage. Aucune clé de démonstration par défaut. Pour une intégration par configuration, create_app(api_key=...) côté service et api_key dans les paramètres du garde-fou priment sur la variable d'environnement. Ne jamais coder une vraie clé dans un fichier versionné.

Le garde-fou joint cet en-tête à inspect, restore et aux lectures de politiques, y compris celle du démarrage. Une réponse 401 bloque l'envoi au modèle même si la politique en cache est fail_open: true ; un échec de restauration conserve les jetons dans la réponse. La clé est indépendante de LITELLM_MASTER_KEY et des clés des fournisseurs de modèles.

Pour une rotation, mettre à jour les deux configurations puis redémarrer fahmek et LiteLLM ensemble ; une ancienne clé est alors refusée. Éviter de journaliser les en-têtes dans les reverse proxies, traces HTTP ou commandes curl -v. Ne jamais inclure la clé dans une URL, le corps JSON ou les logs. L'exemple curl ci-dessus développe la variable dans les arguments du processus : sur un hôte partagé, préférer un client lisant le secret depuis l'environnement. Garder le trafic sur un réseau de confiance ou sous TLS : le secret partagé authentifie les services, sans chiffrer le transport ni autoriser individuellement les locataires.

Règles d'exploitation#

  • Redis sans persistance, obligatoire. Le service lit CONFIG GET save et appendonly au démarrage. Exécuter Redis avec :

    redis-server --save "" --appendonly no

    Avec persistance activée, fahmek avertit bruyamment ; si une politique chargée est fail_open: false, il refuse de démarrer.

  • Durée de vie des cartes de ré-identification : reversible.ttl_seconds par politique (120 s pour bank-pilot et bank-ma-strict, 300 s pour default et pilot-high-recall), consommation au premier restore (delete_on_restore: true). Par l'interface web de LiteLLM, la carte d'un appel n'est pas consommée au premier usage (LiteLLM restitue un streaming en plusieurs tours) : la même durée de vie la borne.

  • Journaux : fahmek ne journalise jamais le texte ni les valeurs détectées. Les événements fahmek.degraded (réversibilité indisponible) et fahmek.unreachable (côté passerelle) sont à surveiller.

  • Mise à jour : voir Mettre à jour. Les politiques et les packs sont intégrés à l'image fahmek ; seul auto-gad/config.yaml est monté depuis le dossier.

Tests et démonstration#

make test           # lint + suite complète (dont budget de latence)
make demo           # pile compose + scénario à trois panneaux
make integration    # test bout en bout via compose (long)

Limites et supervision du pilote#

L'image lance python -m fahmek.serve, avec deux processus Uvicorn. FAHMEK_WORKERS accepte 1–32 ; dimensionner selon les cœurs réellement alloués au conteneur. Chaque processus limite la concurrence à 64 connexions. Des workers supplémentaires n'accélèrent pas une requête isolée. Ne pas remplacer ce lanceur par un Uvicorn multi-worker sans configurer également l'agrégation des métriques Prometheus.

ContratLimite / comportement
Texte inspecté8 192 points de code Unicode par message, sinon 413
Corps HTTP inspect128 Kio, contrôlé avant JSON, y compris transfert chunked ; lecture limitée à 5 s
Texte restore262 144 points de code, sinon 413
Corps HTTP restore2 Mio pour absorber l'expansion des jetons
options.budget_msentier de 1 à 15, défaut 15 ; dépassement → 408, sans résultat partiel
Parties de contenutableaux OpenAI contenant uniquement {type: text, text: ...} ; images/audio → 400 ; appels de fonctions JSON pris en charge (voir Compatibilité pilote et nouveaux profils)

Le budget est coopératif : contrôles entre scans/candidats et après le travail CPU. Une expression stdlib re déjà en cours n'est pas interrompue. La limite de taille et le test de charge CPU sur les packs approuvés complètent ce contrôle. Le p99 <15 ms mesure normalisation + détection + politique ; ni la file HTTP, ni Redis, ni les multiples appels du garde-fou ne sont inclus. Les rejets 4xx ne déclenchent jamais fail-open. Ne pas tronquer les messages.

/healthz reste public et mesure seulement la vie du processus. /readyz exige X-Fahmek-Key et renvoie 200 uniquement si Redis est accessible avec save "" et appendonly no, sinon 503. Autoriser ces lectures CONFIG à la sonde ; aucune valeur détectée n'est lue par cette vérification.

/metrics exige la même clé et s'expose uniquement au collecteur interne. Configurer ce collecteur pour ajouter X-Fahmek-Key depuis son gestionnaire de secrets (ou un proxy interne), jamais depuis une URL. Les métriques regroupent tous les workers de ce conteneur ; aucune donnée n'est envoyée automatiquement à un service externe. Exemple PromQL :

sum(rate(fahmek_inspections_total{decision="redact"}[5m]))
histogram_quantile(0.99, sum by (le) (rate(fahmek_request_seconds_bucket{route="/v1/inspect"}[5m])))
histogram_quantile(0.99, sum by (le) (rate(fahmek_inspection_seconds_bucket[5m])))
sum by (subsystem) (increase(fahmek_degraded_total[15m]))
sum by (status) (rate(fahmek_requests_total{route="/v1/inspect"}[5m]))

La latence HTTP est mesurée avant authentification jusqu'à la réponse, avec file d'attente et sérialisation. Les histogrammes donnent une estimation des quantiles. Les journaux fahmek.request portent un identifiant aléatoire de corrélation (également X-Request-ID), la route fixe, le statut et la durée. Ils excluent texte, valeurs, chemins inconnus, paramètres d'URL, en-têtes, identifiants fournis par le client et clé. Les access logs Uvicorn sont désactivés par le lanceur ; vérifier aussi les logs de LiteLLM et du proxy.

Surveiller les rejets 408/413, les dégradations engine, budget, reversible, restore, restore_missing, et la disponibilité de /readyz. La règle d'alerte et le SLO HTTP doivent être adaptés aux ressources du pilote. Voir evaluation/README.md pour la mesure indépendante par registre.

Corpus pilote : pas de collecte automatique#

Recevoir les messages au niveau de la passerelle n'autorise pas leur conservation. Le pilote doit définir un processus d'évaluation distinct, approuvé et local au client. Aucun texte brut ne doit être ajouté aux logs, à Git ou envoyé à un LLM pour produire les annotations. Les messages hypothétiques restent synthétiques, même rédigés par un humain. Voir evaluation/annotation-protocol.md.

Les lignes réelles exigent group_id et split ; une ligne reviewed: true exige deux identifiants opaques distincts dans review. Le rapport final local utilise --split holdout --require-reviewed-holdout. Les corpus synthétiques, non relus ou exploratoires ne constituent pas une mesure de précision réelle.

Compatibilité pilote et nouveaux profils#

Le garde-fou prend maintenant en charge la restauration en streaming des références masquées en entrée, avec le hook itérateur de LiteLLM. Une référence peut traverser plusieurs fragments : elle est retenue jusqu'à sa fermeture. Chaque mapping Redis est consommé une seule fois ; les valeurs vivent ensuite uniquement en mémoire pendant cette réponse, puis sont libérées. Une expiration ou panne de restauration laisse les jetons masqués ; aucun contenu brut n'est journalisé. Ce mécanisme ne détecte pas les nouvelles PII générées par le modèle.

Les appels tool_calls de type function, les anciens function_call et les résultats de rôle function sont acceptés. Les arguments sont décodés en JSON (y compris les échappements Unicode), inspectés puis réencodés. Les noms de clés et types scalaires ne doivent pas changer. Si un numéro de carte est un nombre JSON plutôt qu'une chaîne, ou si une clé contient une donnée à masquer, la requête est rejetée avec 400. Les clés dupliquées, JSON invalide et constantes non finies sont refusés. En sortie streaming, les arguments restent tamponnés jusqu'à la fin du choix pour garantir un JSON valide (maximum cumulé 262 144 points de code). Les définitions d'outils et noms de fonctions restent des configurations de confiance : ne pas y injecter des données clients. Les protocoles autres qu'OpenAI Chat ne sont pas couverts par ces tests.

Deux détecteurs génériques s'ajoutent : EMAIL (syntaxe ASCII courante, pas toute la RFC) et PAN (13–19 chiffres, Luhn, sans preuve d'émission). Ils sont activés dans toutes les politiques livrées. Une carte avec faute de frappe peut ne pas passer Luhn ; noms, adresses, date de naissance, passeports et CVV restent hors couverture. Consulter les documents de format et le catalogue généré.

Choix expliciteEffet
/v1/inspect ; profil interactive par défaut8 192 points de code, corps 128 Kio, budget CPU 1–15 ms
/v1/inspect-document ; FAHMEK_INSPECTION_PROFILE=document dans la passerelle32 768 points de code, corps 512 Kio, budget CPU 1–100 ms, défaut 100
FAHMEK_POLICY_ID=bank-pilotfail-closed ; masque CIN sans contexte et RIB de préfixe inconnu avec clé valide ; davantage de faux positifs possibles

Le profil document conserve les offsets originaux, sans découpage ni troncature. Il ne bénéficie PAS du SLO interactif de 15 ms. Les dépassements renvoient 408/413. Le garde-fou limite également l'ensemble à 128 messages et 262 144 points de code inspectés par appel. Les nouveaux rejets ne déclenchent jamais fail-open.

Une admission par bucket de jetons limite les inspections après authentification, avant lecture du corps : FAHMEK_INSPECT_RPS=50 et FAHMEK_INSPECT_BURST=100 par worker, configurables entre 1 et 10 000. Rejet : 429 et Retry-After: 1. Multiplier par le nombre de workers/réplicas pour comprendre le plafond local ; installer des quotas distribués au niveau de la passerelle pour des engagements par tenant. Liveness, readiness et restauration ne consomment pas ce bucket.

Les métriques CPU des documents sont séparées :

histogram_quantile(0.99, sum by (le) (rate(fahmek_document_inspection_seconds_bucket[5m])))

scripts/load_test.py mesure les tours par seconde selon le mode d'intégration (kit, interface web, passerelle entière), avec statuts et p50/p95/p99 ; la méthode et des mesures datées sont dans dimensionnement-production.md. Ces chiffres dépendent de la machine et ne sont pas un SLO de production : refaire un test prolongé sur l'infrastructure du client, et conserver les taux de 408/429, pas seulement la latence des réponses réussies.

Avant production (voir aussi Passer en production) : TLS au proxy interne, réseau fermé entre gateway/fahmek/Redis, authentification et autorisation des tenants au gateway, secrets gérés et rotation, quotas par tenant, architecture Redis HA sans persistance et procédure de reprise. Le secret partagé n'autorise pas un client à choisir librement un tenant. La configuration actuelle ne fournit pas ces garanties ; ne pas exposer directement les ports du compose de démonstration. Aucun changement à une infrastructure de production n'est réalisé par ces ajouts.

Choix de la politique et interfaces web#

La politique est une configuration : FAHMEK_POLICY_ID côté passerelle, policy_id dans chaque requête directe. GET /v1/policies renvoie pour chaque politique livrée une description et ses consequences en langage clair (ce qui est masqué en plus, en moins, et le comportement en cas de panne). make policies affiche, par registre, l'effet mesuré de chaque politique sur le corpus d'évaluation (spans sensibles manqués, masquages indésirables, messages bloqués) à côté de ce texte. Les chiffres du corpus synthétique attestent la cohérence entre registres ; seul un holdout client relu établit une exactitude réelle.

Interfaces web disponibles dans la pile de démonstration :

  • http://localhost:8100/docs — référence interactive de l'API fahmek ; bouton Authorize pour saisir FAHMEK_API_KEY ;
  • http://localhost:4000/ui — console d'administration LiteLLM (utilisateur admin, mot de passe = LITELLM_MASTER_KEY). Elle exige une base Postgres : la pile de démonstration n'en fournit pas, la connexion répond « Not connected to DB ». Le LiteLLM de répétition (docker-compose.litellm-ui.yml, port 4001) en a une. L'API de la passerelle fonctionne sans (Authorization: Bearer <LITELLM_MASTER_KEY> sur :4000/v1) ;
  • http://localhost:8100/metrics — métriques Prometheus (authentifié).

Il n'existe pas d'interface d'administration fahmek : hors périmètre v1.

Cette page est le guide livré avec fahmek, tenu à jour à chaque version.