Maison >développement back-end >Tutoriel Python >Masquage des données confidentielles dans les invites à l'aide de Regex et spaCy
Les gens ont des problèmes de confidentialité concernant les LLM populaires comme OpenAI, Gemini, Claude etc...,. On ne sait pas vraiment ce qui se passe derrière les écrans, sauf s'il s'agit d'un modèle open source. Nous devons donc être prudents de notre côté.
La première chose serait le traitement des informations que nous transmettons aux LLM. Les experts recommandent d’éviter d’inclure des informations confidentielles ou des identifiants personnels dans les invites. Cela semble plus simple, mais à mesure que la taille du contexte des LLM augmente, nous pouvons transmettre des textes volumineux aux modèles. Cela pourrait donc devenir difficile à vérifier et masquer tous les identifiants.
J'ai donc essayé de créer un script python qui détecterait et masquerait les identifiants et les informations confidentielles. Regex est magique et mis en œuvre pour reconnaître différentes informations confidentielles et les remplacer par des masques. Également utilisé la bibliothèque Spacy pour détecter les identifiants courants tels que le nom, le lieu, etc.,
Remarque : pour le moment, cela convient au contexte indien, mais un identifiant commun peut toujours être détecté.
Regardons donc la mise en œuvre (j'ai pris l'aide de LLM pour la mise en œuvre)
Si vous souhaitez ignorer l'explication.
Voici le lien vers la base de code : aditykris/prompt-masker-Indian-context
Importer les modules/bibliothèques nécessaires
import re from typing import Dict, List, Tuple import spacy nlp = spacy.load("en_core_web_sm")
Vous devez installer manuellement "en_core_web_sm" à l'aide de l'extrait ci-dessous
python -m spacy télécharger en_core_web_sm
Définition des informations confidentielles indiennes communes.
class IndianIdentifier: '''Regex for common Indian identifiers''' PAN = r'[A-Z]{5}[0-9]{4}[A-Z]{1}' AADHAR = r'[2-9]{1}[0-9]{3}\s[0-9]{4}\s[0-9]{4}' INDIAN_PASSPORT = r'[A-PR-WYa-pr-wy][1-9]\d\s?\d{4}[1-9]' DRIVING_LICENSE = r'(([A-Z]{2}[0-9]{2})( )|([A-Z]{2}-[0-9]{2}))((19|20)[0-9][0-9])[0-9]{7}' UPI_ID = r'[\.\-a-z0-9]+@[a-z]+' INDIAN_BANK_ACCOUNT = r'\d{9,18}' IFSC_CODE = r'[A-Z]{4}0[A-Z0-9]{6}' INDIAN_PHONE_NUMBER = r'(\+91|\+91\-|0)?[789]\d{9}' EMAIL = r'[\w\.-]+@[\w\.-]+\.\w+' @classmethod def get_all_patterns(cls) -> Dict[str, str]: """Returns all regex patterns defined in the class""" return { name: pattern for name, pattern in vars(cls).items() if isinstance(pattern, str) and not name.startswith('_') }
Donc, je révisais les classes et les méthodes Python, alors j'ai commencé à les implémenter ici.
J'ai trouvé que l'expression régulière de ces identifiants de DebugPointer était très utile.
Passons maintenant à la fonction de détection. Un simple re.finditer() a été utilisé pour parcourir différents modèles afin de trouver des correspondances. Les correspondances sont stockées dans une liste.
def find_matches(text: str, pattern: str) -> List[Tuple[int, int, str]]: """ Find all matches of a pattern in text and return their positions and matched text """ matches = [] for match in re.finditer(pattern, text): matches.append((match.start(), match.end(), match.group())) return matches
Utilisation d'un simple dictionnaire pour stocker les textes de remplacement. Enveloppé dans une fonction pour renvoyer le texte de remplacement.
def get_replacement_text(identifier_type: str) -> str: """ Returns appropriate replacement text based on the type of identifier """ replacements = { 'PAN': '[PAN_NUMBER]', 'AADHAR': '[AADHAR_NUMBER]', 'INDIAN_PASSPORT': '[PASSPORT_NUMBER]', 'DRIVING_LICENSE': '[DL_NUMBER]', 'UPI_ID': '[UPI_ID]', 'INDIAN_BANK_ACCOUNT': '[BANK_ACCOUNT]', 'IFSC_CODE': '[IFSC_CODE]', 'INDIAN_PHONE_NUMBER': '[PHONE_NUMBER]', 'EMAIL': '[EMAIL_ADDRESS]', 'PERSON': '[PERSON_NAME]', 'ORG': '[ORGANIZATION]', 'GPE': '[LOCATION]' } return replacements.get(identifier_type, '[MASKED]')
Ah ! la partie principale commence.
def analyze_identifiers(text: str) -> Tuple[str, Dict[str, List[str]]]: """ Function to identify and hide sensitive information. Returns: - masked_text: Text with all sensitive information masked - found_identifiers: Dictionary containing all identified sensitive information """ # Initialize variables masked_text = text found_identifiers = {} positions_to_mask = [] # First, find all regex matches for identifier_name, pattern in IndianIdentifier.get_all_patterns().items(): matches = find_matches(text, pattern) if matches: found_identifiers[identifier_name] = [match[2] for match in matches] positions_to_mask.extend( (start, end, identifier_name) for start, end, _ in matches ) # Then, process named entities using spaCy doc = nlp(text) for ent in doc.ents: if ent.label_ in ["PERSON", "ORG", "GPE"]: positions_to_mask.append((ent.start_char, ent.end_char, ent.label_)) if ent.label_ not in found_identifiers: found_identifiers[ent.label_] = [] found_identifiers[ent.label_].append(ent.text) # Sort positions by start index in reverse order to handle overlapping matches positions_to_mask.sort(key=lambda x: x[0], reverse=True) # Apply masking for start, end, identifier_type in positions_to_mask: replacement = get_replacement_text(identifier_type) masked_text = masked_text[:start] + replacement + masked_text[end:] return masked_text, found_identifiers
Cette fonction prend l'invite en entrée et renvoie l'invite masquée ainsi que les éléments identifiés comme dictionnaire.
Laissez-moi vous expliquer un par un.
Boucle suivante à travers les regex de différents identifiants pour trouver la correspondance dans l'invite. S'il est trouvé, alors il :
1. Stockez les informations identifiées dans un dictionnaire avec le type d'identifiant comme clé pour garder une trace.
2. Note les positions et les stocke dans positions_to_mask afin que nous puissions appliquer le masquage plus tard.
import re from typing import Dict, List, Tuple import spacy nlp = spacy.load("en_core_web_sm")
Maintenant, c'est l'heure de l'espace. C'est une excellente bibliothèque pour les tâches de traitement du langage naturel (NLP). Nous pouvons extraire les identifiants du texte en utilisant le module nlp.
Actuellement, je l'ai utilisé pour détecter le nom, l'organisation et les emplacements.
Cela fonctionne comme la même boucle ci-dessus pour identifier et stocker l'emplacement.
class IndianIdentifier: '''Regex for common Indian identifiers''' PAN = r'[A-Z]{5}[0-9]{4}[A-Z]{1}' AADHAR = r'[2-9]{1}[0-9]{3}\s[0-9]{4}\s[0-9]{4}' INDIAN_PASSPORT = r'[A-PR-WYa-pr-wy][1-9]\d\s?\d{4}[1-9]' DRIVING_LICENSE = r'(([A-Z]{2}[0-9]{2})( )|([A-Z]{2}-[0-9]{2}))((19|20)[0-9][0-9])[0-9]{7}' UPI_ID = r'[\.\-a-z0-9]+@[a-z]+' INDIAN_BANK_ACCOUNT = r'\d{9,18}' IFSC_CODE = r'[A-Z]{4}0[A-Z0-9]{6}' INDIAN_PHONE_NUMBER = r'(\+91|\+91\-|0)?[789]\d{9}' EMAIL = r'[\w\.-]+@[\w\.-]+\.\w+' @classmethod def get_all_patterns(cls) -> Dict[str, str]: """Returns all regex patterns defined in the class""" return { name: pattern for name, pattern in vars(cls).items() if isinstance(pattern, str) and not name.startswith('_') }
Dans certains cas de tests, j'ai remarqué que certains masques manquaient et cela était principalement dû au chevauchement des identifiants. Ainsi, le tri dans l'ordre inverse a aidé à le résoudre.
def find_matches(text: str, pattern: str) -> List[Tuple[int, int, str]]: """ Find all matches of a pattern in text and return their positions and matched text """ matches = [] for match in re.finditer(pattern, text): matches.append((match.start(), match.end(), match.group())) return matches
Ensuite, nous masquons ce qui se passe en utilisant les données de found_identifiers et positions_to_mask.
def get_replacement_text(identifier_type: str) -> str: """ Returns appropriate replacement text based on the type of identifier """ replacements = { 'PAN': '[PAN_NUMBER]', 'AADHAR': '[AADHAR_NUMBER]', 'INDIAN_PASSPORT': '[PASSPORT_NUMBER]', 'DRIVING_LICENSE': '[DL_NUMBER]', 'UPI_ID': '[UPI_ID]', 'INDIAN_BANK_ACCOUNT': '[BANK_ACCOUNT]', 'IFSC_CODE': '[IFSC_CODE]', 'INDIAN_PHONE_NUMBER': '[PHONE_NUMBER]', 'EMAIL': '[EMAIL_ADDRESS]', 'PERSON': '[PERSON_NAME]', 'ORG': '[ORGANIZATION]', 'GPE': '[LOCATION]' } return replacements.get(identifier_type, '[MASKED]')
Un exemple d'entrée de ce programme serait :
Entrée :
def analyze_identifiers(text: str) -> Tuple[str, Dict[str, List[str]]]: """ Function to identify and hide sensitive information. Returns: - masked_text: Text with all sensitive information masked - found_identifiers: Dictionary containing all identified sensitive information """ # Initialize variables masked_text = text found_identifiers = {} positions_to_mask = [] # First, find all regex matches for identifier_name, pattern in IndianIdentifier.get_all_patterns().items(): matches = find_matches(text, pattern) if matches: found_identifiers[identifier_name] = [match[2] for match in matches] positions_to_mask.extend( (start, end, identifier_name) for start, end, _ in matches ) # Then, process named entities using spaCy doc = nlp(text) for ent in doc.ents: if ent.label_ in ["PERSON", "ORG", "GPE"]: positions_to_mask.append((ent.start_char, ent.end_char, ent.label_)) if ent.label_ not in found_identifiers: found_identifiers[ent.label_] = [] found_identifiers[ent.label_].append(ent.text) # Sort positions by start index in reverse order to handle overlapping matches positions_to_mask.sort(key=lambda x: x[0], reverse=True) # Apply masking for start, end, identifier_type in positions_to_mask: replacement = get_replacement_text(identifier_type) masked_text = masked_text[:start] + replacement + masked_text[end:] return masked_text, found_identifiers
Sortie :
Texte masqué :
for identifier_name, pattern in IndianIdentifier.get_all_patterns().items(): matches = find_matches(text, pattern) if matches: found_identifiers[identifier_name] = [match[2] for match in matches] positions_to_mask.extend( (start, end, identifier_name) for start, end, _ in matches )
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!