NOPE LinkedIn

Catégories:
Blog

L'Usine à Cerveaux : Automatiser la Spécialisation des LLM

L'Usine à Cerveaux : Automatiser la Spécialisation des LLM image

Rubrique: Blog Tag: OpenAPI Tag: openapi Tag: Cybersécurité Tag: LoRA Tag: MLOps Tag: LLM Tag: mlops Tag: Pydantic Tag: lora Tag: Fine-tuning Tag: tuning Tag: cybersécurité Tag: fine

L’Usine à Cerveaux : Automatiser la Spécialisation des LLM

Dans le paysage actuel de la cybersécurité, la réactivité n’est plus une option ; c’est une question de survie. Un analyste SOC (Security Operations Center) moderne doit jongler entre une multitude d’interfaces : SIEM (Wazuh), plateformes d’orchestration (TheHive/Cortex), firewalls de nouvelle génération (OPNsense/Stormshield), et outils de threat intelligence (MISP).

L’idée d’un “Agent de Sécurité IA” capable d’unifier ces interfaces est séduisante, mais elle se heurte à un obstacle de taille : la précision technique absolue. En sécurité, “presque correct” est synonyme de faille.

C’est ici qu’intervient l’Usine à LoRAs (lora-factory), un projet conçu pour automatiser la montée en compétence des LLMs sur des APIs métier complexes via une approche industrielle et modulaire.


Le Problème : Pourquoi les LLMs généralistes échouent en Production

Un LLM généraliste comme GPT-4o ou Mistral-Large possède une connaissance superficielle de nombreux outils. Cependant, dès qu’il s’agit d’exécuter une action spécifique via une API REST sécurisée, les problèmes surgissent :

  1. Hallucination de Schéma : Le modèle invente un paramètre ip_address alors que l’API attend un objet JSON { "address": "...", "type": "IPv4" }.
  2. Sensibilité au Format : Une virgule manquante ou un type “String” envoyé au lieu d’un “Integer” invalide toute l’automatisation.
  3. Manque de Contexte Métier : Le modèle ne comprend pas qu’une règle de firewall ne devient effective qu’après un appel explicite à apply_changes.
  4. Fenêtre de Contexte Saturée : Tenter d’injecter 1000 pages de documentation API dans le prompt “Few-Shot” dégrade les performances et explose les coûts.

Le fine-tuning classique est une solution, mais il est manuellement exhaustif et difficile à maintenir à mesure que les APIs évoluent. Nous avons donc besoin d’une Usine capable de régénérer des experts certifiés à la volée.

Architecture du Pipeline : De la Doc au Modèle

L’Usine à LoRAs n’est pas un simple script d’entraînement. C’est un pipeline industriel où chaque étape est conçue pour garantir que le modèle final sera non seulement intelligent, mais aussi techniquement irréprochable.

graph TD
    A[Spécification OpenAPI] --> B[Fetcher & Cleaner]
    B --> C[Générateur Pydantic]
    C --> D[Moteur de Traces ReAct]
    D --> E[Formateur SFT]
    E --> F[Entraîneur QLoRA Unsloth]
    F --> G[Evaluateur de Sécurité]
    G --> H[Registre de LoRAs]
    H --> I[Runtime MoE / Multi-LoRA]

1. Le Fetcher OpenAPI : La Fondation de la Vérité Terrain

Tout commence par la documentation technique. Cependant, les spécifications OpenAPI réelles sont souvent un cauchemar architectural : références circulaires, schémas imbriqués, ou documentation obsolète. Notre OpenAPIFetcher réalise un travail de curation automatique indispensable :

  • Dé-référencement total : Transformation des $ref en structures plates. Un LLM comprend beaucoup mieux un objet plat qu’une suite de pointeurs JSON.
  • Filtrage de périmètre : En cybersécurité, certains endpoints sont dangereux (gestion des mots de passe admin) ou simplement informatifs. Nous filtrons la spécification pour ne garder que les “Actions de Sécurité” critiques.
  • Enrichissement Sémantique : Nous ajoutons des tags de dangerosité (CRITICAL, READ-ONLY, DESTRUCTIVE) sur les fonctions, ce qui servira de guide au modèle durant le fine-tuning.
