ML Engineer

Un agent AutoML qui ne voit jamais son propre jeu de test réservé

ML Engineer est un agent autonome qui profile un jeu de données, planifie des expériences, écrit du code scikit-learn et l’exécute dans une sandbox, le tout dans un banc d’essai conçu pour vérifier qu’il travaille honnêtement. L’agent ne voit jamais le jeu de test réservé (holdout) : non par convention, mais par construction. Son chiffre phare n’est pas son meilleur score, mais l’écart entre le score de validation croisée qu’il a optimisé et le score sur le holdout qu’il n’a jamais vu.

Type
Projet personnel, 2026, conçu et réalisé de bout en bout
Agent
Boucle sur mesure avec quatre rôles LLM : planificateur, codeur, critique, sélectionneur
Sandbox
Docker, sans réseau, 2 Go, 2 CPU, racine en lecture seule, entraînements limités à 10 minutes
Tâches
Six tâches tabulaires : cinq avec un piège planté, une de contrôle sans piège
Tests
188 cas de test (pytest), dont 16 dans Docker

Un prototype fonctionnel. Tout le pipeline tourne de bout en bout avec une doublure scriptée à la place du modèle ; aucun benchmark sur un modèle n’a encore été publié.

Le holdout ne croise jamais l’agent

Le découpage a lieu avant que l’agent n’existe, et la fonction qui lance l’agent n’a aucun paramètre capable de transporter un holdout. La notation se fait dans un autre conteneur, une fois que l’agent a rendu la main.

Une tâche à travers le banc d’essai
Jeu de données tâche générée ou CSV Découpage holdout temps, groupe ou classe Exécution agent ne voit que le train Holdout masqué à la prédiction Notation holdout conteneur séparé Rapport écart, pièges, baseline Jeu de données tâche générée ou CSV Découpage holdout temps, groupe ou classe Exécution agent ne voit que le train Holdout masqué à la prédiction Notation holdout conteneur séparé Rapport écart, pièges, baseline
  • Entrée ou sortie
  • Appel LLM
  • Code
  • Stockage
  • Contrôle
  1. Jeu de données Une tâche de churn générée à partir d’un seed, avec un piège planté : une colonne renseignée après le résultat. Ou votre propre CSV, jusqu’à 64 Mo.
  2. Découpage holdout Les tâches temporelles réservent les 20 % les plus récents, les tâches groupées 25 % des groupes, les autres 20 % stratifiés. Les colonnes connues seulement après le résultat sont vidées dans le holdout, comme elles le seraient au moment de la prédiction.
  3. Exécution agent Planificateur, codeur, critique et sélectionneur ne travaillent que sur train.parquet, pour 4 expériences au maximum. Le holdout ne leur est pas caché ; il ne leur a jamais été transmis.
  4. Notation holdout Une fois que l’agent a rendu la main, un autre conteneur charge chaque modèle entraîné et le holdout. Toutes les expériences sont notées, pas seulement celle qui a été soumise.
  5. Rapport L’écart CV − holdout par expérience (au-delà de 0,05, elle est comptée comme auto-trompée), les pièges détectés, manqués et signalés à tort, et une baseline naïve entraînée sur toutes les colonnes dans la même sandbox.

Les autres chemins du graphe

  • Pas de sandbox disponible : le benchmark s’arrête plutôt que d’exécuter du code sur l’hôte. L’exécution sur l’hôte exige un flag explicite, et chaque résultat le mentionne.
  • Une tâche échoue : elle est journalisée et les autres continuent.

Dans la boucle de l’agent

Quatre rôles tenus par un modèle de langage, appelés tour à tour par du simple Python. Aucun ne peut en appeler un autre, et aucun ne peut contredire l’arithmétique qui s’intercale entre eux.

Deux expériences, dont une prise en flagrant délit de fuite
Briefing profil + audit colonnes Planificateur hypothèses classées Codeur écrit candidate.py Fit en sandbox sans réseau, train seul Détection fuites 7 contrôles, sans LLM Critique un modèle plus petit Sélectionneur choisit sur la CV seule Briefing profil + audit colonnes Planificateur hypothèses classées Codeur écrit candidate.py Fit en sandbox sans réseau, train seul Détection fuites 7 contrôles, sans LLM Critique un modèle plus petit Sélectionneur choisit sur la CV seule
  • Entrée ou sortie
  • Appel LLM
  • Code
  • Contrôle
  1. Briefing Du code, pas un modèle, profile les données d’entraînement, audite les colonnes et suggère un schéma de CV, étiqueté « un conseil, pas une règle » : que l’agent le suive ou non fait partie de ce qui est mesuré.
  2. Planificateur Renvoie des hypothèses classées, en JSON validé par schéma.
  3. Codeur Écrit candidate.py pour la première hypothèse. Il voit les scores de CV, les 4 dernières leçons et les 3 derniers échecs ; aucun score sur le holdout n’existe encore.
  4. Fit en sandbox Docker sans réseau, 2 Go de mémoire, 2 CPU, un système de fichiers en lecture seule et uniquement train.parquet. En sortie : score et stratégie de CV, features utilisées, modèle entraîné.
  5. Détection fuites Supposons que le modèle ait gardé la colonne postérieure au résultat : une feature qui sépare les classes avec une AUC de 0,97 ou plus est un constat critique, et l’expérience est mise en quarantaine.
  6. Critique Un modèle plus petit, pour limiter en partie le biais d’un modèle favorable à son propre travail, lit les constats et pivote : supprimer la colonne. Un seul pivot est autorisé par exécution.
  7. Codeur L’hypothèse suivante, avec la leçon.
  8. Fit en sandbox Une deuxième expérience, dans un conteneur neuf.
  9. Détection fuites Aucun constat critique cette fois.
  10. Critique Arrêt.
  11. Sélectionneur Choisit une expérience à partir des seuls scores de CV et des constats. S’il en désigne une qui n’existe pas, le banc d’essai se rabat sur la meilleure CV non mise en quarantaine.

