Lección 2 — Suivre et évaluer

Le registre de prompts et ses alias

6 min de lectura

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

Dans la première version de l'assistant de Valoria, le prompt est une chaîne de caractères dans assistant.py. Le changer demande une revue de code, un déploiement, et rien ne relie la réponse d'un mardi au texte qui l'a produite. Pire : pour comparer deux prompts, il faut deux branches. Le prompt est une configuration qui évolue plus vite que le code ; il lui faut son propre cycle de version.

Le mécanisme

Le registre de prompts de MLflow stocke des versions immuables d'un prompt nommé, et des alias mobiles qui pointent vers une version. Le code ne référence jamais un numéro : il demande prompts:/lab-assistant-rh@production. Promouvoir, c'est déplacer l'alias ; revenir en arrière, c'est le remettre sur la version précédente, sans déploiement.

flowchart LR
  F1["prompts/v1.txt"] -- "register_prompt" --> V1["version 1<br/>(immuable)"]
  F2["prompts/v2.txt"] -- "register_prompt" --> V2["version 2<br/>(immuable)"]
  AP(["@production"]) -.-> V1
  AP -. "après la porte" .-> V2
  APR(["@previous"]) -.-> V1
  APP["assistant.answer()"] -- "load_prompt(@production), cache 60 s" --> AP
RéférenceDésigneUsage
prompts:/lab-assistant-rh/2la version 2, pour toujoursévaluation d'une version précise
prompts:/lab-assistant-rh@productionce que l'alias désigne au moment du chargementapplication
prompts:/lab-assistant-rh@previousla version remplacée lors de la dernière promotionretour arrière

Les variables s'écrivent entre doubles accolades ({{guide}}, {{question}}) ; prompt.format(...) les remplit, et prompt.variables les liste.

La mise en œuvre

Les fichiers prompts/v1.txt et prompts/v2.txt restent la source, revue en demande de fusion. register() les pousse dans le registre de façon idempotente : une nouvelle version n'est créée que si le texte a changé.

files/lab.py (extrait)
def register() -> dict[int, int]:
    """Idempotent : ne crée une version que si le texte du fichier a changé."""
    versions = {}
    for n in (1, 2):
        template = (HERE / "prompts" / f"v{n}.txt").read_text(encoding="utf-8")
        existing = [v for v in search_versions() if v.template == template]
        pv = existing[0] if existing else mlflow.genai.register_prompt(
            name=PROMPT_NAME, template=template, commit_message=f"prompts/v{n}.txt",
            tags={"source": f"prompts/v{n}.txt"},
        )
        versions[n] = pv.version
    try:
        MlflowClient().get_prompt_version_by_alias(PROMPT_NAME, "production")
    except Exception:
        mlflow.genai.set_prompt_alias(PROMPT_NAME, alias="production", version=versions[1])
    return versions

L'alias production n'est posé que s'il n'existe pas : relancer register() après une promotion ne remet pas v1 en production.

Côté application, le chargement par alias passe par un cache de 60 secondes, pour ne pas interroger le serveur à chaque question :

files/assistant.py (extrait)
@mlflow.trace(name="assistant_rh", span_type="CHAIN")
def answer(question: str, prompt_uri: str = f"prompts:/{PROMPT_NAME}@production",
           guide_text: str | None = None, llm=None) -> str:
    prompt = mlflow.genai.load_prompt(prompt_uri, cache_ttl_seconds=60)
    if mlflow.get_current_active_span():  # absent quand le traçage est désactivé
        mlflow.update_current_trace(tags={"prompt_uri": prompt_uri, "prompt_version": str(prompt.version)})
    text = prompt.format(guide=guide_text or guide(), question=question)
    return generate(text, llm=llm)

Le test vérifie l'idempotence et que toutes les variables sont remplies :

files/tests/test_lab.py (extrait)
first = lab.register()
again = lab.register()
assert first == again == {1: 1, 2: 2}
...
assert "{{" not in llm.prompts[0]  # toutes les variables du template sont remplies

