Skip to content

suite

suite

La suite d'une conversation hérite de son sujet.

21 septembre 2026, 23 h : « Qui est le président actuel d'Haïti ? », réponse vérifiée en ligne — puis « Raconte-moi l'histoire de ce pays » recevait « il faudrait que tu me dises de quel pays tu parles ! Est-ce que tu veux parler de la France, du Canada, des États-Unis… ». Le fil était bien envoyé en entier (16 722 jetons) ; rejoué par l'API avec le même historique, le 9b répond « Est-ce Haïti, comme suggéré par la question précédente ? » — il VOIT la question d'avant et n'ose pas conclure. Avec une ligne qui dit que « ce pays » renvoie à l'échange précédent, il raconte 1804 sans hésiter.

Le code sait, lui, qu'une demande courte qui dit « ce pays », « cette équipe », « celui-ci », « parle-moi de lui », « son histoire » renvoie à ce qui précède : il le dit au modèle, en citant la question précédente et le début de sa réponse. Rien n'est deviné (§34) — le référent reste à résoudre par le modèle, dans deux phrases au lieu de seize mille jetons.

Classes

Functions:

renvoi

renvoi(texte: str) -> str | None

Le mot par lequel la demande renvoie à ce qui précède, ou None.

Source code in src/diapason/server/suite.py
def renvoi(texte: str) -> str | None:
    """Le mot par lequel la demande renvoie à ce qui précède, ou None."""
    if not texte or len(texte) > LONGUEUR_MAX:
        return None
    brut = texte.strip()
    plat = _plat(brut)
    m = _RENVOI.search(plat)
    if m is None:
        return None
    # Le mot tel qu'il a été écrit (« cette équipe »), quand l'aplatissement
    # a gardé les positions — c'est le cas du français courant.
    if len(plat) == len(brut):
        return brut[m.start() : m.end()].strip()
    return m.group(0).strip()

rappel_du_sujet

rappel_du_sujet(
    demande: str,
    question_precedente: str,
    reponse_precedente: str = "",
) -> str | None

La ligne SYSTEM qui dit au modèle sur quoi porte « ce pays ».

Source code in src/diapason/server/suite.py
def rappel_du_sujet(
    demande: str, question_precedente: str, reponse_precedente: str = ""
) -> str | None:
    """La ligne SYSTEM qui dit au modèle sur quoi porte « ce pays »."""
    mot = renvoi(demande)
    if mot is None or not question_precedente.strip():
        return None
    extrait = _CITATION.sub("", reponse_precedente or "").strip()
    extrait = " ".join(extrait.split())
    if len(extrait) > EXTRAIT_REPONSE:
        extrait = extrait[:EXTRAIT_REPONSE].rsplit(" ", 1)[0] + "…"
    reponse = f" ; début de la réponse : « {extrait} »" if extrait else ""
    question = " ".join(question_precedente.split())
    if len(question) > LONGUEUR_MAX:
        question = question[:LONGUEUR_MAX].rsplit(" ", 1)[0] + "…"
    return RAPPEL_DU_SUJET.format(renvoi=mot, question=question, reponse=reponse)

avec_rappel

avec_rappel(messages: Sequence[Message]) -> list[Message]

Le fil, plus le rappel du sujet quand la dernière demande renvoie à ce qui précède — en fin de fil, après les consignes du tour déjà posées (le rappel des questions suit la demande ; le poser AVANT lui faisait recoller la consigne des questions au rappel du sujet au lieu du prompt d'identité, et le préfixe en cache ne correspondait plus).

Source code in src/diapason/server/suite.py
def avec_rappel(messages: Sequence[Message]) -> list[Message]:
    """Le fil, plus le rappel du sujet quand la dernière demande renvoie à
    ce qui précède — en fin de fil, après les consignes du tour déjà
    posées (le rappel des questions suit la demande ; le poser AVANT lui
    faisait recoller la consigne des questions au rappel du sujet au lieu
    du prompt d'identité, et le préfixe en cache ne correspondait plus)."""
    rang = next(
        (i for i in range(len(messages) - 1, -1, -1) if messages[i].role == Role.USER),
        None,
    )
    if rang is None or messages[rang].images:
        return list(messages)
    demande = messages[rang].content or ""
    tours = [
        (m.role.value if hasattr(m.role, "value") else str(m.role), m.content or "")
        for m in messages[:rang]
        if m.role in (Role.USER, Role.ASSISTANT)
    ]
    precedent = _echange_precedent(tours)
    if precedent is None:
        return list(messages)
    rappel = rappel_du_sujet(demande, *precedent)
    if rappel is None:
        return list(messages)
    return [*messages, Message(role=Role.SYSTEM, content=rappel)]

rappel_pour_la_voix

rappel_pour_la_voix(
    historique: Sequence[dict[str, Any]], demande: str
) -> dict[str, str] | None

Le même rappel, pour l'assemblage vocal (messages en dictionnaires).

Source code in src/diapason/server/suite.py
def rappel_pour_la_voix(
    historique: Sequence[dict[str, Any]], demande: str
) -> dict[str, str] | None:
    """Le même rappel, pour l'assemblage vocal (messages en dictionnaires)."""
    tours = [
        (str(m.get("role") or ""), str(m.get("content") or ""))
        for m in historique
        if m.get("role") in ("user", "assistant")
    ]
    precedent = _echange_precedent(tours)
    if precedent is None:
        return None
    rappel = rappel_du_sujet(demande, *precedent)
    return {"role": "system", "content": rappel} if rappel else None