Accueil / Articles / Les agents ReAct dans LangGraph : étape par étape, réflexion, action et observation.

Les agents ReAct dans LangGraph : étape par étape, réflexion, action et observation.

Mettre en œuvre le cycle ReAct sous forme de nœuds graphiques explicites avec un état typé, des appels d’outils et des conditions de arrêt pouvant être testées.

4143 mots

Ce guide permet de reconstruire un chemin opérationnel pour : ReAct Agents Explained : Une mise en œuvre pas à pas utilisant LangGraph. L’accent est mis sur les contrats, les vérifications et le code que l’on peut intégrer dans un dépôt sans devoir deviner l’intention. Pour une vue d’ensemble, définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans avoir à deviner l’état caché. Préférez des unités petites et testables plutôt que des scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité plutôt qu’un processus embrouillé.

Introduction

Pour l’introduction, définissez les entrées, le responsable de l’étape et les critères de fin avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Nommez les artefacts, définissez des vérifications de succès et refusez toute exécution partielle silencieuse. Séparez la planification de l’exécution par les outils. Le planificateur propose ; l’exécutant modifie ; le vérificateur compare les résultats à l’objectif.

Qu’est-ce qu’un agent ReAct ?

Pour « Qu’est-ce qu’un agent ReAct ? », il convient de définir les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu, sans avoir à deviner l’état caché. Enregistrez les temps d’exécution et les coûts à côté des résultats fonctionnels. Une visibilité précoce évite des factures inattendues lorsque le parcours passe d’un environnement de démonstration à un environnement partagé. Séparez la planification de l’exécution des outils : le planificateur propose ; l’exécutant modifie ; le vérificateur contrôle les résultats par rapport à l’objectif.

Pourquoi ReAct est meilleur que la simple chaîne de réflexion

Pour comprendre pourquoi ReAct est supérieur au Chain-of-Thought pur, il faut définir les entrées, le responsable de chaque étape ainsi que les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Conservez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans avoir à lire l’ensemble du système. Séparez la planification de l’exécution des outils. Le planificateur propose ; l’exécutant met à jour ; le vérificateur contrôle les résultats par rapport à l’objectif. Pour comprendre pourquoi ReAct est supérieur au Chain-of-Thought pur, il faut définir les entrées, le responsable de chaque étape ainsi que les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Préférez des unités petites et testables aux scripts complexes. Lorsqu’une étape échoue, l’échec doit permettre d’identifier une seule responsabilité.

plutôt qu’un pipeline enchevêtré.

Thought: I don’t know the answer yet. I should search.
Action: Search("Paris weather this week")
Observation: It will rain on Thursday.
Thought: I should suggest indoor activities.
Final Answer: ...

ReAct Prompting

Au sein de ReAct Prompting, il convient de définir les entrées, le responsable de chaque étape ainsi que les critères d’arrêt avant de modifier du code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu, sans avoir à deviner l’état caché. Considérez cette phase comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définites des vérifications de succès et refusez les complétions partielles silencieuses. Restreignez strictement les schémas des outils. Des arguments de texte libre trop larges favorisent les injections et rendent les audits coûteux.

Objectif de ReAct Prompting

Afin d’utiliser la technique ReAct Prompting, il convient de définir les entrées, le responsable de chaque étape ainsi que les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu, sans avoir à deviner l’état caché. Il faut enregistrer les temps d’exécution et les coûts à côté des résultats fonctionnels. Une visibilité précoce permet d’éviter des factures inattendues lorsque le processus passe de l’environnement de démonstration à des environnements partagés. Les schémas des outils doivent être strictement encadrés ; des arguments de texte libre trop larges favorisent les injections et rendent les audits coûteux.

Éléments clés de ReAct Prompting

Pour les éléments clés de l’approche ReAct Prompting, définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier du code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Conservez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans avoir à lire l’ensemble du système. Restreignez strictement les schémas des outils. Des arguments de texte libre trop larges favorisent les injections et rendent les audits coûteux. Pour les éléments clés de l’approche ReAct Prompting, définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier du code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Préférez des unités petites et testables aux scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité plutôt qu’un processus embrouillé.

1. Raisonnement par chaîne de pensée

