Un agent LangGraph qui gère des réservations doit quitter la mémoire vive pour une vraie base de données. Sinon, vos données disparaissent au prochain redémarrage.

UN AGENT QUI FONCTIONNE COMME UN VRAI SERVICE CLIENT

Imaginez un agent LangGraph qui gère un processus de réservation de 15 minutes comme un vrai conseiller clientèle. Il orchestrerait toutes les étapes : prise de rendez-vous, vérification des créneaux, confirmation de la réservation. Jusqu’ici, c’était une démo fonctionnelle avec une interface Streamlit pour améliorer l’expérience utilisateur.

Mais une démo, même impressionnante, ne suffit pas pour un produit réel. Il manquait la pièce maîtresse : un backend solide qui conserve les données de réservation pour de vrai. Sans cela, votre agent reste un prototype qui s’efface dès qu’on ferme l’application.

Une base de données en mémoire vive, c’est comme un post-it : pratique pour les tests, mais inutile pour une entreprise.

LE PROBLÈME DE LA MÉMOIRE VIVE : DES DONNÉES QUI DISPARAISSENT

Dans la version initiale, l’agent utilisait deux objets Python simples qui vivaient dans la mémoire vive de l’ordinateur. Le premier s’appelait un checkpointer : une couche de persistance qui sauvegarde l’état du graphe à chaque étape de l’exécution.

graph.compile(checkpointer=checkpointer or MemorySaver())

Grâce à lui, l’agent peut reprendre une conversation là où elle s’était arrêtée. Sans lui, chaque message du client serait considéré comme le début d’une nouvelle conversation. Mais attention : si l’application redémarre, tout disparaît.

Le second objet était une simple liste Python protégée par un verrou (threading.RLock). Elle stockait les réservations confirmées dans la mémoire vive :

class InMemoryBookingRepository:
    def __init__(self) -> None:
        self._lock = threading.RLock()
        self.technicians = {.}  # des techniciens codés en dur
        self._bookings: list[Booking] = []

    def list_bookings(self) -> list[Booking]:
        with self._lock:
            return list(self._bookings)

    def create_booking(self, option, details, price) -> Booking:
        # vérification des chevauchements en Python, puis ajout à self._bookings
        .

Cette structure ultra-simple était parfaite pour des tests rapides et des démos locales. Mais elle avait un défaut majeur : tout disparaît dès que le processus s’arrête. Pire encore, comme tout est en mémoire, deux sessions utilisateur ne voient pas les mêmes données. Si un client réserve un créneau dans une session, un autre client dans une autre session ne le verra pas et pourrait réserver le même créneau.

POURQUOI IL FAUT UNE VRAIE BASE DE DONNÉES : L’EXEMPLE DES RÉSERVATIONS

Prenons l’exemple d’un créneau de nettoyage disponible à 14h. Votre agent propose ce créneau à un client. Pendant ce temps, un autre client, dans une autre session, réserve le même créneau. Dans la version mémoire, l’agent ne voit pas la réservation du second client et confirme le créneau à tort. Résultat : deux réservations pour le même créneau, et deux clients mécontents.

Avec une vraie base de données comme PostgreSQL, toutes les sessions voient les mêmes données en temps réel. Plus de chevauchements, plus de données perdues au redémarrage. Et surtout, votre agent peut enfin être utilisé comme un produit réel, pas comme une démo.

LE CHOIX DE POSTGRESQL : GRATUIT, PUISSANT ET FIABLE

PostgreSQL est un système de gestion de base de données relationnelle gratuit et open source. Il permet de stocker les informations sur les techniciens et les réservations dans des tables séparées mais liées entre elles. Contrairement à une simple liste en mémoire, PostgreSQL garantit :

  • La persistance des données même après un redémarrage
  • L’accès simultané par plusieurs utilisateurs
  • La cohérence des données (pas de chevauchements de réservations)

Avant d’implémenter PostgreSQL, la structure de l’agent ressemblait à ceci : un graphe LangGraph avec une mémoire vive pour l’état et une liste pour les réservations. Après l’implémentation de PostgreSQL, la structure devient plus complexe mais bien plus robuste :

