Construire une pile d'agents locaux avec Llama.cpp et Pi.dev

2026-08-08 (updated 2026-08-17)
Posts #agentic #auto-hébergé #llama.cpp #llm #pi.dev

Dans cet article, je partage quelques astuces de workflow et des ressources que j’ai rassemblées en travaillant avec des agents locaux.

J’ai longtemps ignoré l’engouement pour les agents. La plupart des premiers frameworks me semblaient être des surcouches sur-conçues qui dépendaient soit d’API cloud intrusives, soit étaient trop fragiles pour un véritable travail de développement. J’avais moi-même construit une application de terminal agentique expérimentale aux débuts, avant même que les LLM ne gèrent les appels d’outils (tool calls), où j’explorais certaines idées et souhaits que j’imaginais pour un futur IDE basé sur des agents. Cependant, je n’allais jamais utiliser l’un d’entre eux connecté à un service cloud. J’ai enfin commencé à m’y intéresser lorsque les modèles locaux ont semblé atteindre un point de bascule en termes de précision et de fiabilité des appels d’outils, notamment autour de la sortie de Qwen 3.5 35B.

Même alors, mon approche était conservatrice. J’ai expérimenté LocalAI et les agents assez tôt, mais je ne les ai jamais intégrés dans mon flux de travail réel. Mon utilisation se limitait à des requêtes ponctuelles via une instance llama.cpp hébergée localement. Pour des sessions plus longues, j’utilisais aichat, une application CLI basée sur Rust qui reste discrète.

À la mi-2025, j’ai décidé de donner une nouvelle chance aux workflows agentiques locaux avec OpenCode et QwenChat. J’ai maintenu une contrainte stricte : tout doit être hébergé localement. L’expérience a été frustrante : plus de la moitié des appels d’outils échouaient et je passais plus de temps à déboguer l’environnement et à bidouiller la configuration qu’à écrire du code. J’avais l’impression que les outils se battaient contre moi, alors je suis retourné sur Neovim en sélectionnant manuellement les tâches de complétion.

J’ai découvert pi.dev par hasard via un commentaire Reddit et j’ai été immédiatement séduit par sa philosophie minimaliste de “construisez votre propre harnais”. Il fournit exactement ce que je recherchais : une couche mince qui gère les bases et me laisse construire le reste. Bien qu’il soit loin d’être parfait, il répond à mes besoins actuels. Un inconvénient majeur étant qu’il est conçu avec TypeScript et repose sur l’écosystème NodeJS, parfois instable.

Cet article présente certains des outils et compétences (skills) que j’ai développés pour résoudre des tâches spécifiques dans mon workflow. Je continuerai à mettre à jour cet article à mesure que j’ajouterai des outils à ma collection.

La Pile (The Stack)

Ma configuration est centrée sur une station de travail locale avec plusieurs GPU, tous les composants étant conteneurisés via Docker.

Matériel

La machine est propulsée par un AMD Ryzen Threadripper 2950X (16 cœurs / 32 threads) sur un chipset X399 avec 128 Go de DDR4. Pour l’inférence, j’utilise deux NVIDIA RTX 3090, acquises avant que les prix ne s’envolent, offrant une quantité généreuse de VRAM pour garder plusieurs modèles prêts ou pour en faire tourner de plus grands avec un contexte plus élevé.

Orchestration des modèles

J’ai déployé llama-swap qui agit comme un proxy gérant les instances llama.cpp. Au lieu de garder chaque modèle chargé en VRAM, llama-swap charge les modèles à la demande et les décharge après une période d’inactivité. Il gère également le routage, me permettant de basculer entre différents niveaux de modèles — comme un raisonneur lourd pour les tâches complexes et un modèle plus petit pour des itérations rapides — sans intervention manuelle.

J’ai développé un système de build automatisé pour llama.cpp, ou ses forks, qui construit une nouvelle version à partir de n’importe quel tag de release upstream. Le système de build fera l’objet d’un article de blog dédié.

Hiérarchie des modèles

Voici mes modèles de prédilection ces derniers temps :

Harnais

L’ensemble du workflow est lié par pi.dev. Il fournit un harnais minimaliste avec un prompt système léger et un ensemble d’outils de base. Sa nature “build-your-own” me permet d’étendre les capacités de l’agent en ajoutant des outils et des compétences personnalisés selon les besoins.

La collection (The stash)

Voici quelques-uns des outils et compétences que j’ai rassemblés et construits avec l’aide des LLM pour résoudre des problèmes courants et améliorer la stabilité des sessions de longue durée. Je vais essayer de continuer à mettre à jour le dépôt avec de nouveaux éléments pertinents.

Recherche Internet

La recherche sur Internet est un outil essentiel pour tout workflow agentique sérieux. Le moteur de recherche est une instance SearxNG auto-hébergée tournant dans un conteneur sur un serveur distant accessible depuis mes machines. Lors du déploiement du moteur, vous devez vous assurer d’activer le mode de sortie json dans le fichier de configuration du moteur.

L’interface de l’agent avec le moteur est un client CLI léger écrit en Go, compilé en un seul binaire exporté dans le $PATH de l’agent et lié symboliquement sous le nom net-search.

Les agents ne connaissent pas SearxNG. Ils ne voient qu’un moteur de méta-recherche opaque via un simple CLI.

net-search-go abstrait tous les détails spécifiques à SearxNG et n’expose qu’un moteur de méta-recherche générique. L’aide de la commande est auto-documentée et présente le strict minimum pour une utilisation rapide. Elle encourage l’agent à explorer progressivement les fonctionnalités de recherche avancées.

