Aller au contenu principal

Conversation state et gestion de session

Mis à jour le 29 juillet 2026

Conversation state et gestion de session

La Responses API prend en charge nativement l’état de la conversation, ce qui simplifie considérablement le maintien du contexte entre les échanges. Vous n’avez plus à reconstituer l’historique à chaque requête : le serveur le fait pour vous, et vous ne transportez qu’un identifiant.

Le chaînage avec previous_response_id

Le mécanisme tient en un champ. Chaque réponse possède un id ; passez-le dans previous_response_id à l’appel suivant, et le modèle dispose de tout ce qui précède. Dans l’échange ci-dessous, la troisième question — « et comment les additionner ? » — n’a aucun sens isolée : c’est le chaînage qui permet au modèle de savoir qu’il s’agit de fractions, et que son interlocuteur débute.

from openai import OpenAI
client = OpenAI()

# Premier échange
r1 = client.responses.create(
    model="gpt-5.6-terra",
    instructions="Vous êtes un professeur de mathématiques patient.",
    input="Je voudrais apprendre les fractions."
)
print(f"R1 : {r1.output_text}")

# Deuxième échange — le contexte est conservé automatiquement
r2 = client.responses.create(
    model="gpt-5.6-terra",
    input="Pouvez-vous me donner un exemple concret ?",
    previous_response_id=r1.id
)
print(f"R2 : {r2.output_text}")

# Troisième échange — toute la conversation est accessible
r3 = client.responses.create(
    model="gpt-5.6-terra",
    input="Et comment les additionner ?",
    previous_response_id=r2.id
)
print(f"R3 : {r3.output_text}")
# Le modèle sait qu'on parle de fractions depuis le début

Mesurez l’écart avec l’approche antérieure. Avec Chat Completions, la conversation vivait dans une liste que vous entreteniez vous-même et que vous réexpédiiez intégralement à chaque tour, avec un coût qui croissait à mesure que l’échange s’allongeait.

# ANCIEN — vous deviez tout gérer vous-même
historique = [
    {"role": "system", "content": "Vous êtes un professeur de maths."},
    {"role": "user", "content": "Je voudrais apprendre les fractions."},
    {"role": "assistant", "content": "Bien sûr ! Les fractions..."},
    {"role": "user", "content": "Un exemple concret ?"},
    {"role": "assistant", "content": "Prenons une pizza..."},
    {"role": "user", "content": "Comment les additionner ?"},
]
# Tout renvoyé à chaque appel = coûts croissants

Le même échange se réduit désormais à une référence :

# NOUVEAU — un seul ID suffit
r = client.responses.create(
    model="gpt-5.6-terra",
    input="Comment les additionner ?",
    previous_response_id="resp_precedent_id"
)
# Le serveur gère l'historique pour vous

Faire cohabiter plusieurs conversations

Dès que votre application sert plusieurs utilisateurs, la question devient : quel identifiant appartient à qui ? La réponse tient en une table d’association entre l’utilisateur et le dernier response.id de sa conversation. Le gestionnaire ci-dessous encapsule cette mécanique : il ajoute previous_response_id uniquement si une session existe déjà, puis mémorise le nouvel identifiant. Alice et Bob dialoguent ainsi en parallèle sans que leurs contextes se mélangent, et « nouvelle session » se résume à oublier l’identifiant courant.

class SessionManager:
    """Gestionnaire de sessions de conversation."""

    def __init__(self):
        self.sessions: dict[str, str] = {}  # user_id -> last_response_id

    def envoyer_message(self, user_id: str, message: str,
                        instructions: str = "") -> str:
        """Envoie un message dans la session de l'utilisateur."""
        kwargs = {
            "model": "gpt-5.6-terra",
            "input": message,
        }

        # Ajouter les instructions si fournies
        if instructions:
            kwargs["instructions"] = instructions

        # Chaîner avec la réponse précédente si elle existe
        if user_id in self.sessions:
            kwargs["previous_response_id"] = self.sessions[user_id]

        response = client.responses.create(**kwargs)

        # Sauvegarder l'ID pour le prochain tour
        self.sessions[user_id] = response.id

        return response.output_text

    def nouvelle_session(self, user_id: str):
        """Démarre une nouvelle conversation pour l'utilisateur."""
        if user_id in self.sessions:
            del self.sessions[user_id]

# Utilisation
manager = SessionManager()

