LiteLLM : Passe d'OpenAI à Mistral sans changer une ligne de code

LiteLLM : Passe d'OpenAI à Mistral sans changer une ligne de code

De quoi avez-vous besoin

Version de Python

3.x

Packages

  • {"nom":"litellm","version":"1.50+"}

Difficulté

Intermédiaire

Tu as construit ton application autour de l'API d'OpenAI, et tout fonctionne. Puis vient le jour où tu veux tester Mistral, comparer les prix d'Anthropic, ou passer à un modèle local. Chaque fournisseur a sa propre bibliothèque, sa propre signature, ses propres types de réponse. Réécrire ton code à chaque changement de modèle est un cauchemar. LiteLLM propose une solution radicale : une API unique, au format OpenAI, qui parle à plus d'une centaine de modèles en coulisses.

Avec LiteLLM, changer de fournisseur se résume à changer la valeur d'un paramètre : le nom du modèle. Le reste de ton code, lui, ne bouge pas. Ce tutoriel te montre comment passer d'OpenAI à Mistral sans toucher une ligne de logique, puis comment aller plus loin avec le streaming, l'asynchrone, le suivi des coûts et le serveur proxy.

LiteLLM ne se contente pas de traduire les appels : il apporte aussi des briques de production dont toute application multi-modèles a besoin, comme la répartition de charge, la bascule automatique en cas de panne, le suivi des coûts, le cache, et même un serveur proxy qui expose une API OpenAI unique à toute une équipe. Autrement dit, c'est à la fois un adaptateur et une plaque tournante.

Au fil de ce tutoriel, tu vas découvrir comment appeler OpenAI puis Mistral avec le même code, streamer et paralléliser tes requêtes, surveiller les coûts, répartir la charge entre plusieurs déploiements et exposer le tout derrière un proxy compatible OpenAI. À la fin, changer de fournisseur ne sera plus qu'une formalité.

Le problème : une API par fournisseur

Chaque fournisseur de LLM a développé son propre client et ses propres conventions. Résultat, un même besoin « envoyer un message et lire la réponse » s'écrit différemment partout :

  • OpenAI : from openai import OpenAI, puis client.chat.completions.create(...).
  • Anthropic : from anthropic import Anthropic, puis client.messages.create(...).
  • Mistral : son propre SDK avec mistral.chat.complete(...).
  • Cohere, Google, Azure, Bedrock : chacun sa bibliothèque, chacun ses types.

Ajoute à cela des formats de réponse hétérogènes, des noms de champs différents (content vs text), et des modes de streaming incompatibles, et tu obtiens une application verrouillée sur un seul fournisseur. LiteLLM met un terme à cette fragmentation en offrant un format commun calqué sur celui d'OpenAI, devenu le standard de fait.

Installation et clés API

LiteLLM est une bibliothèque Python légère. Son point fort : elle n'impose pas d'installer les SDK de chaque fournisseur, elle dialogue directement avec les API HTTP.

bash
pip install litellm

Pour appeler un fournisseur, il suffit que la clé correspondante soit présente en variable d'environnement. LiteLLM détecte automatiquement la clé selon le modèle demandé.

python
import os

# Une clé par fournisseur, stockée en variable d'environnement
os.environ["OPENAI_API_KEY"] = "sk-..."
os.environ["MISTRAL_API_KEY"] = "..."
os.environ["ANTHROPIC_API_KEY"] = "sk-ant-..."

# Astuce : vérifie que litellm voit bien tes clés
import litellm
print(litellm.validate_environment())

Ton premier appel unifié

La fonction completion est le cœur de LiteLLM. Sa signature est strictement identique à celle d'OpenAI : un modèle, une liste de messages, et en retour un objet dont on lit response.choices[0].message.content.

python
from litellm import completion

response = completion(
    model="gpt-4o-mini",
    messages=[
        {"role": "system", "content": "Tu réponds en français, de façon concise."},
        {"role": "user", "content": "Explique la photosynthèse en trois phrases."},
    ],
)
print(response.choices[0].message.content)

Passer à Mistral sans changer ton code

