English | 日本語 | Español | Tiếng Việt | 中文 | 한국어 | Français | Deutsch | Português | Italiano
Utilisez les LLM de pointe sans leur montrer vos PII. Anonymisation réversible, sur l'appareil — n'échangez pas l'intelligence contre la vie privée.
Aujourd'hui, vous avez deux options. Exécuter un modèle local — privé, mais vous renoncez à l'intelligence de pointe. Ou coller votre texte dans ChatGPT / Claude / Gemini et vous surveiller vous-même, prompt après prompt. Prompt Anonymizer se situe entre les deux :
| Intelligence | Vie privée | Ce à quoi vous devez faire confiance | |
|---|---|---|---|
| Modèle local | ✗ sacrifiée | ✓ | rien |
| Modèle de pointe, brut | ✓ | ✗ | le fournisseur, et votre propre vigilance |
| Modèle de pointe + Prompt Anonymizer | ✓ | ✓ | du code que vous pouvez lire + une relecture finale |
Il remplace les PII par des étiquettes cohérentes (<人名_1>, <Name_1>,
<Nombre_1>, <Tên_1>, …) avant que le texte ne quitte votre machine.
Comme la même valeur reçoit toujours la même étiquette, la réponse du LLM
reste cohérente. Quand la réponse revient, le mapping — qui n'a jamais
quitté votre appareil — restaure les valeurs réelles.
Langues prises en charge : anglais (en), japonais (ja), espagnol (es),
vietnamien (vi) et — nouveauté — chinois (zh), coréen (ko), français
(fr), allemand (de), portugais (pt) et italien (it). La valeur par
défaut de PromptAnonymizer(languages=…) reste ("en", "ja") ; toutes les
autres langues s'activent via languages=[...]. Tous les sélecteurs de
langue des interfaces et la détection automatique couvrent les dix. La prise
en charge des langues est pilotée par un registre — ajouter une langue se
résume à une entrée dans le registre (languages.py / languages.ts) plus un
fichier d'étiquettes.
La détection s'exécute sur l'appareil (WebGPU / WASM dans le navigateur, spaCy ou transformers locaux en Python). Ne nous croyez pas sur parole : ouvrez les DevTools, surveillez l'onglet réseau ou lisez le code source. Le projet est sous licence MIT et assez petit pour être audité d'une traite.
Table des matières
- Démo
- Essayez-le
- Démarrage rapide (Python)
- Démarrage rapide (JavaScript / TypeScript)
- Démarrage rapide (proxy local)
- Démarrage rapide (serveur MCP)
- Garde-fou au commit / en CI (
scan) - Pourquoi pas… ?
- Comment ça marche
- Entités prises en charge
- Précision
- Limites
- Feuille de route
- Contributing / Security / License
Anonymiser → le mapping reste local → la réponse du LLM conserve les étiquettes → restaurer :
| Cible | Comment | Notes |
|---|---|---|
| Navigateur (WebGPU) | akazah.github.io/prompt-anonymizer | 100 % sur l'appareil : le NER s'exécute dans votre navigateur via WebGPU (repli WASM). Votre texte n'est jamais envoyé à un serveur — vérifiez-le dans l'onglet réseau. |
| App de bureau | Téléchargez depuis Releases (.dmg / .msi / .exe / .AppImage / .deb / .rpm) |
Tauri 2. Non signée pour l'instant — votre OS affichera un avertissement au premier lancement. |
| Extension Chrome | prompt-anonymizer-extension-*.zip depuis Releases |
Décompressez → chrome://extensions → activez le mode développeur → « Charger l'extension non empaquetée ». Sélectionnez du texte → clic droit → Anonymize selection. |
| Python / CLI | pip install prompt-anonymizer |
Presidio + spaCy. Voir le démarrage rapide ci-dessous. |
| CLI Node (npx) | npx @prompt-anonymizer/cli |
Mêmes commandes et options que la CLI Python ; NER transformers.js, entièrement sur l'appareil. |
| Web Component | @prompt-anonymizer/element |
Élément <prompt-anonymizer> indépendant du framework : intégrez le panneau complet anonymiser → restaurer dans n'importe quel site (HTML pur, Svelte, Angular, …). |
| React / Vue | @prompt-anonymizer/react / @prompt-anonymizer/vue |
Composant <AnonymizerPanel /> prêt à l'emploi plus un hook useAnonymizer() / composable pour les interfaces personnalisées. Voir le démarrage rapide ci-dessous. |
| Proxy local + GUI d'administration | npx @prompt-anonymizer/proxy |
Proxy inverse compatible OpenAI : pointez OPENAI_BASE_URL vers lui et les PII sont masquées avant de quitter votre machine, les étiquettes étant restaurées dans les réponses (streaming inclus). GUI d'administration sur http://127.0.0.1:8787/admin/. Voir le démarrage rapide ci-dessous. |
| Serveur MCP | npx @prompt-anonymizer/mcp |
Outils anonymize / deanonymize / scan pour tout client MCP (Claude Desktop, Claude Code, Cursor, …). Le mapping des étiquettes reste en mémoire serveur (mapping_id) et n'est jamais montré au modèle sauf demande explicite. Voir le démarrage rapide ci-dessous. |
| Hook de commit / garde-fou CI | prompt-anonymizer scan (les deux CLI) + .pre-commit-hooks.yaml |
Garde-fou PII par code de sortie pour les vérifications au commit et en CI : rapporte file:line:col et le type d'entité, jamais le texte détecté. Hors ligne et sans modèle par défaut. Voir ci-dessous. |
pip install prompt-anonymizer
python -m spacy download ja_core_news_sm # en: en_core_web_sm; es: es_core_news_sm
python -m spacy download xx_ent_wiki_sm # vi : pas de pipeline spaCy officiel — WikiNER
# zh: zh_core_web_sm; ko: ko_core_news_sm; fr/de/pt/it: *_core_news_sm — ou
# installez tous les modèles sm d'un coup : uv sync --group models (lg : --group models-lg)from prompt_anonymizer import PromptAnonymizer
pa = PromptAnonymizer(languages=["ja"])
result = pa.anonymize("山田太郎の電話は090-1234-5678", language="ja")
result.text # '<人名_1>の電話は<電話番号_1>'
result.mapping # {'<人名_1>': '山田太郎', '<電話番号_1>': '090-1234-5678'}
pa_es = PromptAnonymizer(languages=["es"])
pa_es.anonymize(
"El cliente es Javier Moreno, teléfono 612 345 678", language="es"
).text # 'El cliente es <Nombre_1>, teléfono <Teléfono_1>'
# les noms vi nécessitent le backend transformer (voir « Backend NER transformer optionnel »)
pa_vi = PromptAnonymizer(languages=["vi"], ner_backend="hf")
pa_vi.anonymize(
"Tôi tên là Nguyễn Văn An, số điện thoại 0912 345 678", language="vi"
).text # 'Tôi tên là <Tên_1>, số điện thoại <SốĐiệnThoại_1>'
llm_output = call_your_llm(result.text) # les étiquettes survivent à l'aller-retour
pa.deanonymize(llm_output, result.mapping) # valeurs réelles restaurées, en localCLI (-l ja|en|es|vi|zh|ko|fr|de|pt|it) :
prompt-anonymizer anonymize -l ja --interactive --mapping-file mapping.json \
-t "山田太郎の電話は090-1234-5678"
prompt-anonymizer anonymize -l es -t "El cliente es Javier Moreno, teléfono 612 345 678"
prompt-anonymizer anonymize -l fr -t "Le client est Pierre Durand, téléphone 06 12 34 56 78"
prompt-anonymizer deanonymize --mapping-file mapping.json -t "<人名_1>様 ..."La CLI Node reproduit la CLI Python (mêmes commandes, options et sortie JSON), en exécutant le cœur TypeScript avec le NER transformers.js sur l'appareil :
npx @prompt-anonymizer/cli anonymize -t "山田太郎の電話は090-1234-5678"Pour intégrer le panneau anonymiser → restaurer prêt à l'emploi dans n'importe quel frontend, utilisez le web component indépendant du framework :
<script type="module">
import { definePromptAnonymizer } from "@prompt-anonymizer/element";
definePromptAnonymizer();
</script>
<prompt-anonymizer language="auto"></prompt-anonymizer>React (@prompt-anonymizer/react) et Vue 3 (@prompt-anonymizer/vue)
fournissent un <AnonymizerPanel /> typé qui enveloppe cet élément :
import { AnonymizerPanel } from "@prompt-anonymizer/react"; // ou "@prompt-anonymizer/vue"
<AnonymizerPanel language="auto" denyList={["ProjectX"]}
onAnonymize={(result) => console.log(result.text)} />Pour les interfaces personnalisées, les deux paquets exposent aussi la session anonymiser → LLM → restaurer sous forme de hook / composable :
import { useAnonymizer } from "@prompt-anonymizer/react"; // ou "@prompt-anonymizer/vue"
const { anonymize, restore, mapping, busy, error } = useAnonymizer();
const result = await anonymize(input, { language: "ja" });
// envoyez result.text au LLM — le mapping ne quitte jamais l'appareil — puis :
const { text: restored, unresolved } = await restore(llmReply);Par défaut, la détection repose uniquement sur des expressions régulières
(e-mails, numéros de téléphone, …) ; passez un ner (p. ex.
new TransformersNerBackend() de @prompt-anonymizer/core) pour masquer
aussi les noms et les lieux.
Lancez le proxy compatible OpenAI et pointez n'importe quel client vers lui — les PII sont masquées avant que la requête ne quitte votre machine et les étiquettes sont restaurées dans la réponse (streaming inclus). Les mappings restent dans la mémoire du proxy, par requête :
npx @prompt-anonymizer/proxy # écoute sur http://127.0.0.1:8787
# Dans votre app / shell :
export OPENAI_BASE_URL=http://127.0.0.1:8787/v1La GUI d'administration sur http://127.0.0.1:8787/admin/ affiche l'état en
direct et les événements de masquage (étiquettes et décomptes uniquement),
permet d'éditer la configuration du proxy (upstream, NER, listes deny/allow)
et offre un bac à sable d'anonymisation strictement local. Le proxy écoute
sur 127.0.0.1 par défaut ; les valeurs originales ne peuvent être révélées
dans la GUI que si vous activez explicitement --record-mappings.
Offrez des outils d'anonymisation sur l'appareil à tout client MCP — Claude Desktop, Claude Code, Cursor, … :
# Claude Code:
claude mcp add prompt-anonymizer -- npx -y @prompt-anonymizer/mcpTrois outils, tous conçus pour que les PII ne pénètrent pas le contexte du
modèle : anonymize renvoie le texte masqué et un mapping_id (le mapping
reste en mémoire serveur sauf demande explicite), deanonymize restaure par
mapping_id — éventuellement directement dans un fichier — et scan vérifie
les fichiers pour des PII, en ne rapportant que file:line:col et le type
d'entité, jamais le texte détecté. Passez --ner dans les arguments du serveur
pour masquer aussi noms/lieux (téléchargement unique du modèle au premier usage).
Les deux CLI incluent une sous-commande scan conçue pour les hooks git et
la CI : elle sort avec le code 0 quand les entrées sont propres, 1 quand
des PII sont trouvées et 2 en cas d'erreur. Elle ne rapporte que
file:line:col et le type d'entité — le texte détecté n'est jamais
imprimé, si bien que la sortie du hook et les logs de CI restent exempts
de PII. Par défaut, elle est hors ligne, déterministe et sans modèle (PII
structurées : e-mails, numéros de téléphone, codes postaux JP, My Number,
cartes de crédit — plus les termes --deny) ; --ner active la détection
des noms/lieux là où des modèles sont disponibles.
prompt-anonymizer scan src/prompt.txt docs/*.md # fichiers (p. ex. en stage)
git diff --cached -U0 | prompt-anonymizer scan # ou passez un diff en pipe
prompt-anonymizer scan --deny ProjectX --json -t "..."Avec le framework pre-commit
(définition du hook : .pre-commit-hooks.yaml) :
repos:
- repo: https://github.com/akazah/prompt-anonymizer
rev: v0.3.0
hooks:
- id: prompt-anonymizer-scan
# args: [--deny, ProjectX, --allow, support@example.com]Les projets Node peuvent brancher le même garde-fou via husky + lint-staged
(npx @prompt-anonymizer/cli scan) :
{ "lint-staged": { "*": "prompt-anonymizer scan" } }Comme tout le reste ici, la détection fonctionne au mieux : considérez
scan comme un filet de sécurité contre les fuites évidentes, pas comme une
garantie.
Pourquoi ne pas utiliser Presidio directement ? Utilisez Microsoft Presidio directement si vous avez besoin d'un framework généraliste de détection / anonymisation de PII. Prompt Anonymizer utilise Presidio comme moteur de son cœur Python et ajoute par-dessus le flux aller-retour avec le LLM : placeholders cohérents, prompt anonymisé en sortie, restauration locale après la réponse — plus des surfaces navigateur, extension et bureau qui ne nécessitent aucun Python.
Pourquoi pas LLM Guard ? LLM Guard est une solide suite de garde-fous côté Python avec ses propres Anonymize/Deanonymize. Prompt Anonymizer s'en distingue sur trois points : une détection pensée d'abord pour le japonais (noms japonais, adresses, My Number avec validation du chiffre de contrôle), des surfaces pour non-développeurs (collez du texte dans une page de navigateur — aucune installation Python) et une base de code assez petite pour être réellement lue.
Pourquoi pas une extension Chrome « 100 % locale » ? Plusieurs extensions closed-source revendiquent un traitement local. Une affirmation n'est pas un audit. Ce projet est sous licence MIT : ouvrez l'onglet réseau ou lisez le code source. (Des extensions malveillantes de « confidentialité IA » qui exfiltrent des conversations ont été documentées — la catégorie a mérité ce scepticisme.)
- Détection — NER Presidio + spaCy (Python) ou NER transformers.js + reconnaisseurs regex (navigateur/bureau/extension), étendus par des motifs téléphoniques par région pilotés par le registre (JP, US/NANP, ES, VN, CN, KR, FR, DE, PT, IT) et des reconnaisseurs spécifiques au Japon (codes postaux 〒, My Number avec validation du chiffre de contrôle). Les e-mails et les cartes de crédit sont indépendants de la langue ; JP_POSTAL_CODE et JP_MY_NUMBER sont détectés dans tous les modes de langue.
- Étiquetage cohérent — les spans sont fusionnés (priorité au score) et remplacés par offset depuis la fin ; les valeurs identiques partagent une même étiquette.
- Réversion —
deanonymize(text, mapping)restaure les originaux, l'étiquette la plus longue d'abord. Le mapping vous est renvoyé et n'est jamais persisté par la bibliothèque ; le stocker en sécurité relève de votre responsabilité.
| Entité | étiquette ja | étiquette en | étiquette es | étiquette vi | Moteur |
|---|---|---|---|---|---|
| PERSON | 人名 | Name | Nombre | Tên | NER |
| LOCATION | 住所 | Location | Dirección | ĐịaChỉ | NER |
| EMAIL_ADDRESS | メールアドレス | Correo | motif | ||
| PHONE_NUMBER | 電話番号 | Phone | Teléfono | SốĐiệnThoại | motifs par langue pilotés par le registre + régions libphonenumber (JP/US/ES/VN/CN/KR/FR/DE/PT/IT) |
| JP_POSTAL_CODE | 郵便番号 | PostalCode | CódigoPostal | MãBưuĐiện | motif (personnalisé) |
| JP_MY_NUMBER | マイナンバー | MyNumber | MyNumber | MyNumber | motif + chiffre de contrôle (personnalisé) |
| CREDIT_CARD | クレジットカード | CreditCard | Tarjeta | ThẻTínDụng | motif + contrôle de Luhn (les deux cœurs, toutes les langues) |
| CUSTOM (deny list) | 秘匿情報 | Custom | Personalizado | TùyChỉnh | correspondance exacte |
| US_SSN (optionnel) | 社会保障番号 | SSN | SSN | SSN | motif + règles d'invalidation (les deux cœurs, toutes les langues) |
| IBAN_CODE (optionnel) | IBAN | IBAN | IBAN | IBAN | motif + contrôle mod-97 (les deux cœurs, toutes les langues) |
Les étiquettes des six nouvelles langues (zh, ko, fr, de, pt, it) sont
fournies dans src/prompt_anonymizer/labels/*.yaml (Python) et dans
LABELS dans web/packages/core/src/labeling.ts (TS).
deny_list force le masquage de chaînes précises ; allow_list les en
exempte. Les entités optionnelles ne sont pas détectées par défaut —
demandez-les explicitement : PromptAnonymizer(entities=[...]),
new Anonymizer({ entities }) ou
--entities PERSON,EMAIL_ADDRESS,US_SSN,IBAN_CODE sur l'une ou l'autre CLI.
Le NER par défaut est spaCy, le modèle de chaque langue étant résolu depuis
le registre central (voir le tableau ci-dessous ; installez tous les modèles
sm avec uv sync --group models, les lg avec --group models-lg, ou
utilisez python -m spacy download <modèle>). Le vietnamien n'a pas de
pipeline spaCy officiel — les deux tailles de modèle utilisent le modèle
WikiNER multilingue xx_ent_wiki_sm pour la tokenisation et un NER PER/LOC
de base. Pour un bon rappel des noms/lieux vietnamiens, utilisez plutôt le
backend transformer (voir ci-dessous).
Pour un rappel PERSON/LOCATION nettement meilleur (surtout ja et vi),
installez l'extra hf et changez de backend — modèles Hugging Face par
langue, entièrement en local :
| Langue | spaCy (sm / lg) |
HF NER (ner_backend="hf") |
|---|---|---|
ja |
ja_core_news_sm / ja_core_news_lg |
tsmatz/xlm-roberta-ner-japanese |
en |
en_core_web_sm / en_core_web_lg |
dslim/bert-base-NER |
es |
es_core_news_sm / es_core_news_lg |
Davlan/bert-base-multilingual-cased-ner-hrl |
vi |
xx_ent_wiki_sm (les deux tailles) |
NlpHUST/ner-vietnamese-electra-base |
zh |
zh_core_web_sm / zh_core_web_lg |
Davlan/bert-base-multilingual-cased-ner-hrl |
ko |
ko_core_news_sm / ko_core_news_lg |
Davlan/bert-base-multilingual-cased-ner-hrl |
fr |
fr_core_news_sm / fr_core_news_lg |
Davlan/bert-base-multilingual-cased-ner-hrl |
de |
de_core_news_sm / de_core_news_lg |
Davlan/bert-base-multilingual-cased-ner-hrl |
pt |
pt_core_news_sm / pt_core_news_lg |
Davlan/bert-base-multilingual-cased-ner-hrl |
it |
it_core_news_sm / it_core_news_lg |
Davlan/bert-base-multilingual-cased-ner-hrl |
Le modèle HRL multilingue couvre nativement de/es/fr/it/pt/zh ;
le coréen n'a pas de checkpoint dédié dans cette famille et repose sur le
transfert interlingue de mBERT.
Le cœur TypeScript (navigateur / extension / bureau / CLI Node) exécute des
modèles ONNX transformers.js : ja et en utilisent les mêmes familles que
ci-dessus ; es, vi, zh, ko, fr, de, pt et it utilisent tous
Xenova/bert-base-multilingual-cased-ner-hrl (il n'existe pas d'export ONNX
d'un modèle NER vietnamien dédié ; le modèle multilingue se transfère bien
au vietnamien, et la même réserve de transfert s'applique au coréen).
pip install "prompt-anonymizer[hf]"pa = PromptAnonymizer(languages=["ja"], ner_backend="hf") # CLI: --ner-backend hf
pa_vi = PromptAnonymizer(languages=["vi"], ner_backend="hf") # recommandé pour les noms viLe traitement par lots est également disponible et bien plus rapide qu'une boucle :
results = pa.anonymize_batch(texts, language="ja", batch_size=16)Mesurée au niveau des spans sur un jeu doré synthétique généré avec graine
(200 documents pour chacune des dix langues dans
tests/golden/golden_{lang}.json) — voir docs/EVAL.md pour
le tableau complet et uv run python -m prompt_anonymizer.evals pour
reproduire (les dix langues par défaut). Points saillants (cœur Python,
modèles sm) : rappel 1.00 pour ja PHONE_NUMBER / EMAIL_ADDRESS /
JP_POSTAL_CODE / CREDIT_CARD ; rappel ja PERSON de 0.82 avec spaCy, 1.00
avec ner_backend="hf". Le rappel es/vi PHONE_NUMBER est aussi de 1.00 ;
vi PERSON/LOCATION bénéficient fortement de ner_backend="hf". Le rappel
des PII structurées (téléphone / e-mail / carte) est de 1.00 pour les six
nouvelles langues (zh, ko, fr, de, pt, it) sur le jeu doré —
docs/EVAL.md contient le tableau du cœur TS ; les chiffres
NER Python sont produits par l'évaluation hebdomadaire.
Ces chiffres servent à détecter des régressions, pas à promettre un rappel sur du texte réel.
- La détection fonctionne au mieux et n'est pas garantie. Des faux
négatifs se produisent ; relisez toujours le texte anonymisé avant de
l'envoyer où que ce soit (
--interactiveet les tableaux de mapping dans les interfaces existent pour cela). - L'anonymisation masque les identifiants, pas le contexte. Des détails quasi identifiants dans le texte environnant (un intitulé de poste rare, un événement précis) peuvent encore réduire le champ de qui ou de quoi vous parlez.
- LOCATION est l'entité au rappel le plus faible, surtout pour les adresses japonaises partielles.
- Le modèle NER du navigateur représente un téléchargement unique d'environ 100–300 Mo (mis en cache ensuite).
- Les builds bureau et extension ne sont pas signés pour l'instant.
Consultez les issues ouvertes et IMPLEMENTATION_PLAN.md. Points saillants : publication PyPI / npm (Trusted Publishing — installable aujourd'hui via GitHub Releases), Chrome Web Store, signature du code, modèles NER japonais plus petits, PII structurées multi-régions (plus de formats de téléphone / d'identifiants nationaux avec validation par somme de contrôle).
- docs/INTEGRATIONS.md — recettes pour LiteLLM, OpenWebUI, clients MCP, hooks git et CI
- CONTRIBUTING.md — configuration de développement (uv / pnpm), commandes de test et d'évaluation
- docs/AUDIT.md — vérifiez vous-même les affirmations on-device, étape par étape
- SECURITY.md — signalement des vulnérabilités et des contournements d'anonymisation
- MIT


