Prompt versioning et gestion de catalogue
Mis à jour le 28 juillet 2026
Prompt versioning et gestion de catalogue
En production, vos prompts évoluent autant que votre code. Sans versioning, vous perdez la trace des modifications, vous ne pouvez pas revenir en arrière, et vous ne savez pas quelle version tourne en production. Cette leçon vous montre comment gérer vos prompts comme des artefacts logiciels.
Pourquoi versionner les prompts
Un changement de prompt peut modifier radicalement le comportement de votre application. Contrairement au code, ces changements sont souvent subtils et difficiles à détecter sans tests automatisés : une phrase reformulée, un mot déplacé, et le modèle se met à classer différemment un cas limite sur dix. Aucun test unitaire classique ne verra cet écart, mais le support le remontera trois semaines plus tard sous la forme d’une plainte diffuse sur des tickets mal orientés.
Le versioning répond à quatre besoins distincts. Tracer d’abord : savoir qui a modifié quoi, et quand, transforme une discussion d’équipe stérile en consultation d’historique. Revenir en arrière ensuite, car lorsqu’une régression apparaît, la question immédiate n’est pas de comprendre pourquoi mais de restaurer le comportement connu. Comparer les performances entre versions, aussi, ce qui suppose que les anciennes soient encore exécutables telles quelles, avec leur modèle et leurs réglages d’origine. Auditer, enfin : pour un dispositif soumis au RGPD ou à Qualiopi, il faut démontrer quelles instructions étaient en vigueur à une date donnée, et une capture d’écran ne fera pas l’affaire.
Structure d’un catalogue de prompts
La classe PromptCatalog matérialise ces besoins avec le minimum d’infrastructure : un dossier par prompt, un fichier JSON par version, numéroté de façon croissante. Chaque fichier embarque le texte du system prompt mais aussi le modèle et la température associés — un prompt réglé à 0.1 et le même prompt à 0.7 ne sont pas la même chose, et les figer ensemble évite qu’on restaure un texte en oubliant son réglage.
Le champ status porte le cycle de vie. Une version naît en draft, ce qui permet de la préparer et de la tester sans qu’elle atteigne le trafic. activate la fait passer en active et rétrograde automatiquement la précédente en deprecated, garantissant qu’une seule version sert le trafic à un instant donné. C’est cette méthode qui rend le rollback trivial : réactiver la version antérieure est un appel, pas un déploiement. get_active est celle que votre application appelle en production, tandis que list_versions sert aux revues et aux audits.
import json
from datetime import datetime
from pathlib import Path
class PromptCatalog:
"""Gestionnaire de catalogue de prompts avec versioning."""
def __init__(self, catalog_dir: str = "prompts/"):
self.catalog_dir = Path(catalog_dir)
self.catalog_dir.mkdir(parents=True, exist_ok=True)
def save_prompt(self, name: str, system_prompt: str,
model: str, temperature: float,
metadata: dict = None) -> str:
"""Sauvegarde une nouvelle version d'un prompt."""
prompt_dir = self.catalog_dir / name
prompt_dir.mkdir(exist_ok=True)
# Déterminer le numéro de version
versions = sorted(prompt_dir.glob("v*.json"))
version = len(versions) + 1
version_str = f"v{version:03d}"
# Sauvegarder
prompt_data = {
"name": name,
"version": version_str,
"created_at": datetime.now().isoformat(),
"model": model,
"temperature": temperature,
"system_prompt": system_prompt,
"metadata": metadata or {},
"status": "draft" # draft | active | deprecated
}
filepath = prompt_dir / f"{version_str}.json"
filepath.write_text(json.dumps(prompt_data, indent=2,
ensure_ascii=False))
return version_str
def get_prompt(self, name: str,
version: str = "latest") -> dict:
"""Récupère un prompt par nom et version."""
prompt_dir = self.catalog_dir / name
if version == "latest":
versions = sorted(prompt_dir.glob("v*.json"))
if not versions:
raise FileNotFoundError(f"Prompt '{name}' non trouvé")
filepath = versions[-1]
else:
filepath = prompt_dir / f"{version}.json"
return json.loads(filepath.read_text())
def get_active(self, name: str) -> dict:
"""Récupère la version active d'un prompt."""
prompt_dir = self.catalog_dir / name
for filepath in sorted(prompt_dir.glob("v*.json"),
reverse=True):
data = json.loads(filepath.read_text())
if data["status"] == "active":
return data
raise FileNotFoundError(
f"Aucune version active pour '{name}'"
)
def activate(self, name: str, version: str):
"""Active une version et désactive les autres."""
prompt_dir = self.catalog_dir / name
for filepath in prompt_dir.glob("v*.json"):
data = json.loads(filepath.read_text())
if data["version"] == version:
data["status"] = "active"
elif data["status"] == "active":
data["status"] = "deprecated"
filepath.write_text(json.dumps(data, indent=2,
ensure_ascii=False))
def list_versions(self, name: str) -> list[dict]:
"""Liste toutes les versions d'un prompt."""
prompt_dir = self.catalog_dir / name
versions = []
for filepath in sorted(prompt_dir.glob("v*.json")):
data = json.loads(filepath.read_text())
versions.append({
"version": data["version"],
"status": data["status"],
"created_at": data["created_at"],
"model": data["model"]
})
return versions
Utilisation dans votre application
Côté appelant, la discipline tient en une règle : votre code ne contient plus aucun texte de prompt, il demande au catalogue la version active. Un prompt reste ainsi modifiable sans toucher au code applicatif, et sans qu’une chaîne de trente lignes s’enterre au milieu d’une fonction. Les métadonnées enregistrées à la sauvegarde — auteur, description, changelog — n’ont aucun effet sur l’exécution mais rendent l’historique lisible six mois plus tard, quand plus personne ne se souvient pourquoi la formulation a changé.
Le détail décisif se trouve à la fin de classifier_ticket : la réponse renvoyée embarque prompt_version. Journalisez ce champ avec chaque résultat, et l’analyse d’un incident devient possible — vous saurez que les tickets mal classés du mardi après-midi proviennent tous de la v004, sans avoir à recouper des horodatages de déploiement.
from openai import OpenAI
client = OpenAI()
catalog = PromptCatalog("prompts/")
# Sauvegarder un prompt
catalog.save_prompt(
name="classificateur-tickets",
system_prompt="""Tu es un classificateur de tickets de support.
Catégorise chaque ticket parmi : bug, feature, question, billing.
Réponds en JSON avec category, priority (1-5), summary.""",
model="gpt-5.6-terra",
temperature=0.1,
metadata={
"author": "marie.dupont",
"description": "Classification automatique des tickets Zendesk",
"changelog": "Version initiale"
}
)
# En production : utiliser la version active
def classifier_ticket(ticket_text: str) -> dict:
"""Classifie un ticket en utilisant le prompt actif."""
prompt_config = catalog.get_active("classificateur-tickets")
response = client.responses.create(
model=prompt_config["model"],
instructions=prompt_config["system_prompt"],
input=ticket_text,
temperature=prompt_config["temperature"]
)
return {
"result": json.loads(response.output_text),
"prompt_version": prompt_config["version"]
}
Versioning avec Git
Pour les équipes, stockez vos prompts dans un dépôt Git dédié. L’arborescence ci-dessous applique une convention simple : un dossier par prompt, contenant sa définition, ses cas de test et son changelog. Que les tests vivent à côté du prompt qu’ils vérifient n’est pas cosmétique — une pull request modifie les deux ensemble, et le relecteur juge la modification sur pièces plutôt que sur intention déclarée.
prompts/
classificateur-tickets/
prompt.yaml
tests/
test_cases.json
CHANGELOG.md
extracteur-factures/
prompt.yaml
tests/
test_cases.json
CHANGELOG.md
Le format YAML remplace ici le JSON pour une raison précise : les prompts sont des textes multilignes, et la syntaxe | les affiche tels qu’ils seront envoyés, sans échappement ni \n parasites. Un diff Git sur ce fichier se lit comme un diff de prose — le relecteur voit qu’une phrase a été ajoutée. Le schéma de sortie voyage dans le même fichier, de sorte que la configuration complète d’un appel tient en un artefact unique.
# prompts/classificateur-tickets/prompt.yaml
name: classificateur-tickets
model: gpt-5.6-terra
temperature: 0.1
max_output_tokens: 500
system_prompt: |
Tu es un classificateur de tickets de support.
Catégorise chaque ticket parmi : bug, feature, question, billing.
Réponds en JSON avec category, priority (1-5), summary.
schema:
type: object
properties:
category:
type: string
enum: [bug, feature, question, billing]
priority:
type: integer
summary:
type: string
required: [category, priority, summary]
additionalProperties: false
Bonnes pratiques
Ce qui sépare un catalogue vivant d’un dossier d’archives tient à quelques règles de tenue. Gardez un prompt par fichier : mélanger plusieurs prompts dans un même document rend les diffs illisibles et les rollbacks impossibles à cibler. Imposez un changelog documentant non pas ce qui a changé — le diff le montre — mais pourquoi : « ajout de la consigne sur les tickets multilingues suite aux retours du support » vaut dix lignes de description technique.
Associez systématiquement un jeu de test à chaque prompt, sans quoi une modification qui casse un comportement acquis vous sera signalée par vos utilisateurs. Faites passer les modifications de prompts par la même revue de code que le code lui-même : un prompt qui part en production sans relecture produit les mêmes surprises que du code non relu. Vérifiez que le rollback tient en moins d’une minute, et testez-le un jour calme plutôt qu’en plein incident. Marquez enfin les versions déployées avec des tags, pour relier sans ambiguïté un comportement observé à un état précis du dépôt.
Éprouver le catalogue par un rollback
Montez un PromptCatalog pour trois prompts réels de votre application, puis enregistrez deux ou trois versions de chacun en faisant évoluer les instructions par petites touches — une consigne de ton, une catégorie ajoutée. C’est sur ces modifications fines que le versioning révèle son intérêt, parce que ce sont elles qu’on ne se rappelle pas avoir faites. Mettez en place l’activation et la désactivation, et branchez un jeu de tests sur chaque prompt.
Terminez par l’exercice le plus instructif : activez volontairement une version dégradée, constatez la régression via les tests, et exécutez le rollback en chronométrant. Si l’opération dépasse la minute, c’est la procédure qu’il faut corriger avant d’ajouter le moindre prompt au catalogue — un rollback lent n’est pas un rollback, c’est un incident prolongé.
Points clés à retenir
- Les prompts sont des artefacts logiciels qui nécessitent du versioning
- Chaque version est un fichier JSON ou YAML avec métadonnées
- Le statut (draft, active, deprecated) permet le déploiement progressif
- Stockez les prompts dans Git avec des tests et un changelog
- Le rollback doit être instantané en cas de régression