Pour le 1. Raisonnement par chaîne de pensée, il convient de définir les entrées, le responsable de chaque étape ainsi que les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Nommez les artefacts, définissez des vérifications de succès et refusez toute exécution partielle silencieuse. Créez un point de contrôle après chaque appel coûteux au modèle afin qu’un nouvel essai ne génère pas de frais supplémentaires pour le même travail.

2. Espace d’action explicite

2. Espace d’action explicite : définissez les entrées, le responsable de l’étape et les critères de sortie avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu, sans avoir à deviner l’état caché. Enregistrez les temps d’exécution et les coûts à côté des résultats fonctionnels. Une visibilité précoce évite des factures inattendues lorsque le parcours passe d’un environnement de démonstration à un environnement partagé. Prévoyez un point de contrôle après des appels de modèle coûteux afin qu’un nouvel essai ne génère pas à nouveau des frais pour le même travail.

3. Intégration de l’observation

Pour 3. Intégration des observations, définissez les entrées, le responsable de l’étape et les critères de sortie avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Conservez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans devoir lire l’ensemble du système. Créez un point de contrôle après des appels coûteux au modèle afin qu’un nouvel essai ne reprenne pas le même travail. Pour 3. Intégration des observations, définissez les entrées, le responsable de l’étape et les critères de sortie avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Préférez des unités petites et testables plutôt que des scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité et non un pipeline embrouillé.

4. Boucles itératives

Pour la 4e étape : boucle itérative, définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Considérez cette phase comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définites des vérifications de succès et refusez toute exécution partielle silencieuse. Séparez la planification de l’exécution par les outils. Le planificateur propose ; l’exécutant modifie ; le vérificateur compare les résultats à l’objectif.

5. Génération de la réponse finale

Pour la 5e étape : génération de la réponse finale, définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Enregistrez les temps d’exécution et les coûts à côté des résultats fonctionnels. Une visibilité précoce évite des factures inattendues lorsque le processus passe d’un environnement de démonstration à un environnement partagé. Séparez la planification de l’exécution des outils : le planificateur propose, l’exécutant modifie, et le vérificateur contrôle les résultats par rapport à l’objectif.

Structure canonique du prompt ReAct

Pour une structure de prompt ReAct canonique, définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Conservez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans devoir lire l’ensemble du système. Séparez la planification de l’exécution des outils. Le planificateur propose ; l’exécutant modifie ; le vérificateur contrôle les résultats par rapport à l’objectif. Pour une structure de prompt ReAct canonique, définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Préférez des unités petites et testables plutôt que des scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité plutôt qu’un ensemble d’éléments entremêlés.

Question: <user question>Thought: <reason about what to do next>

Action: <selected tool>
Action Input: <tool input>
Observation: <tool output>
... (repeat as needed)
Thought: I now know the final answer
Final Answer: <answer to the user>

Incitation ReAct sans exemple préalable

Pour l’incitation ReAct sans exemple préalable, il faut définir les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définites des vérifications de succès et refusez les complétions partielles silencieuses. Restreignez strictement les schémas des outils. Des arguments de texte libre trop larges favorisent les injections et rendent les audits coûteux.

Incitation ReAct vs Agents ReAct

Pour ReAct Prompting par rapport à ReAct Agents, il convient de définir les entrées, le responsable de chaque étape ainsi que les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu, sans avoir à deviner l’état caché. Enregistrez les temps d’exécution et les coûts à côté des résultats fonctionnels. Une visibilité précoce permet d’éviter des factures inattendues lorsque le processus passe de l’environnement de démonstration à des environnements partagés. Restreignez strictement les schémas des outils ; des arguments de texte libre trop larges facilitent les injections et rendent les audits coûteux.

Pourquoi LangGraph pour ReAct Agents ?

Pour comprendre pourquoi choisir LangGraph pour les agents ReAct, il convient de définir les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Conservez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans avoir à lire l’ensemble du graphe. Restreignez strictement les schémas des outils. Des arguments de texte libre trop larges favorisent les injections et rendent les audits coûteux. Pour comprendre pourquoi choisir LangGraph pour les agents ReAct, il convient de définir les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Préférez des unités petites et testables plutôt que des scripts complexes. Lorsqu’une étape échoue, l’échec doit pointer vers une seule responsabilité et non vers un processus embrouillé.

