--- title: "Gabarits, variables et versionnage" duration: 30min description: "Sortir les prompts du code, les paramétrer proprement et suivre leurs versions comme du code." --- > [!NOTE] > Dans cette leçon, vous apprenez à transformer un prompt en **gabarit réutilisable** avec des variables, à le ranger dans un fichier versionné par git, et à savoir à tout moment quelle version a produit quelle réponse. ## Du prompt collé au gabarit ```mermaid flowchart LR A["Prompt copié-collé dans 5 scripts"] --> B["Un fichier par prompt"] B --> C["Variables nommées : client, commande, politique"] C --> D["Version dans le fichier et dans git"] D --> E["Version enregistrée avec chaque réponse"] ``` | Avant | Après | |---|---| | Le même prompt copié dans le bot WhatsApp, le script de mails et un notebook, avec trois variantes divergentes | Un fichier `prompts/reponse_retard.txt`, chargé par les trois outils | | `"Réponds à " + nom + " au sujet de " + commande` construit à la main | Gabarit avec `$prenom`, `$numero_commande`, remplis par `string.Template` | | « Qui a modifié le prompt mardi ? » Personne ne sait | `git log prompts/reponse_retard.txt` | | Une réponse étrange en production, impossible de savoir avec quel prompt | Chaque réponse loggée avec `prompt_id` et `version` | ## Des variables, avec la bibliothèque standard `string.Template` suffit : ses `$variables` ne se confondent pas avec les accolades d'un JSON présent dans le prompt (contrairement à `str.format`). ```text title="prompts/reponse_retard.txt" # id: reponse_retard # version: 1.2.0 # description: Réponse à un client dont la commande est en retard. Tu es conseiller du service client de $entreprise. Numéro $numero_commande, date de livraison prévue : $date_prevue. $message Réponds en 3 phrases maximum, vouvoiement, en $langue. Format : {"reponse": "...", "escalade": true|false} ``` ```python title="gabarits.py" from pathlib import Path from string import Template def charger(nom: str) -> tuple[dict, Template]: lignes = Path(f"prompts/{nom}.txt").read_text(encoding="utf-8").splitlines() meta = {l[2:].split(":")[0]: l.split(":", 1)[1].strip() for l in lignes if l.startswith("# ")} corps = "\n".join(l for l in lignes if not l.startswith("# ")) return meta, Template(corps) meta, gabarit = charger("reponse_retard") prompt = gabarit.substitute(entreprise="Vélos du Sud", numero_commande="A-1029", date_prevue="12 octobre", message="Toujours rien reçu !", langue="français") print(meta["version"]) # 1.2.0 ``` > [!TIP] > `substitute()` lève une erreur si une variable manque. C'est voulu : mieux vaut un plantage en test qu'un prompt envoyé avec `$numero_commande` en clair. ```quiz question: "Pourquoi préférer string.Template à str.format pour un prompt qui contient un exemple JSON ?" options: - "string.Template est plus rapide" - "Les accolades du JSON seraient interprétées comme des variables par str.format" - "str.format n'accepte pas les accents" answer: 1 explanation: "str.format traite chaque { } comme un emplacement : un JSON d'exemple provoque une erreur ou doit être échappé avec {{ }}. Les $variables de string.Template n'ont pas ce problème." ``` ## Versionner ses prompts Un prompt est du code qui s'exprime en français : il se versionne de la même façon. | Changement | Exemple | Version | |---|---|---| | Correction mineure (faute, reformulation sans effet attendu) | Faute de frappe | 1.2.0 → 1.2.1 | | Nouveau comportement compatible | Ajout de la langue arabe | 1.2.1 → 1.3.0 | | Changement qui casse le format de sortie | Nouveau champ JSON obligatoire | 1.3.0 → 2.0.0 | Règles simples : 1. **Un fichier par prompt**, dans un dossier `prompts/` du dépôt. 2. **Une modification = un commit** avec un message qui dit pourquoi (« ajoute l'escalade allergie suite au ticket 88 »). 3. **Loggez `id` et `version` avec chaque appel**, ainsi que le nom du modèle : la même version de prompt peut se comporter différemment sur un autre modèle. 4. **Pas de modification directe en production** : on passe par les tests (leçon suivante). > [!WARNING] > Changer de **modèle** est un changement aussi important que changer de prompt. Relancez toute la suite de tests avant de basculer. ```quiz question: "Que faut-il enregistrer avec chaque réponse produite en production ? (plusieurs réponses)" options: - "L'identifiant et la version du prompt" - "Le nom du modèle utilisé" - "La clé d'API" - "La date de l'appel" answer: [0, 1, 3] explanation: "Identifiant, version, modèle et date permettent de rejouer et d'expliquer une réponse. La clé d'API ne doit jamais apparaître dans des logs." ``` ## Exercice Transformez ce prompt écrit en dur dans un script en gabarit versionné, avec ses métadonnées et ses variables : ```python prompt = "Écris une relance pour Mme Martin qui n'a pas payé la facture F-778 de 340 euros échue le 1er octobre. Ton courtois." ```
Voir la solution ```text title="prompts/relance_facture.txt" # id: relance_facture # version: 1.0.0 # description: Première relance courtoise d'une facture impayée. Tu es chargé de recouvrement chez $entreprise. Ton courtois, jamais menaçant. Client : $civilite $nom. Facture $numero de $montant, échue le $echeance. Rédige un mail de relance de 80 mots maximum : rappelle la facture, propose de contacter le service comptable en cas d'erreur, termine par « Bien cordialement ». ``` ```python meta, g = charger("relance_facture") prompt = g.substitute(entreprise="Atelier Bois", civilite="Mme", nom="Martin", numero="F-778", montant="340 EUR", echeance="1er octobre") ``` Les données personnelles (nom, montant) restent hors du fichier versionné : elles ne sont injectées qu'au moment de l'appel.
## Je sais faire - [ ] Ranger un prompt dans un fichier avec `id`, `version` et `description`. - [ ] Remplir un gabarit avec `string.Template` et détecter une variable manquante. - [ ] Choisir le bon numéro de version selon le type de changement. - [ ] Logger prompt, version et modèle avec chaque réponse.