Un grand modèle de langage, seul, ne sait rien faire. Il prédit du texte. Il ne peut pas consulter votre base de commandes, appeler une API météo, ni même additionner deux grands nombres de façon fiable. Un agent, lui, le peut — non parce que le modèle est plus puissant, mais parce qu’on l’a placé dans une boucle qui lui donne des outils et exécute ses décisions.

Dans ce premier chapitre, on construit cette boucle à la main avec LangGraph. Pas de create_react_agent magique : on assemble les pièces une à une pour que, à la fin, plus rien ne soit une boîte noire.

Le problème : un LLM ne boucle pas tout seul

Posez une question à un modèle nu et demandez-lui la météo. Il répondra une phrase plausible… et inventée. Il n’a aucun moyen d’aller chercher l’information.

C’est le point de départ qu’il faut bien avoir en tête : un LLM est une fonction sans état et sans bras. Il transforme une suite de messages en un nouveau message. Pour qu’il devienne un agent, il faut lui adjoindre trois choses — des outils qu’il peut demander, un exécuteur qui réalise ces demandes, et une boucle qui relie les deux jusqu’à ce que la tâche soit finie.

Fig.01 · Boucle ReAct
sinon → STOP tool_calls ToolMessage START modèle llm.invoke() outils ToolNode END
Le graphe que nous allons construire : le modèle décide à chaque tour d'appeler un outil (et de boucler) ou de répondre (et de s'arrêter).

Ce schéma est l’ossature de tout l’article. Chaque section qui suit en construit une partie : d’abord les outils, puis l’état, puis les deux nœuds, et enfin l’arête conditionnelle qui choisit le chemin.

Brique 1 — Déclarer des outils

La première brique consiste à donner au modèle des fonctions Python qu’il a le droit d’appeler.

from langchain_core.tools import tool

@tool
def meteo(ville: str) -> str:
    """Renvoie la météo actuelle pour une ville donnée."""
    # En production : un vrai appel d'API. Ici, on simule.
    fake = {"Paris": "18°C, nuageux", "Lyon": "22°C, ensoleillé"}
    return fake.get(ville, f"Pas de données pour {ville}.")

@tool
def addition(a: float, b: float) -> float:
    """Additionne deux nombres."""
    return a + b

outils = [meteo, addition]

Le décorateur @tool ne se contente pas d’enregistrer la fonction : il en extrait la signature et la docstring pour produire un schéma que le modèle comprendra. C’est ce schéma qui permet le tool calling — le modèle ne reçoit pas votre code, mais sa description.

Brique 2 — L’état, mémoire de travail de l’agent

Un agent a besoin de se souvenir de la conversation en cours : la question, ses propres décisions, les résultats des outils. En LangGraph, cette mémoire de travail est l’état (state), un objet qui circule entre les étapes.

Pour un agent conversationnel, l’état se résume souvent à une liste de messages. LangGraph fournit un raccourci, mais écrivons-le explicitement pour voir le mécanisme clé — le reducer :

from typing import Annotated
from typing_extensions import TypedDict
from langgraph.graph.message import add_messages

class Etat(TypedDict):
    messages: Annotated[list, add_messages]

add_messages est un reducer : il indique à LangGraph que, lorsqu’un nœud renvoie de nouveaux messages, il faut les ajouter à la liste existante plutôt que de l’écraser. Sans lui, chaque étape effacerait l’historique et l’agent perdrait le fil.

Concrètement, l’état va s’enrichir à chaque tour de boucle. Voici comment il grossit sur une question qui déclenche un outil :

Fig.02 · Croissance de l'état
entrée HumanMessage « météo à Lyon ? »
le modèle décide AIMessage tool_calls: [meteo]
l'outil répond ToolMessage « 22°C, ensoleillé »
le modèle conclut AIMessage réponse finale
À chaque étape, un message s'ajoute à l'état via le reducer add_messages — rien n'est jamais écrasé.

Brique 3 — Les deux nœuds : penser, puis agir

Un agent ReAct ne fait que deux choses, en alternance :

  1. Penser — le modèle examine l’état et décide : répondre, ou appeler un outil ?
  2. Agir — si un outil est demandé, on l’exécute et on renvoie son résultat au modèle.

Cela donne deux nœuds. Le premier appelle le modèle, augmenté de la connaissance des outils via bind_tools :

from langchain.chat_models import init_chat_model

modele = init_chat_model("openai:gpt-4o-mini")
modele_outille = modele.bind_tools(outils)

def noeud_modele(etat: Etat) -> dict:
    reponse = modele_outille.invoke(etat["messages"])
    return {"messages": [reponse]}

bind_tools attache les schémas des outils au modèle. Sa réponse pourra désormais contenir des tool_calls : la demande structurée d’exécuter telle fonction avec tels arguments.

