Ce que fait vraiment un appel à un LLM
Intégrer un grand modèle de langage (LLM) dans une application revient, techniquement, à envoyer une requête HTTP à un service (un fournisseur dans le cloud ou un serveur que vous hébergez) et à exploiter la réponse. La plupart des API de chat partagent la même logique, même si les noms exacts des champs varient d’un fournisseur à l’autre : vous envoyez une liste de messages et des paramètres, vous recevez un message de réponse accompagné de métadonnées.
L’architecture à retenir est toujours la même :
navigateur → votre API (serveur) → service de modèle
← ←
Le navigateur n’appelle jamais le modèle directement. Sinon, la clé d’API serait visible dans le code de la page, n’importe qui pourrait l’utiliser à vos frais, et vos prompts (votre savoir-faire) seraient lisibles. Le serveur ajoute la clé, les consignes, la journalisation et les contrôles.
Les messages et leurs rôles
Une conversation est une liste ordonnée de messages, chacun avec un rôle :
messages = [
{"role": "system", "content": "Tu es l'assistant du service client. Réponds en français, en trois phrases maximum."},
{"role": "user", "content": "Ma commande n'est pas arrivée."},
{"role": "assistant", "content": "Pouvez-vous me donner votre numéro de commande ?"},
{"role": "user", "content": "C'est A100."},
]
system: les consignes de votre application (ton, périmètre, format). L’utilisateur ne l’écrit pas.user: ce que l’utilisateur a saisi.assistant: les réponses précédentes du modèle, que vous renvoyez pour donner le contexte.
Le modèle n’a pas de mémoire
Chaque appel est indépendant. Si le modèle « se souvient » de la conversation, c’est parce que l’application renvoie l’historique à chaque requête. Cet historique consomme des tokens (et donc du temps et du coût) et doit tenir dans la fenêtre de contexte du modèle. On garde donc en général le message système, les derniers échanges et la nouvelle question :
def build_messages(system, history, question, max_turns=3):
messages = [{"role": "system", "content": system}]
for user_text, assistant_text in history[-max_turns:]:
messages.append({"role": "user", "content": user_text})
messages.append({"role": "assistant", "content": assistant_text})
messages.append({"role": "user", "content": question})
return messages
Pour de longues conversations, on peut aussi résumer les anciens échanges plutôt que de les supprimer.
Lire la réponse
Une réponse contient au minimum trois informations utiles. Dans ce cours, nous utilisons un format simplifié et neutre :
response = {
"output": {"role": "assistant", "content": "Votre commande A100 est expédiée."},
"stop_reason": "end", # "end", "max_tokens" ou "tool_call"
"usage": {"input_tokens": 52, "output_tokens": 9},
}
- Le texte de la réponse.
- La raison d’arrêt :
endsignifie que le modèle a terminé ;max_tokenssignifie que la réponse a été coupée par la limite que vous avez fixée. Une réponse coupée ne doit pas être traitée comme complète (un JSON coupé est invalide, une phrase coupée est trompeuse). - L’usage en tokens d’entrée et de sortie, indispensable pour suivre les coûts (leçon 5).
Les paramètres courants
max_tokens: longueur maximale de la réponse. C’est un garde-fou de coût et de latence.temperature: faible (0 à 0,3) pour des tâches qui demandent de la constance (classer, extraire) ; plus élevée pour de la rédaction variée. Même à température nulle, une sortie identique n’est pas garantie par tous les services.- Le modèle lui-même : un petit modèle rapide suffit souvent pour classer ou résumer ; on réserve les gros modèles aux tâches difficiles.
Simuler le modèle pour développer
Dans les exercices, le modèle est une fonction Python locale et déterministe qui renvoie une réponse au même format. C’est aussi une bonne pratique en entreprise : vos tests automatisés ne doivent dépendre ni du réseau ni d’une réponse aléatoire. On teste la logique de l’application (construction des messages, lecture de la réponse, gestion des erreurs) avec un faux modèle, et l’on évalue la qualité du vrai modèle à part.
À retenir
- Le serveur appelle le modèle ; le navigateur appelle votre serveur.
- Une requête = rôles
system,user,assistantdans l’ordre ; l’historique est renvoyé par l’application. - Vérifiez toujours la raison d’arrêt et enregistrez l’usage en tokens.
Exercices
Les solutions et les tests restent sur le serveur. Dans l'espace étudiant, ton code est exécuté et corrigé, et le tuteur donne des indices avant la solution.
Construire les messages d'une requête
Écrivez
build_messages(system, history, question, max_turns=3):historyest une liste de tuples(question_utilisateur, réponse_assistant)du plus ancien au plus récent ;- le résultat commence par le message
system, puis lesmax_turnsderniers échanges (un messageuserpuis un messageassistantpar échange), puis la nouvelle question en messageuser; - la question est débarrassée de ses espaces de début et de fin ; si elle est vide, levez
ValueError; - avec
max_turns=0, aucun échange n’est repris.
def build_messages(system, history, question, max_turns=3): ...Lire la réponse et détecter une coupure
Le modèle simulé
fake_model(messages, max_tokens)est fourni. Écrivez :read_response(response)qui renvoie un dictionnaire{"text": ..., "truncated": ..., "tokens": ...}: le texte deoutput.contentsans espaces de bord,truncatedvrai sistop_reasonvaut"max_tokens", ettokens= tokens d’entrée + tokens de sortie ;ask(model, messages, max_tokens=300)qui appellemodel(messages, max_tokens=max_tokens), lit la réponse avecread_responseet lèveRuntimeErrorsi le texte est vide.
def fake_model(messages, max_tokens=300): """Modèle simulé : répond en reprenant la dernière question (déterministe).""" question = messages[-1]["content"] words = ("Réponse à : " + question).split() truncated = len(words) > max_tokens words = words[:max_tokens] return { "output": {"role": "assistant", "content": " ".join(words)}, "stop_reason": "max_tokens" if truncated else "end", "usage": {"input_tokens": sum(len(m["content"].split()) for m in messages), "output_tokens": len(words)}, } def read_response(response): ... def ask(model, messages, max_tokens=300): ...Où placer la clé d'API ?
Vous ajoutez un assistant à une application web (front Vue, API back-end). Où doit se trouver la clé d’API du service de modèle ?
- Dans une variable d'environnement du front, injectée au build
- Dans une variable d'environnement du serveur, utilisée uniquement par l'API
- Dans le localStorage de l'utilisateur après connexion
- Dans le prompt système, pour que le modèle s'authentifie
Quiz de fin de leçon
Pourquoi renvoie-t-on l’historique de la conversation à chaque appel ?
- Pour que le fournisseur facture correctement
- Parce que le modèle ne conserve aucune mémoire entre deux appels
- Pour accélérer la réponse
Une réponse a
stop_reason = "max_tokens". Qu’en déduire ?- La réponse est complète et courte
- La réponse a été coupée par la limite de longueur
- Le modèle veut appeler un outil
Quel rôle porte les consignes de votre application (ton, périmètre, format) ?
- user
- assistant
- system
La suite du cours
- Sorties structurées : obtenir et valider du JSON20 min
- Appels d'outils : laisser le modèle utiliser vos fonctions20 min
- Streaming : afficher la réponse au fil de l'eau15 min
- Coûts, limites et journalisation20 min