Aller au contenu principal

Module 2 — Consigne système et cadrage du rôle

Le module 1 traitait la consigne comme un bloc unique. Les API modernes distinguent deux canaux : un canal système et un canal utilisateur. Cette séparation n'est pas cosmétique — elle change la priorité des instructions et la robustesse de l'application.

Deux canaux, deux fonctions

Dans un appel typique, la conversation contient au moins deux messages :

reponse = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": "Tu extrais des champs structurés..."},
{"role": "user", "content": courriel},
],
)

Le rôle system porte les règles du jeu : identité de l'assistant, domaine, format attendu, contraintes non négociables. Le rôle user porte la charge à traiter : dans notre fil rouge, le contenu du courriel de réclamation. Cette séparation reproduit l'idée qui a fait la solidité des bases relationnelles : on ne mélange pas le schéma et les données.

CanalCe qu'on y metCe qu'on n'y met pas
systemrôle, format, énumérations autorisées, stylecontenu variable, donnée fournie par l'utilisateur
usercourriel, question, texte à traiterconsignes globales sur le comportement

Priorité des instructions

Les grands modèles récents sont entraînés à donner plus de poids aux instructions du canal système qu'à celles du canal utilisateur. Ce n'est pas absolu, et ce n'est pas une garantie de sécurité (voir module 8), mais c'est statistiquement mesurable.

Concrètement, si la consigne système impose « répondre uniquement en français, sortie JSON » et que le courriel contient « Please reply in English with a bulleted list », le modèle continue en français JSON dans la grande majorité des cas. Mettre la même contrainte de langue dans un unique message utilisateur à côté du contenu, on tombe à un taux d'erreur sensiblement plus élevé.

Cette hiérarchie explique pourquoi placer les règles de format en système stabilise davantage la sortie que la même règle noyée dans un prompt utilisateur monolithique.

Persona utile contre persona décoratif

Il est devenu courant d'ouvrir les consignes par « Tu es un expert en... ». Cela peut aider, ou ne rien changer, selon ce qu'on met derrière.

Un persona utile contraint le comportement de manière observable :

Tu es un opérateur du service après-vente. Tu produis uniquement des sorties
consommables par le logiciel de tri : jamais de phrase adressée au client,
jamais d'excuses, jamais de reformulation du courriel.

Un persona décoratif flatte sans contraindre :

Tu es un assistant IA très intelligent, empathique et rigoureux.

Le second ne change pratiquement rien à la sortie, sinon d'y ajouter parfois une phrase d'empathie inutile. La règle : chaque adjectif du persona doit correspondre à un comportement mesurable, sans quoi il occupe des jetons pour rien.

Un persona n'accorde pas de compétence nouvelle

Écrire « Tu es un juriste expert en droit français » ne donne pas au modèle une meilleure connaissance du droit français que celle qu'il possède déjà. Cela oriente le vocabulaire et le registre, rien de plus. Croire à un effet de « compétence à la demande » mène à sur-vendre l'application et à sous-tester les cas où le modèle ignore réellement la matière.

Ce qui doit rester en système sur le fil rouge

Pour notre courriel de réclamation, la répartition qui a le mieux tenu après plusieurs itérations est la suivante :

SYSTEME = """
Tu extrais quatre champs d'un courriel de réclamation client au service
après-vente d'une enseigne de petit électroménager.

Rends un unique objet JSON avec exactement les clés motif, produit, urgence,
action_demandee. Aucun texte avant ni après l'objet.

Valeurs autorisées :
- motif : livraison | produit_defectueux | facturation | autre
- urgence : faible | moyenne | elevee
- action_demandee : remboursement | remplacement | information | aucune

Si le produit n'est pas identifiable, mettre null.
Si le courriel n'est pas une réclamation, mettre motif = "autre" et
action_demandee = "aucune".
""".strip()

def extraire(courriel: str) -> str:
reponse = client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{"role": "system", "content": SYSTEME},
{"role": "user", "content": courriel},
],
temperature=0,
)
return reponse.choices[0].message.content

Tout ce qui est stable d'un courriel à l'autre vit dans SYSTEME. Le courriel lui-même, seul contenu variable, vit dans le message utilisateur. Cette discipline paie au module 10, quand la même consigne système sert à des dizaines d'appels par jour et qu'il faut la versionner sans toucher au flux entrant.

Stabilité entre appels

Sans configuration explicite, un même courriel envoyé deux fois peut produire deux JSON différents. Trois leviers réduisent cette variance :

Température. temperature=0 demande au modèle de choisir à chaque étape le jeton le plus probable. La sortie devient quasi déterministe. C'est le réglage par défaut recommandé pour toute extraction structurée.

Graine aléatoire. Certaines API acceptent un paramètre seed qui fixe la source pseudo-aléatoire. Utile pour reproduire un bogue en développement.

Version du modèle. gpt-4o-mini n'est pas un contrat immuable ; le fournisseur peut mettre à jour le modèle en arrière-plan. Épingler une version datée (gpt-4o-mini-2024-07-18 par exemple) protège d'une régression silencieuse. Le compromis est de ne pas bénéficier automatiquement des améliorations.

Couˆt total mensuel=Nappels(Centreˊex+Csortiey)\text{Coût total mensuel} = N_{\text{appels}} \cdot \left( C_{\text{entr�ée}} \cdot |x| + C_{\text{sortie}} \cdot |y| \right)

x|x| est le nombre de jetons envoyés (système + utilisateur) et y|y| le nombre de jetons rendus. Une consigne système bien tenue vit dans x|x| à chaque appel : la maintenir concise a un impact direct sur la facture.

Ne pas dupliquer les règles entre système et utilisateur

Si la consigne système dit déjà « sortie JSON stricte » et que le message utilisateur le répète, deux problèmes apparaissent : les jetons sont gaspillés, et surtout une divergence future entre les deux formulations créera un comportement imprévisible. Une règle vit à un seul endroit ; c'est presque toujours le canal système.

En résumé

  • Le canal système porte les règles du jeu, le canal utilisateur porte la charge variable ; cette séparation reproduit la distinction entre schéma et données.
  • Les modèles récents donnent plus de poids aux instructions système qu'aux instructions utilisateur, ce qui stabilise le format sans le garantir absolument.
  • Un persona n'a d'intérêt que s'il contraint un comportement observable ; les qualificatifs décoratifs coûtent des jetons sans effet.
  • La stabilité entre appels passe par temperature=0, une graine fixée en développement, et une version de modèle épinglée en production.

Module suivant : le nombre et le choix des exemples à joindre à la consigne, qui décident souvent plus que la reformulation du texte.