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#
- L'employé écrit dans le chatbot (Open WebUI) et appuie sur Entrée.
- 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. - LiteLLM demande à auto-gad de corriger l'orthographe et la grammaire. auto-gad interroge LanguageTool, un moteur d'orthographe sur la même machine.
- LiteLLM envoie le message corrigé et masqué au modèle d'IA.
- La réponse revient. LiteLLM demande à fahmek les vraies valeurs et les remet à la place des étiquettes.
- L'employé lit une réponse normale, avec le vrai numéro.
Choisir son parcours#
| Vous voulez | Suivre |
|---|---|
| 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 client | Client sur Kubernetes (chart Helm ; pas d'étapes 1 à 7) |
| Servir beaucoup d'utilisateurs | Un 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 processeurs | uname -m | Nom pour Docker | Machines typiques |
|---|---|---|---|
| Intel ou AMD | x86_64 | amd64 | Presque tous les serveurs de banque ou d'entreprise, les machines virtuelles du cloud, la plupart des PC Windows |
| ARM | aarch64 | arm64 | Mac 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.
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 curlInstaller Docker avec son installateur officiel :
curl -fsSL https://get.docker.com | sudo shAutoriser votre utilisateur à lancer Docker sans
sudo:sudo usermod -aG docker $USERSe déconnecter puis se reconnecter (ou redémarrer la machine). Sans cela, la commande suivante répond permission denied.
Vérifier que Docker fonctionne :
docker run --rm hello-world docker compose versionVous 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.*.ymlChaque 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.*.ymlGit 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 .envLe relire avec cat .env : quatre réglages, avec de longues valeurs
aléatoires.
| Ligne | Ce qu'elle veut dire | La changer ? |
|---|---|---|
FAHMEK_API_KEY | Le secret entre les parties | Jamais à la main ; le garder secret |
LITELLM_MASTER_KEY | Le mot de passe des applications qui appellent la passerelle | Ne le donner qu'aux applications qui en ont besoin |
FAHMEK_POLICY_ID | Le niveau de sévérité du masquage (voir Changer les réglages) | Garder bank-pilot pour commencer |
FAHMEK_TRACE | 1 affiche chaque message, masqué, pour le voir fonctionner | 0 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
EOFLa 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.
Copier le fichier reçu sur la machine. Supposons qu'il soit dans
Téléchargements.Le placer dans un dossier
license, sous le nom exactfahmek.license:mkdir -p license cp ~/Téléchargements/*.license license/fahmek.license ls licenseLa 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 $CLa 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; donePuis démarrer. Avec l'archive (option A), les parties de fahmek sont déjà chargées :
$C up -d --remove-orphansAvec Git (option B), les construire d'abord, dans la même commande :
$C up --build -d --remove-orphansPatience. 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 psVous 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 +aPuis coller chaque vérification et comparer avec la bonne réponse.
| # | Vérification | Commande | Bonne réponse |
|---|---|---|---|
| 1 | fahmek est vivant | curl -s localhost:8100/healthz | {"status":"ok"} |
| 2 | La licence est acceptée | curl -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 |
| 3 | Redis est prêt et n'écrit rien sur disque | curl -s -H "X-Fahmek-Key: $FAHMEK_API_KEY" localhost:8100/readyz | {"status":"ready"} |
| 4 | auto-gad joint LanguageTool | curl -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#
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 traceSi 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.Ouvrir le chatbot. Dans un navigateur sur la même machine, aller sur http://localhost:3000. Depuis un autre ordinateur, remplacer
localhostpar l'adresse de la machine (hostname -I).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.
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.
Choisir le modèle
demo-modelen haut d'une nouvelle conversation.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 à envoyer | La 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
jeouil 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 tapez | Le modèle reçoit | Pourquoi |
|---|---|---|
| je veu changer | je veux changer | Grammaire : le verbe après je |
| il a envoyer le virement | il a envoyé le virement | Grammaire : participe passé après a |
| a la banque | à la banque | Grammaire : a ou à |
| Ces mon compte | C'est mon compte | Grammaire : homophones |
| Their is a problem | There is a problem | Grammaire : homophones |
| numero de telefone | numéro de téléphone | Orthographe |
| les numero | les numéro | Accent corrigé ; accord laissé, la grammaire propose deux solutions |
| fone | fone (inchangé) | Mot court : sous 5 lettres, seulement les accents |
| bghit nbdl RIB dyali | inchangé | La darija n'est jamais corrigée |
| أريد تغيير رقمي | inchangé | L'écriture arabe n'est jamais corrigée |
| «MA_PHONE_1», Karim, wifi2 | inchangé | É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âche | Qui | Comment | Quand |
|---|---|---|---|
| Renouveler la licence | L'éditeur envoie le fichier, l'admin du site le copie | Remplacer license/fahmek.license (ci-dessous) | Une fois par an |
| Vérifier la licence | Admin du site | Vérification 2 de l'étape 6 | Chaque mois, ou quand les journaux avertissent |
| Changer la sévérité du masquage | Admin du site, avec l'équipe sécurité | Modifier FAHMEK_POLICY_ID dans .env, puis $C up -d | Rarement |
| Installer une mise à jour | Admin du site | Mettre à jour | Quand l'éditeur en annonce une |
| Relire la liste de mots darija | Une personne de langue maternelle darija | auto-gad/config.yaml | Avant le pilote, puis chaque année |
| Lire les journaux | Admin du site | Journaux | Quand quelque chose semble anormal |
La licence, mois après mois#
fahmek relit license/fahmek.license chaque minute et passe tout seul par
ces états :
| État | Quand | Ce que voient les employés | Que faire |
|---|---|---|---|
valid | Jusqu'à 30 jours avant l'échéance | Rien | Rien |
expiring | Les 30 derniers jours | Rien ; un avertissement par jour dans les journaux de fahmek | Demander le renouvellement à l'éditeur |
grace | Jusqu'à 14 jours après l'échéance | Rien ; un avertissement par jour | Renouveler maintenant |
expired | Après la période de grâce | Chaque message échoue avec fahmek license not valid; request not forwarded | Renouveler ; le chat refonctionne en moins d'une minute |
missing / invalid | Pas de fichier, fichier modifié, ou signé par une clé inconnue | Comme expired | Recopier 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.licenseLe 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_ID | Si 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ée | Masqué |
bank-ma-strict | Message bloqué | Non masquée | Masqué ; tout le message est bloqué si un RIB est trouvé avec une confiance élevée |
pilot-high-recall | Message envoyé sans contrôle | Masquée | Non masqué |
default | Message envoyé sans contrôle | Non masquée | Non 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 autogadMettre à 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-orphansAvec Git (option B ; l'éditeur envoie un nouveau jeton si l'ancien a expiré) :
git pull
$C up --build -d --remove-orphansJournaux#
| Commande | Montre |
|---|---|
$C logs --tail 50 fahmek | Service de masquage, avertissements de licence |
$C logs --tail 50 litellm | Passerelle, et la trace quand FAHMEK_TRACE=1 |
$C logs --tail 50 autogad | Service de correction |
$C logs --tail 50 languagetool | Moteur d'orthographe |
make trace | La 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é#
.envlisible 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_KEYdans.envet 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 LiteLLM | Kit d'intégration | |
|---|---|---|
| Pour | un administrateur qui gère LiteLLM seulement depuis sa page web | une passerelle gérée par son config.yaml |
| Sur le serveur LiteLLM | rien ne change | trois fichiers Python et quelques lignes de configuration |
| Réponses en streaming | vraies valeurs remises à partir de LiteLLM v1.96 | vraies valeurs remises |
| Identifiant dans les arguments d'un appel de fonction | requête refusée | masqué puis remis |
| Guide | ci-dessous | integrations/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 -dfahmek 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.
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.Guardrails → Submitted Guardrails → Add Guardrail. Remplir une ligne du tableau, choisir l'équipe
fahmek, cliquer sur Submit for Review. Trois fois :Guardrail Name Mode API Base URL Additional litellm_params fahmek-maskPre Call http://<machine fahmek>:8100{"api_key": "<FAHMEK_API_KEY>", "default_on": true, "unreachable_fallback": "fail_closed"}fahmek-restorePost Call http://<machine fahmek>:8100{"api_key": "<FAHMEK_API_KEY>", "default_on": true, "streaming_transform_mode": "incremental_diff"}auto-gad(facultatif)Pre Call http://<machine fahmek>:8200{"api_key": "<FAHMEK_API_KEY>", "default_on": true}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.
- Models + Endpoints → Add Model. Provider : par exemple
Anthropic, ouOpenAI-Compatible Endpointspour un autre serveur compatible (avec son API Base). LiteLLM Model Name(s) : un modèle de la liste (par exempleclaude-opus-5-5), ouCustom 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. - 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. - 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 fautincremental_diff(LiteLLM v1.96 ou plus récent), sinon la réponse garde des étiquettes«MA_RIB_…»(aucune fuite). - 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_fallbackde LiteLLM qui décide, pas la politique : garderfail_closedavecbank-pilotoubank-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- Ouvrir
http://<adresse de la machine>:4001/ui. Se connecter avec l'utilisateuradminet la valeur deLITELLM_MASTER_KEYde.envcomme mot de passe. Un bandeau rouge sur « environment-credential login » est normal sur une démonstration. - Faire le point 2 ci-dessus avec ces adresses (noms des conteneurs dans
Docker) :
http://fahmek.internal:8000pour les deux garde-fous fahmek ethttp://autogad.internal:8200pour auto-gad. - 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 machine | Images poussées dans le registre privé du client |
.env | Les deux Secrets ci-dessous, plus un petit fichier de valeurs |
license/fahmek.license | Un Secret ; le renouveler ne demande pas de redémarrage |
$C up -d, $C logs | helm upgrade, kubectl logs |
| Vérifications de l'étape 6 | helm test |
| Un seul fahmek | Deux 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"
doneSans 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.licenseGarder 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 --logsLe 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-fou | API Base URL |
|---|---|
fahmek-mask, fahmek-restore | http://fahmek.fahmek.svc.cluster.local:8000 |
auto-gad | http://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âche | Commande |
|---|---|
| Renouveler la licence | kubectl -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églage | Modifier fahmek-values.yaml, puis helm upgrade fahmek ./deploy/helm/fahmek -n fahmek -f fahmek-values.yaml |
| Changer la liste de mots d'auto-gad | Mettre tout le fichier sous autogad.config dans les valeurs, puis helm upgrade (auto-gad redémarre tout seul) |
| Installer une mise à jour | Pousser les nouvelles images (point 1, nouveau tag), décompresser la nouvelle archive, puis helm upgrade avec le nouveau chart |
| Lire les journaux | kubectl -n fahmek logs deploy/fahmek (et deploy/fahmek-autogad) |
| Vérifier | helm test fahmek -n fahmek --logs |
| Monter en charge | fahmek.replicas, ou fahmek.autoscaling.enabled: true (2 à 10 pods selon le CPU) |
| Test de charge | kubectl -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=250mVé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 noet 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) : sousbank-pilot, fahmek refuse alors de démarrer. Pour un Redis existant, mettreredis.enabled: falseetredis.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 testa réussi (disponibilité, licence, un CIN fictif masqué, auto-gad). Ce premier essai sur un vrai cluster a montré des pods refusés enCreateContainerConfigError(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 tourUn 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égration | Appels fahmek/s | Workers | Exemple de répartition |
|---|---|---|---|
| Interface web, sans streaming | 667 | 7 | 2 instances × 4 workers |
| Interface web, streaming en 5 tours environ | 2 000 | 20 | 5 instances × 4 workers |
| Kit, conversations de 10 messages | 4 700 | 47 | 12 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églage | Par défaut | Production |
|---|---|---|
FAHMEK_WORKERS | 2 | un par cœur (1 à 32) |
FAHMEK_INSPECT_RPS | 50 par worker | environ deux fois les inspections que reçoit chaque worker au pic |
FAHMEK_INSPECT_BURST | 100 par worker | deux fois FAHMEK_INSPECT_RPS |
| Instances fahmek | 1 | au moins 2 derrière un répartiteur de charge, partageant un Redis sans persistance |
FAHMEK_TRACE | 1 dans ce guide | 0 |
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 ;
/readyzprêt sur chaque instance. -
FAHMEK_TRACE=0; stockage des prompts de LiteLLM désactivé. -
bank-pilot(ou plus strict) etunreachable_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.
| Adresse | Ce que c'est | Connexion |
|---|---|---|
| http://localhost:3000 | Chatbot (Open WebUI) | Le premier compte créé est administrateur |
| http://localhost:4000/v1 | Passerelle LiteLLM : là où les applications envoient leurs requêtes d'IA | Authorization: Bearer <LITELLM_MASTER_KEY> |
| http://localhost:4001/ui | Second LiteLLM avec son interface web (seulement après la répétition) | admin / LITELLM_MASTER_KEY |
| http://localhost:8100/docs | API de fahmek, cliquable | Authorize avec FAHMEK_API_KEY |
| http://localhost:8200/v1/correct | API d'auto-gad | En-tête X-Fahmek-Key: <FAHMEK_API_KEY> |
| http://localhost:9200/last_request | Ce que le backend de test a reçu en dernier | Aucune (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"
doneCopier 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"; doneContinuer 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 voyez | Pourquoi | Que faire |
|---|---|---|
permission denied avec docker.sock | Pas de déconnexion après l'étape 1.3 | Se déconnecter et se reconnecter, ou redémarrer |
git clone répond Authentication failed | Jeton faux ou expiré | Demander un nouveau jeton à l'éditeur, ou l'archive |
up: command not found | Le 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 directory | Mauvais dossier, ou ancienne copie de fahmek | cd ~/fahmek, puis mettre à jour (Mettre à jour) |
pull access denied for fahmek, ou une erreur sur un Dockerfile absent | Parties 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 guardrail | Pas de .env dans ce dossier | cd ~/fahmek ; si ls -a ne montre pas .env, refaire l'étape 3 |
Les vérifications répondent 401 ou Unauthorized | Mots de passe non chargés dans ce terminal | set -a; . ./.env; set +a |
| Chaque message échoue : fahmek license not valid; request not forwarded | Licence absente, modifiée, ou expirée après la période de grâce | La 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 fournisseur | Clé fausse, pas de crédits API, ou nom de modèle faux | Vé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échargement | Connexion instable sur un gros fichier | Relancer 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échargement | Limite de Docker Hub pour les téléchargements anonymes | Attendre 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: unavailable | LanguageTool charge encore | Attendre 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 propre | Voir Ce que corrige auto-gad |
| Rien n'apparaît dans la fenêtre de trace | Trace désactivée, ou mauvais dossier | FAHMEK_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 chatbot | Les couper (étape 7.4) |
La réponse affiche MA_PHONE_…_1 au lieu du numéro | Le modèle a modifié le code de l'étiquette | Aucune fuite ; sans les « », en gras ou avec des \_, elle est restituée |
| L'interface web de LiteLLM répond Must be a valid URL | L'adresse de fahmek n'a pas de point | Utiliser 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.96 | Corriger 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ébit | Relever FAHMEK_INSPECT_RPS (Passer en production) |
La ligne fahmek de $C ps indique Exited | Redis configuré pour écrire sur disque sous une politique stricte | Utiliser le service Redis fourni dans le fichier compose, sans le modifier |
| Le chatbot ne liste aucun modèle | Passerelle 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 DB | La passerelle de démonstration n'a pas de base de données | Normal ; pour l'interface web, utiliser la répétition sur le port 4001 |
Kubernetes : les pods restent en CreateContainerConfigError | Un Secret manque ou porte un autre nom, ou le chart date d'avant le 2026-10-19 | kubectl -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 ImagePullBackOff | imageRegistry faux, image non poussée, ou registre qui demande une connexion | kubectl -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 faux | kubectl -n fahmek logs deploy/fahmek ; helm test fahmek -n fahmek --logs dit lequel |
Kubernetes : helm test échoue avec license not usable | Le 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 fahmek | Règles réseau : LiteLLM n'est pas dans networkPolicy.clients | Corriger le sélecteur dans le fichier de valeurs, puis helm upgrade |
Arrêter, redémarrer, réinitialiser#
| Pour | Commande | Ce qui est gardé |
|---|---|---|
| Tout arrêter | $C down | Tout, historique du chat compris |
| Redémarrer | $C up -d | Tout |
| Installer une mise à jour | Mettre à jour | Réglages, licence, historique du chat |
| Effacer l'historique du chat et repartir à zéro | $C down -v | Réglages et licence seulement |
| Libérer de l'espace disque | docker image prune | Tout 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#
| Variable | Défaut | Rôle |
|---|---|---|
FAHMEK_RECOGNIZERS_DIR | recognizers | Répertoire des packs YAML |
FAHMEK_POLICIES_DIR | policies | Répertoire des politiques YAML |
FAHMEK_REDIS_URL | redis://localhost:6379/0 | Carte de ré-identification |
FAHMEK_API_KEY (service et garde-fou) | aucun, obligatoire | Secret partagé envoyé dans X-Fahmek-Key |
FAHMEK_URL (garde-fou) | http://fahmek:8000 | Adresse de fahmek vue du proxy |
FAHMEK_POLICY_ID (garde-fou) | default | Politique appliquée par la passerelle |
FAHMEK_TENANT_ID (garde-fou) | litellm | Locataire pour la réversibilité |
FAHMEK_INSPECTION_PROFILE | interactive | document pour /v1/inspect-document (voir plus bas) |
FAHMEK_WORKERS | 2 | Processus Uvicorn (1 à 32), un par cœur |
FAHMEK_INSPECT_RPS / FAHMEK_INSPECT_BURST | 50 / 100 | Admission des inspections par worker (1 à 10 000) |
FAHMEK_TRACE (garde-fou) | 0 | 1 = 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.20policies/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 saveetappendonlyau démarrage. Exécuter Redis avec :redis-server --save "" --appendonly noAvec 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_secondspar politique (120 s pourbank-pilotetbank-ma-strict, 300 s pourdefaultetpilot-high-recall), consommation au premierrestore(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) etfahmek.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.yamlest 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.
| Contrat | Limite / comportement |
|---|---|
| Texte inspecté | 8 192 points de code Unicode par message, sinon 413 |
| Corps HTTP inspect | 128 Kio, contrôlé avant JSON, y compris transfert chunked ; lecture limitée à 5 s |
| Texte restore | 262 144 points de code, sinon 413 |
| Corps HTTP restore | 2 Mio pour absorber l'expansion des jetons |
options.budget_ms | entier de 1 à 15, défaut 15 ; dépassement → 408, sans résultat partiel |
| Parties de contenu | tableaux 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 explicite | Effet |
|---|---|
/v1/inspect ; profil interactive par défaut | 8 192 points de code, corps 128 Kio, budget CPU 1–15 ms |
/v1/inspect-document ; FAHMEK_INSPECTION_PROFILE=document dans la passerelle | 32 768 points de code, corps 512 Kio, budget CPU 1–100 ms, défaut 100 |
FAHMEK_POLICY_ID=bank-pilot | fail-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 saisirFAHMEK_API_KEY;http://localhost:4000/ui— console d'administration LiteLLM (utilisateuradmin, 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.