class OpenAPIFetcher:
    def __init__(self, spec_url: str):
        self.raw_spec = requests.get(spec_url).json()
        
    def resolve_references(self, schema):
        # Logique récursive pour aplatir les schémas complexes
        if isinstance(schema, dict):
            if "$ref" in schema:
                ref_path = schema["$ref"].split("/")[-1]
                # Cache pour éviter les cycles infinis
                return self.resolve_references(self.raw_spec["components"]["schemas"][ref_path])
            return {k: self.resolve_references(v) for k, v in schema.items()}
        return schema

2. Génération de Modèles Pydantic : La Rigueur du Contrat

Une fois la spécification nettoyée, nous ne la donnons pas directement au LLM. Nous générons des classes Pydantic. C’est le secret de la fiabilité de lora-factory. En transformant les schémas JSON en code Python strict, nous obtenons une validation “Type-Safe” immédiate.

Le Mapping de Types Complexe

Notre générateur gère les cas complexes d’OpenAPI :

  • Enums : Un firewall n’accepte que pass, block ou reject.
  • Unions : Un paramètre peut être une IP unique ou une liste d’IPs.
  • Constraints : Validation des plages de ports (1-65535) et des motifs CIDR.
from pydantic import create_model, Field, validator
from typing import List, Optional, Union

def generate_dynamic_pydantic(endpoint: dict):
    fields = {}
    for name, prop in endpoint['requestBody']['content']['application/json']['schema']['properties'].items():
        # Mapping logique OpenAPI -> Python
        py_type = map_openapi_type(prop['type'])
        if 'enum' in prop:
            py_type = Literal[tuple(prop['enum'])]
        
        fields[name] = (
            Optional[py_type] if name not in endpoint.get('required', []) else py_type,
            Field(description=prop.get('description', ''))
        )
    
    return create_model(f"{endpoint['operationId']}Input", **fields)

Cette étape permet de créer un “contrat de service” entre le LLM et l’outil. Durant l’inférence, si le modèle génère un JSON invalide, Pydantic lève une exception que nous utilisons pour forcer une auto-correction (Self-Correction Loop).


3. Le Chef d’Orchestre : LoRAFactory

La classe LoRAFactory centralise cette logique industrielle. Elle gère le cycle de vie complet, de l’ingestion à la certification de l’agent.

class LoRAFactory:
    """Orchestrateur principal du pipeline de création d'experts."""
    
    def __init__(self, config_path: str):
        self.config = load_config(config_path)
        self.fetcher = OpenAPIFetcher(self.config.spec_url)
        self.generator = TraceGenerator(
            model=self.config.teacher_model,
            api_key=os.getenv("TEACHER_API_KEY")
        )
        
    async def produce_specialist(self, tool_name: str):
        print(f"🏭 Phase 1 : Ingestion de l'API {tool_name}")
        spec = self.fetcher.get_clean_spec()
        
        print(f"🏭 Phase 2 : Génération du dataset synthétique (Cible : {self.config.trace_count})")
        traces = await self.generator.generate_bulk_traces(spec)
        
        print(f"🏭 Phase 3 : Forge technique (QLoRA + Unsloth)")
        trainer = LoRATrainer(traces, self.config.training_params)
        model_path = trainer.train()
        
        print(f"🏭 Phase 4 : Certification et Métriques de Sécurité")
        evaluator = LoRAEvaluator(model_path)
        metrics = evaluator.run_check(spec)
        
        if metrics.fnr < 0.05: # Seuil de sécurité strict
            return Registry.register(tool_name, model_path, metrics)
        else:
            raise SafetyException("L'agent ne répond pas aux critères de sécurité FNR.")

4. Hardware et MLOps : Dimensionner l’Usine

L’Usine a été conçue pour être accessible tout en restant performante. Elle tourne sur des stations de travail “Engineer-Grade” utilisant des GPUs avec au moins 24GB de VRAM.

Niveau d’Expertise Modèle Base VRAM (Entraînement) Temps de Cycle
Agent Compact Mistral-7B 12 GB 5 min
Agent Cyber (Standard) Mistral-Nemo-12B 18 GB 8 min
Agent Ultra (Complexe) Qwen-2.5-32B 44 GB 22 min

Le choix de Mistral-Nemo-12B (en collaboration avec NVIDIA) s’est avéré être le “sweet spot” absolu : une compréhension fine des structures JSON complexes, une mémoire à long terme robuste, et une vitesse d’entraînement foudroyante grâce aux optimisations d’Unsloth.

