How does the anonymization logic work in detail?

HEAD
a979becMerge pull request #9 from AI-SmartTalk/fix-env-deploy
Avatar of avariable2
A.Variable
posted

Explique moi la logique d'anonymisation de ce repo. En detail

Avatar of anthropic/claude-haiku-4.5
Claude Haiku 4.5
commented

Excellent! Maintenant j'ai une vue complète. Créons un document de synthèse détaillé:

##🔐 EXPLICATION DÉTAILLÉE DE LA LOGIQUE D'ANONYMISATION - AnonDocs

###1. Architecture Générale

Le système fonctionne sur 5 couches principales:

Routes HTTP → Contrôleurs → Service Anonymisation → Service LLM → Réponse

###2. Flux d'Anonymisation - Détail Complet

####ÉTAPE 1 : Réception du Contenu

L'API accepte 3 formats:

  • Texte brut : via POST /api/anonymize
  • Documents (DOCX, PDF, TXT) : via POST /api/document (upload de fichier)
  • Streaming : via POST /api/stream/* (SSE - Server-Sent Events)

Validation basique:

// src/controllers/anonymize.controller.ts
if (!text || typeof text !== 'string') {
  // Erreur: texte obligatoire et doit être string
}
if (text.trim().length === 0) {
  // Erreur: texte ne peut pas être vide
}

####ÉTAPE 2 : Découpage en Chunks (Chunking)

Le texte est divisé en morceaux gérables pour le LLM.

Service: src/services/chunking.service.ts

Logique:

chunkText(text: string): string[] {
  // Si texte < chunk_size → retourner texte entier
  if (text.length <= this.chunkSize) {
    return [text];
  }
  
  // Sinon, diviser avec:
  // - Taille: config.CHUNK_SIZE (défaut: 1500 caractères)
  // - Chevauchement: config.CHUNK_OVERLAP (défaut: 200 caractères)
  // - Limite intelligente: casse aux limites de phrases (. ! ?)
}

Exemple:

Texte original (5000 caractères)
            ↓
     Chunk 1 (1500 chars) [0-1500]
     Chunk 2 (1500 chars) [1300-2800]  ← 200 chars d'overlap
     Chunk 3 (1500 chars) [2600-4100]
     Chunk 4 (reste)

Avantage: Les chunks qui se chevauchent garantissent que les PII à la limite d'un chunk ne seront pas manquées.


####ÉTAPE 3 : Anonymisation de chaque Chunk

Service: src/services/llm.service.ts → méthode anonymizeChunk()

Processus:

  1. System Prompt : Instructions au LLM pour identifier et remplacer les PII

  2. Sélection du Provider LLM (défaut: configurable)

    • OpenAI (ou compatible: LocalAI, LM Studio)
    • Anthropic Claude
    • Ollama (LLMs locaux comme Mistral, Llama, etc.)
  3. Envoi au LLM avec contexte:

const systemPrompt = `
You are an expert document anonymization assistant. Your task is to:
1. Identify and remove all Personally Identifiable Information (PII)
2. Replace PII with generic placeholders: [NAME], [ADDRESS], [EMAIL], [PHONE], [DATE], [ORGANIZATION]
3. Maintain structure and readability
4. Return JSON with: anonymizedText, piiDetected, replacements

PII includes:
- Personal names
- Physical addresses
- Email addresses
- Phone numbers
- Dates of birth / identifying dates
- Organization names (if identifying individuals)
- ID numbers (SSN, passport, driver's license, etc.)
- Financial information (credit card, bank account numbers)
`;
  1. Réponse attendue du LLM (format JSON):
{
  "anonymizedText": "John Smith called... → [NAME] called...",
  "piiDetected": {
    "names": ["John Smith"],
    "addresses": [],
    "emails": ["john@example.com"],
    "phoneNumbers": ["+33 6 12 34 56 78"],
    "dates": [],
    "organizations": [],
    "other": []
  },
  "replacements": [
    {"original": "John Smith", "anonymized": "[NAME]"},
    {"original": "john@example.com", "anonymized": "[EMAIL]"},
    {"original": "+33 6 12 34 56 78", "anonymized": "[PHONE]"}
  ]
}
  1. Extraction et validation:
// Extraction JSON from response (handle markdown formatting)
const jsonMatch = content.match(/\{[\s\S]*\}/);
const result = JSON.parse(jsonMatch[0]) as AnonymizationResult;

####ÉTAPE 4 : Traitement des Chunks

Deux modes disponibles (configurable):

Mode Séquentiel (par défaut):

if (config.chunking.enableParallel === false) {
  for (let i = 0; i < textChunks.length; i++) {
    const result = await llmService.anonymizeChunk(chunk);
    // Attendre avant le chunk suivant
    progressEmitter.emit('progress', {
      type: 'chunk_completed',
      progress: Math.round(((i + 1) / totalChunks) * 90),
      currentChunk: i + 1,
      totalChunks: totalChunks
    });
  }
}

Mode Parallèle (plus rapide):

if (config.chunking.enableParallel === true) {
  results = await Promise.all(
    textChunks.map(chunk => llmService.anonymizeChunk(chunk))
  );
}

####ÉTAPE 5 : Agrégation des Résultats

Service: src/services/anonymization.service.ts

// Fusionner tous les chunks traités
for (const result of results) {
  anonymizedChunks.push(result.anonymizedText);
  
  // Agréger les PII détectés
  allPiiDetected.names.push(...result.piiDetected.names);
  allPiiDetected.emails.push(...result.piiDetected.emails);
  // ... etc pour tous les types
  
  // Lister toutes les remplacements
  allReplacements.push(...result.replacements);
}

// Joindre les chunks avec une séparation
const anonymizedText = anonymizedChunks.join('\n\n');

####ÉTAPE 6 : Gestion Spécifique des DOCX

Service: src/services/docx-formatter.service.ts

Point crucial : Préserver la mise en forme (polices, couleurs, styles, images, etc.)

Processus:

  1. Lecture du DOCX (c'est un ZIP contenant du XML):
const data = fs.readFileSync(inputPath);
const zip = await JSZip.loadAsync(data);
const documentXml = await zip.file('word/document.xml')?.async('string');
  1. Extraction du texte (pour anonymisation):
// Parser le XML DOCX
const parsedXml = await parseXml(documentXml);

// Extraire les noeuds de texte <w:t>
private extractTextNodes(obj: any): TextNode[] {
  // Traverser récursivement l'arborescence XML
  if ('w:t' in obj) {  // Nœud texte trouvé
    textNodes.push({
      text: textContent,
      startPos, endPos,
      xmlPath: [chemin vers ce nœud]
    });
  }
  // Continuer récursivement...
}
  1. Anonymisation du texte (comme étape 1-5):
const result = await anonymizationService.anonymizeText(text, provider);
// Retourne: result.replacements = [
//   {"original": "John", "anonymized": "[NAME]"},
//   ...
// ]
  1. Application des remplacements au XML (sans casser la structure):
private applyReplacementsToXml(obj: any, replacements: PiiReplacement[]): void {
  if ('w:t' in obj) {
    // Obtenir le texte du nœud
    const textContent = obj['w:t'];
    
    // Appliquer TOUS les remplacements
    for (const replacement of replacements) {
      const regex = new RegExp(escapeRegex(replacement.original), 'g');
      obj['w:t'] = obj['w:t'].replace(regex, replacement.anonymized);
    }
  }
  
  // Récursivement pour tous les nœuds
  if (Array.isArray(obj)) {
    obj.forEach(item => this.applyReplacementsToXml(item, replacements));
  } else {
    Object.keys(obj).forEach(key => {
      this.applyReplacementsToXml(obj[key], replacements);
    });
  }
}
  1. Reconstruction du DOCX:
// Reconvertir le XML modifié
const newDocumentXml = builder.buildObject(parsedXml);

// Réintégrer dans le ZIP
zip.file('word/document.xml', newDocumentXml);

// Générer le nouveau DOCX
const buffer = await zip.generateAsync({ type: 'nodebuffer' });
fs.writeFileSync(outputPath, buffer);

###3. Types de PII Détectés

Le LLM est instruit à détecter:

┌─────────────────────────────────────────┐
│        PII CATEGORIES SUPPORTÉES        │
├─────────────────────────────────────────┤
│ • Names (noms personnels)               │
│ • Addresses (adresses physiques)        │
│ • Emails (adresses email)               │
│ • Phone Numbers (numéros de téléphone)  │
│ • Dates (dates de naissance, etc.)      │
│ • Organizations (entreprises identifi.) │
│ • ID Numbers (SSN, passeport, etc.)     │
│ • Financial Info (CB, comptes bancaires)│
│ • Other (autres PII détectés)           │
└─────────────────────────────────────────┘

###4. Flux Complet - Exemple Concret

INPUT:
"John Smith (john@example.com, +33612345678) works at Acme Corp"

        ↓ [Pas de chunking, < 1500 chars]

LLM SYSTEM PROMPT:
"Identifiez et remplacez tous les PII par des placeholders"

        ↓ [Invocation LLM]

LLM RESPONSE (JSON):
{
  "anonymizedText": "[NAME] ([EMAIL], [PHONE]) works at [ORGANIZATION]",
  "piiDetected": {
    "names": ["John Smith"],
    "emails": ["john@example.com"],
    "phoneNumbers": ["+33612345678"],
    "organizations": ["Acme Corp"]
  },
  "replacements": [
    {"original": "John Smith", "anonymized": "[NAME]"},
    {"original": "john@example.com", "anonymized": "[EMAIL]"},
    {"original": "+33612345678", "anonymized": "[PHONE]"},
    {"original": "Acme Corp", "anonymized": "[ORGANIZATION]"}
  ]
}

        ↓ [Aggregation des résultats]

OUTPUT FINAL:
{
  "anonymizedText": "[NAME] ([EMAIL], [PHONE]) works at [ORGANIZATION]",
  "piiDetected": {...},
  "replacements": {...},
  "chunksProcessed": 1,
  "wordsPerMinute": 3500,
  "processingTimeMs": 1234
}

###5. Mécanismes de Sécurité

MécanismeDétail
Pas de persistanceAucune donnée stockée en DB
Traitement localContrôle total sur où s'exécute le LLM
Confidentialité des donnéesPour Ollama: 100% local, zéro réseau externe
Validations d'entréeVérification du texte, du provider, du MIME type
Gestion des fichiersSuppression immédiate après traitement
Path traversal protectionValidation stricte des noms de fichiers

###6. Points Clés de l'Architecture

Modularité : Chaque service a une responsabilité unique
Flexibilité : Support multiple LLM providers
Scalabilité : Chunking + traitement parallèle optionnel
Intégrité des documents : Préservation de la mise en forme DOCX
Traçabilité : Logging détaillé + métriques (WPM, temps)
Stateless : Pas de session, API RESTful pure


###7. Configuration Clé

// src/config/index.ts
{
  chunking: {
    chunkSize: 1500,           // Taille de chunk en caractères
    chunkOverlap: 200,         // Chevauchement pour continuité
    enableParallel: false      // Parallélisation des chunks
  },
  llm: {
    defaultProvider: 'ollama',  // openai | anthropic | ollama
    ollama: {
      baseUrl: 'http://localhost:11434',
      model: 'mistral',
      temperature: 0             // 0 = déterministe (recommandé)
    }
  }
}

C'est un système robuste, modulaire et pensé pour la confidentialité! 🔒


END OF POST

How does the anonymization logic work in detail? — AI-SmartTalk/AnonDocs