C'est ici que la magie opère. Pour basculer de GPT vers Mistral, tu ne changes qu'une seule chose : la valeur de model, préfixée par mistral/. Le reste du code est rigoureusement identique.

python
from litellm import completion

# Même code, seule la valeur de model change : on passe chez Mistral.
response = completion(
    model="mistral/mistral-large-latest",
    messages=[
        {"role": "user", "content": "Explique la photosynthèse en trois phrases."},
    ],
)
print(response.choices[0].message.content)

Cette uniformité a une conséquence pratique immédiate : tu peux écrire une fonction qui prend le modèle en paramètre et la réutiliser partout. Comparer les fournisseurs devient aussi simple qu'itérer sur une liste de noms.

Tous les fournisseurs, une seule signature

LiteLLM couvre plus d'une centaine de fournisseurs. La convention de nommage est simple : un préfixe identifie le fournisseur (mistral/, anthropic/, command-r/...), et certains modèles très connus sont même reconnus sans préfixe, comme les modèles Claude d'Anthropic.

python
from litellm import completion

question = "Donne-moi un synonyme de 'rapide' en un mot."

# OpenAI
r1 = completion(model="gpt-4o-mini", messages=[{"role": "user", "content": question}])

# Mistral (préfixe mistral/)
r2 = completion(model="mistral/mistral-large-latest", messages=[{"role": "user", "content": question}])

# Anthropic Claude (préfixe anthropic/ ou nom nu)
r3 = completion(model="claude-3-5-sonnet-20241022", messages=[{"role": "user", "content": question}])

# Cohere (préfixe command-r/)
r4 = completion(model="command-r", messages=[{"role": "user", "content": question}])

for nom, r in [("OpenAI", r1), ("Mistral", r2), ("Claude", r3), ("Cohere", r4)]:
    print(f"{nom:10s} -> {r.choices[0].message.content}")

Chaque fournisseur peut avoir ses particularités (certains ne supportent pas le champ system, d'autres ont des limites de contexte différentes), mais LiteLLM traduit silencieusement ton appel au format attendu par le fournisseur cible.

Le streaming, mot à mot

Le streaming se configure avec un simple paramètre stream=True, et la boucle de lecture est identique quel que soit le fournisseur derrière.

python
from litellm import completion

response = completion(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "Raconte une histoire courte de 80 mots."}],
    stream=True,
)

for chunk in response:
    # Le delta est parfois vide (métadonnées) : on l'ignore
    delta = chunk.choices[0].delta.content
    if delta:
        print(delta, end="", flush=True)

C'est un des gros avantages concrets : sur OpenAI, les deltas arrivent dans chunk.choices[0].delta.content ; sur d'autres fournisseurs le format interne diffère, mais LiteLLM normalise tout pour que ta boucle de streaming fonctionne à l'identique partout.

L'asynchrone avec acompletion

Pour les applications à forte concurrence, LiteLLM fournit des variantes asynchrones : acompletion, aembedding, etc. Combinées à asyncio.gather, elles permettent d'interroger plusieurs modèles en parallèle sans bloquer la boucle d'événements.

python
import asyncio
from litellm import acompletion

async def interroger(model, question):
    response = await acompletion(
        model=model,
        messages=[{"role": "user", "content": question}],
    )
    return model, response.choices[0].message.content

async def main():
    # Deux appels en parallèle sur deux fournisseurs différents
    resultats = await asyncio.gather(
        interroger("gpt-4o-mini", "Un mot pour dire 'rapide' ?"),
        interroger("mistral/mistral-large-latest", "Un mot pour dire 'rapide' ?"),
    )
    for model, reponse in resultats:
        print(f"{model:30s} -> {reponse}")

asyncio.run(main())

Suivre les coûts

Quand on multiplie les fournisseurs, la question du coût devient centrale. LiteLLM estime le coût de chaque appel (calculé à partir des prix publics des modèles) et l'expose dans les paramètres cachés de la réponse.

python
import litellm
from litellm import completion

# Active la journalisation détaillée des appels (très utile pour déboguer)
litellm.set_verbose = True

response = completion(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "Dis bonjour."}],
)