# Utilisateur Alice
print(manager.envoyer_message("alice", "Bonjour, parlez-moi de Python."))
print(manager.envoyer_message("alice", "Quels sont ses avantages ?"))

# Utilisateur Bob (conversation indépendante)
print(manager.envoyer_message("bob", "Bonjour, parlez-moi de Rust."))

# Alice continue sa conversation sur Python
print(manager.envoyer_message("alice", "Comment débuter ?"))

Quand reprendre la main sur l’historique

Le chaînage automatique a une limite : vous ne pouvez pas modifier ce qui a déjà été dit. Si votre application doit résumer les anciens tours, supprimer un message contenant des données personnelles ou réinjecter un contexte reformulé, repassez à l’input structuré et gérez la liste vous-même. Vous y perdez la simplicité, vous y gagnez le contrôle total — et la possibilité de suivre l’inflation des tokens tour après tour.

def conversation_manuelle():
    """Conversation avec gestion manuelle de l'historique."""
    historique = []

    while True:
        user_input = input("Vous : ")
        if user_input.lower() == "quit":
            break

        historique.append({"role": "user", "content": user_input})

        response = client.responses.create(
            model="gpt-5.6-terra",
            instructions="Vous êtes un assistant concis.",
            input=historique
        )

        assistant_msg = response.output_text
        historique.append({"role": "assistant", "content": assistant_msg})

        print(f"Assistant : {assistant_msg}")
        print(f"  (Tokens cumulés : {response.usage.input_tokens})")

# conversation_manuelle()

Survivre à un redémarrage

Un dictionnaire en mémoire disparaît au premier redéploiement, et avec lui toutes les conversations en cours. En production, persistez les identifiants — le fichier JSON ci-dessous illustre le principe, que vous transposerez à votre base de données ou à Redis. Notez qu’on enregistre au passage l’horodatage et la consommation, deux informations qui serviront respectivement à l’expiration et au suivi des coûts.

import json
from datetime import datetime

class PersistentSessionManager:
    """Sessions persistantes avec sauvegarde."""

    def __init__(self, db_path: str = "sessions.json"):
        self.db_path = db_path
        self.sessions = self._charger()

    def _charger(self) -> dict:
        try:
            with open(self.db_path, "r") as f:
                return json.load(f)
        except FileNotFoundError:
            return {}

    def _sauvegarder(self):
        with open(self.db_path, "w") as f:
            json.dump(self.sessions, f, indent=2)

    def envoyer(self, session_id: str, message: str) -> str:
        kwargs = {"model": "gpt-5.6-terra", "input": message}

        if session_id in self.sessions:
            kwargs["previous_response_id"] = self.sessions[session_id]["last_id"]

        response = client.responses.create(**kwargs)

        self.sessions[session_id] = {
            "last_id": response.id,
            "updated_at": datetime.now().isoformat(),
            "total_tokens": response.usage.total_tokens
        }
        self._sauvegarder()

        return response.output_text

Prévoyez enfin une expiration. Sans nettoyage, votre table de sessions ne fait que croître, et un utilisateur qui revient trois semaines plus tard reprendrait une conversation dont il ne se souvient plus. Vingt-quatre heures constituent un point de départ raisonnable, à ajuster selon votre usage.

from datetime import datetime, timedelta

def nettoyer_sessions(sessions: dict, max_age_heures: int = 24) -> dict:
    """Supprime les sessions expirées."""
    maintenant = datetime.now()
    actives = {}

    for sid, data in sessions.items():
        updated = datetime.fromisoformat(data["updated_at"])
        if maintenant - updated < timedelta(hours=max_age_heures):
            actives[sid] = data

    supprimees = len(sessions) - len(actives)
    if supprimees > 0:
        print(f"{supprimees} session(s) expirée(s) supprimée(s)")

    return actives

En résumé de ces choix : utilisez previous_response_id pour les conversations simples, c’est le plus efficace ; passez à l’input structuré quand vous devez modifier l’historique ; stockez les response.id de façon fiable, en base et non en mémoire ; nettoyez les sessions inactives ; et surveillez les tokens cumulés, faute de quoi la facturation vous rappellera à l’ordre.

Points clés à retenir

  • previous_response_id chaîne les réponses automatiquement sans renvoyer l’historique
  • Chaque réponse a un id unique utilisable pour le chaînage
  • Gérez des sessions multiples avec un dictionnaire user_id -> response_id
  • L’input structuré (liste de messages) offre plus de contrôle sur l’historique
  • Implémentez la persistance et l’expiration des sessions en production