Accueil / Articles / Comment les agents LangGraph survivent aux redémarrages : points de contrôle, gestionnaires de points de contrôle, threads

Comment les agents LangGraph survivent aux redémarrages : points de contrôle, gestionnaires de points de contrôle, threads

Apprenez comment fonctionne la persistance de LangGraph : pourquoi les agents perdent tout sans elle, et comment l’état, les points de contrôle, les gestionnaires de points de contrôle ainsi que les identifiants de thread permettent à une exécution de reprendre après un plantage.

4200 mots

La persistance constitue également la base de plusieurs fonctionnalités qui sont souvent abordées séparément : les étapes d’approbation par un humain, l’appel à des outils au cours de plusieurs tours de conversation et les agents à exécution prolongée dépendent tous de la capacité d’arrêter un graphe pour le reprendre plus tard. Comprendre ce concept en premier rend ces fonctionnalités bien moins mystérieuses.

Pourquoi les agents ont besoin d’un état qui dépasse la durée du processus

Pensez à avoir tapé un long document pendant deux heures sans l’enregistrer, puis à voir la lumière s’éteindre. Le travail disparaît simplement et vous devez recommencer depuis la première page. Un agent sans persistance se retrouve dans cette situation à chaque redémarrage : il n’a aucun souvenir de ce qui s’est passé quelques secondes auparavant, encore moins quelques jours plus tôt.

En une phrase, la persistance signifie stocker l’état d’une application dans un endroit fiable afin qu’il puisse être récupéré et laissé en l’état après l’arrêt du programme. Cela fonctionne comme un bouton de sauvegarde automatique qui s’active presque à chaque étape, sans que personne n’ait besoin de le cliquer.

Cela est encore plus important pour les agents que pour le code classique demande/réponse, car les agents modernes ne consistent rarement en une seule question et une seule réponse. Ils ont généralement :

  • des conversations qui se prolongent sur des heures ou des jours ;
  • besoin de s’arrêter et d’attendre que quelqu’un approuve une action ;
  • à effectuer plusieurs appels d’outils pour terminer une tâche ;
  • à raisonner à travers de nombreuses étapes sur une longue période ;
  • à survivre à des redémarrages et des pannes sans perdre leurs progrès.

La RAM est rapide, mais elle est volatile : ses données sont effacées lorsque le processus se termine. Tout ce dont un agent a besoin au-delà de la durée de vie d’un processus doit être enregistré dans un stockage durable pour être lu ultérieurement. Dans LangGraph, ce stockage ainsi que le mécanisme qui le gère correspondent à ce qu’on entend par « persistance ».

Que se passe-t-il lorsque l’agent n’a pas de persistance ?

Le problème est le plus facile à comprendre à travers des modes de défaillance concrets. Chacun d’eux est courant en environnement de production.

Perte d’électricité au milieu d’une tâche longue

Un agent est en train de résumer un rapport de 200 pages et a atteint la page 100 lorsque la machine perd l’électricité. Sans état enregistré, il n’y a aucun signe indiquant que la moitié du travail a été accomplie, donc le prochain lancement commence à la page 1.

Redémarrage habituel du serveur

L’agent s’exécute sur un serveur cloud, et une déploiement normal le redémarre. Chaque utilisateur en pleine conversation perd son historique. Le chatbot ne se souvient même plus du nom d’un utilisateur donné deux minutes auparavant.

Une panne au milieu d’une tâche en plusieurs étapes

Dans le cadre d’une tâche plus large, l’agent appelle une API externe qui expire, ce qui provoque la mort du processus Python en raison d’une exception non gérée. L’étape échouée est perdue, tout comme tout ce que l’agent avait accompli auparavant.

Des flux de travail qui durent des heures ou des jours

Une approbation humaine qui prend des heures

Raisonnement en plusieurs étapes qui échoue tardivement

