Conversations server-side : pourquoi c'est la clé du multi-provider

Entrepôt d'archives aux étagères remplies de boîtes

Le routing multi-provider resout un problème mais en créé un autre : si chaque message peut aller sur un provider different, comment maintient-on le contexte de la conversation ?

Le problème du contexte stateless

L'approche classique : le client envoie tout l'historique a chaque requête.

messages = [
    {"role": "user", "content": "Bonjour"},
    {"role": "assistant", "content": "Bonjour !"},
    {"role": "user", "content": "Comment vas-tu ?"},
    ...  # 20 messages plus tard
]

Ca marche mais ca a 4 gros defauts :

  1. Coût qui grossit : à chaque question, on repaie tout l'historique déjà envoyé
  2. Cache sementique cassee : chaque prompt a un embedding unique (il contient tout l'historique)
  3. Context window dépassé : sur de longues conversations, certains modèles finissent par rejeter la requête
  4. Risque de perte : rafraichir la page = perdre la conversation

La solution : persistance server-side

On stocke les conversations côté serveur, dans une base de données. Le client envoie juste l'ID + le nouveau message. Le serveur reconstruit le contexte.

# Le client envoie uniquement le nouveau message
headers = {
    "Authorization": "Bearer sk-...",
    "X-Conversation-Id": "conv_abc123",  # en-tête d'exemple
}
body = {
    "model": "auto",
    "messages": [{"role": "user", "content": "Continue..."}],
}

Le serveur :

  1. Charge les N derniers messages depuis la DB
  2. Ajoute le nouveau message
  3. Envoie au LLM
  4. Persiste la réponse

La fenetre glissante (sliding window)

Garder TOUS les messages en contexte finit par depasser le context window. La solution classique : garder seulement les N derniers (20 dans l'exemple ci-dessous).

def build_llm_context(conversation_id, new_message, window=20):
    messages = get_messages(conversation_id, limit=window)
    messages.append({"role": "user", "content": new_message})
    return messages

La bonne valeur de N dépend de la longueur de vos messages et de la fenêtre de contexte des modèles visés.

Attention : la fenetre glissante fait perdre de l'information. Si l'utilisateur etablit un contexte au debut ("je suis developpeur Python, je travaille sur une app de RAG"), ce contexte sera oublie après 20 echanges.

L'étape suivante : summarization

Pour pallier la perte de contexte, on peut resumer automatiquement les messages au-dela de la fenetre. Ce resume devient un "message système virtuel" prepend a chaque requête.

def build_llm_context_v2(conversation_id, new_message, window=20):
    # Les N derniers messages tels quels
    recent = get_messages(conversation_id, limit=window)

    # Resume des messages plus anciens
    summary = get_conversation_summary(conversation_id)

    messages = []
    if summary:
        messages.append({"role": "system", "content": f"Contexte: {summary}"})
    messages.extend(recent)
    messages.append({"role": "user", "content": new_message})
    return messages

Le résumé peut être généré par un petit modèle économique quand la fenêtre dépasse la limite. Le coût est faible par rapport au bénéfice sur les longues conversations, mais le résumé reste une perte d'information : à tester sur vos cas réels.

Les pieges du changement de provider

1. System message vs user message

Tous les providers ne gèrent pas les system messages pareil :

Une passerelle compatible OpenAI et Anthropic doit convertir le format pour chaque provider. Sans ca, un system envoye a Gemini est traite comme un user et degrade les réponses.

2. Format des messages

OpenAI accepte {role, content}. Anthropic aussi mais exige max_tokens. Gemini utilise contents avec parts. Encore du mapping a faire.

3. Les tokens de delimitation

Si un utilisateur met [INST] dans son prompt (Llama) ou Human: (Claude), le provider peut interpreter ca comme une delimitation de message et s'emmeler. Un filtre contre l'injection de délimiteurs devient essentiel en mode multi-provider (voir pourquoi 396 patterns anti-injection).

L'UX : sidebar comme ChatGPT

Côté frontend, la persistance server-side permet une UX type ChatGPT : liste des conversations, historique accessible, reprise d'une discussion sur n'importe quel appareil.

Le client envoie juste l'ID, le serveur fait le reste. Aucune logique de gestion d'historique cote client.

Voir routtx en démonstration

Lors de la démonstration, vous voyez comment routtx choisit le modèle avec model="auto", bascule d'un fournisseur à l'autre et consigne chaque échange dans son journal.

Réserver une démo Intégration

← Retour au blog