# Le coût estimé de l'appel est disponible dans les paramètres cachés
cout = response._hidden_params.get("response_cost")
print(f"Coût estimé de l'appel : {cout}")

Pour un suivi sérieux en production, LiteLLM se branche sur des plateformes d'observabilité (Langfuse, LangSmith, Helicone...) via de simples callbacks. Tu peux ainsi tracer chaque appel, son coût et sa latence, quel que soit le fournisseur.

Le Router : répartition de charge et bascule

Le Router répond à un besoin de production : gérer plusieurs déploiements d'un même modèle (ex. OpenAI + Azure), répartir la charge entre eux, et basculer automatiquement sur un modèle de secours en cas d'échec.

python
import os
from litellm import Router

# Un routeur distribue les requêtes entre plusieurs modèles et
# bascule automatiquement sur un modèle de secours en cas d'échec.
router = Router(
    model_list=[
        {
            "model_name": "gpt-4o-mini",
            "litellm_params": {
                "model": "gpt-4o-mini",
                "api_key": os.environ["OPENAI_API_KEY"],
            },
        },
        {
            # Un deuxième déploiement du même modèle logique (ici Azure)
            "model_name": "gpt-4o-mini",
            "litellm_params": {
                "model": "azure/gpt-4o-mini",
                "api_key": os.environ["AZURE_API_KEY"],
                "api_base": os.environ["AZURE_API_BASE"],
                "api_version": "2024-08-01-preview",
            },
        },
    ],
    # Répartit la charge entre les déploiements disponibles
    routing_strategy="simple-shuffle",
    # Si tous les "gpt-4o-mini" échouent, on bascule sur Mistral
    fallbacks=[{"gpt-4o-mini": ["mistral/mistral-large-latest"]}],
)

# Le code appelant utilise toujours le nom logique "gpt-4o-mini"
response = router.completion(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "Salut, ça va ?"}],
)
print(response.choices[0].message.content)

Le code appelant continue d'utiliser le nom logique gpt-4o-mini : il ne sait pas (et n'a pas besoin de savoir) quel déploiement physique a traité la requête. routing_strategy=simple-shuffle alterne entre les déploiements ; fallbacks définit la file de secours.

Le serveur Proxy : une API OpenAI pour toute ton équipe

Si tu veux exposer tes modèles à plusieurs services, en plusieurs langages, sans réimplémenter LiteLLM partout, déploie le serveur proxy. Il expose une API compatible OpenAI sur un port local, et tu peux ensuite l'appeler avec n'importe quel SDK OpenAI standard.

bash
# 1. Installer le serveur proxy
pip install 'litellm[proxy]'

# 2. Le lancer avec les modèles exposés
litellm --model gpt-4o-mini --model mistral/mistral-large-latest --port 4000

# 3. Le proxy expose une API compatible OpenAI sur http://localhost:4000

Côté client, aucun import LiteLLM : on utilise le SDK OpenAI classique pointé vers le proxy. C'est la porte d'entrée idéale pour migrer une équipe entière vers le multi-fournisseur sans réécrire les applications existantes.

python
# Côté client, on utilise le SDK OpenAI standard,
# pointé vers le proxy. Zéro dépendance à LiteLLM.
from openai import OpenAI

client = OpenAI(
    base_url="http://localhost:4000",   # le proxy LiteLLM
    api_key="sk-litellm",               # clé définie dans la config du proxy
)

resp = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "Bonjour via le proxy !"}],
)
print(resp.choices[0].message.content)

Gérer les erreurs et les retries

Les API de LLM échouent de temps en temps : limite de débit atteinte, indisponibilité passagère, timeouts. LiteLLM intègre des mécanismes de robustesse que tu configures en quelques lignes.

python
import litellm
from litellm import completion

# Nombre de tentatives automatiques en cas d'échec temporaire
litellm.num_retries = 3

# Timeout en secondes (None = pas de limite)
litellm.request_timeout = 60

# Fallback global : si le modèle échoue, on tente ces modèles dans l'ordre
litellm.set_verbose = False