Les autres chemins du graphe

  • Le codeur échoue : la tentative est enregistrée comme un échec du codeur et la boucle continue, sans exécution en sandbox ni critique.
  • Le planificateur échoue : l’exécution se termine en erreur.
  • Le critique échoue : l’expérience est acceptée et la boucle continue.
  • 4 expériences au maximum ; une file vide mène aussi au sélectionneur.

Des contrôles qu’un modèle ne peut pas discuter

La détection des fuites de données est de la pure arithmétique, sans aucun modèle de langage dans le module : un détecteur qu’un modèle peut convaincre à force d’arguments rapporte ce que le modèle espérait.

Cinq des sept contrôles de fuite, avec leurs seuils dans le code
ContrôleSignale quand
Une seule feature sépare les classesAUC ≥ 0,90 (critique à partir de 0,97)
Les valeurs manquantes séparent les classesséparation ≥ 0,85
Validation croisée trop belle pour être vraieCV ≥ 0,985
Lignes quasi dupliquéespart ≥ 0,15
Déséquilibre des classestaux de positifs < 0,05

Ce qui est mesuré

  • L’écart

    Score de CV moins score sur le holdout, pour chaque expérience. Un écart supérieur à 0,05 est étiqueté auto-trompé.

  • Les pièges

    Cinq tâches portent chacune un piège planté (fuite de la cible, ordre temporel, groupes, doublons, déséquilibre des classes) et une sixième sert de contrôle sans piège. Pièges détectés, pièges manqués et fausses alertes sont comptés séparément.

  • Une baseline naïve

    Du gradient boosting sur toutes les colonnes, entraîné dans la même sandbox. Elle est censée tomber dans les pièges, ce qui montre ce que l’agent a évité.

  • Le coût

    Tokens, coût et temps réel de chaque exécution, tirés des mêmes enregistrements.

Choix d’ingénierie

  • L’isolation par la signature

    Le point d’entrée de l’agent n’a aucun paramètre capable de transporter un holdout. Des tests vérifient cette signature, vérifient que la boucle n’importe jamais le module de notation, et cherchent dans chaque prompt enregistré chaque score sur le holdout, de 2 à 8 décimales.

  • Masqué comme au moment de la prédiction

    Les colonnes postérieures au résultat sont vidées dans le holdout ; sinon, une fuite plantée passerait simplement pour une bonne feature.

  • Conseiller, ne pas imposer

    Le contrôle du découpage recommande un schéma de CV au lieu d’en imposer un. L’imposer rendrait les pièges inatteignables.

  • Échec fermé (fail closed)

    Pas de sandbox, pas d’exécution. Tourner sur l’hôte exige un flag explicite, et chaque résultat le signale.

  • Les features vivent dans le pipeline

    La préparation des données ne peut que supprimer des lignes : toute transformation se trouve donc dans le pipeline, qui tourne aussi au moment de la prédiction.

  • Les échecs sont des données

    Un entraînement qui plante se termine proprement en indiquant l’étape en échec, et devient une expérience enregistrée, pas un crash.

Technologies

Agent
boucle Python sur mesure, SDK compatible OpenAI, JSON validé par schéma, mode strict avec solution de repli
Machine learning
scikit-learn, pandas, NumPy, SciPy, PyArrow, joblib
Sandbox
Docker : sans réseau, CPU, mémoire et processus plafonnés, racine en lecture seule
Backend
Python 3.12, FastAPI, WebSocket, SQLModel, SQLite, cassettes d’enregistrement et de rejeu
Frontend
React 18, Vite, Tailwind CSS 4, graphiques SVG dessinés à la main
Qualité
pytest, ruff, GitHub Actions

Vous construisez quelque chose de similaire ?

Je détaille volontiers la conception, les compromis, ou ce que je changerais ensuite.