Schéma comparatif avant/après l'implémentation de PostgreSQL

CRÉER UNE INTERFACE STABLE : LE PROTOCOLE DE RÉSERVATION

Pour que le graphe et les nœuds de l’agent ne dépendent pas directement de PostgreSQL (ou de la mémoire vive), on utilise un protocole en Python. Ce protocole définit une interface stable que tous les systèmes de persistance doivent respecter :

from typing import Protocol

class BookingRepository(Protocol):
    """Interface de persistance utilisée par la planification et la confirmation."""

    @property
    def technicians(self) -> dict[str, Technician]:
        """Retourne les techniciens indexés par leur identifiant."""

    def list_bookings(self) -> list[Booking]:
        """Retourne toutes les réservations confirmées."""

    def create_booking(
        self, option: TimeOption, details: BookingDetails, price: float
    ) -> Booking:
        """Persiste une réservation après avoir vérifié les chevauchements ; lève une erreur si le créneau est déjà pris."""

Avec ce protocole, on peut brancher soit PostgresBookingRepository, soit InMemoryBookingRepository au démarrage de l’application. Les nœuds du graphe appellent ensuite les méthodes list_bookings() et create_booking(), sans se soucier de savoir si les données sont en mémoire ou dans PostgreSQL.

DEUX MODES DE FONCTIONNEMENT : MÉMOIRE VIVE OU POSTGRESQL

Avec InMemoryBookingRepository, aucune table de base de données n’est créée. Les réservations confirmées sont stockées dans une liste Python à l’intérieur du processus en cours. Les méthodes list_bookings() et create_booking() fonctionnent exactement comme avec PostgreSQL, sauf qu’elles ne touchent jamais à une base de données.

Ce mode mémoire est idéal pour :

  • Les tests unitaires
  • Les démos locales rapides
  • Le Développement sans dépendance à PostgreSQL

Mais attention : tout disparaît dès que l’application redémarre. C’est pourquoi ce mode ne convient pas pour un produit en production.

Avec PostgresBookingRepository, il y a une vraie base de données. Le schéma de la base se compose de deux tables : technicians et bookings. La classe PostgresBookingRepository définit les méthodes list_bookings() et create_booking() qui interagissent avec ces tables.

DÉMARRER AVEC LA BONNE PERSISTANCE

Au démarrage de l’application, la fonction create_persistence() choisit automatiquement entre PostgreSQL et la mémoire vive en fonction de la présence d’une variable d’environnement DATABASE_URL :

  • Si DATABASE_URL est défini, le système utilise PostgreSQL pour le dépôt de réservations et le checkpointer de LangGraph.
  • Sinon, tout reste en mémoire vive.

Le dépôt de réservations est donc soit un PostgresBookingRepository, soit un InMemoryBookingRepository. Les deux respectent le protocole BookingRepository. Cette instance est ensuite passée à la fonction build_graph() qui construit le graphe de l’agent :

def build_graph(
    llm: BaseChatModel,
    *,
    repository: BookingRepository | None = None,
    checkpointer: Any | None = None,
) -> Any:
    """Construire un graphe de réservation multi-tours compilé."""
    repository = repository or InMemoryBookingRepository()
    graph = StateGraph(AgentState)
    # . (le code est tronqué ici)

COMMENT LES NŒUDS INTERAGISSENT AVEC LA BASE DE DONNÉES

Seuls deux nœuds du graphe communiquent directement avec le dépôt de réservations (BookingRepository) :

  1. generatescheduleoptions_node : ce nœud appelle la fonction generatescheduleoptions() pour proposer des créneaux disponibles.
  2. confirmbookingnode : ce nœud crée une réservation dans la base de données après confirmation du client.

Les autres nœuds ne font que lire ou mettre à jour l’état de l’agent (AgentState). Ils ne consultent jamais la table des réservations.

PROPOSER DES CRÉNEAUX : LE NŒUD DE PLANIFICATION

Le nœud generatescheduleoptions_node utilise la fonction generatescheduleoptions() pour proposer des créneaux optimisés au client :