try:
    response = completion(
        model="gpt-4o-mini",
        messages=[{"role": "user", "content": "Test de robustesse."}],
        fallbacks=["mistral/mistral-large-latest", "claude-3-5-sonnet-20241022"],
    )
    print(response.choices[0].message.content)
except Exception as e:
    print("Tous les modèles ont échoué :", e)
  • num_retries : nombre de tentatives automatiques avant d'abandonner.
  • request_timeout : délai maximal d'attente d'une réponse, en secondes.
  • fallbacks : liste de modèles de secours tentés dans l'ordre si le modèle principal échoue.
  • set_verbose : journalise le détail des appels (URL, payload, latence) pour le débogage.

Les embeddings, même principe

L'uniformité ne concerne pas que la génération de texte. LiteLLM applique la même logique aux embeddings, ces vecteurs qui représentent le sens d'un texte et servent à la recherche sémantique, au clustering ou à la classification. La fonction embedding suit le format OpenAI, quel que soit le fournisseur.

python
from litellm import embedding

response = embedding(
    model="text-embedding-3-small",   # OpenAI
    input=["Bonjour le monde", "Un autre texte"],
)

# Les vecteurs sont dans response.data[i]["embedding"]
print("Nombre de vecteurs :", len(response.data))
print("Dimension d'un vecteur :", len(response.data[0]["embedding"]))

Pour changer de fournisseur d'embedding, il suffit là encore de changer le nom du modèle, par exemple en text-embedding-004 (Google) ou en un modèle local via Ollama. Tes index vectoriels et tes pipelines de recherche restent inchangés, seul le backend de vectorisation diffère.

Les préfixes de fournisseurs, en un coup d'œil

Le nom du modèle indique à LiteLLM vers quel fournisseur router la requête. Voici les préfixes les plus courants :

  • gpt-4o-mini, gpt-4o, o1... : modèles OpenAI, reconnus sans préfixe.
  • mistral/mistral-large-latest : les modèles Mistral.
  • anthropic/claude-3-5-sonnet-20241022 (ou le nom nu claude-3-5-sonnet-20241022) : les modèles Claude.
  • command-r : les modèles Cohere.
  • bedrock/... : les modèles exposés par AWS Bedrock.
  • azure/... : les déploiements Azure OpenAI.
  • ollama/llama3.2 : les modèles locaux servis par Ollama.

Cette convention unique est le cœur de LiteLLM : c'est elle qui te permet de traiter tous les fournisseurs comme un seul et même service, avec une seule clé mentale à retenir.

Gérer les erreurs proprement

LiteLLM lève des exceptions typées, héritées de la classe OpenAIError, qui te permettent de distinguer un modèle introuvable d'une limite de débit atteinte ou d'une clé invalide. C'est bien plus propre que de parser des codes HTTP à la main.

python
from litellm import completion, exceptions

try:
    response = completion(
        model="modele-qui-nexiste-pas",
        messages=[{"role": "user", "content": "Salut"}],
    )
except exceptions.NotFoundError as e:
    print("Modèle introuvable :", e)
except exceptions.AuthenticationError as e:
    print("Clé API invalide :", e)
except exceptions.RateLimitError as e:
    print("Limite de débit atteinte, réessaie plus tard :", e)
except exceptions.APIError as e:
    print("Erreur générique du fournisseur :", e)

Ces exceptions étant homogènes quel que soit le fournisseur, ton code de gestion d'erreur s'écrit une seule fois. C'est un gain de robustesse considérable quand tu enchaînes plusieurs modèles dans la même application.

Configurer par fichier YAML

Quand la liste des modèles s'allonge, la passer en dur dans le code devient ingérable. LiteLLM accepte un fichier de configuration YAML qui centralise les modèles, les clés et les réglages globaux. Le Router le charge directement.

yaml
# config.yaml
model_list:
  - model_name: gpt-4o-mini
    litellm_params:
      model: gpt-4o-mini
      api_key: os.environ/OPENAI_API_KEY
  - model_name: claude-sonnet
    litellm_params:
      model: anthropic/claude-3-5-sonnet-20241022
      api_key: os.environ/ANTHROPIC_API_KEY