Le problème fondamental : ReAct est une machine à états, pas un prompt

Pour le problème fondamental : ReAct est une machine à états, pas un prompt, il convient de définir les entrées, le responsable de chaque étape ainsi que les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Considérez cette phase comme un contrat entre les entrées et les sorties validées. Nommez les artefacts, définissez des vérifications de succès et refusez toute complétion partielle silencieuse. Créez un point de contrôle après chaque appel coûteux au modèle afin qu’un nouvel essai ne reprenne pas le même travail.

Ce qui ne fonctionne plus sans LangGraph

Pour ce qui échoue sans LangGraph, définissez les entrées, le responsable de l’étape et les critères de sortie avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Enregistrez les temps d’exécution et les coûts à côté des résultats fonctionnels. Une visibilité précoce évite des factures inattendues lorsque le chemin passe d’un environnement de démonstration à un environnement partagé. Créez un point de contrôle après des appels de modèle coûteux afin qu’un nouvel essai ne facture pas à nouveau le même travail.

1. Flux de contrôle implicite

Pour 1. Flux de contrôle implicite, définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Conservez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans avoir à lire l’ensemble du système. Créez un point de contrôle après des appels coûteux au modèle afin qu’un nouvel essai ne fasse pas payer à nouveau le même travail. Pour 1. Flux de contrôle implicite, définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Préférez des unités petites et testables aux scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité plutôt qu’un processus embrouillé.

while True:
    llm_output = llm(prompt)
    if "Action:" in llm_output:
        tool_result = call_tool(...)
    else:
        break

2. Gestion fragile de l’état

Pour 2. Gestion fragile de l’état, définissez les entrées, le responsable de l’étape et les critères de sortie avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Nommez les artefacts, définissez des vérifications de succès et refusez toute complétion partielle silencieuse. Séparez la planification de l’exécution des outils. Le planificateur propose ; l’exécutant modifie ; le vérificateur compare les résultats à l’objectif.

3. Aucune sémantique de boucle de première classe

Pour le point 3 : en l’absence de sémantique de boucle de première classe, il convient de définir les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu, sans avoir à deviner l’état caché. Enregistrer les temps d’exécution et les coûts à côté des résultats fonctionnels permet d’éviter des factures inattendues lorsque le parcours passe de l’environnement de démonstration à des environnements partagés. Séparer la planification de l’exécution par l’outil : le planificateur propose, l’exécutant modifie, et le vérificateur contrôle les résultats par rapport à l’objectif.

4. Faible prêt pour la production

Pour le point 4 : faible prêt à la production, définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier du code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Conservez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans devoir lire l’ensemble du système. Séparez la planification de l’exécution des outils. Le planificateur propose ; l’exécutant modifie ; le vérificateur contrôle les résultats par rapport à l’objectif. Pour le point 4 : faible prêt à la production, définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier du code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Préférez des unités petites et testables aux scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité plutôt qu’un processus embrouillé.

Concepts clés dans LangGraph (vue axée sur l’agent)

Pour les concepts clés dans LangGraph (vue axée sur l’agent), il convient de définir les entrées, le responsable de chaque étape ainsi que les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Considérez cette phase comme un contrat entre les entrées et les sorties validées : donnez des noms aux artefacts, définites des vérifications de succès et refusez toute complétion partielle silencieuse. Restreignez strictement les schémas des outils ; des arguments de texte libre trop larges facilitent les injections et rendent les audits coûteux.

1. État : la mémoire de l’agent

Pour l’état 1 : La mémoire de l’agent, il faut définir les entrées, le responsable de l’étape et les critères de sortie avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Enregistrer les temps d’exécution et les coûts à côté des résultats fonctionnels permet d’éviter des factures inattendues lorsque le parcours passe de l’environnement de démonstration à des environnements partagés. Restreignez strictement les schémas des outils ; des arguments de texte libre trop larges facilitent les injections et rendent les audits coûteux.

2. Nœuds : unités cognitives et opérationnelles