Pourquoi « simplement le relancer » n’est pas une stratégie

  • Argent. Chaque appel à un LLM consomme des tokens, donc relancer six étapes réussies parce que la septième a échoué signifie en payer le prix deux fois.
  • Temps. Les utilisateurs doivent attendre que le travail pour lequel ils ont déjà patienté soit répété.
  • Foi. Un assistant bancaire qui oublie une demande de prêt à chaque recharge de la page ne constitue pas un véritable produit.
  • Effets secondaires. Si une étape antérieure a déjà envoyé un e-mail ou débité une carte, la répétition de cette étape provoque des dommages concrets. Certaines étapes ne peuvent pas être exécutées à nouveau en toute sécurité.
  • Ce dernier point est le plus important. L’objectif de la persistance n’est pas seulement d’économiser des efforts ; il s’agit également de permettre à une application de faire une pause, d’échouer, de redémarrer ou d’attendre avant de reprendre exactement là où elle s’est arrêtée, afin que le travail accompli reste achevé.

    Une définition précise de la persistance

    Au vu du problème, une définition plus précise est utile : la persistance est la capacité d’un système à enregistrer ses données internes, son état, dans un stockage durable, de sorte que ces données survivent à l’exécution en cours et puissent être chargées à nouveau pour reprendre l’exécution exactement là où elle s’était interrompue.

    Cette définition comprend trois capacités distinctes :

    1. Enregistrement de l’état : conservation de ce que l’agent sait actuellement et de ce qu’il a fait.
    2. Récupération d’une exécution antérieure : lecture de ces enregistrements, même après un redémarrage.
    3. Poursuite du flux de travail : continuation à partir du point récupéré plutôt qu’au début.

    Mémoire temporaire versus stockage durable

    Une source fréquente de confusion est la différence entre conserver des données et les persister. Un dictionnaire Python ordinaire contenant la conversation constitue une mémoire temporaire : lorsque le processus s’arrête, le dictionnaire disparaît. La persistance consiste à prendre un instantané de ces données et à l’enregistrer dans un endroit qui survive au processus, généralement une base de données. Telle est l’idée fondamentale ; le reste de ce guide explique comment LangGraph la met en œuvre.

    Quels avantages vous apporte la persistance en production

    En plus d’éviter la perte de travail, la persistance permet de développer des types de systèmes différents.

    • Agents à exécution prolongée. Les agents qui parcourent de nombreuses pages, traitent de gros ensembles de données ou attendent des événements externes peuvent être mis en pause et repris à tout moment, sur n’importe quel ordinateur pouvant accéder au stockage.
    • Tolérance aux pannes. Un système tolérant aux pannes continue de fonctionner correctement en cas de plantages, de pannes réseau ou d’expirations de délai. Avec la persistance, une panne ne coûte que l’étape en cours, et non toute la tâche.
    • Flux de travail d’approbation.Lorsqu’une personne doit examiner quelque chose, l’agent peut s’arrêter aussi longtemps que nécessaire sans rien perdre. La mise en place de tels flux devient alors simple lorsque l’état est persistant.
  • Récupération sans code personnalisé. Vous n’avez pas besoin d’écrire de routines de récupération spéciales. Vous chargez le dernier point de contrôle enregistré et vous continuez ; LangGraph s’en occupe pour vous une fois la persistance activée.
  • Produits fiables. Les utilisateurs réels ne supporteront pas d’être contraints de tout recommencer après chaque déploiement. La persistance fait toute la différence entre une démo fragile et un produit véritable.
  • Costs réduits. Les tâches coûteuses qui ont déjà réussi, comme de longues générations ou des appels d’outils lents, ne sont pas facturées à nouveau.
  • Mieux expérience utilisateur. Les gens s’attendent à ce qu’un assistant de chat se souvienne de la conversation après avoir fermé la fenêtre et y être revenu. Cette mémoire, c’est bien la persistance en action.
  • Un test rapide pour déterminer si vous en avez besoin : si le processus redémarrait maintenant, un utilisateur serait-il mécontent ? Si la réponse est oui, le graphe a besoin de persistance.

    État : ce qui est réellement enregistré

    La persistance dans LangGraph n’a de sens que si vous comprenez le état, car persister un graphe signifie en réalité enregistrer son état à des moments bien choisis.

    L’état est généralement déclaré comme un dictionnaire typé. La première étape consiste à l’importer :

    from typing import TypedDict
    

    Le schéma énumère ensuite les clés avec lesquelles travaille le graphe et leurs types. Ici, l’état gère une liste de messages, le nom de l’utilisateur et un compteur d’étapes :

    class State(TypedDict):
        messages: list
        user_name: str
        step_count: int
    

    Puisque State est un TypedDict, il s’agit simplement d’un dictionnaire disposant d’un ensemble fixe de clés et de types déclarés. Chaque nœud reçoit l’état actuel et renvoie une mise à jour partielle, que LangGraph fusionne dans l’état partagé.

    Pourquoi tout tourne autour de l’état

    L’état est au cœur d’une application LangGraph :

    • les nœuds le lisent pour décider de leurs actions ;
    • les nœuds y enregistrent à nouveau leurs résultats ;
    • les arêtes peuvent rediriger vers différents nœuds en fonction des valeurs qu’il contient ;
    • la persistance le sauvegarde et le restaure.

    Lorsque cela est activé, la persistance se résume à une seule phrase : après chaque étape, prenez une photo de l’état actuel et conservez-la en lieu sûr.

    Prises de vue après chaque étape

    Gardez ce modèle pour le reste du guide. Chaque fois qu’un nœud est terminé, LangGraph enregistre l’état actuel et le stocke. Si le processus s’arrête juste après la fin du deuxième nœud, la prise de vue de ce moment existe toujours, permettant ainsi de reprendre l’exécution à partir de là plutôt qu’à partir du premier nœud. Cette prise de vue a un nom : un point de contrôle.

    Points de contrôle : prises de vue de l’état à un instant donné

    point de contrôle est une prise de vue de l’état du graphe à un moment précis. Ce terme provient du même domaine que dans les jeux vidéo : il s’agit d’un endroit sûr auquel on peut revenir sans devoir rejouer tout le niveau.

    Ce que permettent les points de contrôle

    Les points de contrôle sont créés automatiquement

    Qu’est-ce qu’un point de contrôle contient

    Un point de contrôle enregistre généralement :

    • id : un identifiant unique, ordonné de manière à ce que les points de contrôle ultérieurs viennent après ceux antérieurs ;
    • ts : la date à laquelle le point de contrôle a été créé ;
    • channel_values : les données d’état elles-mêmes, telles que les messages et autres variables à ce moment-là ;
    • channel_versions : des compteurs de version internes utilisés par LangGraph pour suivre quels éléments de l’état ont changé ;
    • des métadonnées : des informations de suivi telles que le nœud qui a généré le point de contrôle et le numéro d’étape.

    Il n’est pas nécessaire de mémoriser cette structure. La définition de base suffit : un point de contrôle est une capture d’état accompagnée de quelques informations de suivi, enregistrées à un moment donné. Si vous souhaitez voir comment ces éléments sont stockés en interne, y compris les écritures en attente et les blobs, un guide plus détaillé se trouve dans Inside LangGraph's InMemorySaver.

    Un compteur, étape par étape

    Considérez un graphe avec un seul nœud qui incrémente un nombre, exécuté trois fois consécutivement. Après la première exécution, l’état enregistré contient {count: 1}, après la deuxième {count: 2}, et après la troisième {count: 3}, chacun servant de point de contrôle distinct. Si le programme plante immédiatement après l’enregistrement du deuxième point de contrôle, une redémarrage peut se poursuivre à partir de {count: 2} sans avoir à répéter les deux premiers incréments.

    Checkpointers : le composant qui enregistre et charge

    Si un point de contrôle représente une capture d’écran, un checkpointer est le composant qui prend ces captures d’écran, les stocke et les restitue. Il s’agit de la couche de persistance de LangGraph.

    Une analogie appropriée est une caméra intégrée à un classeur. Chaque fois qu’un nœud est terminé, la caméra prend une photo de l’état et le enregistre. Ce classeur peut être la mémoire de traitement, un fichier local ou une base de données, en fonction du checkpointer choisi.

    Trois fonctions d’un checkpointer

    1. Enregistrer l’état : écrire le nouveau snapshot dans le stockage après chaque étape.
    2. Charger l’état : lire le snapshot le plus récent lorsque le graphe est de nouveau exécuté pour le même thread (les threads sont abordés dans la section suivante).
    3. Rétablir l’exécution : transmettre ce snapshot au système d’exécution afin que le graphe reprenne là où il s’est arrêté plutôt que de commencer à zéro.

    Attacher un checkpointer au moment de la compilation

    La persistance est activée lors de la compilation du graphe. Vous importez une classe checkpointer ainsi que l’outil de construction du graphe ; les étiquettes du code indiquent ce fragment comme étant en JavaScript, mais il s’agit en réalité de Python :

    from langgraph.checkpoint.memory import InMemorySaver
    from langgraph.graph import StateGraph
    

    Ensuite, vous créez l’objet checkpointer et vous le passez à la fonction compile(). Notez que dans le fragment affiché, le commentaire et l’instruction checkpointer = InMemorySaver() ont été regroupés sur une seule ligne, ce qui ferait de l’instruction d’affectation une partie du commentaire ; dans un code réel, ils doivent se trouver sur des lignes distinctes :

    # ... assume `builder` is a StateGraph you've already defined ...checkpointer = InMemorySaver()
    graph = builder.compile(checkpointer=checkpointer)
    

    Cet argument checkpointer=checkpointer active la persistance pour l’ensemble du graphe. Sans lui, LangGraph ne stocke rien, et chaque appel à invoke() débute avec un état vide.

    Le cycle résultant est chargement, exécution, enregistrement, répété après chaque étape. C’est pourquoi la persistance semble automatique : vous n’appelez jamais vous-même les fonctions d’enregistrement ou de chargement, car le graphe compilé s’en charge dans le cadre de l’exécution normale.

    Il existe une précaution facile à négliger. Un point de contrôle ne conserve que les graphes auxquels il a été transmis. Si le même fichier compile un deuxième graphe sans point de contrôle, ce deuxième graphe n’a absolument aucune capacité de persistance.

    Threads : séparer les conversations

    La valeur thread_id apparaît dans tout le code de LangGraph, et elle mérite une explication précise. Un thread représente une conversation ou tâche continue. Chaque thread possède un ID unique, et chaque point de contrôle généré pour cette conversation est regroupé sous celui-ci.

    Pourquoi chaque conversation a besoin de son propre thread

    Imaginez un chatbot de support qui sert des milliers de clients en même temps. Un client demande un remboursement tandis qu’un autre s’enquiert d’une livraison en retard. Il s’agit de conversations distinctes qui se déroulent parallèlement, et les confondre, par exemple en informant le premier client au sujet du colis du second, constituerait une grave erreur.

    Les IDs de thread empêchent cela en fonctionnant comme des étiquettes sur des dossiers. Chaque point de contrôle est classé sous un seul ID de thread, de sorte que les informations provenant de différentes conversations ne se mélangent jamais.

    Transfert d’un ID de thread dans le code

    Le thread est sélectionné à l’aide d’un dictionnaire de configuration. Le thread_id se trouve sous la clé configurable (ce passage et le suivant sont en Python, pas en texte brut) :

    config = {"configurable": {"thread_id": "customer-a-session-101"}}
    

    Cette configuration est transmise avec les données d’entrée à chaque appel :

    result = graph.invoke({"messages": [{"role": "user", "content": "Where's my refund?"}]}, config)
    

    Chaque appel à graph.invoke() ou graph.stream() reçoit un paramètre config contenant le thread_id. LangGraph l’utilise pour :

    • retrouver les points de contrôle existants correspondant à ce thread, si des données historiques doivent être chargées ;
    • enregistrer de nouveaux points de contrôle sous le même ID tant que l’exécution se poursuit.

    En utilisant un thread_id différent, on obtient une conversation nouvelle et vide, comme si l’état avait été réinitialisé, même si le graphe compilé et les points de contrôle sont en réalité les mêmes objets.

    Comment les threads correspondent aux produits réels

    • Une interface de chat : chaque conversation ouverte constitue en fait son propre thread, et changer de conversation revient à changer de thread_id. Ce que vous dites dans une conversation ne s’infiltre pas dans une autre.
  • Un service d’assistance : chaque ticket peut être associé à un identifiant de thread, permettant de conserver l’historique de ce client de manière isolée et facilement consultable ultérieurement.
  • Un assistant personnel : un assistant qui gère le calendrier et la liste des tâches d’une personne peut utiliser un seul thread à longue durée de vie lié au compte utilisateur, afin que les préférences soient conservées sur plusieurs jours.
  • Une conséquence pratique est que les identifiants de thread doivent être générés et stockés intentionnellement, par exemple à partir d’un numéro de ticket ou d’un identifiant de session dans votre propre base de données. Si l’ID est perdu, les points de contrôle existent toujours, mais rien ne permet de les retrouver.

    Un graphe compilé, de nombreux threads

    Vous n’avez pas besoin d’un graphe par utilisateur. Un seul graphe compilé peut servir un nombre indéterminé de threads en même temps ; ce dont vous avez besoin, c’est d’un identifiant unique de thread par utilisateur ou par conversation. Le graphe définit le comportement, tandis que le thread indique sur quel état ce comportement s’applique.

    Suivre une exécution du début à la panne puis jusqu’à la reprise

    En combinant l’état, les points de contrôle, le pointeur de vérification et les threads, on obtient une vue complète de ce qui se passe pendant l’exécution. Le diagramme ci-dessous (syntaxe Mermaid, affiché sous forme de texte) retrace une exécution, y compris la panne et le chemin de retour qui suit :

    flowchart TD
        A[1. Graph starts with invoke] --> B[2. Checkpointer checks thread_id for existing State]
        B --> C{State exists for this thread?}
        C -->|Yes| D[3a. Load last saved State]
        C -->|No| E[3b. Start with fresh empty State]
        D --> F[4. Node executes]
        E --> F
        F --> G[5. State updates in memory]
        G --> H[6. Checkpoint saved to storage]
        H --> I{More nodes to run?}
        I -->|Yes| F
        I -->|No| J[7. Return final result to caller]
        H -.->|💥 Crash happens here| K[Process restarts]
        K --> B
    

    En prose, la séquence est la suivante :

    1. Le graphe démarre. Vous appelez graph.invoke(input, config) avec un thread_id spécifique.
  • L’état est créé ou chargé. Le checkpointer vérifie si ce thread dispose déjà de points de contrôle. S’il en existe, le plus récent devient l’état de départ ; sinon, l’exécution commence à partir d’un état vide.
  • Un nœud s’exécute en utilisant l’état qui lui a été transmis.
  • L’état est mis à jour. Ce que le nœud renvoie est intégré à l’état existant.
  • Un point de contrôle est enregistré. Le checkpointer stocke le nouvel état sous l’ID du thread.
  • Le nœud suivant s’exécute, et les étapes 3 à 5 se répètent.
  • Une panne survient, disons juste après l’enregistrement du point de contrôle pour le nœud 2 mais avant que le nœud 3 ne commence.
  • La exécution reprend. En invoquant à nouveau le graphe sur le même thread, on charge le dernier point de contrôle écrit avec succès, celui qui suit le nœud 2, et l’exécution se poursuit avec le nœud 3, et non avec le nœud 1.
  • Aucun mode de récupération distinct à mettre en œuvre. Il suffit d’appeler à nouveau le graphe sur le même thread pour que LangGraph détermine où continuer.

    Un détail mérite d’être précisé. Pour reprendre une exécution qui a été interrompue ou échouée, vous invoquez le graphe en utilisant None comme entrée ainsi que la même configuration, ce qui indique à LangGraph de reprendre à partir du point de contrôle enregistré plutôt que de lancer une nouvelle exécution. L’envoi d’une entrée nouvelle sur un thread existant déclenche une nouvelle exécution qui s’appuie sur l’état enregistré : les clés dotées d’un réducteur, comme une liste de messages, s’accumulent, tandis que les clés simples sont remplacées par la nouvelle valeur. Vous pouvez examiner ce qui a été enregistré avec graph.get_state(config) pour obtenir le dernier snapshot et graph.get_state_history(config) pour la séquence complète.

    C’est ce même mécanisme qui permet à un agent d’arrêter délibérément, par exemple pour attendre une décision humaine, puis de reprendre son travail des heures ou des jours plus tard sur une machine différente, à condition que cette dernière puisse accéder au même stockage persistant. Pour voir concrètement ce fonctionnement, consultez Pauser et reprendre les agents LangGraph avec interrupt et Command.

    Le backend le plus simple : InMemorySaver

    LangGraph ne vous lie pas à un seul système de stockage. Il prend en charge plusieurs backends, c’est-à-dire des endroits où les points de contrôle sont physiquement conservés, et ils diffèrent principalement en termes de durabilité ainsi que du nombre de processus pouvant les partager. Le plus simple est InMemorySaver.

    Qu’est-ce que c’est et comment stocke-t-il les points de contrôle

    InMemorySaver est un mécanisme de point d’arrêt qui conserve chaque point d’arrêt dans la RAM du processus Python en cours, au sein d’un dictionnaire ordinaire en mémoire indexé par l’ID de thread. Il n’y a ni fichier ni base de données, seulement un objet Python.

    Le premier exemple utilise un état typé avec une seule clé count et un nœud qui y ajoute 1. Le nœud est relié de START à END, le graphe est compilé avec un InMemorySaver, et il est exécuté sur thread-1 avec un compteur de départ égal à 1, ce qui donne 2. La source le présente comme du JavaScript, mais il s’agit en réalité de Python ; notez également qu’il suppose que TypedDict, StateGraph, START, END et InMemorySaver ont été importés précédemment :

    class StateInt(TypedDict):
        count: int
    
    def add_one(state: StateInt) -> dict:
        return {"count": state["count"] + 1}
    
    builder = StateGraph(StateInt)
    builder.add_node("add_one", add_one)
    builder.add_edge(START, "add_one")
    builder.add_edge("add_one", END)
    
    memory = InMemorySaver()
    graph = builder.compile(checkpointer=memory)
    
    config = {"configurable": {"thread_id": "thread-1"}}
    result = graph.invoke({"count": 1}, config)
    print(result)  # {'count': 2}
    

    Le fragment suivant n’est qu’une légende mettant en contraste une version minimale du graphe avec celle, plus réaliste, présentée ci-dessus :

    Two ways to write the same graph — minimal vs. real-world.
    

    La variante minimale nécessite asyncio en plus du checkpointer et du constructeur de graphe :

    import asyncio
    from langgraph.checkpoint.memory import InMemorySaver
    from langgraph.graph import StateGraph
    

    Il utilise ensuite un simple int comme état global, enregistre une lambda en tant que nœud, marque ce nœud comme point d’entrée et de sortie, puis exécute le graphe de manière asynchrone à l’aide de ainvoke. Comme indiqué, plusieurs instructions ont été exécutées ensemble sur des lignes séparées (par exemple l’appel à set_finish_point et l’affectation à InMemorySaver()) ; il convient donc de les séparer avant d’exécuter le code ; ce fragment est également en Python, malgré son étiquette :

    builder = StateGraph(int)
    builder.add_node("add_one", lambda x: x + 1)
    builder.set_entry_point("add_one")
    builder.set_finish_point("add_one")memory = InMemorySaver()
    graph = builder.compile(checkpointer=memory)config = {"configurable": {"thread_id": "thread-1"}}
    result = asyncio.run(graph.ainvoke(1, config))
    print(result)  # Output: 2
    

    Passons en revue les lignes importantes :

    • InMemorySaver() crée un stockage de points de contrôle vide en mémoire.
  • builder.compile(checkpointer=memory) attache ce stockage au graphe, ce qui active la persistance.
  • config = {"configurable": {"thread_id": "thread-1"}} lie l’appel à une conversation spécifique.
  • graph.ainvoke(1, config) est le point d’entrée asynchrone ; ici, il est lancé avec l’intégralité 1 en tant qu’état sur le thread "thread-1".
  • L’appel répété avec le même thread_id fonctionne à partir des points de contrôle déjà enregistrés pour ce thread, plutôt qu’à partir d’un historique vide. Gardez à l’esprit la différence avec la section précédente : dans ce petit graphe, l’exécution est déjà terminée et l’état correspond à une seule valeur écrasée ; par conséquent, de nouvelles entrées la remplacent simplement, tandis qu’une entrée None permettrait de poursuivre une exécution inachevée.

    Avantages

    • Aucune configuration requise : aucune base de données ni service externe n’est nécessaire.
    • Très rapide, car il n’y a ni latence disque ni latence réseau.
    • Idéal pour les tests unitaires.
    • Pratique pour l’apprentissage et les expériences dans des notebooks.

    Limites

    • Tout disparaît lorsque le processus s’arrête. Il s’agit simplement de RAM, ce qui correspond exactement au problème décrit au début de ce guide.
    • Il ne peut pas être partagé entre processus ou serveurs, car chaque processus dispose de sa propre mémoire.
    • Il n’est pas sûr à utiliser en production, car un redémarrage ordinaire efface toutes les conversations.

    Quand l’utiliser

    Les utilisations appropriées sont :

    • le développement local et le débogage ;
    • les tests automatisés, tant les tests unitaires que les pipelines CI ;
    • les prototypes rapides et les notebooks où la survie après un redémarrage n’a pas d’importance.

    Tout ce sur quoi comptent les utilisateurs réels nécessite un mécanisme de point d’arrêt géré par une base de données. La documentation de LangGraph indique clairement que InMemorySaver est conçu pour le débogage et les tests, et recommande une implémentation fiable comme PostgresSaver pour le environnement de production. Mettre en place ce type d’implémentation, ainsi que d’autres systèmes de stockage comme SQLite et Redis, des pointeurs d’arrêt personnalisés et des flux d’approbation basés sur interrupt() et Command, constitue la prochaine étape logique ; un exemple orienté production montrant comment exécuter LangGraph sur Postgres et Redis est présenté dans Self-hosting a LangGraph agent server.

    Points clés

    • La persistance permet d’enregistrer l’état du graphe dans un stockage fiable, ce qui garantit que le processus continue de fonctionner en cas de panne, de redémarrage ou d’attente prolongée, sans avoir à tout recommencer.
  • Recommencer n’est pas seulement lent : cela répète les appels payants aux LLM et peut provoquer à nouveau des effets secondaires tels que des e-mails ou des paiements.
  • L’état correspond aux données partagées qui circulent dans le graphe, et c’est précisément ce qui est enregistré.
  • Un point de contrôle représente une capture d’écran de cet état après un super-étape ; un checkpointer crée, stocke et recharge automatiquement les points de contrôle une fois passé à compile().
  • Un thread_id isole une conversation ou une tâche, permettant ainsi à un même graphe compilé de servir en toute sécurité de nombreux utilisateurs.
  • Réprendre une exécution interrompue signifie invoquer le même thread avec None ; une nouvelle entrée sur un thread existant démarre une nouvelle exécution à partir de l’état enregistré.
  • InMemorySaver est idéal pour les tests et les prototypes, mais comme il vit en mémoire de processus, les systèmes de production nécessitent un checkpointer basé sur une base de données.
  • Lectures complémentaires