litellm_settings:
  num_retries: 3
  request_timeout: 60
  drop_params: true   # ignore les paramètres non supportés par le fournisseur
python
from litellm import Router

# Le routeur lit la configuration depuis le fichier
router = Router(config_path="config.yaml")

response = router.completion(
    model="claude-sonnet",   # nom logique défini dans le YAML
    messages=[{"role": "user", "content": "Bonjour !"}],
)
print(response.choices[0].message.content)

La syntaxe os.environ/OPENAI_API_KEY indique à LiteLLM d'aller lire la clé dans la variable d'environnement correspondante : tes secrets restent hors du fichier, ce qui est essentiel dès que la config est versionnée ou partagée.

Le cache pour réduire les coûts

Beaucoup d'applications répètent les mêmes requêtes (un même prompt de système, une même question fréquente). LiteLLM peut mettre en cache les réponses et ainsi économiser à la fois de l'argent et de la latence. Le cache s'active en une ligne.

python
import litellm
from litellm import completion

# Active un cache en mémoire, valable pour tous les appels suivants
litellm.enable_cache()

messages = [{"role": "user", "content": "Qui es-tu ?"}]

r1 = completion(model="gpt-4o-mini", messages=messages)
r2 = completion(model="gpt-4o-mini", messages=messages)
# r2 est servi depuis le cache : aucun appel réseau, coût nul

print(r1.choices[0].message.content == r2.choices[0].message.content)  # True

En production, tu remplaces le cache mémoire par un cache Redis partagé entre plusieurs instances, ce qui démultiplie les économies sur une flotte de services. C'est un levier simple et trop souvent négligé.

Budgets et clés virtuelles avec le proxy

Le proxy LiteLLM va plus loin que la simple passerelle : il sait gérer des clés virtuelles avec des budgets, des quotas et des listes de modèles autorisés. C'est l'outil de gouvernance qui manque quand tu ouvres l'accès aux LLM à une équipe.

bash
# Démarre le proxy avec un modèle autorisé
litellm --model gpt-4o-mini --port 4000

# Génère une clé virtuelle limitée à 10 dollars via l'API du proxy
curl http://localhost:4000/key/generate \
  -H "Authorization: Bearer sk-litellm" \
  -H "Content-Type: application/json" \
  -d '{"models": ["gpt-4o-mini"], "max_budget": 10}'

Chaque clé virtuelle est traçable individuellement : tu sais qui a consommé quoi, combien, et tu peux révoquer un accès en une commande. Pour une organisation qui adopte plusieurs LLM, c'est la brique de contrôle qui rend l'ensemble gouvernable.

Migrer depuis le SDK OpenAI

Si tu pars d'une application OpenAI existante, la migration est triviale : la signature de completion reproduit celle du SDK, à l'import près. Tu peux donc basculer progressivement, sans réécrire ta logique métier.

python
# AVANT : SDK OpenAI
from openai import OpenAI
client = OpenAI()
resp = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "Salut"}],
)
print(resp.choices[0].message.content)

# APRÈS : LiteLLM, signature quasi identique
from litellm import completion
resp = completion(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "Salut"}],
)
print(resp.choices[0].message.content)

La seule vraie différence, c'est le point d'entrée : la fonction completion à la place du client OpenAI. Une fois ce changement fait, tu peux immédiatement tester d'autres fournisseurs en changeant la valeur de model, sans rien toucher d'autre.

Points d'attention et limites