Pour les nœuds 2 : unités cognitives et opérationnelles, définissez les entrées, le responsable de l’étape ainsi que les critères de sortie avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Conservez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les indicateurs fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans avoir à lire l’ensemble du système. Restreignez strictement les schémas des outils. Des arguments de texte libre trop larges favorisent les injections et rendent les audits coûteux. Pour les nœuds 2 : unités cognitives et opérationnelles, définissez les entrées, le responsable de l’étape ainsi que les critères de sortie avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Préférez des unités petites et testables aux scripts complexes. Lorsqu’une étape échoue, l’échec doit pointer vers une seule responsabilité plutôt que vers un processus embrouillé.

3. Bords : flux de contrôle explicite

Pour la section 3. Bords : flux de contrôle explicite, il faut définir les entrées, le responsable de l’étape et les critères de sortie avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définites des vérifications de succès et refusez les terminations partielles silencieuses. Créez un point de contrôle après des appels coûteux au modèle afin qu’un nouvel essai ne facture pas à nouveau le même travail.

4. Exécution déterministe avec flexibilité

Pour 4. Exécution déterministe avec flexibilité, définissez les entrées, le responsable de l’étape et les critères de sortie avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Enregistrez les temps d’exécution et les coûts à côté des résultats fonctionnels. Une visibilité précoce évite des factures inattendues lorsque le parcours passe d’un environnement de démonstration à un environnement partagé. Prévoyez un point de contrôle après des appels de modèles coûteux afin qu’un nouvel essai ne fasse pas payer à nouveau le même travail.

ReAct + LangGraph : une combinaison parfaite

Pour ReAct + LangGraph : une combinaison idéale. Définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Conservez la configuration en dehors du code de l’application. Les fichiers d’environnement, les bases de données secrètes et les flags fonctionnels doivent être regroupés en un seul endroit que les opérateurs peuvent auditer sans devoir lire l’ensemble du graphe. Créez un point de contrôle après des appels coûteux au modèle afin qu’un nouvel essai ne reprenne pas le même travail. Pour ReAct + LangGraph : une combinaison idéale. Définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Préférez des unités petites et testables plutôt que des scripts complexes. Lorsqu’une étape échoue, l’échec doit indiquer une seule responsabilité et non un pipeline embrouillé.

Cas d’usage : Assistant à l’annulation d’hôtel (politique + calcul du remboursement)

Pour le cas d’usage « Assistant à l’annulation d’hôtel (politique + calcul du remboursement) », il convient de définir les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Considérez cette étape comme un contrat entre les entrées et les sorties validées. Donnez des noms aux artefacts, définites des vérifications de succès et refusez les terminaisons partielles silencieuses. Séparez la planification de l’exécution des outils. Le planificateur propose ; l’exécutant modifie ; le vérificateur compare les résultats à l’objectif.

Énoncé du problème

Pour la description du problème, définissez les entrées, le responsable de l’étape et les critères d’arrêt avant de modifier le code. Les opérateurs doivent pouvoir relancer l’étape à partir d’un point de contrôle connu sans deviner l’état caché. Séparez la planification de l’exécution des outils. Le planificateur propose ; l’exécutant modifie ; le vérificateur compare les résultats à l’objectif.

Étape 1 : Installer les dépendances

Séparez la planification de l’exécution des outils. Le planificateur propose ; l’exécutant modifie ; le vérificateur compare les résultats à l’objectif.

pip install -U langgraph langchain langchain-openai
export OPENAI_API_KEY="..."

Étape 2 : Définir les outils (vos « Actions »)

Restreignez strictement les schémas des outils. Des arguments de texte libre trop larges favorisent les injections et rendent les audits coûteux.

from typing import TypedDict, Annotated
from datetime import datetime
import json

from pydantic import BaseModel
from langchain_openai import ChatOpenAI
from langchain_core.messages import (
    BaseMessage,
    HumanMessage,
    ToolMessage,
    SystemMessage
)
from langchain_core.tools import tool
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langgraph.prebuilt import tools_condition

@tool
def get_cancellation_policy(rate_plan: str) -> str:
    """
    Returns cancellation policy text for a given rate plan.
    """
    policies = {
        "flexible": "Free cancellation until 24 hours before check-in. After that, first night is charged.",
        "semi-flex": "Free cancellation until 72 hours before check-in. After that, 50% of the stay is charged.",
        "non-refundable": "No refund after booking. Full stay amount is charged on cancellation."
    }
    key = rate_plan.strip().lower()
    return policies.get(key, "Policy not found. Supported: flexible, semi-flex, non-refundable.")
