
Construire un agent LangGraph à partir de zéro
Le terme «Agent IA » est l’un des plus populaires à l’heure actuelle. Ils ont émergé après le battage médiatique du LLM, lorsque les gens ont réalisé que les dernières capacités du LLM sont impressionnantes mais qu’ils ne peuvent effectuer que des tâches pour lesquelles ils ont été explicitement formés. En ce sens, les LLM normaux ne disposent pas d’outils qui leur permettraient de faire quoi que ce soit en dehors de leur champ de connaissances.
CHIFFON
Pour résoudre ce problème, Génération augmentée par récupération (RAG) a ensuite été introduit pour récupérer du contexte supplémentaire à partir de sources de données externes et l’injecter dans l’invite, afin que le LLM prenne conscience de davantage de contexte. Nous pouvons dire en gros que RAG a rendu le LLM plus compétent, mais pour des problèmes plus complexes, l’approche LLM + RAG échouait toujours lorsque le chemin de la solution n’était pas connu à l’avance.

Agents
Les agents sont un concept remarquable construit autour des LLM qui introduisent État, prise de décisionet mémoire. Les agents peuvent être considérés comme un ensemble d’outils prédéfinis permettant d’analyser les résultats et de les stocker en mémoire pour une utilisation ultérieure avant de produire la réponse finale.
LangGraph
LangGraph est un framework populaire utilisé pour créer des agents. Comme leur nom l’indique, les agents sont construits à l’aide de graphiques avec des nœuds et des arêtes.
Les nœuds représentent l’état de l’agent, qui évolue dans le temps. Les bords définissent le flux de contrôle en spécifiant les règles et conditions de transition entre les nœuds.
Pour mieux comprendre LangGraph en pratique, nous allons passer par un exemple détaillé. Bien que LangGraph puisse sembler trop verbeux pour le problème ci-dessous, il a généralement un impact beaucoup plus important sur les problèmes complexes avec de grands graphiques.
Tout d’abord, nous devons installer les bibliothèques nécessaires.
langgraph==1.0.5
langchain-community==0.4.1
jupyter==1.1.1
notebook==7.5.1
langchain[openai]
Ensuite, nous importons les modules nécessaires.
import os
from dotenv import load_dotenv
import json
import random
from pydantic import BaseModel
from typing import Optional, List, Dict, Any
from langgraph.graph import StateGraph, START, END
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
from langchain.chat_models import init_chat_model
from langchain.tools import tool
from IPython.display import Image, display
Il faudrait également créer un .env fichier et ajoutez un OPENAI_API_KEY là:
OPENAI_API_KEY=...
Puis, avec load_dotenv()nous pouvons charger les variables d’environnement dans le système.
load_dotenv()
Fonctionnalités supplémentaires
La fonction ci-dessous nous sera utile pour afficher visuellement des graphiques construits.
def display_graph(graph):
return display(Image(graph.get_graph().draw_mermaid_png()))
Agent
Initialisons un agent basé sur GPT-5-nano à l’aide d’une simple commande :
llm = init_chat_model("openai:gpt-5-nano")
État
Dans notre exemple, nous allons construire un agent capable de répondre à des questions sur le football. Sa réflexion s’appuiera sur les statistiques récupérées sur les joueurs.
Pour ce faire, nous devons définir un État. Dans notre cas, il s’agira d’une entité contenant toutes les informations dont un LLM a besoin sur un joueur. Pour définir un état, nous devons écrire une classe qui hérite de pydantic.BaseModel:
class PlayerState(BaseModel):
question: str
selected_tools: Optional[List[str]] = None
name: Optional[str] = None
club: Optional[str] = None
country: Optional[str] = None
number: Optional[int] = None
rating: Optional[int] = None
goals: Optional[List[int]] = None
minutes_played: Optional[List[int]] = None
summary: Optional[str] = None
Lors du déplacement entre les nœuds LangGraph, chaque nœud prend en entrée une instance de État du joueur qui spécifie comment traiter l’état. Notre tâche sera de définir comment exactement cet état est traité.
Outils
Tout d’abord, nous définirons certains des outils qu’un agent peut utiliser. UN outil peut être grossièrement considéré comme une fonction supplémentaire qu’un agent peut appeler pour récupérer les informations nécessaires pour répondre à la question d’un utilisateur.
Pour définir un outil, nous devons écrire une fonction avec un @outil décorateur. Il est important d’utiliser des noms de paramètres et des docstrings de fonction clairs, car l’agent les prendra en compte lorsqu’il décidera d’appeler ou non l’outil en fonction du contexte d’entrée.
Pour simplifier nos exemples, nous allons utiliser des données fictives au lieu de données réelles récupérées depuis des sources externes, ce qui est généralement le cas pour les applications de production.
Dans le premier outil, nous renverrons des informations sur le club et le pays d’un joueur par son nom.
@tool
def fetch_player_information_tool(name: str):
"""Contains information about the football club of a player and its country"""
data = {
'Haaland': {
'club': 'Manchester City',
'country': 'Norway'
},
'Kane': {
'club': 'Bayern',
'country': 'England'
},
'Lautaro': {
'club': 'Inter',
'country': 'Argentina'
},
'Ronaldo': {
'club': 'Al-Nassr',
'country': 'Portugal'
}
}
if name in data:
print(f"Returning player information: {data[name]}")
return data[name]
else:
return {
'club': 'unknown',
'country': 'unknown'
}
def fetch_player_information(state: PlayerState):
return fetch_player_information_tool.invoke({'name': state.name})
Vous vous demandez peut-être pourquoi nous plaçons un outil dans une autre fonction, ce qui semble être une ingénierie excessive. En fait, ces deux fonctions ont des responsabilités différentes.
La fonction fetch_player_information() prend un état comme paramètre et est compatible avec le framework LangGraph. Il extrait le champ de nom et appelle un outil qui opère au niveau des paramètres.
Il fournit une séparation claire des préoccupations et permet une réutilisation facile du même outil sur plusieurs nœuds graphiques.
Nous avons ensuite une fonction analogue qui récupère le numéro de maillot d’un joueur :
@tool
def fetch_player_jersey_number_tool(name: str):
"Returns player jersey number"
data = {
'Haaland': 9,
'Kane': 9,
'Lautaro': 10,
'Ronaldo': 7
}
if name in data:
print(f"Returning player number: {data[name]}")
return {'number': data[name]}
else:
return {'number': 0}
def fetch_player_jersey_number(state: PlayerState):
return fetch_player_jersey_tool.invoke({'name': state.name})
Pour le troisième outil, nous récupérerons la note FIFA du joueur :
@tool
def fetch_player_rating_tool(name: str):
"Returns player rating in the FIFA"
data = {
'Haaland': 92,
'Kane': 89,
'Lautaro': 88,
'Ronaldo': 90
}
if name in data:
print(f"Returning rating data: {data[name]}")
return {'rating': data[name]}
else:
return {'rating': 0}
def fetch_player_rating(state: PlayerState):
return fetch_player_rating_tool.invoke({'name': state.name})
Maintenant, écrivons plusieurs autres fonctions de nœud graphique qui récupéreront des données externes. Nous n’allons pas les qualifier d’outils comme auparavant, ce qui signifie que ce ne sera pas quelque chose que l’agent décidera d’appeler ou non.
def retrieve_goals(state: PlayerState):
name = state.name
data = {
'Haaland': [25, 40, 28, 33, 36],
'Kane': [33, 37, 41, 38, 29],
'Lautaro': [19, 25, 27, 24, 25],
'Ronaldo': [27, 32, 28, 30, 36]
}
if name in data:
return {'goals': data[name]}
else:
return {'goals': [0]}
Voici un nœud graphique qui récupère le nombre de minutes jouées au cours des dernières saisons.
def retrieve_minutes_played(state: PlayerState):
name = state.name
data = {
'Haaland': [2108, 3102, 3156, 2617, 2758],
'Kane': [2924, 2850, 3133, 2784, 2680],
'Lautaro': [2445, 2498, 2519, 2773],
'Ronaldo': [3001, 2560, 2804, 2487, 2771]
}
if name in data:
return {'minutes_played': data[name]}
else:
return {'minutes_played': [0]}
Vous trouverez ci-dessous un nœud qui extrait le nom d’un joueur à partir d’une question utilisateur.
def extract_name(state: PlayerState):
question = state.question
prompt = f"""
You are a football name extractor assistant.
Your goal is to just extract a surname of a footballer in the following question.
User question: {question}
You have to just output a string containing one word - footballer surname.
"""
response = llm.invoke([HumanMessage(content=prompt)]).content
print(f"Player name: ", response)
return {'name': response}
C’est maintenant le moment où les choses deviennent intéressantes. Vous souvenez-vous des trois outils que nous avons définis ci-dessus ? Grâce à eux, nous pouvons désormais créer un planificateur qui demandera à l’agent de choisir un outil spécifique à appeler en fonction du contexte de la situation :
def planner(state: PlayerState):
question = state.question
prompt = f"""
You are a football player summary assistant.
You have the following tools available: ['fetch_player_jersey_number', 'fetch_player_information', 'fetch_player_rating']
User question: {question}
Decide which tools are required to answer.
Return a JSON list of tool names, e.g. ['fetch_player_jersey_number', 'fetch_rating']
"""
response = llm.invoke([HumanMessage(content=prompt)]).content
try:
selected_tools = json.loads(response)
except:
selected_tools = []
return {'selected_tools': selected_tools}
Dans notre cas, nous demanderons à l’agent de créer un résumé d’un joueur de football. Il décidera lui-même quel outil appeler pour récupérer des données supplémentaires. Les docstrings sous outils jouent un rôle important : ils fournissent à l’agent un contexte supplémentaire sur les outils.
Vous trouverez ci-dessous notre nœud graphique final, qui prendra plusieurs champs récupérés des étapes précédentes et appellera le LLM pour générer un résumé final.
def write_summary(state: PlayerState):
question = state.question
data = {
'name': state.name,
'country': state.country,
'number': state.number,
'rating': state.rating,
'goals': state.goals,
'minutes_played': state.minutes_played,
}
prompt = f"""
You are a football reporter assistant.
Given the following data and statistics of the football player, you will have to create a markdown summary of that player.
Player data:
{json.dumps(data, indent=4)}
The markdown summary has to include the following information:
- Player full name (if only first name or last name is provided, try to guess the full name)
- Player country (also add flag emoji)
- Player number (also add the number in the emoji(-s) form)
- FIFA rating
- Total number of goals in last 3 seasons
- Average number of minutes required to score one goal
- Response to the user question: {question}
"""
response = llm.invoke([HumanMessage(content=prompt)]).content
return {"summary": response}
Construction de graphiques
Nous disposons désormais de tous les éléments pour construire un graphique. Tout d’abord, nous initialisons le graphe en utilisant le Graphique d’état constructeur. Ensuite, nous ajoutons des nœuds à ce graphique un par un en utilisant le add_node() méthode. Il prend deux paramètres : une chaîne utilisée pour attribuer un nom au nœud et une fonction appelable associée au nœud qui prend un état de graphe comme seul paramètre.
graph_builder = StateGraph(PlayerState)
graph_builder.add_node('extract_name', extract_name)
graph_builder.add_node('planner', planner)
graph_builder.add_node('fetch_player_jersey_number', fetch_player_jersey_number)
graph_builder.add_node('fetch_player_information', fetch_player_information)
graph_builder.add_node('fetch_player_rating', fetch_player_rating)
graph_builder.add_node('retrieve_goals', retrieve_goals)
graph_builder.add_node('retrieve_minutes_played', retrieve_minutes_played)
graph_builder.add_node('write_summary', write_summary)
Pour l’instant, notre graphique n’est constitué que de nœuds. Nous devons y ajouter des bords. Les arêtes dans LangGraph sont orientées et ajoutées via le ajouter_edge() méthode, spécifiant les noms des nœuds de début et de fin.
La seule chose dont nous devons tenir compte est le planificateur, qui se comporte légèrement différemment des autres nœuds. Comme indiqué ci-dessus, il peut renvoyer le outils_sélectionnés champ, qui contient 0 à 3 nœuds de sortie.
Pour cela, nous devons utiliser le add_conditional_edges() méthode prenant trois paramètres :
- Le nom du nœud du planificateur ;
- Une fonction appelable prenant un nœud LangGraph et renvoyant une liste de chaînes indiquant la liste des noms de nœuds doit être appelée ;
- Un dictionnaire mappant les chaînes du deuxième paramètre aux noms de nœuds.
Dans notre cas, nous définirons le route_tools() nœud pour renvoyer simplement le état.selected_tools champ suite à une fonction de planification.
def route_tools(state: PlayerState):
return state.selected_tools or []
Ensuite, nous pouvons construire des nœuds :
graph_builder.add_edge(START, 'extract_name')
graph_builder.add_edge('extract_name', 'planner')
graph_builder.add_conditional_edges(
'planner',
route_tools,
{
'fetch_player_jersey_number': 'fetch_player_jersey_number',
'fetch_player_information': 'fetch_player_information',
'fetch_player_rating': 'fetch_player_rating'
}
)
graph_builder.add_edge('fetch_player_jersey_number', 'retrieve_goals')
graph_builder.add_edge('fetch_player_information', 'retrieve_goals')
graph_builder.add_edge('fetch_player_rating', 'retrieve_goals')
graph_builder.add_edge('retrieve_goals', 'retrieve_minutes_played')
graph_builder.add_edge('retrieve_minutes_played', 'write_summary')
graph_builder.add_edge('write_summary', END)
START et END sont des constantes LangGraph utilisées pour définir les points de début et de fin du graphique.
La dernière étape consiste à compiler le graphique. Nous pouvons éventuellement le visualiser à l’aide de la fonction d’assistance définie ci-dessus.
graph = graph_builder.compile()
display_graph(graph)

