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.
- Entrée ou sortie
- Appel LLM
- Code
- Stockage
- Contrôle
- 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.
- 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.
- 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.
- 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.
- 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.
Cette requête ne passe pas par ici. Les autres chemins sont listés plus bas.
Voilà toute la requête. Envie de voir tourner quelque chose de ce genre sur vos données ?
Sélectionnez une case du schéma, ou suivez la requête étape par étape.
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.
- Entrée ou sortie
- Appel LLM
- Code
- Contrôle
- 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é.
- Planificateur Renvoie des hypothèses classées, en JSON validé par schéma.
- 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.
- 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é.
- 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.
- 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.
- Codeur L’hypothèse suivante, avec la leçon.
- Fit en sandbox Une deuxième expérience, dans un conteneur neuf.
- Détection fuites Aucun constat critique cette fois.
- Critique Arrêt.
- 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.
Cette requête ne passe pas par ici. Les autres chemins sont listés plus bas.
Voilà toute la requête. Envie de voir tourner quelque chose de ce genre sur vos données ?
Sélectionnez une case du schéma, ou suivez la requête étape par étape.
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.
| Contrôle | Signale quand |
|---|---|
| Une seule feature sépare les classes | AUC ≥ 0,90 (critique à partir de 0,97) |
| Les valeurs manquantes séparent les classes | séparation ≥ 0,85 |
| Validation croisée trop belle pour être vraie | CV ≥ 0,985 |
| Lignes quasi dupliquées | part ≥ 0,15 |
| Déséquilibre des classes | taux 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.