Lección 4 — Améliorer

Gérer des prompts versionnés

7 min de lectura

Este contenido aún no está traducido. Se muestra la versión original.

Gérer des prompts versionnés

Chez Arvea, le prompt de l'assistant vivait dans le code. La responsable qualité du service client voulait que chaque réponse cite la fiche de procédure utilisée ; il a fallu un ticket, une revue de code, un déploiement, puis un second déploiement pour revenir en arrière quand une formulation a dégradé les réponses. Pendant ce temps, personne ne pouvait dire quelle version du prompt avait produit une réponse donnée. Un registre de prompts sépare le texte du code, garde chaque version et relie chaque génération à la version qui l'a produite.

Le mécanisme : versions immuables, étiquettes mobiles

Dans Langfuse, un prompt a un nom (conseiller-reponse) et une suite de versions numérotées. Une version ne change jamais : texte, type (text ou chat) et config (ici la température et max_output_tokens) sont figés. Les étiquettes (labels) sont des pointeurs déplaçables : production désigne la version servie, staging celle en cours d'évaluation, latest la plus récente. Déployer un prompt, c'est déplacer une étiquette ; revenir en arrière, c'est la remettre sur l'ancienne version.

flowchart LR
  subgraph PG["PostgreSQL de Langfuse"]
    V1["v1 : réponse courte<br/>config temperature 0.2"]
    V2["v2 : cite la référence<br/>config temperature 0.2"]
  end
  P(["production"]) -->|"avant l'expérience"| V1
  S(["staging"]) --> V2
  P -.->|"après promotion"| V2
  APP["Application<br/>get_prompt(label=production)"] --> C["Cache du SDK<br/>60 s, rafraîchi en arrière-plan"]
  C --> PG
  C -->|"Langfuse injoignable au 1er appel"| F["PROMPT_SECOURS<br/>dans le code"]

La mise en œuvre

1. Publier les versions

catalogue.py crée v1 avec l'étiquette production et v2 avec staging, une seule fois :

files/src/assistant/catalogue.py (extrait)
config = {"temperature": 0.2, "max_output_tokens": 1024}
langfuse.create_prompt(name=NOM_PROMPT, type="chat", prompt=PROMPT_SECOURS, config=config,
                       labels=["production"], commit_message="v1 : réponse courte fondée sur les fiches")
langfuse.create_prompt(name=NOM_PROMPT, type="chat", prompt=PROMPT_V2, config=config,
                       labels=["staging"], commit_message="v2 : cite la référence de la fiche")

Le prompt est de type chat : un message système et un message utilisateur avec deux variables, {{fiches}} et {{question}}. Les paramètres du modèle sont dans config, donc versionnés avec le texte : une baisse de température est un changement de prompt comme un autre, avec sa trace.

2. Le lire, avec un filet

files/src/assistant/appli_gemini.py (extrait)
def charger_prompt(langfuse, label):
    return langfuse.get_prompt(NOM_PROMPT, type="chat", label=label,
                               fallback=PROMPT_SECOURS, cache_ttl_seconds=60)

