- Avec l’adoption récente croissante des agents IA, l’usage de stacks hybrides basées sur le langage Go est en hausse
- Les agents se caractérisent par une longue durée d’exécution, un coût élevé et de nombreuses attentes d’entrée/sortie
- Go fournit un modèle de concurrence haute performance avec des goroutines légères, un mécanisme d’annulation centralisé et une messagerie basée sur des channels
- Sa bibliothèque standard est très riche, et les outils de profiling comme
pprof facilitent le suivi des fuites mémoire et de threads
- En revanche, Go présente aussi des limites, notamment un écosystème machine learning insuffisant, des performances de pointe qui ne sont pas exceptionnelles et un support tiers plus faible que dans d’autres langages
Qu’est-ce qu’un agent ?
- Un agent s’exécute dans une boucle répétitive et désigne un processus capable de décider lui-même de l’étape suivante de son exécution
- Contrairement à un workflow qui suit un chemin prédéfini, sa fin est déterminée par des conditions (par ex. « test réussi ») ou par un nombre maximal d’itérations
- En production, un agent peut s’exécuter longtemps, de quelques secondes à plusieurs heures, et son coût est élevé en raison des appels à des LLM, de la manipulation de navigateur, etc.
- Comme il doit traiter les entrées de l’utilisateur (ou d’un autre agent), il y a souvent beaucoup de temps d’attente en entrée/sortie (I/O)
Pourquoi le langage Go convient aux agents
Concurrence haute performance
- Les goroutines de Go peuvent exécuter simultanément des milliers à des dizaines de milliers de threads légers avec seulement 2 KB de mémoire
- Chaque goroutine exploite le multicœur pour le traitement parallèle, ce qui permet aussi d’exécuter sans difficulté des agents en attente ou bloqués sur des I/O
- La communication basée sur les channels permet d’implémenter la synchronisation par passage de messages plutôt que par partage mémoire (en minimisant l’usage de
Mutex)
- Cela convient bien à des agents qui échangent des messages de façon asynchrone et gèrent leur état
Mécanisme d’annulation centralisé
- Avec context.Context en Go, la plupart des bibliothèques et API prennent en charge les signaux d’annulation, ce qui rend l’arrêt de l’exécution très simple
- Là où Node.js et Python mélangent plusieurs modèles d’annulation, Go permet une annulation cohérente et sûre ainsi qu’une récupération fiable des ressources
Une bibliothèque standard riche
- Go dispose d’une bibliothèque standard très vaste, couvrant presque tous les domaines : HTTP/web, fichiers, I/O réseau, etc.
- Tous les I/O supposent un comportement bloquant dans les goroutines, ce qui permet d’écrire la logique métier de manière linéaire
- En Python, la coexistence d’
asyncio, des threads, des processus et d’autres modèles de concurrence rend l’ensemble plus complexe
Outils de profiling et de diagnostic
- Les outils intégrés de Go comme pprof permettent de suivre en temps réel les fuites mémoire et les fuites de goroutines (threads)
- C’est un atout pour diagnostiquer les problèmes de fuite pouvant survenir dans des agents exécutés longtemps et en parallèle
Excellent support du codage avec les LLM
- Grâce à sa syntaxe simple et à sa riche bibliothèque standard, Go permet aux LLM d’écrire facilement du code idiomatique Go
- La dépendance aux frameworks est faible, donc les LLM ont moins à se soucier des versions ou des patterns
Les limites de Go
- L’écosystème et les bibliothèques tierces restent moins riches qu’en Python ou TypeScript
- Go n’est pas bien adapté à une implémentation directe du machine learning (limitations en performances et en support)
- Si des performances maximales sont requises, Rust ou C++ sont de meilleurs choix
- Pour les développeurs peu tolérants à une gestion d’erreurs explicite, cela peut sembler quelque peu inconfortable
2 commentaires
Plutôt que Java, Go ; plutôt que Go, Rust :)
Avis Hacker News
Souligne que, dans la plupart des systèmes d’agents, le principal facteur de latence reste au final les appels au LLM. Les avantages mentionnés dans l’article ne favoriseraient pas un langage en particulier : il s’agit surtout de longues attentes, d’une utilisation coûteuse des ressources, d’entrées provenant des utilisateurs ou d’autres agents, et d’un temps d’attente I/O important. Dans ce contexte, plus que la vitesse d’exécution ou l’efficacité côté serveur, le véritable atout serait plutôt le vaste écosystème de bibliothèques IA et le support dont dispose un langage comme Python. Certains font remarquer qu’il faut se préoccuper de
asyncioou des bibliothèques de multithreading en Python, mais en pratique le développement d’agents n’est pas si difficile, et comme d’autres ont déjà conçu des workflows associés, il est facile de se lancerD’expérience, construire des agents en Go présente un gros avantage : les modèles de gestion de la concurrence et de la backpressure y sont bien établis. Les agents impliquent le plus souvent des transactions avec des services externes lents, et pour ce type de travail les patterns de concurrence de Go sont très utiles. Bien sûr, le langage importe peu au fond, et JavaScript semble être le plus utilisé. En revanche, pour la génération de code, la combinaison de Go et des LLM semble offrir une bonne synergie
Go se distingue de Python par son excellente gestion de la concurrence et par sa facilité de déploiement. Il suffit de distribuer un binaire statique, ce qui évite les problèmes d’environnement et de dépendances qu’on rencontre avec Python
Les agents jouent un rôle de couche d’orchestration, et Go, Erlang et Node semblent particulièrement adaptés. Il n’est pas indispensable d’avoir une énorme quantité de bibliothèques liées à l’IA ; pour les tâches fortement orientées I/O, on peut les abstraire derrière des interfaces d’outils propres au domaine et construire les sous-systèmes dans le langage nécessaire
Go n’apporte pas vraiment de grand avantage pour ce type de charge de travail, car l’essentiel du temps est passé à attendre les I/O. Son système de types est limité, et beaucoup de fonctionnalités fournies nativement par les langages modernes doivent y être contournées. TypeScript est un excellent langage glue pour l’IA et, avec Python, bénéficie d’un très bon support en bibliothèques. Si je le préfère à Python, c’est parce que son système de types est bien plus puissant et mature. Python s’améliore aussi rapidement. L’affirmation selon laquelle il serait très difficile d’interrompre des tâches de longue durée sous Node.js et Python ne me semble pas étayée ; la plupart des outils prennent déjà cela en charge, et les principaux langages restent Python et JS
J’ai expérimenté des frameworks d’agents basés sur Elixir et BEAM, et je pense que la combinaison BEAM + SQLite est actuellement la plus idéale pour les agents. On peut remplacer des agents en toute sécurité sans redéployer l’application, et la concurrence de BEAM est largement suffisante pour ce travail. Les implémentations d’agents avec état ou temporaires sont également très simples. À l’avenir, je compte construire des agents de base en Python, TypeScript et Rust, et créer aussi des serveurs MCP pour permettre le développement d’agents complexes selon les préférences linguistiques de chacun
Je recommande le projet Extism et le SDK Elixir. Avec cette combinaison, on peut développer en Elixir le service central, le routage et le passage de messages, profiter des avantages de BEAM/OTP, et intégrer comme plugins des agents sous forme de petits modules Wasm légers écrits dans d’autres langages
Extism
Elixir SDK
Je me demande s’il y a une raison particulière d’avoir choisi SQLite plutôt que
mnesia, le datastore intégré de BEAMDocumentation mnesia
La majeure partie du temps d’un agent est consacrée à l’attente des réponses du LLM et aux appels à des services externes (API, DB). En pratique, les performances du runtime du langage ont très peu d’impact. S’il y a une fonctionnalité de langage vraiment importante pour les performances et la scalabilité des agents, ce serait probablement la performance de sérialisation et désérialisation JSON
C’est pourquoi je pense qu’il vaut mieux utiliser un langage comme TypeScript, qui manipule JSON nativement. Le système de types de TypeScript est aussi bien plus puissant que celui de Go
D’après mon expérience, en dehors des appels au LLM, ce qui coûte le plus cher dans un agent est la résolution des conflits lors d’éditions asynchrones (
merge,diff,patch). On peut aussi déléguer cette tâche à des bibliothèques de bas niveau, mais c’est un problème aussi difficile à optimiser que la sérialisationCela me rappelle ce guide de construction d’agents d’ampcode.com. Grâce à sa nature dynamique, Python permet naturellement de transformer des méthodes en appels d’outils via des décorateurs, de générer des listes par itération de fonctions d’outils, ou de convertir rapidement vers des schémas JSON. En revanche, pour une architecture où plusieurs déclencheurs externes (par exemple une saisie utilisateur, un e-mail Gmail, un message Slack, etc.) lancent de nouvelles exécutions d’agents, l’utilisation des channels de Go avec une boucle
switch form’a semblé bien plus intuitive. En Python, il faut créer séparément plusieurs files et threads, ce qui complique les chosesSi l’on suit la logique de l’article, alors Elixir est idéal pour les agents
Le système de types limité et insuffisant de Go le rend inadapté à presque toutes les applications. En réalité, le plus gros défaut de Go serait le langage lui-même. Ce sont plutôt les aspects extra-linguistiques qui le rendent tolérable
J’ai programmé en Go pendant plusieurs années, et je suis d’accord sur le fait que son système de types pose beaucoup de problèmes. Les gens du domaine LLM semblent n’utiliser presque que Python ou JavaScript. Je pense que tout le monde devrait migrer vers des langages modernes, mais Go peut tout de même être un meilleur choix face au désordre des imports et des packages en Python/JavaScript
J’aimerais entendre plus concrètement en quoi les limites du système de types de Go deviennent un obstacle dans la création d’agents
En réalité, si Go a adopté un système de types statique, c’est parce qu’il n’a pas trouvé d’autre moyen d’atteindre ses objectifs de performance. Il serait plus juste de dire qu’il est utilisé comme un langage à typage dynamique, et que cela résulte d’une mauvaise compréhension de ses objectifs de conception. On peut affirmer que les langages à typage dynamique sont globalement inadaptés, mais dans les faits, vu l’usage intensif de Python, Erlang ou Elixir, le typage dynamique semble plutôt mieux convenir au domaine du problème
Les multiples valeurs de retour se composent mal, la gestion des erreurs est meilleure que les exceptions mais reste très verbeuse, les channels sont faciles à mal utiliser, et les types
enumsont décevants. Malgré cela, les interfaces fonctionnent étonnamment bien, et le système de packaging est assez fluide. En apprenant Rust, j’ai réalisé que la structure des fichiers y est bien plus complexe qu’en Go. Et comme le langage reste simple, il est aussi plus facile de créer divers linters et outils de génération de code. À long terme, la maintenance du code Go inquiète moins que celle de Python/JSCe serait vraiment bien s’il existait un dialecte LISP/Scheme bien maintenu compilant vers Go
Dans les processus longs et coûteux à exécuter, l’inconvénient est que si le processus meurt, tout le travail est perdu. Il peut être plus sûr de sérialiser l’état dans une base de données pendant l’attente, mais il ne semble pas exister de langage qui rende cela facile à implémenter. Écrire des machines à états à checkpoints n’est pas simple
Les machines à états à checkpoints sont précisément une fonctionnalité clé de plateformes comme Hatchet(hatchet.run) et Temporal(temporal.io). Elles stockent l’historique d’exécution des fonctions dans le workflow et rejouent automatiquement cet historique en cas d’interruption. L’idée est que l’historique de progression par sortie est bien plus efficace qu’un memory sink. (Fondateur de Hatchet)
Qu’il s’agisse de goroutines, de threads ou de longues chaînes d’exécution, il faut au final découper le travail en unités atomiques, et la sérialisation de l’état est indispensable. Cela répond aux besoins de reprise après échec, de traçage des erreurs, de re-référence des résultats et de distribution multi-nœuds. Le framework Oban(github.com/oban-bg/oban) pour Elixir suit cette approche, et je recommande aussi cet article sur Oban, qui insiste sur l’importance de la persistance des tâches asynchrones. (Auteur d’Oban)
Je développe une bibliothèque d’agents basée sur golang, et je me suis dit qu’avec une journalisation suffisante, on pouvait toujours restaurer l’état d’un agent. Si l’on connaît l’horodatage et l’exécution parente (
run), on peut reconstruire l’arbre des exécutions enfants et des branches. On peut gérer les sessions avec une combinaison de maps et de base de données, puis les reconstruire au besoin. Au lieu de conserver des objets individuels, on recherche les objets stateless par identifiant dans une map, et on place les actions précédentes, les étapes et le contexte dans un objet d’état. La cohérence agent/workflow est également assurée en gérant les résultats via des hash. Pour l’instant, seuls les agents/outils de base sont implémentés, et la journalisation, la restauration et la logique d’annulation ne sont pas encore développéesTemporal est assez utile pour le checkpointing de processus longs, et reste neutre vis-à-vis des langages
Je réfléchissais moi aussi aux files de tâches, et j’envisage de créer une queue rudimentaire dans Postgres. L’avantage, c’est de répartir la charge entre serveurs, d’éviter la perte des tâches après l’arrêt d’un processus et d’obtenir une meilleure visibilité. En contrepartie, la complexité du code peut fortement augmenter, ce qui rend difficile la conception d’une architecture simple
Les ingénieurs IA évitent JavaScript de manière quasi viscérale. L’arrêt de TensorFlow for Swift a marqué la fin de la diversité des langages en IA
Je pense que cette aversion pour JavaScript ne concerne pas seulement les ingénieurs IA. En tant que personne qui code en JS depuis plus de 30 ans, je suis d’accord moi aussi
JS est un très mauvais langage, et l’avoir amené côté backend a été une erreur. TypeScript ne résout pas non plus les problèmes du JS sous-jacent. J’évite autant que possible JS et TS, et je préfère d’autres alternatives comme Go, Rust, Python, Ruby, Elixir ou F#
Je me demande ce qui rendrait JS particulièrement bon pour les agents
J’ai le sentiment que le domaine du ML a besoin d’un meilleur modèle de concurrence. J’ai essayé le ML en Go, mais en pratique cela reste quasiment impossible à cause du manque de support en bibliothèques et de la dépendance à des appels gRPC externes ou à des wrappers. Python a ses limites, et C++ est tellement verbeux qu’il nuit souvent à la productivité