@tool
def calculate_refund(
    rate_plan: str,
    check_in: str,
    cancel_date: str,
    nightly_rate: float,
    nights: int
) -> str:
    """
    Calculates refund amount based on a simplified policy model.
    Dates format: YYYY-MM-DD
    """
    rp = rate_plan.strip().lower()
    ci = datetime.strptime(check_in, "%Y-%m-%d").date()
    cd = datetime.strptime(cancel_date, "%Y-%m-%d").date()
    total = nightly_rate * nights
    days_before = (ci - cd).days
    if rp == "non-refundable":
        refund = 0.0
        charged = total
        rule = "Non-refundable: no refund."
    elif rp == "flexible":
        if days_before >= 1:
            refund = total
            charged = 0.0
            rule = "Flexible: cancelled >= 24h before check-in, full refund."
        else:
            charged = nightly_rate  # 1 night penalty
            refund = max(total - charged, 0.0)
            rule = "Flexible: late cancel, 1 night charged."
    elif rp == "semi-flex":
        if days_before >= 3:
            refund = total
            charged = 0.0
            rule = "Semi-flex: cancelled >= 72h before check-in, full refund."
        else:
            charged = 0.5 * total
            refund = total - charged
            rule = "Semi-flex: late cancel, 50% charged."
    else:
        return "Unsupported rate plan. Use: flexible, semi-flex, non-refundable."
    return (
        f"Rule: {rule}\n"
        f"Days before check-in: {days_before}\n"
        f"Total: ${total:.2f}\n"
        f"Charged: ${charged:.2f}\n"
        f"Refund: ${refund:.2f}"
    )

Étape 3 : Créer un cycle ReAct dans LangGraph (Raisonnement → Outil → Raisonnement)

Restreignez strictement les schémas des outils. Des arguments de texte libre trop larges favorisent les injections et rendent les audits coûteux.

from typing import TypedDict, Annotated
from langchain_core.messages import BaseMessage, HumanMessage
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages

from langchain_openai import ChatOpenAI
from langchain_core.tools import Tool
from langgraph.prebuilt import ToolNode, tools_condition
# 1) Define state
class AgentState(TypedDict):
    messages: Annotated[list[BaseMessage], add_messages]
    booking_id: str
# 2) Define structured Output schema
class RefundDecision(BaseModel):
    booking_id: str
    rate_plan: str
    total_amount: float
    charged_amount: float
    refund_amount: float
    policy_summary: str
    explanation: str
# 2) Choose model and System prompt
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
SYSTEM_PROMPT = SystemMessage(
    content="""
You are a hotel cancellation assistant.
Rules:
- Use tools when needed.
- Never guess policy or refund.
- Final answer MUST be valid JSON with this schema:
{
  "booking_id": "...",
  "rate_plan": "...",
  "total_amount": number,
  "charged_amount": number,
  "refund_amount": number,
  "policy_summary": "...",
  "explanation": "..."
}
"""
)
# 3) Register tools
tools = [get_cancellation_policy, calculate_refund]
def safe_tool_node(state):
    last_msg = state["messages"][-1]
    if not hasattr(last_msg, "tool_calls") or not last_msg.tool_calls:
        return {}
    tool_call = last_msg.tool_calls[0]
    tool_name = tool_call["name"]
    if tool_name not in ALLOWED_TOOLS:
        return {
            "messages": [
                ToolMessage(
                    content=f"Tool '{tool_name}' is not allowed.",
                    tool_call_id=tool_call["id"]
                )
            ]
        }
    for tool in tools:
        if tool.name == tool_name:
            result = tool.invoke(tool_call["args"])
            return {
                "messages": [
                    ToolMessage(
                        content=result,
                        tool_call_id=tool_call["id"]
                    )
                ]
            }
# 4)Before reasoning, check if we already processed this booking.
REFUND_MEMORY = {}
def memory_lookup_node(state):
    booking_id = state["booking_id"]
    if booking_id in REFUND_MEMORY:
        return {
            "messages": [
                HumanMessage(
                    content=f"Cached decision found:\n{REFUND_MEMORY[booking_id]}"
                )
            ]
        }
    return {}
