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 :
- Cout qui explose : a la 20e question, vous payez 20x le cout initial
- Cache sementique cassee : chaque prompt a un embedding unique (il contient tout l'historique)
- Context window depasse : après 50 echanges, certains modèles rejettent
- 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 :
- Charge les N derniers messages depuis la DB
- Ajoute le nouveau message
- Envoie au LLM
- 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.
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 :
- OpenAI, Anthropic, Mistral :
systemen premier message - Gemini : champ
system_instructionsepare - Cohere :
preambledédié
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
| Metrique | Sans persistance | Avec persistance + fenetre glissante |
|---|---|---|
| Tokens après 20 echanges | ~20 000 | ~6 000 |
| Cache hit rate | 2% | 18% |
| Context overflow | Risque après 50 echanges | Jamais |
| Refresh safe | Perte de l'historique | Conservation integrale |
Utilisez les conversations Routtx
Inclus des le plan Pro. Sidebar, historique, changement de provider sans perte de contexte.
Voir la doc