def generatescheduleoptions_node(state: AgentState) -> dict[str, Any]:
    options = generatescheduleoptions(state["booking_details"], repository)
    lines = ["Super—choisissez l'un de ces rendez-vous optimisés :"]
    for index, option in enumerate(options, 1):
        lines.append(f"{index}. {option.startat} ({option.technicianid})")
    return {
        "time_options": options,
        "status": "awaitingslotselection",
        "messages": [AIMessage(content="\n".join(lines))],
    }

Ce nœud appelle repository.list_bookings() pour éviter les chevauchements de réservation. Ensuite, il génère une liste de créneaux disponibles et met à jour l’état de l’agent avec time_options, status et messages.

CONFIRMER UNE RÉSERVATION : LE NŒUD DE VALIDATION

Une fois que le client a choisi un créneau, le nœud selectslotnode ne fait que mettre à jour l’état de l’agent avec le créneau sélectionné. La réservation elle-même est créée dans le nœud confirmbookingnode :

def confirmbookingnode(state: AgentState) -> dict[str, Any]:
    option = state.get("selected_slot")
    if option is None:
        raise ValueError("Un créneau doit être sélectionné avant confirmation.")
    booking = repository.create_booking(
        option, state["bookingdetails"], float(state["calculatedprice"])
    )
    return {
        "booking_id": booking.id,
        "status": "confirmed",
        "messages": [
            AIMessage(
                content=(
                    f"Confirmé . Réservation {booking.id} programmée pour "
                    f"{option.start_at}. Votre total est de ${booking.price:.2f}."
                )
            )
        ],
    }

Ce nœud utilise repository.create_booking() pour créer la réservation dans la base de données. Si tout se passe bien, LangGraph fusionne l’identifiant de la réservation, le statut confirmed et le message de confirmation dans l’état de l’agent. Le checkpointer sauvegarde alors cet état pour la conversation.

L’ÉTAT DE L’AGENT : LA MÉMOIRE DE TRAVAIL

L’AgentState est comme la mémoire de travail de l’agent. Il contient toutes les informations nécessaires pour gérer une conversation :

class AgentState(TypedDict):
    messages: Annotated[list[AnyMessage], add_messages]
    booking_details: BookingDetails
    calculated_price: NotRequired[float | None]
    time_options: NotRequired[list[TimeOption]]
    selected_slot: NotRequired[TimeOption | None]
    status: BookingStatus
    booking_id: NotRequired[str | None]

Chaque nœud du graphe retourne une mise à jour partielle de cet état. LangGraph fusionne ces mises à jour dans l’état global. Le checkpointer, lui, persiste cet état dans PostgreSQL à chaque étape de la conversation.

DE L’IDÉE À LA PRODUCTION : UN PRODUIT RÉEL

Avec ce backend en PostgreSQL, votre agent LangGraph passe du statut de démo à celui de produit réel. Plus de perte de données au redémarrage, plus de conflits de réservation, et surtout, la possibilité de connecter plusieurs interfaces utilisateur (Streamlit, WhatsApp, etc.) au même backend.

Dans le prochain article, on verra comment déployer cette solution avec Docker et comment pointer l’application vers une instance PostgreSQL hébergée.

Un agent IA sans base de données, c'est comme une entreprise sans fichier client : ça ne tient pas la route.

ET APRÈS ? VERS UNE AUTOMATISATION HUMAINE

Ce projet s’inscrit dans une démarche plus large : rendre l’automatisation IA accessible aux chefs de produit. L’objectif est de sortir des conjectures et de s’appuyer sur des outils concrets pour améliorer les processus métiers.

D’autres guides pratiques explorent des sujets comme :

  • L’automatisation du réglage des modèles avec Gemini, LangGraph et Streamlit
  • L’utilisation des garde-fous pour sécuriser les systèmes multi-agents
  • La construction de systèmes prêts pour la production avec des agents IA

Tous ces projets sont conçus pour être exécutés localement, sans clé API ni service cloud payant.

Sources :
  • Towards Data Science

L'indépendance de CLODCO est votre garantie.

Pour que l'actualité de l'IA reste sans filtre et sans concession, votre soutien est indispensable. Votre contribution est le seul moteur de notre liberté éditoriale.

Soutenir CLODCO