Exemple
Nous pouvons enfin utiliser notre graphique ! Pour ce faire, nous pouvons utiliser la méthode Invoke et transmettre un dictionnaire contenant le champ de question avec une question utilisateur personnalisée :
result = graph.invoke({
'question': 'Will Haaland be able to win the FIFA World Cup for Norway in 2026 based on his recent performance and stats?'
})
Et voici un exemple de résultat que nous pouvons obtenir !
{'question': 'Will Haaland be able to win the FIFA World Cup for Norway in 2026 based on his recent performance and stats?',
'selected_tools': ['fetch_player_information', 'fetch_player_rating'],
'name': 'Haaland',
'club': 'Manchester City',
'country': 'Norway',
'rating': 92,
'goals': [25, 40, 28, 33, 36],
'minutes_played': [2108, 3102, 3156, 2617, 2758],
'summary': '- Full name: Erling Haaland\n- Country: Norway 🇳🇴\n- Number: N/A
- FIFA rating: 92\n- Total goals in last 3 seasons: 97 (28 + 33 + 36)\n- Average minutes per goal (last 3 seasons): 87.95 minutes per goal\n- Will Haaland win the FIFA World Cup for Norway in 2026 based on recent performance and stats?\n - Short answer: Not guaranteed. Haaland remains among the world’s top forwards (92 rating, elite goal output), and he could be a key factor for Norway. However, World Cup success is a team achievement dependent on Norway’s overall squad quality, depth, tactics, injuries, and tournament context. Based on statistics alone, he strengthens Norway’s chances, but a World Cup title in 2026 cannot be predicted with certainty.'}
Ce qui est intéressant, c’est que nous pouvons observer l’intégralité de l’état du graphique et analyser les outils que l’agent a choisis pour générer la réponse finale. Le résumé final a l’air génial !
Conclusion
Dans cet article, nous avons examiné les agents IA qui ont ouvert un nouveau chapitre pour les LLM. Equipés d’outils et d’outils de prise de décision de pointe, nous disposons désormais d’un potentiel bien plus important pour résoudre des tâches complexes.
Un exemple que nous avons vu dans cet article nous a présenté LangGraph, l’un des frameworks les plus populaires pour les agents de construction. Sa simplicité et son élégance permettent de construire des chaînes de décision complexes. Bien que, pour notre exemple simple, LangGraph puisse sembler excessif, il devient extrêmement utile pour les projets plus importants où les structures d’état et de graphe sont beaucoup plus complexes.



