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

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. Cout qui explose : a la 20e question, vous payez 20x le cout initial
  2. Cache sementique cassee : chaque prompt a un embedding unique (il contient tout l'historique)
  3. Context window depasse : après 50 echanges, certains modèles rejettent
  4. Risque de perte : rafraichir la page = perdre la conversation

La solution : persistance server-side

On stocke les conversations en DB (Supabase). 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-gw-...",
    "X-LLM-Gateway-Conversation-Id": "conv_abc123",
}
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 par défaut chez Routtx).

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

20 messages, c'est environ 4000-6000 tokens. Confortable pour tous les providers.

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 resume est généré par un petit modèle (Mistral Small, Groq llama-3.1-8b) quand la fenetre depasse la limite. Cout marginal, benefice enorme sur les longues conversations.

Les pieges du changement de provider

1. System message vs user message

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

Le gateway 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. Le filtre anti-delimiter-injection devient essentiel en mode multi-provider.

L'UX : sidebar comme ChatGPT

Cote frontend, la persistance server-side permet une UX type ChatGPT : sidebar avec la liste des conversations, historique accessible, reprise d'une discussion sur n'importe quel device.

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

Benchmarks chez Routtx

MetriqueSans persistanceAvec persistance + fenetre glissante
Tokens après 20 echanges~20 000~6 000
Cache hit rate2%18%
Context overflowRisque après 50 echangesJamais
Refresh safePerte de l'historiqueConservation integrale

Utilisez les conversations Routtx

Inclus des le plan Pro. Sidebar, historique, changement de provider sans perte de contexte.

Voir la doc

← Retour au blog