Un agent, c'est quoi ?
Un modèle de langage (LLM) seul ne sait que produire du texte. Posez-lui une question, il répond — mais il ne peut rien faire : ni lire un fichier, ni lancer une commande, ni vérifier son propre travail. C'est un cerveau sans mains.
Un agent, c'est ce même modèle auquel on donne deux choses : des outils (des mains pour agir sur le monde) et une boucle (la possibilité d'agir plusieurs fois de suite, en observant le résultat de chaque action avant de décider de la suivante). Rien de plus. La « magie » des agents de codage tient entièrement dans ces deux ingrédients.
Vous écrivez → il répond. Une passe. Le texte est la fin du chemin.
Vous écrivez → il agit, observe, recommence, jusqu'à ce que la tâche soit réellement faite.
Tout ce guide s'appuie sur un agent réel et minimal. Il parle à n'importe quel serveur compatible OpenAI (LM Studio, Ollama, un modèle local, une API cloud). Pas de framework, pas de SDK : juste la bibliothèque standard de Go, pour que chaque mécanisme reste lisible et démystifié.
La boucle agentique
Le cœur de tout agent tient en une boucle. À chaque tour (un message de l'utilisateur), l'agent répète une étape tant qu'il a quelque chose à faire :
Dans le code, c'est la fonction handleTurn. Lisez-la comme une
recette : on demande au modèle, on regarde s'il veut un outil, si oui on
l'exécute et on remet le résultat dans l'historique, puis on recommence.
for step := 0; step < a.maxSteps; step++ {
a.compactContext(ctx) // garder le contexte sous contrôle
msg, err := a.client.Chat(ctx, a.history, a.registry.Schemas()) // 1. demander au modèle
if err != nil { /* on rend la main proprement */ return }
a.history = append(a.history, msg) // mémoriser sa réponse
if len(msg.ToolCalls) > 0 { // 2. le modèle veut agir ?
for _, call := range msg.ToolCalls {
result := a.runTool(ctx, call.Function.Name, parseJSONArgs(call.Function.Arguments))
a.history = append(a.history, Message{ // 3. l'observation retourne au modèle
Role: "tool", ToolCallID: call.ID,
Name: call.Function.Name, Content: result,
})
}
continue // 4. on reboucle
}
return // pas d'outil demandé → tour terminé
}
maxSteps) garantit
qu'un tour finit toujours. Le code ajoute même un détecteur de boucle : si
l'agent redemande exactement la même action qu'à l'étape précédente,
on s'arrête — il ne progresse plus.
Les outils : les mains de l'agent
Un outil, c'est simplement une fonction que le modèle peut décider d'appeler.
Dans cet agent, un outil est une fonction pure (contexte, arguments) → (résultat, erreur).
Il ne fait aucun affichage : il calcule un résultat, point. C'est ce qui
le rend simple à écrire, à tester et à réutiliser.
type Tool struct {
Name string // ex. "read_file"
Description string // lu par le modèle pour savoir quand l'utiliser
Parameters map[string]any // le schéma JSON des arguments
Confirm func(args) (bool, string) // garde-fou optionnel (actions risquées)
Run ToolFunc // (ctx, args) → (résultat, erreur)
}
L'agent fournit trois outils de base — c'est tout ce qu'il faut pour coder :
read_file
Lit le contenu d'un fichier texte.
write_file
Écrit ou écrase un fichier.
execute_shell
Lance une commande (build, tests, git…).
Comment le modèle sait-il quels outils existent ? On lui décrit chacun dans un schéma qu'on envoie à chaque requête. Le modèle lit le nom, la description et les paramètres, puis il choisit lequel appeler.
r.Register(Tool{
Name: "read_file",
Description: "Lit le contenu d'un fichier texte.",
Parameters: map[string]any{
"type": "object",
"properties": map[string]any{
"path": map[string]any{"type": "string", "description": "Chemin du fichier à lire"},
},
"required": []string{"path"},
},
Run: toolReadFile,
})
Tool.
Le reste de la boucle ne change pas. C'est là toute la puissance du modèle :
le registre d'outils est extensible sans toucher au moteur.
Parler au modèle
Reste à connecter le modèle. La conversation est une simple liste de messages (système, utilisateur, assistant, résultats d'outils) qu'on renvoie en entier à chaque étape — le modèle n'a pas de mémoire propre, le contexte est la mémoire.
Le function calling : comment le modèle « appelle » un outil
Le modèle ne lance pas l'outil lui-même. Il renvoie une intention
structurée — « je veux appeler read_file avec path=math.go » —
et c'est notre code qui l'exécute, puis lui renvoie le résultat.
Le streaming : voir la réponse se former
Plutôt que d'attendre la réponse complète, on lit un flux (SSE) et on affiche chaque fragment dès son arrivée. Les appels d'outils, eux, arrivent en morceaux qu'on réassemble par leur index.
for _, d := range delta.ToolCalls {
tc := toolCalls[d.Index] // un fragment par index d'outil
if d.Function.Name != "" { tc.Function.Name = d.Function.Name }
tc.Function.Arguments += d.Function.Arguments // les arguments arrivent par bouts
}
Le filet de sécurité : et si le modèle ne sait pas appeler d'outils ?
Tous les modèles ne supportent pas le function calling natif. L'agent prévoit
donc un repli : si le modèle écrit son intention en texte
(Action: read_file(path="…")), on la repère par une expression
régulière. Subtilité : on n'accepte que le préfixe Action: en
début de ligne — sinon, quand le modèle récapitule ses
actions, on les ré-exécuterait en boucle.
// (?m) : ^ s'ancre en début de ligne — évite de ré-exécuter une action citée
// dans un récapitulatif ("1. Action: write_file(...)").
pattern := `(?sm)^[ \t]*Action\s*:\s*(` + strings.Join(names, "|") + `)\s*\(\s*(.*)\)`
Les garde-fous
Donner à un modèle le droit de lancer des commandes shell, c'est puissant — et dangereux. Un bon agent n'est pas qu'une boucle : c'est une boucle prudente. Quatre protections, simples mais essentielles :
Confirmation des actions risquées
Une commande qui matche un motif dangereux (rm -rf, sudo, dd, fork bomb…) demande une validation humaine avant de s'exécuter.
Détection de boucle
Si l'agent répète exactement la même action, on arrête le tour : il ne progresse plus, inutile de brûler des tokens.
Timeouts
Chaque commande et chaque appel au modèle a une limite de temps. Une commande bloquée ne fige jamais l'agent.
Troncature des sorties
Le résultat d'un outil est plafonné avant d'être réinjecté : une sortie énorme ne sature pas le contexte.
var dangerousPatterns = []*regexp.Regexp{
regexp.MustCompile(`\brm\s+-[a-zA-Z]*[rf]`), // rm -rf
regexp.MustCompile(`\bdd\s+if=`),
regexp.MustCompile(`:\s*\(\)\s*\{`), // fork bomb
regexp.MustCompile(`\b(shutdown|reboot|halt)\b`),
regexp.MustCompile(`\bsudo\b`),
// …
}
La mémoire & le contexte
À chaque étape, l'historique grossit : messages, appels d'outils, résultats. Or la fenêtre de contexte d'un modèle est limitée. Sans rien faire, on finit par la dépasser. La solution de l'agent : compacter.
Quand l'historique dépasse un budget de tokens, on garde intacts les messages récents (le travail en cours), et on demande au modèle de résumer les plus anciens en quelques puces. Le résumé remplace les vieux messages. La mémoire longue devient compacte, la mémoire courte reste précise.
if totalTokens(a.history) <= a.maxCtx { return } // sous le budget : rien à faire
older := rest[:keepFrom] // les anciens messages
recent := rest[keepFrom:] // les ~60% récents, gardés tels quels
summary, err := a.client.Summarize(ctx, older) // le modèle résume les anciens
// → [système initial] + [résumé] + [messages récents]
Tout assembler
On a tous les morceaux. Le programme principal les relie : il crée le registre d'outils, le client modèle, et lance une boucle de lecture (un REPL). Chaque message de l'utilisateur déclenche un tour d'agent — la boucle qu'on a disséquée.
Et la « personnalité » de l'agent ? Elle tient dans un prompt système : quelques phrases qui lui rappellent sa mission, ses outils et ses règles.
`Tu es un agent de codage autonome.
Règles :
1. Décompose les missions en étapes et appelle les outils nécessaires.
2. Analyse le résultat de chaque outil ; en cas d'erreur, corrige puis réessaie.
3. Une action destructive peut demander une confirmation à l'utilisateur.
4. Quand la mission est accomplie, fais un court récapitulatif puis rends la main.`
En une phrase
Un agent de codage, c'est une boucle qui envoie l'historique à un modèle, exécute les outils qu'il demande, lui renvoie les résultats, et recommence — le tout entouré de garde-fous et d'une gestion du contexte. Aucune magie : de l'ingénierie lisible.
Questions fréquentes
Quelle est la différence entre un agent et un chatbot ?
Un chatbot répond en une seule passe : message puis réponse. Un agent agit, observe le résultat de chaque action et recommence dans une boucle, en utilisant des outils, jusqu'à ce que la tâche soit réellement accomplie.
Faut-il un framework pour construire un agent de codage ?
Non. L'agent de ce guide est écrit en Go avec la seule bibliothèque standard, en ~1000 lignes et zéro dépendance. Il fonctionne avec n'importe quel serveur compatible OpenAI (LM Studio, Ollama, vLLM, API cloud).
Comment un modèle de langage exécute-t-il des actions ?
Il ne les exécute pas lui-même. Via le function calling, le modèle renvoie une intention structurée (nom d'outil et arguments) ; c'est le code de l'agent qui exécute réellement l'outil, puis renvoie le résultat au modèle.
Comment éviter qu'un agent tourne en boucle ou exécute une action dangereuse ?
Avec des garde-fous : une limite d'étapes par tour, une détection de répétition d'action, une confirmation humaine pour les commandes risquées, des timeouts, et la troncature des sorties trop longues. Les erreurs sont réinjectées au modèle pour qu'il se corrige.
Comment gérer une fenêtre de contexte limitée ?
En compactant l'historique : quand il dépasse un budget de tokens, les messages récents sont gardés intacts tandis que les plus anciens sont résumés par le modèle et remplacés par ce résumé.
Le code & pour aller plus loin
Tout ce guide décrit un agent réel et complet. Le code est ouvert, commenté ligne par ligne, et tourne avec n'importe quel serveur compatible OpenAI. Le meilleur moyen de comprendre : le lire, le lancer, le modifier.
Agent de codage en Go — boucle LLM + outils, ~1000 lignes, zéro dépendance.
Lancer l'agent en 30 secondes
git clone https://github.com/Nhilo94/comprendre-agent-llm
cd comprendre-agent-llm
go run . # puis choisissez votre modèle et discutez
Quelques pistes d'extension
- Ajouter un outil de recherche web ou d'appel d'API.
- Remplacer le résumé par une vraie mémoire vectorielle.
- Donner à l'agent la possibilité de planifier avant d'agir.
- Brancher un second agent qui relit le travail du premier.