# 5) Reasoning node: LLM decides next action (tool call) or final answer
def agent_node(state: AgentState):
    # Bind tools so the model can produce tool calls
    llm_with_tools = llm.bind_tools(tools)
    response = llm_with_tools.invoke(state["messages"])
    return {"messages": [response]}
# 6) After final decision, store it.
def memory_write_node(state):
    booking_id = state["booking_id"]
    final_answer = state["messages"][-1].content
    REFUND_MEMORY[booking_id] = final_answer
    return {}

Construire et compiler le graphe

Les schémas des outils doivent être strictement définis. Des arguments de texte libre trop larges favorisent les injections et rendent les audits coûteux.

# 7) Build the graph
builder = StateGraph(AgentState)

# Nodes
builder.add_node("memory_lookup", memory_lookup_node)
builder.add_node("agent", agent_node)
builder.add_node("tools", safe_tool_node)
builder.add_node("memory_write", memory_write_node)
# Flow
builder.add_edge(START, "memory_lookup")
builder.add_edge("memory_lookup", "agent")
builder.add_conditional_edges(
    "agent",
    tools_condition,
    {
        "tools": "tools",   # model wants to act
        END: "memory_write" # model finished reasoning
    }
)
builder.add_edge("tools", "agent")
builder.add_edge("memory_write", END)
graph = builder.compile()

Étape 4 : Exécuter l’agent sur le cas d’utilisation

Les schémas des outils doivent être strictement définis. Des arguments de texte libre trop larges favorisent les injections et rendent les audits coûteux.

query = """
Booking details:
Rate plan: Non-Refundable
Check-in: 2026-01-20
Nights: 2
Nightly rate: 120
Cancelled on: 2026-01-18
"""

result = graph.invoke({
    "booking_id": "BKG-12345",
    "messages": [
        SYSTEM_PROMPT,
        HumanMessage(content=query)
    ]
})
final_output = result["messages"][-1].content
print(final_output)

Résultat

Les schémas des outils doivent être strictement définis. Des arguments de texte libre trop larges favorisent les injections et rendent les audits coûteux.

{
  "booking_id": "BKG-12345",
  "rate_plan": "Non-Refundable",
  "total_amount": 240.0,
  "charged_amount": 240.0,
  "refund_amount": 0.0,
  "policy_summary": "Non-refundable bookings do not allow refunds after confirmation.",
  "explanation": "The booking was made under a non-refundable rate plan, which charges the full stay amount regardless of cancellation timing."
}

Ce qui se passe en interne (comportement ReAct)

Les schémas des outils doivent être strictement définis. Des arguments de texte libre trop larges favorisent les injections et rendent les audits coûteux.

1) Réflexion (Raisonnement)

Les schémas des outils doivent être strictement définis. Des arguments de texte libre trop larges favorisent les injections et rendent les audits coûteux.

2) Action (Appel d’outil)

Les schémas des outils doivent être strictement définis. Des arguments de texte libre trop larges favorisent les injections et rendent les audits coûteux.

3) Observation (Résultats des outils)

Point de contrôle après les appels coûteux au modèle afin qu’un nouvel essai ne facture pas à nouveau le même travail.

4) Réponse finale

Point de contrôle après les appels coûteux au modèle afin qu’un nouvel essai ne facture pas à nouveau le même travail.

Pourquoi c’est « ReAct » (et pas seulement des outils)

Point de contrôle après les appels coûteux au modèle afin qu’un nouvel essai ne facture pas à nouveau le même travail.

Conclusion

Séparer la planification de l’exécution des outils. Le planificateur propose ; l’exécutant modifie ; le vérificateur contrôle les résultats par rapport à l’objectif.

Liste de vérification opérationnelle

Restreindre strictement les schémas des outils. Des arguments de texte libre trop larges facilitent les injections et rendent les audits coûteux.

Les arêtes conditionnelles doivent encoder les règles métier sous forme de fonctions nommées, et non dans du texte d’instruction caché.

Colocaliser les types avec les composants et limiter le nombre de propriétés. Des ensembles de propriétés trop larges deviennent la dette que TypeScript était conçu pour éviter.

Rédigez un petit guide opérationnel : comment faire tourner les clés, comment vider la file d’attente, comment annuler la dernière modification.