L'uniformisation a ses limites, et il est sain de les connaître pour ne pas tomber dans les pièges classiques :

  • Paramètres spécifiques : certains réglages (top_k d'Anthropic, par exemple) n'existent pas partout. Active drop_params pour que LiteLLM les ignore silencieusement sur les fournisseurs qui ne les connaissent pas.
  • Limites de contexte : chaque modèle a sa propre fenêtre. Un prompt qui passe sur GPT peut dépasser la limite d'un autre modèle. Vérifie max_tokens au cas par cas.
  • Capacités inégales : la vision, l'audio ou l'appel d'outils ne sont pas disponibles sur tous les modèles. Un appel qui marche sur GPT peut échouer sur un modèle plus limité.
  • Coûts et latence variables : deux modèles ne se valent pas en prix ni en vitesse. Mesure avant de basculer un service en production.

Ces limites ne remettent pas en cause l'intérêt de LiteLLM : elles rappellent simplement qu'unifier l'interface ne signifie pas unifier les capacités. Le bon réflexe est de tester chaque modèle sur tes cas réels avant de généraliser.

Tester et évaluer plusieurs modèles

LiteLLM rend l'évaluation comparative triviale : une boucle sur une liste de modèles suffit pour mesurer la latence et comparer la qualité des réponses. C'est la première chose à faire avant de choisir un modèle pour un cas d'usage précis.

python
import time
from litellm import completion

modeles = [
    "gpt-4o-mini",
    "mistral/mistral-large-latest",
    "claude-3-5-sonnet-20241022",
]
question = "Explique le concept d'inflation en deux phrases."

for modele in modeles:
    debut = time.time()
    try:
        r = completion(model=modele, messages=[{"role": "user", "content": question}])
        duree = time.time() - debut
        print(f"{modele:35s} | {duree:.2f}s | {r.choices[0].message.content}")
    except Exception as e:
        print(f"{modele:35s} | ERREUR : {e}")

Cette boucle te donne, en quelques secondes, un premier aperçu de la latence et du style de chaque fournisseur. Pour une évaluation plus rigoureuse, fixe un jeu de questions de référence et compare les réponses sur des critères objectifs (pertinence, format, coût). C'est le préalable à toute décision de migration.

En combinant ce type de benchmark avec le Router, tu peux même automatiser le choix du modèle : acheminer les requêtes simples vers le modèle le moins cher et réserver le plus puissant aux cas complexes. LiteLLM te donne les briques, l'orchestration fine reste ton affaire.

LiteLLM dans l'écosystème Python

LiteLLM ne vit pas en vase clos : il s'intègre aux principaux frameworks du moment. LangChain peut router ses appels via LiteLLM, et le proxy expose une API compatible OpenAI que consomment nativement des dizaines de bibliothèques, des SDK officiels aux outils d'agents.

Concrètement, adopter LiteLLM ne t'enferme dans aucun écosystème : c'est au contraire un moyen de rendre tes modèles accessibles au plus grand nombre d'outils, sans verrouillage. Le proxy joue le rôle de plaque tournante : tes applications, quels que soient leur langage ou leur framework, parlent toutes à la même porte.

  • LangChain : utilise LiteLLM comme backend d'appel aux modèles.
  • LlamaIndex : peut s'appuyer sur LiteLLM pour la génération et les embeddings.
  • Tout SDK compatible OpenAI : pointe vers le proxy et profite du multi-fournisseur sans rien réécrire.

Bonnes pratiques pour la production

Passer en production avec plusieurs fournisseurs demande un peu de discipline. Quelques réflexes qui évitent les mauvaises surprises :

  • Stocke les clés en variables d'environnement, jamais en dur dans le code ni dans les fichiers versionnés.
  • Configure num_retries et request_timeout dès le départ : les API de LLM échouent régulièrement, anticipe-le.
  • Active le cache pour les requêtes répétées, c'est de l'argent économisé sans effort.
  • Mesure les coûts par modèle avant de généraliser : un modèle plus cher peut se justifier, mais encore faut-il le savoir.
  • Prévois toujours un fallback : aucun fournisseur n'est infaillible, un modèle de secours évite une panne complète.

Conclusion

LiteLLM fait disparaître la friction du multi-fournisseur. Une seule signature d'appel, un simple changement de nom de modèle pour changer de fournisseur, et des outils de production (router, proxy, retries, suivi des coûts) qui évitent de réinventer la roue. C'est la bibliothèque à connaître dès que ton application touche à plus d'un LLM, ou que tu veux garder la liberté d'en changer.

Pour aller plus loin, explore la configuration complète du proxy (clés virtuelles, budgets par équipe, cache, load balancing avancé) et les callbacks d'observabilité. La documentation de LiteLLM est exhaustive et constamment à jour, ce qui est précieux dans un écosystème qui bouge aussi vite.

Sources