Accueil / Articles

LangGraph peut se compiler et réussir en sautant un nœud requis.

La fonction compile() de LangGraph vérifie le câblage, et non la complétude des opérations commerciales. Un graphe allant d’une commande à une facture peut fonctionner en sautant un nœud, il est donc nécessaire de s’assurer que les étapes requises ont été exécutées.

Éditeur : REACTAPP.TOP5 min de lecture1149 mots

Sources: The Node That Never Ran: What LangGraph Does Not Check at Compile Time

compile() valide — ainsi que dans l’absence de deux mécanismes de contrôle permettant de détecter ce problème.

La reproduction

graph.add_edge("create_purchase_order_from_request", "create_an_invoice")

Réexécutez :

Executing: load_purchase_request
Executing: validate_purchase_request_is_approved
Executing: create_purchase_order_from_request

L’exécution s’arrête après la création de la commande d’achat. La ligne correspondant à la facture n’apparaît jamais. L’état final reste à une valeur intermédiaire. Aucune exception n’est levée. Les utilisateurs voient un retour normal qui ne révèle pas l’absence du document financier.

Ce que compile() vérifie réellement

StateGraph.compile() vérifie que les arêtes font référence à des nœuds réels, que START existe bien, et que les destinations des arêtes sont des nœuds ou END. Cependant, il ne prouve pas que chaque nœud est accessible, que chaque nœud peut atteindre END, ou que chaque nœud est exécuté. Les tutoriels qui affirment que ces vérifications de connectivité décrivent pleinement les capacités de l’API exagèrent.

Une lacune, deux modes d’échec

  1. Nœud orphelin — enregistré mais sans arête entrante ; il n’est jamais exécuté ; la compilation réussit néanmoins.
  2. Chemin brisé vers END — la suppression d’une arête au milieu du graphe entraîne une réussite partielle silencieuse accompagnée d’un statut opérationnel non final.

Les deux cas semblent correspondre à des compilations « réussies ».

Pourquoi le moteur indique-t-il une réussite

LangGraph considère comme terminée une séquence qui atteint un nœud sans arêtes sortantes — ou qui revient sans provoquer d’erreur. Le succès signifie « le temps de exécution n’a pas planté », et non « tous les jalons métier ont été atteints ». Les champs de statut relèvent des conventions de l’application, sauf si cela est imposé.

Pourquoi END n’est pas suffisamment spécial

END est un marqueur terminal pour les arêtes qui le prévoient. Les nœuds qui s’arrêtent simplement ne provoquent pas automatiquement d’échec de l’appel. En l’absence d’assertions sur les jalons requis, les graphes partiels semblent fonctionnels.

Pourquoi le framework fait ce choix

L’analyse de la reachabilité et des nœuds obligatoires relève de problèmes de logique de programme. La bibliothèque valide le câblage structurel, puis laisse le code du produit définir quels nœuds sont nécessaires. Cela permet une compilation rapide et évite des règles métier subjectives — mais transfère la responsabilité aux tests.

Comment le détecter

Ajoutez une vérification explicite après l’exécution (ou au niveau du graphe) pour s’assurer que les nœuds requis ont été exécutés — via des compteurs dans l’état, des hooks de rappel ou un prédicat de fin d’exécution :

def find_detached_nodes(compiled):
    g = compiled.get_graph()
    sources = {e.source for e in g.edges}
    targets = {e.target for e in g.edges}
    dead_ends = [n for n in g.nodes if n not in sources and n != "__end__"]
    orphans  = [n for n in g.nodes if n not in targets and n != "__start__"]
    return dead_ends, orphans

Ajoutez également des tests unitaires qui suppriment délibérément des arêtes et s’attendent à ce que la protection échoue. Des vérifications statiques optionnelles peuvent examiner la liste d’adjacence pour déterminer si l’on peut atteindre chaque nœud requis depuis START jusqu’à END.

Points clés

  • compile() ≠ complétude fonctionnelle.
  • Preférez des plantages ou un statut d’échec explicite plutôt que des sauts silencieux pour les workflows financiers.
  • Encodez les jalons requis dans l’état et vérifiez-les avant d’indiquer un succès aux systèmes intermédiaires.
  • Dokumentez les lacunes pour chaque ingénieur qui transpose un tutoriel linéaire dans des processus financiers réels.

Association du bug aux mouvements d’argent

Purchase-to-invoice is unforgiving. If create_invoice never runs, accounts payable systems, tax exports, and vendor portals diverge. Returning success with status=purchase_order_created is an application smell: upstream services interpret non-exception returns as completion. Align HTTP/job status with a checklist of required node fingerprints stored in state.