Résultat obtenu (2026-10-16)

Premier make e2e sur une pile neuve, puis second sur la même pile :

INFO lab: prompt v1 -> version 1
INFO lab: prompt v2 -> version 2
INFO lab: alias production -> version 1
...
INFO lab: prompt v1 -> version 1 (déjà là)
INFO lab: prompt v2 -> version 2 (déjà là)

Après la promotion (leçon 5), l'interface montre les deux versions et leurs alias :

Registre de prompts MLflow (2026-10-16) : lab-assistant-rh, version 2 avec l'alias production, version 1 avec l'alias previous, texte du prompt v2

Et l'onglet de comparaison met les deux textes côte à côte :

Comparaison des versions 2 et 1 du prompt dans l'interface MLflow (2026-10-16)

Ce qui casse en production

  • Prompt et modèle de même nom. Prompts et modèles partagent un espace de noms. create_registered_model("lab-assistant-rh") est refusé (« name is already taken by a prompt »), mais log_model(registered_model_name="lab-assistant-rh") crée sans erreur une « version 3 » du prompt qui contient un modèle (vérifié avec MLflow 3.17.1). Parade : le modèle s'appelle lab-assistant-rh-modele.
  • Alias déplacé, application inchangée pendant une minute. Le cache de 60 s fait coexister deux versions pendant ce délai sur plusieurs instances. Parade : l'accepter et le dire (la trace porte prompt_version), ou un délai plus court si une promotion doit être instantanée ; ne pas désactiver le cache, qui ajoute un appel au serveur par question.
  • Variable oubliée. Un prompt qui contient {{politique}} au lieu de {{guide}} part chez Gemini avec les accolades littérales. Parade : le test « pas de {{ dans le texte envoyé », et la comparaison de prompt.variables à la liste attendue avant l'enregistrement.
  • Serveur MLflow indisponible. load_prompt échoue et l'assistant ne répond plus. Parade : en production, le prompt est aussi embarqué au déploiement (l'artefact du modèle, leçon 5) et le chargement par alias sert à changer de version sans redéployer ; décidez explicitement lequel l'emporte en cas de panne.

La règle de décision

Versionnez le prompt dans le dépôt (revue) et dans le registre (exécution). Le code ne référence qu'un alias ; les évaluations référencent un numéro de version. Un alias ne se déplace que par la porte de promotion, jamais à la main dans l'interface, sauf retour arrière documenté.

Pratique

Une réponse fausse est signalée à 10 h 02. La promotion de v3 a eu lieu à 10 h 01 et l'application a quatre instances. Quelle version a produit la réponse ?

Un collègue corrige une faute de frappe directement dans le texte de la version 2 du prompt depuis l'interface. Que se passe-t-il ?

Exercice. Écrivez rollback() : remet l'alias production sur la version désignée par previous, et échoue proprement si previous n'existe pas.

Voir la solution
def rollback() -> int:
    client = MlflowClient()
    try:
        prev = client.get_prompt_version_by_alias(PROMPT_NAME, "previous")
    except Exception:
        sys.exit("Aucun alias previous : rien à restaurer.")
    cur = client.get_prompt_version_by_alias(PROMPT_NAME, "production")
    mlflow.genai.set_prompt_alias(PROMPT_NAME, alias="production", version=prev.version)
    mlflow.genai.set_prompt_alias(PROMPT_NAME, alias="previous", version=cur.version)
    print(f"Alias production : v{cur.version} -> v{prev.version}")
    return int(prev.version)

Échanger les deux alias permet d'annuler le retour arrière lui-même. En moins de 60 secondes, toutes les instances servent la version restaurée, sans déploiement.

  • J'enregistre les prompts depuis les fichiers du dépôt, de façon idempotente.
  • Mon application ne charge qu'un alias, avec un cache court.
  • Chaque trace porte la version du prompt réellement utilisée.
  • Je sais revenir en arrière en déplaçant un alias.
Siguiente