Frameworks d’agents#
Le chapitre précédent a introduit les agents LLM — des systèmes capables de raisonner, planifier et agir de manière autonome en utilisant des outils. Construire un agent depuis zéro est un exercice pédagogique précieux, mais dès que le système dépasse la complexité d’un prototype, la question de l’outillage se pose : gestion de l’état, routage conditionnel, persistance, observabilité, gestion d’erreurs. C’est précisément le rôle des frameworks d’agents, qui fournissent des abstractions pour orchestrer les interactions entre le LLM, les outils, la mémoire et l’environnement extérieur.
L’écosystème des frameworks d’agents évolue à une vitesse remarquable. En l’espace de deux ans (2023–2025), des dizaines de bibliothèques ont vu le jour, chacune proposant sa vision de ce que devrait être l’interface de programmation d’un agent. Certains frameworks privilégient la simplicité et l’accessibilité, d’autres la flexibilité et le contrôle fin. Comprendre leurs forces, leurs limites et leurs compromis est essentiel pour choisir l’outil adapté à un projet donné — ou pour décider de ne pas en utiliser du tout.
Ce chapitre présente les principaux frameworks (LangChain, LangGraph, CrewAI, AutoGen, Semantic Kernel, Haystack), détaille les concepts fondamentaux de LangChain et LangGraph, identifie les patterns architecturaux récurrents dans la conception d’agents, propose une comparaison structurée des frameworks, et termine par une réflexion sur le moment opportun pour adopter — ou éviter — un framework.
Ecosystème des frameworks d’agents#
Le paysage des frameworks d’agents peut être organisé selon deux axes : le niveau d’abstraction (haut niveau / bas niveau) et le paradigme d’orchestration (chaînes linéaires, graphes d’état, multi-agents). Voici les principaux acteurs.
LangChain (Harrison Chase, 2022) est le framework pionnier. Il a popularisé les concepts de chain, prompt template, tool, memory et retriever, offrant une interface unifiée pour composer des applications LLM. Son succès initial repose sur sa large couverture d’intégrations (OpenAI, Anthropic, Hugging Face, bases vectorielles, etc.) et sur sa communauté très active.
LangGraph (LangChain Inc., 2024) est une extension de LangChain qui modélise les workflows d’agents sous forme de graphes d’état. Contrairement aux chaînes linéaires, LangGraph permet des boucles, du routage conditionnel et de la persistance d’état, ce qui le rend adapté aux agents complexes nécessitant un contrôle humain (human-in-the-loop).
CrewAI (2024) se concentre sur les systèmes multi-agents avec des rôles définis. Chaque agent possède un rôle, un objectif et un ensemble d’outils. Les agents collaborent au sein d’un crew selon un processus séquentiel ou hiérarchique. CrewAI privilégie la simplicité d’utilisation et les métaphores organisationnelles.
AutoGen (Microsoft, 2023) propose un paradigme de conversation multi-agents. Les agents communiquent par messages dans un protocole conversationnel. AutoGen se distingue par sa flexibilité dans la définition des interactions et par son support natif de l’exécution de code.
Semantic Kernel (Microsoft, 2023) adopte une approche orientée entreprise, intégrant les LLM dans des applications existantes via des plugins et des planificateurs. Il est conçu pour s’intégrer dans l’écosystème Microsoft (Azure, .NET, Python).
Haystack (deepset, 2020) est initialement un framework de recherche documentaire (NLP) qui a évolué vers l’orchestration d’agents. Sa version 2.x introduit un système de pipelines modulaires et composables, avec un accent sur la robustesse en production.
Remarque 74
L’écosystème des frameworks d’agents est en mutation rapide. Les versions, les API et les paradigmes changent fréquemment. Les exemples de ce chapitre illustrent les concepts fondamentaux et les patterns architecturaux qui, eux, restent stables. Le lecteur est invité à consulter la documentation officielle de chaque framework pour les détails d’implémentation les plus récents.
LangChain : chaines et abstraction#
LangChain est le framework le plus connu et le plus utilisé pour construire des applications autour des LLM. Il repose sur plusieurs abstractions centrales.
Définition 68 (Chaîne (Chain))
Une chaîne (chain) est une séquence composable d’opérations qui transforme une entrée en une sortie. Chaque maillon de la chaîne peut être un appel à un LLM, un traitement de texte, une recherche dans une base de données, un appel à un outil, ou toute autre fonction. Les chaînes se composent de manière déclarative : la sortie d’un maillon est transmise comme entrée au suivant.
Les abstractions principales de LangChain sont :
PromptTemplate : un gabarit de prompt paramétrable, avec des variables à remplir dynamiquement.
LLM / ChatModel : une interface unifiée vers les modèles de langage (OpenAI, Anthropic, Ollama, etc.).
OutputParser : transforme la sortie brute du LLM en un format structuré (JSON, liste, objet Pydantic).
Tool : encapsule une fonction externe (recherche web, calculatrice, API) que l’agent peut appeler.
Memory : gère l’historique de la conversation et le contexte persistent entre les appels.
Retriever : interface vers les systèmes de recherche documentaire (bases vectorielles, BM25, etc.).
LCEL : LangChain Expression Language#
LCEL est le langage déclaratif de LangChain pour composer des chaînes. Il utilise l’opérateur pipe (|) pour connecter les composants.
Exemple 50 (Pipeline RAG avec LangChain (LCEL))
L’exemple suivant montre un pipeline RAG typique construit avec LCEL. Le code est présenté à titre illustratif et n’est pas exécutable dans ce notebook (LangChain n’est pas dans les dépendances).
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings
# Composants
embeddings = OpenAIEmbeddings()
vectorstore = FAISS.load_local("mon_index", embeddings)
retriever = vectorstore.as_retriever(search_kwargs={"k": 4})
llm = ChatOpenAI(model="gpt-4o", temperature=0)
prompt = ChatPromptTemplate.from_template("""
Réponds à la question en utilisant uniquement le contexte suivant.
Contexte : {context}
Question : {question}
Réponse :
""")
# Chaîne LCEL avec l'opérateur pipe
chain = (
{"context": retriever, "question": RunnablePassthrough()}
| prompt
| llm
| StrOutputParser()
)
# Exécution
reponse = chain.invoke("Qu'est-ce que l'attention multi-têtes ?")
print(reponse)
Dans cette chaîne, le retriever récupère les documents pertinents, le prompt formate la requête, le llm génère la réponse, et le StrOutputParser extrait le texte brut. L’opérateur | connecte chaque étape de manière déclarative.
Forces et critiques de LangChain#
LangChain a eu un impact considérable sur l’écosystème des LLM en démocratisant l’accès aux patterns courants (RAG, agents, chaînes). Ses forces incluent une couverture d’intégrations très large, une communauté active, et une documentation abondante.
Cependant, LangChain fait l’objet de critiques récurrentes. Le niveau d’abstraction peut être trop élevé : les développeurs se retrouvent à déboguer des comportements cachés derrière plusieurs couches d’indirection. L’API a connu de nombreux changements cassants (breaking changes), rendant la maintenance difficile. Enfin, pour des cas d’usage simples, LangChain introduit une complexité disproportionnée par rapport à un appel direct à l’API du LLM.
Remarque 75
Toute abstraction a un coût. Un framework facilite les cas courants au prix d’une moindre transparence sur les cas limites. Avant d’adopter LangChain (ou tout autre framework), il est sage de se poser la question : « Est-ce que je pourrais résoudre ce problème avec un appel API direct et quelques fonctions Python ? » Si la réponse est oui, le framework est probablement superflu.
LangGraph : graphes d’état#
LangGraph représente un changement de paradigme par rapport aux chaînes linéaires de LangChain. Au lieu de composer des opérations en séquence, LangGraph modélise le workflow comme un graphe d’état où les noeuds sont des fonctions et les arêtes sont des transitions.
Définition 69 (Graphe d’état (State Graph))
Un graphe d’état est un graphe orienté \(G = (N, E, s_0, S)\) où :
\(N\) est l’ensemble des noeuds, chacun associé à une fonction \(f_i : \mathcal{S} \to \mathcal{S}\) qui transforme l’état ;
\(E \subseteq N \times N\) est l’ensemble des arêtes (transitions entre noeuds) ;
\(s_0 \in \mathcal{S}\) est lӎtat initial ;
\(S\) est l”espace d’état, un dictionnaire typé contenant toutes les variables du workflow (messages, résultats intermédiaires, compteurs, etc.).
Les arêtes peuvent être inconditionnelles (toujours empruntées) ou conditionnelles (empruntées selon une fonction de routage \(r : \mathcal{S} \to N\) qui inspecte l’état courant).
Définition 70 (Machine à états)
Une machine à états (state machine) est un modèle de calcul défini par un ensemble fini d’états, un ensemble de transitions entre ces états, un état initial et un ensemble d’états finaux. À chaque instant, la machine se trouve dans un unique état. Une transition est déclenchée par un événement ou une condition, et amène la machine dans un nouvel état. Les graphes d’état de LangGraph sont une forme de machine à états enrichie, où l’état est un dictionnaire mutable et les transitions sont déterminées par des fonctions.
Concepts clés de LangGraph#
State : un dictionnaire typé (souvent défini avec
TypedDictou Pydantic) qui contient l’ensemble des variables du workflow. L’état est transmis de noeud en noeud et modifié par chaque fonction.Node : une fonction Python qui reçoit l’état courant, effectue un traitement (appel LLM, appel outil, calcul), et retourne un état modifié.
Edge : une transition entre deux noeuds. Les arêtes conditionnelles utilisent une fonction de routage qui inspecte l’état pour décider du prochain noeud.
Checkpoint : un point de sauvegarde de l’état, permettant de reprendre l’exécution après une interruption ou de mettre en oeuvre un contrôle humain (human-in-the-loop).
Exemple 51 (Agent ReAct avec LangGraph)
L’exemple suivant montre un agent ReAct (Reason + Act) implémenté avec LangGraph. Le code est présenté à titre illustratif et n’est pas exécutable dans ce notebook.
from langgraph.graph import StateGraph, END
from typing import TypedDict, Annotated
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage
# Définition de l'état
class AgentState(TypedDict):
messages: list
next_action: str
# Noeuds
def reasoning_node(state: AgentState) -> AgentState:
"""Le LLM raisonne et décide de la prochaine action."""
llm = ChatOpenAI(model="gpt-4o")
response = llm.invoke(state["messages"])
if response.tool_calls:
return {"messages": state["messages"] + [response],
"next_action": "tool"}
return {"messages": state["messages"] + [response],
"next_action": "end"}
def tool_node(state: AgentState) -> AgentState:
"""Exécute l'outil demandé par le LLM."""
last_msg = state["messages"][-1]
result = execute_tool(last_msg.tool_calls[0])
return {"messages": state["messages"] + [result],
"next_action": "reason"}
# Construction du graphe
graph = StateGraph(AgentState)
graph.add_node("reason", reasoning_node)
graph.add_node("tool", tool_node)
graph.set_entry_point("reason")
graph.add_conditional_edges("reason", lambda s: s["next_action"],
{"tool": "tool", "end": END})
graph.add_edge("tool", "reason")
agent = graph.compile()
result = agent.invoke({"messages": [HumanMessage("Quelle est la météo à Paris ?")],
"next_action": "reason"})
Le graphe alterne entre un noeud de raisonnement et un noeud d’exécution d’outils, formant la boucle ReAct caractéristique. L’arête conditionnelle après le noeud reason inspecte l’état pour décider si l’agent doit appeler un outil ou terminer.
Remarque 76
LangGraph est étroitement lié à l’écosystème LangChain. L’utiliser implique souvent d’adopter les abstractions LangChain (messages, modèles, outils). Cette dépendance peut constituer un verrouillage fournisseur (vendor lock-in) qui rend difficile la migration vers un autre framework. Il est important de bien séparer la logique métier des abstractions du framework pour limiter ce risque.
Patterns architecturaux#
Au-delà des frameworks spécifiques, certains patterns architecturaux reviennent systématiquement dans la conception de systèmes à base d’agents. Les comprendre permet de concevoir des workflows robustes indépendamment du framework choisi.
Définition 71 (Workflow)
Un workflow est une séquence organisée d’étapes de traitement qui transforme une entrée en une sortie. Dans le contexte des agents LLM, un workflow orchestre les appels au modèle, les exécutions d’outils, les vérifications et les décisions de routage. Les workflows peuvent être linéaires (chaînes), arborescents (routeurs), cycliques (boucles d’agents) ou parallèles (map-reduce).
Pattern 1 : Chaîne séquentielle#
La chaîne séquentielle est le pattern le plus simple. Les étapes s’exécutent l’une après l’autre, chaque étape recevant la sortie de la précédente. Ce pattern convient aux tâches déterministes : extraction d’information, puis résumé, puis mise en forme.
Pattern 2 : Routeur (branching)#
Le routeur inspecte l’entrée (ou un résultat intermédiaire) et dirige le flux vers l’une de plusieurs branches. Par exemple, un routeur peut classifier une requête utilisateur (question factuelle, demande de code, conversation libre) et la diriger vers un pipeline spécialisé.
Pattern 3 : Boucle d’agent (loop)#
La boucle d’agent est le coeur du pattern ReAct. L’agent alterne entre raisonnement et action jusqu’à ce qu’une condition de terminaison soit satisfaite (réponse trouvée, nombre maximal d’itérations atteint, ou timeout). Ce pattern introduit des cycles dans le graphe, ce qui le rend plus expressif mais aussi plus difficile à déboguer.
Pattern 4 : Map-reduce#
Le map-reduce parallélise le traitement en distribuant des sous-tâches indépendantes à plusieurs instances du LLM (phase map), puis en agrégeant les résultats (phase reduce). Ce pattern est utile pour traiter de grands corpus documentaires : chaque document est résumé individuellement, puis les résumés sont fusionnés.
Pattern 5 : Superviseur#
Le superviseur est un agent de haut niveau qui coordonne plusieurs agents spécialisés. Il reçoit la requête, décide quel agent doit la traiter, collecte les résultats et les synthétise. Ce pattern est au coeur des systèmes multi-agents (chapitre 14).
Propriété 17 (Composabilite des patterns)
Les patterns architecturaux sont composables : un noeud d’un graphe peut lui-même contenir un sous-graphe complet. Par exemple, chaque branche d’un routeur peut contenir une boucle d’agent, et le superviseur d’un système multi-agents peut utiliser un pattern map-reduce pour distribuer les sous-tâches. Cette composabilité permet de construire des systèmes d’une grande complexité à partir de briques simples et bien comprises.
Un framework minimal en Python pur#
Pour démystifier le fonctionnement interne des frameworks d’agents, construisons un micro-framework en Python pur. Ce framework implémente les concepts fondamentaux — état, noeuds, arêtes, routage conditionnel — sans aucune dépendance externe.
Framework minimal charge.
Classes disponibles : State, Node, Edge, StateGraph
Exemple 52 (Agent de recherche avec le framework minimal)
Utilisons notre micro-framework pour implémenter un agent simplifié qui simule une boucle de recherche. L’agent reçoit une question, « cherche » une information (ici simulée), évalue si la réponse est satisfaisante, et itère si nécessaire.
# --- Fonctions des noeuds ---
def analyze(state):
"""Analyse la question et formule une requête de recherche."""
question = state["question"]
state["search_query"] = f"recherche: {question}"
state["iteration"] = state.get("iteration", 0) + 1
return state
def search(state):
"""Simule une recherche (dans un vrai système : appel API)."""
query = state["search_query"]
# Simulation : la recherche réussit après 2 tentatives
if state["iteration"] >= 2:
state["search_result"] = f"Résultat pertinent pour '{query}'"
state["found"] = True
else:
state["search_result"] = "Résultat non pertinent"
state["found"] = False
return state
def evaluate(state):
"""Evalue si le résultat est satisfaisant."""
if state.get("found", False):
state["answer"] = f"Reponse finale : {state['search_result']}"
state["status"] = "done"
else:
state["status"] = "retry"
return state
# --- Construction du graphe ---
graph = StateGraph()
graph.add_node("analyze", analyze)
graph.add_node("search", search)
graph.add_node("evaluate", evaluate)
graph.set_entry_point("analyze")
graph.add_edge("analyze", "search")
graph.add_edge("search", "evaluate")
graph.add_conditional_edge("evaluate",
lambda s: "analyze" if s["status"] == "retry" else StateGraph.END)
# --- Execution ---
result = graph.run(State(data={"question": "Qu'est-ce que LangGraph ?"}))
Le graphe effectue la boucle analyze -> search -> evaluate -> [retry ou fin] jusqu’à ce que le résultat soit satisfaisant. C’est exactement le pattern de boucle d’agent décrit dans la section précédente, implémenté en quelques dizaines de lignes de Python.
Exécution de l'agent de recherche :
--------------------------------------------------
[analyze] Itération 1, requête: 'recherche: Qu'est-ce que LangGraph ?'
[search] Résultat: 'Résultat non pertinent'
[evaluate] Statut: retry
[analyze] Itération 2, requête: 'recherche: Qu'est-ce que LangGraph ?'
[search] Résultat: 'Résultat pertinent pour 'recherche: Qu'est-ce que LangGraph ?''
[evaluate] Statut: done
--------------------------------------------------
Résultat : Réponse finale : Résultat pertinent pour 'recherche: Qu'est-ce que LangGraph ?'
Nombre d'étapes : 6
Comparaison des frameworks#
Le choix d’un framework dépend du contexte : complexité du workflow, taille de l’équipe, contraintes de production, et préférences architecturales. Le tableau suivant compare les principaux frameworks selon des critères objectifs.
Les observations principales de cette comparaison sont les suivantes :
LangChain a la communauté la plus large et la couverture d’intégrations la plus étendue, mais sa complexité et ses changements d’API fréquents peuvent freiner les projets en production.
LangGraph offre la plus grande flexibilité pour les workflows complexes grâce à son modèle de graphe d’état, au prix d’une courbe d’apprentissage plus raide.
CrewAI est le plus accessible pour les systèmes multi-agents simples, mais manque de maturité pour les déploiements en production.
AutoGen excelle dans les scénarios conversationnels multi-agents avec exécution de code.
Semantic Kernel et Haystack sont les mieux positionnés pour les environnements d’entreprise, avec une emphase sur la stabilité et l’intégration dans des écosystèmes existants.
Quand utiliser (ou ne pas utiliser) un framework#
L’adoption d’un framework n’est pas une décision anodine. Elle engage l’architecture du projet, la courbe d’apprentissage de l’équipe et la maintenance à long terme.
Quand un framework est utile#
Un framework se justifie lorsque le workflow est suffisamment complexe pour que la réimplémentation des briques de base (gestion d’état, routage, persistance, retry, observabilité) représente un effort significatif. Concrètement :
Le workflow contient des boucles ou du routage conditionnel non trivial.
Le système nécessite une persistance d’état entre les exécutions (checkpoints, reprise sur erreur).
Plusieurs agents doivent collaborer avec un protocole de communication structuré.
L’équipe a besoin d”intégrations pré-construites avec des fournisseurs de LLM, des bases vectorielles ou des outils externes.
Le projet vise la production et nécessite des fonctionnalités d’observabilité (traces, logs, métriques).
Quand un framework est superflu#
Pour de nombreux cas d’usage, un framework est une complexité inutile :
Un appel API direct (via le SDK OpenAI, Anthropic, etc.) suffit pour les tâches simples : résumé, classification, extraction d’information.
Un script Python de quelques dizaines de lignes peut implémenter un pipeline RAG basique sans framework.
Les prototypes et preuves de concept gagnent en rapidité et en clarté sans framework.
Lorsque le framework impose des abstractions incompatibles avec les besoins du projet, le contourner coûte plus cher que de construire sur mesure.
Exemple 53 (Construction sur mesure vs framework)
Un pipeline RAG minimal peut s’écrire en Python pur en moins de 30 lignes, sans aucun framework :
import openai
import numpy as np
def embed(texts, client):
"""Encode des textes en vecteurs."""
resp = client.embeddings.create(model="text-embedding-3-small", input=texts)
return np.array([e.embedding for e in resp.data])
def search(query_vec, doc_vecs, documents, k=3):
"""Recherche les k documents les plus similaires."""
scores = doc_vecs @ query_vec
top_k = np.argsort(scores)[-k:][::-1]
return [documents[i] for i in top_k]
def rag(question, documents, client):
"""Pipeline RAG complet."""
doc_vecs = embed(documents, client)
q_vec = embed([question], client)[0]
context = search(q_vec, doc_vecs, documents)
prompt = f"Contexte : {chr(10).join(context)}\n\nQuestion : {question}"
response = client.chat.completions.create(
model="gpt-4o", messages=[{"role": "user", "content": prompt}])
return response.choices[0].message.content
Ce code est plus court, plus lisible et plus facile à déboguer que l’équivalent LangChain. Le framework ne se justifie que lorsque la complexité dépasse ce niveau.
Remarque 77
La règle d’or est la suivante : commencer simple et ajouter de la complexité uniquement lorsque le besoin est avéré. Il est toujours plus facile de migrer d’un script Python vers un framework que de se défaire d’un framework devenu un obstacle. Cette approche incrémentale limite le risque de sur-ingénierie (over-engineering) et préserve l’agilité du projet.
Résumé#
Ce chapitre a présenté les frameworks d’agents, les abstractions qu’ils proposent et les arbitrages qu’ils imposent.
L”écosystème des frameworks d’agents est en évolution rapide. Les principaux acteurs — LangChain, LangGraph, CrewAI, AutoGen, Semantic Kernel, Haystack — couvrent un spectre allant des chaînes linéaires simples aux systèmes multi-agents complexes.
LangChain a popularisé les concepts de chaîne, prompt template, outil et mémoire. LCEL (LangChain Expression Language) permet de composer des pipelines de manière déclarative avec l’opérateur pipe. Le framework offre une couverture d’intégrations très large, mais fait l’objet de critiques sur sa complexité et l’instabilité de son API.
LangGraph modélise les workflows comme des graphes d’état où les noeuds sont des fonctions et les arêtes des transitions (conditionnelles ou non). Ce paradigme permet de représenter des boucles, du routage conditionnel et de la persistance d’état, ce qui le rend adapté aux agents complexes et au contrôle humain via des checkpoints.
Les patterns architecturaux récurrents — chaîne séquentielle, routeur, boucle d’agent, map-reduce, superviseur — sont indépendants des frameworks et constituent le vocabulaire commun de la conception d’agents. Ces patterns sont composables : un noeud peut contenir un sous-graphe complet.
La comparaison des frameworks révèle des compromis entre courbe d’apprentissage, flexibilité, support multi-agents, taille de la communauté et maturité en production. Le choix dépend du contexte du projet.
Un framework n’est pas toujours nécessaire. Pour les tâches simples, un appel API direct ou un script Python de quelques dizaines de lignes est souvent préférable. La règle d’or est de commencer simple et d’ajouter de la complexité uniquement lorsque le besoin est avéré.
Construire un micro-framework en Python pur (état, noeuds, arêtes, routage conditionnel) est un exercice qui permet de comprendre les mécanismes internes des frameworks professionnels et de démystifier leur fonctionnement.