Le second nœud exécute ces appels. LangGraph fournit ToolNode, qui lit les tool_calls du dernier message, exécute les bonnes fonctions et renvoie leurs résultats sous forme de ToolMessage :

from langgraph.prebuilt import ToolNode

noeud_outils = ToolNode(outils)

Pour bien voir ce que fait ToolNode, déplions une invocation d’outil — ce qui se passe entre le moment où le modèle « demande » et le moment où il « reçoit » :

Fig.03 · Cycle d'un appel d'outil
AIMessage tool_calls + args
ToolNode route + exécute
fonction meteo('Lyon')
ToolMessage résultat → modèle
Le modèle ne fait que demander ; c'est le ToolNode qui exécute la fonction Python et renvoie le résultat au modèle.

Brique 4 — L’arête conditionnelle, le cœur agentique

Reste la question décisive : après que le modèle a parlé, où va-t-on ? S’il a demandé un outil, vers le nœud outils. Sinon, c’est qu’il a sa réponse finale — on s’arrête.

Cette décision prise à l’exécution, en inspectant l’état, c’est une arête conditionnelle. C’est elle qui transforme un graphe figé en agent.

from langgraph.graph import StateGraph, START, END

def router(etat: Etat) -> str:
    dernier = etat["messages"][-1]
    # Le modèle a-t-il demandé un ou plusieurs outils ?
    if dernier.tool_calls:
        return "outils"
    return END

Assembler le graphe

On a toutes les pièces. On les câble :

Déclarer le graphe et ses nœuds

graphe = StateGraph(Etat)
graphe.add_node("modele", noeud_modele)
graphe.add_node("outils", noeud_outils)

Définir le point d’entrée

L’exécution commence toujours par le modèle, qui réfléchit à la requête.

graphe.add_edge(START, "modele")

Brancher l’arête conditionnelle

Après le modèle, on route selon sa décision : vers outils, ou vers la fin.

graphe.add_conditional_edges("modele", router, {"outils": "outils", END: END})

Refermer la boucle

Après l’exécution d’un outil, on revient au modèle pour qu’il intègre le résultat. C’est cette arête de retour qui crée la boucle ReAct.

graphe.add_edge("outils", "modele")
agent = graphe.compile()

Le graphe compilé correspond exactement à la Fig.01 : START → modèle, puis la bifurcation conditionnelle vers outils (qui reboucle) ou vers END.

Le faire tourner

resultat = agent.invoke({
    "messages": [{"role": "user", "content": "Quelle météo à Lyon ? Et 21 + 21 ?"}]
})

for m in resultat["messages"]:
    m.pretty_print()

Déroulé typique : le modèle demande meteo("Lyon") et addition(21, 21), le ToolNode exécute les deux, le modèle reçoit "22°C, ensoleillé" et 42, puis rédige une réponse finale en langage naturel. La boucle s’est arrêtée d’elle-même, parce que le dernier message du modèle ne contenait plus de tool_calls.

Observer la boucle en streaming

Plutôt que d’attendre le résultat final, on peut streamer les étapes pour voir l’agent raisonner en direct. C’est le meilleur outil de débogage à ce stade :

for etape in agent.stream(
    {"messages": [{"role": "user", "content": "Météo à Lyon ?"}]},
    stream_mode="values",
):
    etape["messages"][-1].pretty_print()

Chaque itération du stream correspond à un super-step du graphe — un passage par un nœud. Vous voyez littéralement le AIMessage avec ses tool_calls, puis le ToolMessage, puis la réponse. Si l’agent se trompe, c’est ici que vous repérez à quelle étape la trajectoire a dévié.

Et create_react_agent dans tout ça ?

Tout ce que nous venons d’écrire tient en une ligne :

from langgraph.prebuilt import create_react_agent
agent = create_react_agent(modele, outils)

C’est exactement le même graphe — nœud modèle, ToolNode, routage par tools_condition, boucle de retour. Vous l’utiliserez tout le temps. Mais maintenant vous savez ce qu’il y a dedans : aucune magie, juste un graphe d’état avec une arête conditionnelle bien placée.

Ce qu’il faut retenir

  • Un agent = un LLM + des outils + une boucle qui exécute ses décisions.
  • L’état porte la mémoire de travail ; le reducer add_messages empêche l’amnésie.
  • Le nœud modèle décide, le nœud outils agit.
  • L’arête conditionnelle est le cœur agentique : c’est elle qui choisit de boucler ou de s’arrêter.
  • stream() rend la boucle visible — réflexe de débogage n°1.

Notre agent fonctionne, mais ses outils sont rudimentaires. Au chapitre suivant, on approfondit le tool calling : concevoir des outils que le modèle utilise à bon escient, gérer leurs erreurs, et comprendre ce que voit réellement le modèle quand il les appelle.

#langgraph#react#tool-calling#fondamentaux