La Gestion des Schémas Imbriqués (Nested Objects)

L’une des plus grandes difficultés du parsing OpenAPI réside dans les objets imbriqués. Une règle de firewall n’est pas juste une liste de champs plats ; c’est un entrelacs de références ($ref) vers des objets Source, Destination, Protocol, etc.

Notre générateur Pydantic gère cela en créant récursivement des modèles dépendants. Cela permet au LLM de comprendre la structure hiérarchique :

# Exemple de structure imbriquée générée
class FirewallSource(BaseModel):
    network: str = Field(..., description="Alias ou IP source")
    port: Optional[str] = Field(None, description="Port source")

class FilterRuleInput(BaseModel):
    action: Literal["pass", "block", "drop"]
    source: FirewallSource  # Objet imbriqué
    destination: FirewallDestination

Cette hiérarchie élimine les ambiguïtés que l’on trouve souvent dans les datasets plats où tous les champs sont au même niveau.

Le Nettoyage de Spec (The Cleaner Logic)

Donner une spécification OpenAPI brute de 2000 points d’entrée à un générateur de traces est contre-productif. Pour OPNsense, nous appliquons une logique de filtrage par “Capabilities” :

  • Whitelist de Modules : Nous ne gardons que firewall, alias, nat, diagnostics.
  • Exclusion de Méthodes : Les méthodes GET d’information pure sont souvent filtrées si elles n’apportent pas de valeur de raisonnement (ex: get_api_version).
  • Audit de Sécurité : Les endpoints de configuration système (utilisateurs, certificats) sont exclus par défaut pour éviter que l’agent ne soit utilisé pour des actions de contournement de privilèges.
def filter_spec_by_capability(spec: dict, allowed_modules: list):
    filtered_paths = {}
    for path, methods in spec['paths'].items():
        module = path.split('/')[2] # Convention OPNsense
        if module in allowed_modules:
            filtered_paths[path] = methods
    spec['paths'] = filtered_paths
    return spec

5. Inférence et Auto-Correction (Self-Correction Loop)

Même avec un excellent LoRA, une erreur de token est toujours possible. C’est ici que l’architecture “Système” prend le relais. Lors de l’utilisation de l’agent, chaque sortie JSON est validée contre le modèle Pydantic généré en phase 1.

Si la validation échoue, nous ne renvoyons pas d’erreur à l’utilisateur. Nous lançons une boucle d’auto-correction :

  1. Capture de l’Erreur : Pydantic nous donne l’emplacement précis (ex: field 'port' is not a valid integer).
  2. Reprompt Interne : Nous envoyons un message invisible au modèle : “Erreur de validation : le champ ‘port’ doit être un entier. Ta sortie était ‘any’. Corrige ton appel.”
  3. Correction : Le modèle ré-émet le JSON corrigé.

Dans 95% des cas, cette boucle de correction en un seul tour (one-shot correction) suffit à obtenir un résultat valide sans intervention humaine.


Résultat Opérationnel : L’exemple OPNsense

L’aboutissement de cette architecture est la montée en compétence spectaculaire de nos agents. Pour l’agent OPNsense, nous sommes passés d’un modèle capable de router 7 fonctions basiques à un expert maîtrisant 40 endpoints de l’API firewall.

L’Usine a permis de découvrir automatiquement des capacités avancées comme la gestion des GeoIP Aliases, permettant à l’utilisateur de dire simplement : “Bloque le trafic venant de pays sous embargo”. L’IA décompose alors la requête, crée l’alias GeoIP, l’associe à une règle de blocage et applique la configuration.


Conclusion et Structure de l’Usine

L’Usine à LoRAs n’est pas qu’un outil technique, c’est une nouvelle philosophie du développement IA : la Souveraineté par l’Automatisation. En fabriquant nos propres experts, nous ne dépendons plus d’un fournisseur cloud unique et nous gardons le contrôle total sur les données et les capacités de nos agents.

Dans le prochain article, nous plongerons dans les secrets du Moteur de Traces ReAct. Nous verrons comment nous forçons le modèle à “penser” avant d’agir, tout en évitant les biais de répétition grâce à notre moteur de diversité de scenarios.

Suite de la série :