Forge - un outil qui fait passer les modèles 8B de 53 % à 99 % sur les tâches d’agent grâce à des garde-fous
(github.com/antoinezambelli)- Forge est une couche de fiabilité pour l’appel d’outils des LLM auto-hébergés, conçue pour améliorer la stabilité des petits modèles locaux dans des workflows d’agents en plusieurs étapes
- Ses fonctions clés comprennent le rescue parsing pour récupérer les appels d’outils erronés, le déclenchement de nouvelles tentatives, l’imposition d’étapes obligatoires, un budget de tokens tenant compte de la VRAM et une compression de contexte hiérarchique
- La meilleure configuration auto-hébergée actuelle, Ministral-3 8B Instruct Q8 sur llama-server, atteint 86,5 % sur 26 scénarios d’évaluation et 76 % sur le niveau le plus difficile
- Trois modes d’utilisation sont proposés : confier toute la boucle d’agent à
WorkflowRunner, insérer le middleware Guardrails dans une boucle d’orchestration existante, ou l’appliquer de manière transparente via un serveur proxy compatible OpenAI WorkflowRunnergère le prompt système, l’exécution des outils, la compression de contexte et les garde-fous, tandis queSlotWorkerajoute une file de priorité et une préemption automatique aux slots d’inférence GPU partagés- Le serveur proxy se lance avec
python -m forge.proxyet applique les garde-fous entre des clients compatibles OpenAI comme opencode, Continue ou aider, et un serveur de modèles local - Pour les requêtes avec outils, le proxy injecte automatiquement un outil synthétique
respond, afin que le modèle appellerespond(message="...")au lieu de produire du texte brut ; il le retire ensuite dans la réponse pour que le client voie une réponse texte normale - Les backends pris en charge sont Ollama, llama-server (llama.cpp), Llamafile et Anthropic ; llama-server offre les meilleures performances et le plus de contrôle, Ollama la configuration la plus simple, Llamafile l’exécution en binaire unique, et Anthropic sert de référence frontier et de support pour les workflows hybrides
- L’installation se fait avec
pip install forge-guardrails, le client Anthropic s’ajoute avecpip install "forge-guardrails[anthropic]", et les prérequis sont Python 3.12+ ainsi qu’un backend LLM en cours d’exécution - Le harnais d’évaluation comprend 26 scénarios pour mesurer la fiabilité des appels d’outils en plusieurs étapes selon les combinaisons modèle/backend ; il est divisé entre les niveaux de référence OG-18 et 8 niveaux
advanced_reasoning - La configuration de test inclut 865 tests unitaires déterministes ne nécessitant aucun backend LLM, ainsi qu’un harnais d’évaluation destiné aux backends réels
- Le framework de garde-fous Forge et son étude d’ablation ont été publiés sous le titre Forge: A Reliability Layer for Self-Hosted LLM Tool-Calling, avec une licence MIT
1 commentaires
Avis sur Hacker News
J’aime travailler dans ce domaine et je trouve ça utile, mais j’évite les LLM basés sur le cloud et j’utilise surtout des modèles locaux de 4B à 30A3B paramètres
Du coup, même si je manque de repères sur les performances ou la précision des tout derniers LLM, je pense bien connaître le niveau qu’on peut attendre des modèles locaux et leurs goulots d’étranglement
J’ai parcouru l’article rapidement et lu le résumé, et même s’il y est dit qu’un simple réglage peut rendre les choses 10 fois plus rapides ou plus lentes, les métriques et les données semblent presque uniquement centrées sur la précision. Il faut parler de la vitesse
En particulier, dans les workflows agentiques et avec des modèles locaux, la précision des appels de fonctions/outils n’est plus, à mon avis, un gros problème depuis environ QwenCoder3 sur les 6 à 12 derniers mois ; le vrai enjeu, c’est la gestion du contexte et l’impact sur le temps. Si l’agent change souvent de prompt, les optimisations temporelles comme le prompt caching sautent
Là, on dirait qu’on ajoute encore des couches et des wrappers comme des garde-fous et des retries, et sur des modèles locaux, surtout pour un usage agentique, ça peut devenir inutilisable à cause de la latence
Désolé si c’est déjà traité de front, mais il y a si peu de discussion sur l’impact temporel que ça donne l’impression soit de masquer l’ampleur réelle de l’amélioration, soit de l’exagérer. J’aimerais entendre un retour sur la vitesse. Le fait que d’autres ne soulèvent pas ça m’inquiète un peu aussi : soit je m’y prends mal, soit personne n’utilise vraiment de modèles locaux
Je répète depuis longtemps qu’avec un harness correct, même de petits modèles locaux peuvent être étonnamment bons
Si on a un système qui peut tout essayer, il suffit d’empêcher qu’il produise un mauvais résultat au passage pour qu’il finisse par trouver la bonne réponse
Si la tâche est assez complexe, même les modèles récents ont ce problème, et il est encore plus amplifié avec les petits modèles
Leur raisonnement est souvent tout à fait correct et, dans bien des cas, suffisant. Il faut juste parfois les remettre sur les rails, puis ils se débrouillent
Je suis ravi de voir quelqu’un construire bien mieux quelque chose que je voulais prendre le temps de faire moi-même. Une question : y a-t-il, par exemple, un potentiel de parallélisation dans une boucle de retry ?
Les modèles locaux peuvent généralement gérer correctement un nombre limité de requêtes simultanées, de l’ordre de quelques dizaines, même sur du matériel grand public, et ça peut faire monter les tokens/seconde effectifs d’un facteur supérieur à 10
Je réfléchis depuis un moment à des workflows qui exploiteraient ça, et « corrige cette erreur » semble pouvoir être un cas d’usage, même si ce n’est pas parfait. Curieux d’avoir ton avis
J’ai quelques idées dans cet espace et je suis en train de les intégrer à mon harness. Mon harness est assez spécialisé, donc je ne sais pas à quel point c’est généralisable
Je découpe le problème en une exécution planifiée, et je fournis un plan initial qui inclut des objectifs explicites, comme quels outils l’agent d’exécution doit appeler et à quoi ressemble une exécution réussie. Le harness exécute ensuite ce plan dans l’ordre
Pour chaque étape impliquant un appel d’outil, je décompose cet appel en composants. Le harness demande à l’agent des valeurs de paramètres valides pour les arguments actuels de l’outil, et la définition de l’outil contient un validateur pour chaque argument. Si la validation échoue, le harness rembobine la conversation et injecte la raison de l’échec dans la tentative suivante
Une fois qu’une réponse valide est obtenue pour un argument, on passe au suivant, et quand tous les arguments sont remplis, on appelle l’outil. On fournit aussi les attentes initiales de l’agent, les valeurs réelles et les erreurs survenues, puis on lui demande s’il est satisfait du résultat
S’il ne l’est pas, l’agent donne une raison, le harness rembobine la conversation, injecte la raison du retry, puis recommence tout le processus d’appel d’outil depuis le début
L’agent peut demander une replanification s’il découvre des défauts dans le plan initial, et le harness essaie aussi de replanifier s’il y a trop d’échecs consécutifs
Cette approche est assez efficace pour réduire les échecs d’appel d’outil. Un avantage, c’est que le sous-agent reçoit un historique de conversation parfait, sans erreurs. En revanche, je n’ai pas encore benchmarké si le taux réel d’achèvement des tâches est meilleur
Concernant le rembobinage de conversation, j’ai implémenté un repliage des appels d’outil similaire pour l’agent principal, c’est-à-dire celui qui parle à l’utilisateur. Une fois la tâche terminée, les traces des appels d’outil étaient repliées pour garder un contexte propre ; c’était davantage une question d’hygiène que de taille
La partie où le harness pousse le modèle dans ses retranchements est un peu différente, et je n’ai pas essayé cette approche. Forge s’appuie sur l’autocorrection du modèle pour éviter un mode d’erreur dédié, mais si on pouvait abstraire et automatiser le questionnement à partir de choses comme un schéma, ça semble possible
Globalement, j’aime bien l’idée d’un historique de conversation propre, mais pour des outils avec beaucoup d’arguments, ça peut générer bien plus d’allers-retours que de simplement « laisser échouer une première fois puis donner un coup de pouce ». Cela dit, c’est une idée intéressante pour des scénarios ou des tâches plus difficiles
Si on peut dépasser les 50k tokens/seconde avec de petits modèles, ce serait assez énorme
Mais pour l’instant, je suis pris par le boulot et d’autres projets, donc je n’ai testé que quelques dizaines de prompts pour voir si c’était faisable
Excellent. Je ne peux pas utiliser l’inférence locale pour l’instant à cause des coûts, mais quand j’utilise de petits modèles via OpenRouter, les appels d’outil m’inquiètent
Je développe Dokimasia (do-kee-ma-see-ah), un framework de tests d’arguments orienté pytest, et j’aimerais bien avoir ton avis : https://github.com/deevus/dokimasia
Les tests d’arguments ne sont peut-être pas ce dont Forge a besoin, mais comme tu construis des outils IA en profondeur, je me suis dit que tu aurais peut-être un avis
Ça semble être un niveau au-dessus de Forge, davantage orienté vers le test des workflows réels et des points d’intégration qui y apparaissent, par exemple si tel ou tel outil fournit un accès MCP
Je ne pense pas qu’il y aurait de gros problèmes à superposer les deux
Ce qui m’intéresse, c’est la façon dont tu gères la non-déterminisme de ces modèles. Parfois ils font correctement l’appel d’outil, parfois ils crachent du JSON invalide. La suite de tests fait-elle plusieurs tentatives ?
L’ambiguïté autour des appels d’outil existe même à l’échelle des modèles de pointe. J’utilise Claude Code, Codex et Gemini CLI tous les jours en parallèle pour développer, et le mode d’échec le plus fréquent, c’est quand grep/find se termine avec le code de sortie 1, donc aucune correspondance trouvée
Le modèle lit ça non pas comme « la recherche s’est exécutée et n’a rien trouvé », mais comme « l’outil a échoué », puis il abandonne ou retente avec une syntaxe à peine modifiée au lieu d’élargir la recherche
Une couche de retry avec petits coups de pouce correspond presque exactement, en 1:1, à ce que je fais manuellement plusieurs fois par heure. Quelque chose comme : « non, l’outil n’a pas échoué ; c’est juste qu’il n’y a pas ce motif dans ce fichier. Essaie plutôt X »
Encoder cela au niveau du framework semble être la bonne direction
Je me demande aussi si tu as vu si ce type de garde-fous réduit l’écart avec les petits modèles de pointe sur les tâches longues. Intuitivement, j’ai du mal à croire qu’un écart du genre 87→99 sur Sonnet se maintienne au-delà d’environ 50 étapes ; à partir de là, la dérive du contexte doit sans doute dominer davantage que la sémantique du retry
Pour donner un indice utile, forge s’intéresse techniquement à l’exécution des appels d’outil, pas à la qualité intrinsèque du modèle. La vraie réponse, c’est ceci
Sur les petits modèles de l’ordre de 14B, le facteur limitant était l’attention effective. Même quand tout tient largement dans la fenêtre de contexte d’entraînement, on commence à voir une dégradation après un certain point. Je n’ai pas de chiffre précis, mais des modèles comme Opus peuvent tenir beaucoup plus longtemps à ce seuil
J’ai aussi construit une fonctionnalité de repliage de l’historique des messages d’appels d’outil, que je pourrais un jour utiliser directement dans forge. En gros, elle compresse intelligemment l’historique des messages pour que le modèle perde moins le fil
Malgré cela, la suite d’évaluation de codage de mon harness agentique contient des tâches de refactoring et d’ajout de fonctionnalités, toutes réalisées dans de vrais dépôts sandboxés. De petits modèles peuvent quand même mener ce type de tâches jusqu’à 50 à 60 appels d’outil. En revanche, je ne leur confierais probablement pas plus de deux tâches de ce genre dans la même session
C’est un peu hors sujet, mais si tu travailles chez Texas Instruments, je me demande si tu pourrais découvrir ce qu’il est advenu du statut de propriété intellectuelle de la machine Lisp TI Explorer
Je sais qui possède la propriété intellectuelle de Genera, mais je n’ai jamais réussi à savoir pour l’OS Lisp de TI
Pour ceux qui réfléchissent plus largement à la pile de « sécurité agentique », cette direction me semble complémentaire d’outils comme kontext-cli de Kontext (github.com/kontext-dev/kontext-cli) ou OneCLI (github.com/onecli/onecli)
Il y a ce passage : « les mêmes poids Mistral-Nemo 12B ont 7 % de précision avec l’appel de fonction natif de llama-server, contre 83 % en mode prompt dans Llamafile »
Je pensais que Llamafile n’était qu’un simple empaquetage du modèle et de llama.cpp dans un binaire unique ; est-ce que cette différence vient du fait que Llamafile injecte un prompt système par défaut, tandis que l’endpoint brut de llama-server est appelé sans harness ?
Ça ressemble à une comparaison entre des pommes et une tarte aux pommes, avec des ingrédients manquants entre les deux
Mais ça n’explique pas l’écart d’environ +4 points entre le prompt de Lamaserver et llamafile, ni l’écart d’environ +30 points pour Ollama, qui se situe presque au milieu entre llamaserver natif et llamafile
Le backend de serving a eu un impact sur presque toutes les familles de modèles, et c’est un sujet dont je n’avais en réalité presque jamais vu parler
C’est vraiment une excellente direction. On obtient des résultats absurdement bons même avec des modèles bonsaï en 1 bit, et ça fonctionne aussi très bien avec lmstudio
Maintenant, brancher une 7900XTX dans une machine de récup, la poser au sous-sol, lui balancer des objectifs délirants puis l’oublier devient un scénario totalement réaliste