A minimal required-node contract

Maintain state["executed"] as a set reducer. Each node adds its name. Before END (or in the caller after invoke), assert {"load","validate","create_po","create_invoice"} <= executed. Missing names raise a dedicated IncompleteGraphError that monitoring can page on. This turns the framework’s permissive success into a domain-hard failure.

Vérifications d’adjacence statique dans CI

Sérialisez la liste des arêtes du graphe dans les tests. Calculez la capacité d’accès à partir de START. Échouez le CI si un nœud requis est inaccessible ou ne peut pas atteindre END. Ces tests détectent les arêtes commentées dès l’après-midi de leur introduction, sans avoir à attendre un dépassement silencieux en production.

Ce que « compilé » doit signifier dans les guides de fonctionnement

Dokumentez pour l’équipe : « compilé » signifie que le schéma est syntaxiquement valide. « Complet » signifie que les nœuds métier requis ont été exécutés. Une formation qui mélange ces deux notions reproduit l’incident des factures manquantes. Associez ce document au fragment de code permettant de reproduire le problème, afin que les nouveaux contributeurs puissent délibérément perturber un graphe simplifié et observer le mécanisme de protection en action.

Péripéties connexes

  • Arêtes conditionnelles qui peuvent ne mener nulle part si un drapeau d’état n’est pas défini.
  • Essai répété d’appel sans suppression des effets secondaires partiels, création de PO dupliqués tout en continuant à ignorer les factures.
  • Dépendance exclusive aux captures d’écran de LangSmith au lieu d’assertions vérifiables par machine dans le CI.
  • Considérez la topologie du graphe comme des données de production. Validez-la comme des schémas — et non comme des commentaires optionnels.

    Examen d’un appel fonctionnel par rapport à un appel défaillant

    Dans un graphe fonctionnel, les appels en retour ou les instructions d’impression montrent l’étape charge → validation → création_po → création_invoice → marqueur de fin. Les opérateurs qui comparent les journaux à un manuel de procédure peuvent cocher chaque étape. Après la suppression d’une arête, le marqueur de facture n’apparaît jamais, pourtant l’appelant en Python reçoit toujours un dictionnaire. Les systèmes ultérieurs qui ne vérifient que le succès du transport enregistreront « terminé » dans l’outil de suivi. C’est ainsi que les sauts silencieux se transforment en incidents financiers.

    Conception d’enum de statut qui ne peuvent pas mentir

    Préférez des états de terminal définis uniquement par un nœud de finalisation dédié après que l’assertion required-set ait été validée : completed, failed_incomplete, failed_exception. Des états intermédiaires peuvent exister pour une interface utilisateur en flux continu, mais les API qui signalent l’avancement du travail aux autres services ne doivent émettre que completed une fois la vérification effectuée. Associez failed_incomplete à un ticket de compensation qui liste les noms des nœuds manquants.

    Interaction avec les outils de vérification

    Lorsque ces outils reprendent une exécution après un plantage, l’absence d’un bord entraîne toujours le même saut. La persistance ne crée pas de bords. Les tests de reprise doivent inclure des fixtures de mutation de topologie. De même, versionnez la définition du graphe en même temps que les données de vérification afin qu’un ancien point de contrôle ne soit pas réexécuté sur une topologie incompatible en l’absence de logique de migration.

    Apprendre à surmonter les lacunes sans crainte

    Les ingénieurs venant de langages où « compiler signifie que c’est correct » ont besoin d’une démonstration intentionnelle : briser une situation limite dans un environnement de test, observer le succès, puis activer les mécanismes de protection et constater l’échec. Une démonstration en direct évite des mois d’hypothèses sur la sécurité. Joignez ce script de démonstration à côté du README du répertoire concernant le processus d’achat à la facturation.

    Liste de contrôle avant de qualifier un graphe comme « terminé »

    1. Tout nœud métier requis apparaît dans la liste executed.
    2. Les routeurs conditionnels disposent de branches exhaustives, y compris un chemin d’échec explicite.
    3. Aucun nœud ne réalise de I/O irréversible sans clé d’idempotence stockée dans l’état.
    4. Le compilage réussit et les tests de connectivité réussissent dans l’environnement CI.
    5. Les résultats des appels incluent un indicateur de finition lisible par la machine, distinct du code HTTP 200.

    Lorsque ces cinq conditions sont remplies, les factures manquantes deviennent des défauts détectables plutôt que des succès silencieux. Telle est la norme opérationnelle dont ont besoin les applications LangGraph lorsque de l’argent ou du respect des réglementations sont en jeu — et non simplement un journal de compilation affichant « succès ».