03 — Architecture technique
Comment preatorlabs est construit, et comment les composants se parlent.
Vue d'ensemble V1
┌──────────────────────────────────────────────────────┐
│ Navigateur │
│ │
│ ┌────────────────┐ ┌──────────────────────────┐ │
│ │ Landing │ │ Démo │ │
│ │ (HTML/CSS) │ │ (HTML + JS vanilla) │ │
│ │ │ │ │ │
│ │ Présentation │ │ • Segmentation │ │
│ │ scientifique │──▶│ • Config scénarios │ │
│ │ + ancres │ │ • Config 3 axes │ │
│ │ │ │ • Moteur d'ablation │ │
│ └────────────────┘ │ • Visualisation │ │
│ └────────────┬─────────────┘ │
│ │ │
│ Clé API stockée │
│ en localStorage │
└────────────────────────────────────┼─────────────────┘
│
▼
┌───────────────────────────────┐
│ Anthropic API │
│ /v1/messages │
│ │
│ Appel direct CORS-enabled │
│ Modèle : claude-sonnet-4-5 │
│ Temperature : 0 │
└───────────────────────────────┘
Composants V1
1. Landing (web/index.html, sections en haut)
Pure HTML/CSS. Pas de framework. Pas de build.
Contenu :
- Hero : pitch en une phrase + CTA vers la démo
- Le problème : pourquoi preatorlabs existe
- La méthode : explication des 3 axes + ablation
- Comment lire les résultats : guide de lecture
- Démo : ancre vers la section live
2. Démo (web/index.html, section #demo)
JS vanilla. Trois sous-composants :
2a. Module Segmenter
const Segmenter = {
segment(rawText) -> string[]
}
Implémentation : algorithme décrit dans 02-METHODOLOGY.md §1.
2a-bis. Prévisualisation des critères (renderCriteriaPreview)
Avant lancement, le panneau configuration affiche les règles qui seront appliquées :
function renderCriteriaPreview() {
const criteria = compileCriteria(getCriteriaRaw(), nonEmpty(state.segments));
// Liste structuralRules / behavioralRules avec badge auto | manuel
}
Déclencheurs : segmentation, édition de segments, toggles auto-struct/auto-behav, champs manuels (longueur, termes, etc.). Objectif : traçabilité des règles actives sans lancer l'analyse.
2b. Module Scorer
const Scorer = {
structural(output, criteria) -> { score: number|null, applicable: boolean },
behavioral(output, criteria) -> { score: number|null, applicable: boolean },
semantic(outputA, outputB, provider) -> { delta: number|null, applicable: boolean, provider: string }
}
Notes :
structural: parsing local en JS, zéro coût.behavioral: matching de chaînes / regex, zéro coût.semantic: provider switchable. V0.3 garde TF-IDF local (gratuit) et ajoute Voyage AI (optionnel) avec fallback explicite vers TF-IDF si l'appel échoue.
2c. Module AblationEngine
const AblationEngine = {
async run({ segments, scenarios, criteria, apiKey, model }) -> Results
}
Pseudo-code :
async function run({ segments, scenarios, criteria, apiKey, model }) {
const baselines = await Promise.all(
scenarios.map(s => callClaude(joinSegments(segments), s, apiKey, model))
);
const results = [];
for (let i = 0; i < segments.length; i++) {
const ablated = segments.filter((_, idx) => idx !== i);
const promptAblated = joinSegments(ablated);
const outputs = await Promise.all(
scenarios.map(s => callClaude(promptAblated, s, apiKey, model))
);
const deltas = scenarios.map((_, j) => ({
struct: abs(Scorer.structural(baselines[j], criteria).score - Scorer.structural(outputs[j], criteria).score),
behav: abs(Scorer.behavioral(baselines[j], criteria).score - Scorer.behavioral(outputs[j], criteria).score),
sem: Scorer.semantic(baselines[j], outputs[j], provider).delta
}));
results.push(aggregateSegment(i, deltas));
}
return results;
}
Concurrence : appels API en parallèle par scénario. Anthropic supporte 5 req/s sur Tier 1 — au-delà, batching séquentiel.
2d. Module Renderer
const Renderer = {
drawVarianceChart(results, canvas),
drawAxesBreakdown(results, container),
drawSynthesis(results, container)
}
Dépendance externe : Chart.js (UMD via CDN).
Contrats de données
Segment
type Segment = {
id: string; // "S1", "S2", ...
text: string; // le texte du segment
label?: string; // étiquette générée
}
Scenario
type Scenario = {
id: string; // "T1", "T2", ...
input: string; // l'input utilisateur
}
Criteria
type Criteria = {
structural: {
maxWords?: number;
forbidPatterns?: RegExp[];
};
behavioral: {
forbidden: string[];
required?: string[];
};
// sémantique : pas de config en mode B
}
SegmentResult
type SegmentResult = {
id: string;
label: string;
impact: number; // [0, 1]
variance: number; // [0, 1]
activation?: {
overall: number | null;
struct: number | null;
behav: number | null;
sem: number | null;
};
struct: number; // [0, 1]
behav: number; // [0, 1]
sem: number; // [0, 1]
verdict: 'critical' | 'high' | 'context' | 'low' | 'placebo';
perScenario: {
scenarioId: string; // "T1", "T2", ...
input: string; // scénario utilisateur
baselineOutput: string; // output prompt complet
ablatedOutput: string; // output prompt sans segment courant
axisDelta: { // deltas absolus pour ce scénario
struct: number;
behav: number;
sem: number;
};
}[];
}
Stockage local
localStorage est utilisé pour :
preatorlabs.apiKey— clé Anthropic de l'utilisateur (jamais envoyée à un serveur tiers)preatorlabs.voyageApiKey— clé Voyage (si provider Voyage activé)preatorlabs.lastPrompt— dernier prompt analysé (pour reprise)preatorlabs.lastResults— derniers résultats
Le projet n'a pas de backend. Toute la logique tourne dans le navigateur. C'est un choix de privacy-by-design : le prompt et les résultats ne quittent jamais la machine de l'utilisateur, sauf vers l'API du LLM cible.
Sécurité
- La clé API utilisateur n'est jamais loggée, jamais envoyée ailleurs qu'à Anthropic.
localStorageest isolé par origine — pas d'exfiltration cross-site.- L'utilisateur peut effacer la clé d'un clic via l'UI.
Limite assumée : un script malveillant injecté dans la page (XSS) pourrait lire localStorage. Mitigation : pas de contenu user-généré rendu en HTML brut, CSP stricte recommandée pour le déploiement.
V2 (planifiée)
Ajout d'un moteur Python optionnel pour les batches volumineux :
engine/
├── preatorlabs.py # moteur de référence
├── scorers/
│ ├── structural.py
│ ├── behavioral.py
│ └── semantic.py # embeddings via Voyage AI ou local sentence-transformers
└── cli.py # entrypoint CLI
Sortie JSON normalisée, importable dans la web app.
V3 (envisagée)
Support multi-LLM : adaptateurs pour OpenAI, Gemini, Mistral. Interface commune LLMAdapter. Permet de comparer la conformité d'un même prompt sur plusieurs modèles.