Trois comportements sont à connaître :

  • Cache. Le SDK garde le prompt 60 secondes en mémoire. À expiration, il continue de servir l'ancienne version et la rafraîchit en arrière-plan : la latence d'un appel ne dépend pas de Langfuse.
  • Repli. Si le tout premier appel échoue (Langfuse arrêté au démarrage de l'application), fallback fournit le texte du code. prompt.is_fallback vaut alors True : l'application le journalise et la génération n'est pas reliée à une version.
  • Lien. Le prompt passé à start_as_current_observation(..., prompt=prompt) relie la génération à sa version : l'onglet Metrics du prompt agrège latence, coût et scores par version.

3. Promouvoir par l'API

Après l'expérience de la leçon 5, la promotion est une ligne :

files/src/assistant/__main__.py (extrait)
version = lf.get_prompt(NOM_PROMPT, type="chat", label="staging", cache_ttl_seconds=0).version
lf.api.prompt_version.update(name=NOM_PROMPT, version=version, new_labels=["production"])

cache_ttl_seconds=0 force une lecture fraîche : on promeut ce qui est réellement en staging maintenant, pas ce que le cache a vu il y a une minute.

Résultat obtenu

make seed puis lecture directe, le 2026-10-15 :

INFO assistant.catalogue prompt conseiller-reponse : v1 (production) et v2 (staging) créés
INFO assistant.catalogue jeu de données questions-conseillers : 8 éléments

# après make eval (promotion de v2) :
production -> 2 ['production', 'staging', 'latest'] {'temperature': 0.2, 'max_output_tokens': 1024}
v1 labels []
2e appel (cache) 0.0 ms
sans cache 8.5 ms
Page du prompt conseiller-reponse : v2 porte production, latest et staging ; v1 n'a plus d'étiquette

Capture réelle après promotion : v1 est conservée sans étiquette, v2 est en production. Revenir en arrière = remettre production sur v1.

Ce qui casse en production

SymptômeCauseParade
Le modèle répond à « {{question}} »variable mal nommée ou oubliée dans compile() : le SDK laisse le marqueur tel quel, sans erreur (vérifié : compile(fiches="F") renvoie Question du conseiller : {{question}})test unitaire qui compile chaque prompt et cherche {{ dans le résultat
Un retour arrière met une minute à prendre effetcache de 60 s dans chaque processusTTL plus court pour les prompts critiques, ou redémarrage ; le savoir avant l'incident
Après une panne de Langfuse, des générations ne sont plus reliées à un promptle repli local a servi au démarragejournaliser is_fallback, alerte si le taux dépasse zéro
Le repli local diverge de la productionPROMPT_SECOURS jamais mis à jourle repli est la dernière version validée, mise à jour à chaque promotion (revue de code)
Quelqu'un déplace production dans l'interface sans évaluationdroits trop larges sur le projetrôles Langfuse (lecture seule pour la plupart des membres) ; la promotion passe par le script d'évaluation

La règle de décision

Mettez dans le registre ce qui change plus souvent que le code et que des non-développeurs doivent pouvoir proposer : consignes, ton, exemples, paramètres du modèle. Gardez dans le code ce qui est structurel : schéma de sortie, liste des outils, logique de recherche. Une version n'atteint production qu'après une expérience mesurée. Surveillez la part de générations reliées à chaque version et le taux de repli.

Pratique

Lundi 9 h, une nouvelle version du prompt (v7) passée en production vendredi fait chuter la satisfaction. Quelle est l'action la plus rapide et la plus sûre ?

Pendant une maintenance de Langfuse, l'équipe redémarre l'application. Que se passe-t-il pour les conseillers ?

Exercice. Écrivez un test unitaire, sans réseau, qui garantit qu'aucun prompt du catalogue (PROMPT_SECOURS, PROMPT_V2) ne laisse de variable non remplie quand on fournit fiches et question.

Voir la solution

La substitution des variables suit une règle simple ({{nom}} remplacé par la valeur) ; le test la reproduit sur les messages :

import re
from assistant.appli_gemini import PROMPT_SECOURS
from assistant.catalogue import PROMPT_V2

def compiler(messages, **variables):
    return [re.sub(r"\{\{(\w+)\}\}", lambda x: variables.get(x.group(1), x.group(0)), m["content"])
            for m in messages]

def test_aucune_variable_orpheline():
    for prompt in (PROMPT_SECOURS, PROMPT_V2):
        textes = compiler(prompt, fiches="F", question="Q")
        assert not any("{{" in t for t in textes), textes

Ce test est dans tests/test_assistant.py. Ajoutez une variable {{date}} dans PROMPT_V2 : le test échoue, ce qui est le but.

  • Je sais expliquer la différence entre une version et une étiquette.
  • Je sais lire un prompt avec cache et repli, et savoir lequel a servi.
  • Je sais promouvoir et revenir en arrière sans déploiement de code.
  • Je sais ce qui doit rester dans le code et ce qui va dans le registre.
Siguiente