Par défaut, il expose l’interface de recherche avec les filtres les plus courants. Il dispose d’un mode discovery qui peut être invoqué avec -mode discover. Le mode découverte récupère tous les moteurs disponibles et les raccourcis “bang” en analysant la source de la page d’accueil de l’instance SearxNG auto-hébergée.

L'agent découvre les moteurs et les raccourcis searx

Cacher SearxNG à l’agent

Remarquez que nulle part dans l’outil ou la compétence je n’expose le fait que l’agent interagit avec SearxNG. À l’origine, l’outil s’appelait searxng-cli et l’URL de l’hôte était définie dans la variable $SEARXNG_HOST. Cependant, j’ai remarqué que peu importe les instructions, les agents hallucinaient souvent l’URL de l’hôte SearxNG vers http://localhost:8888, qui est l’URL par défaut trouvée dans la documentation de SearxNG.

Cela signifiait que les données d’entraînement avaient un biais fort sur la sortie du LLM. La solution a été de cacher complètement le fait qu’il interagissait avec SearxNG. J’ai supprimé toute référence à SearxNG et je l’ai remplacée par un moteur de méta-recherche générique.

---
name: net-search
description: Search the internet via a self-hosted meta-search engine aggregating dozens of sources. Use for general queries, research, fact-checking, news, code, images, and more.
---

Je vous encourage à lire et à modifier la compétence selon vos besoins. Ne la copiez pas aveuglément.

Extraction de contenu (Scraping)

Pour les pages statiques, j’utilise une approche d’extraction par paliers. Je commence par get-page-content --md, qui utilise un algorithme de lisibilité (readability) pour supprimer le bruit et renvoyer un markdown propre. C’est le moyen le plus efficace de fournir un article ou une page de documentation à un agent.

Lorsque je dois parcourir un site, j’utilise w3m -dump. Il fournit une version texte de la page optimisée pour la console où les liens sont indexés par des numéros, ce qui permet à l’agent d’identifier et de suivre facilement des URL spécifiques.

Pour les données hautement structurées, j’utilise pup. Il permet l’extraction ciblée d’éléments ou d’attributs à l’aide de sélecteurs CSS. Par exemple, extraire tous les liens d’une page est un simple curl -s URL | pup 'a attr{href}'.

Outils de navigateur

Certains sites web sont lourds en JS ou sont des SPA que le scraping statique ne peut pas gérer. Pour ceux-là, j’utilise un ensemble d’outils CDP minimaux qui se connectent à une instance Chrome headless distante. Ces outils sont une version modifiée de ceux créés par Mario Zechner (créateur de pi.dev), qu’il a détaillés dans son article de blog.

Les outils fournissent une navigation de base (brw-goto), l’exécution de JavaScript (brw-eval) et l’extraction de contenu (brw-content). L’un des ajouts les plus utiles est brw-pick, qui me permet de sélectionner interactivement un élément sur la page pour obtenir son sélecteur, que je transmets ensuite à l’agent.

Comme le serveur CDP de Chrome se lie exclusivement à localhost, j’utilise un proxy socat à l’intérieur du conteneur pour faire le pont entre les requêtes réseau externes et le port CDP interne. Combiné avec la gestion côté outil des restrictions d’en-tête CDP Host, cela permet aux outils de se connecter via le réseau sans nécessiter de redirection de port SSH.

Dissection de session

Les sessions Pi sont stockées sous forme de fichiers JSONL. Ceux-ci peuvent devenir assez volumineux, et les lire entièrement est inefficace. J’ai construit dissect pour analyser ces sessions sans charger tout le fichier.

J’utilise fréquemment dissect think pour examiner le processus de raisonnement de l’agent et dissect bashcmds pour extraire des modèles de commandes réussies pour une utilisation future. La commande dissect search permet des recherches rapides par mots-clés à travers les messages, les appels d’outils et les blocs de réflexion.

Récupération du Stderr de Bash

Les agents utilisent souvent des motifs comme 2>&1 >/dev/null pour réduire les sorties verbeuses, mais cela rejette par inadvertance des messages d’erreur critiques. Cette extension intercepte les appels bash et détecte quand stderr est masqué.

Elle réécrit automatiquement ces commandes pour préserver stderr dans un fichier temporaire. Cela permet à l’agent d’utiliser l’outil last_bash_error pour récupérer le flux d’erreurs des appels récents sans avoir à relancer la commande, même si plusieurs appels d’outils sont effectués après l’erreur.

Extension Llama.cpp

Lors de l’utilisation de modèles de raisonnement via llama.cpp, la gestion du budget de réflexion est cruciale. Cette extension mappe les niveaux de réflexion de Pi (minimal, low, medium, high, xhigh) vers reasoning_budget_tokens. Elle gère également les modes de raisonnement de DeepSeek high et max en définissant automatiquement les kwargs du template de chat pour le niveau de réflexion Pi correspondant.

Pour utiliser l’extension, vous devez définir un fournisseur dans votre models.json avec un nom commençant par llama*, il injectera automatiquement les bons paramètres avant que la requête ne soit envoyée.

La fonctionnalité la plus pratique est le contrôle en temps réel. Je l’ai implémentée en analysant la façon dont la webui source de llama.cpp le gère. En appuyant sur Ctrl+E pendant la phase de raisonnement, je peux forcer le modèle à arrêter de réfléchir et à passer directement à la réponse finale. C’est un gain de temps énorme quand l’agent réfléchit trop à une tâche triviale.

Dépôt Github : https://github.com/blob42/agent-stash