Aller au contenu principal

A/B testing de prompts

A/B testing de prompts

L’A/B testing de prompts vous permet de comparer objectivement deux versions d’un prompt sur du trafic réel. Au lieu de choisir un prompt par intuition, vous mesurez lequel produit les meilleurs résultats selon vos métriques métier. C’est le passage obligé entre le prototypage et la production.

Principe

Le trafic entrant est réparti aléatoirement entre deux (ou plusieurs) variantes de prompt. Chaque réponse est évaluée selon des critères définis, et la variante gagnante est déployée pour 100 % du trafic.

Framework d’A/B testing

import random
import json
from datetime import datetime
from openai import OpenAI

client = OpenAI()

class PromptExperiment:
    """Framework d'A/B testing pour prompts."""

    def __init__(self, name: str, variants: dict[str, dict]):
        """
        variants = {
            "control": {"system_prompt": "...", "model": "gpt-5.3",
                        "temperature": 0.3},
            "variant_a": {"system_prompt": "...", "model": "gpt-5.3",
                          "temperature": 0.1},
        }
        """
        self.name = name
        self.variants = variants
        self.results = {v: [] for v in variants}

    def assign_variant(self, user_id: str = None) -> str:
        """Assigne une variante (sticky par user_id si fourni)."""
        if user_id:
            # Assignation déterministe par user
            hash_val = hash(f"{self.name}:{user_id}") % 100
            keys = sorted(self.variants.keys())
            split = 100 // len(keys)
            for i, key in enumerate(keys):
                if hash_val < (i + 1) * split:
                    return key
            return keys[-1]
        return random.choice(list(self.variants.keys()))

    def run(self, input_text: str, user_id: str = None,
            schema: dict = None) -> dict:
        """Exécute le prompt avec la variante assignée."""
        variant_name = self.assign_variant(user_id)
        variant = self.variants[variant_name]

        kwargs = {
            "model": variant["model"],
            "instructions": variant["system_prompt"],
            "input": input_text,
            "temperature": variant.get("temperature", 0.3)
        }

        if schema:
            kwargs["text"] = {
                "format": {
                    "type": "json_schema",
                    "name": "response",
                    "schema": schema,
                    "strict": True
                }
            }

        response = client.responses.create(**kwargs)

        result = {
            "variant": variant_name,
            "input": input_text,
            "output": response.output_text,
            "tokens_input": response.usage.input_tokens,
            "tokens_output": response.usage.output_tokens,
            "timestamp": datetime.now().isoformat()
        }

        self.results[variant_name].append(result)
        return result

    def get_stats(self) -> dict:
        """Calcule les statistiques par variante."""
        stats = {}
        for variant, results in self.results.items():
            if not results:
                continue
            tokens_in = [r["tokens_input"] for r in results]
            tokens_out = [r["tokens_output"] for r in results]
            stats[variant] = {
                "count": len(results),
                "avg_tokens_input": sum(tokens_in) / len(tokens_in),
                "avg_tokens_output": sum(tokens_out) / len(tokens_out),
                "total_cost_estimate": self._estimate_cost(
                    sum(tokens_in), sum(tokens_out)
                )
            }
        return stats

    def _estimate_cost(self, tokens_in: int,
                        tokens_out: int) -> float:
        """Estime le coût en USD."""
        return (tokens_in * 2.5 + tokens_out * 10) / 1_000_000

Métriques d’évaluation

Les métriques dépendent de votre cas d’usage :

def evaluer_classification(result: dict,
                            label_attendu: str) -> dict:
    """Évalue une réponse de classification."""
    try:
        output = json.loads(result["output"])
        prediction = output.get("category", "")
    except json.JSONDecodeError:
        prediction = ""

    return {
        "correct": prediction == label_attendu,
        "prediction": prediction,
        "attendu": label_attendu
    }

def evaluer_qualite_texte(result: dict) -> dict:
    """Évalue la qualité d'une réponse textuelle avec le LLM."""
    eval_response = client.responses.create(
        model="gpt-5.3",
        instructions="Tu es un évaluateur de qualité de texte. "
                     "Note sur 10 selon : pertinence, clarté, "
                     "complétude, ton professionnel.",
        input=f"Question : {result['input']}\n\n"
              f"Réponse : {result['output']}",
        text={
            "format": {
                "type": "json_schema",
                "name": "evaluation",
                "schema": {
                    "type": "object",
                    "properties": {
                        "pertinence": {"type": "integer"},
                        "clarte": {"type": "integer"},
                        "completude": {"type": "integer"},
                        "ton": {"type": "integer"},
                        "note_globale": {"type": "number"},
                        "commentaire": {"type": "string"}
                    },
                    "required": ["pertinence", "clarte",
                                 "completude", "ton",
                                 "note_globale", "commentaire"],
                    "additionalProperties": False
                },
                "strict": True
            }
        },
        temperature=0.1
    )
    return json.loads(eval_response.output_text)

Exemple complet : tester deux styles de prompt

experiment = PromptExperiment(
    name="style-support-client",
    variants={
        "control": {
            "system_prompt": "Tu es un agent de support. "
                             "Réponds de manière professionnelle.",
            "model": "gpt-5.3",
            "temperature": 0.3
        },
        "empathique": {
            "system_prompt": "Tu es un agent de support bienveillant. "
                             "Commence par reconnaître le problème du "
                             "client, montre de l'empathie, puis propose "
                             "une solution concrète avec les étapes.",
            "model": "gpt-5.3",
            "temperature": 0.3
        }
    }
)

# Simuler du trafic
test_tickets = [
    "Mon colis n'est pas arrivé depuis 2 semaines.",
    "L'application plante quand je clique sur Paramètres.",
    "Comment changer mon mot de passe ?",
    "Je veux un remboursement, le produit est défectueux.",
    "Votre API retourne une erreur 500 depuis ce matin.",
]

for ticket in test_tickets:
    result = experiment.run(ticket)
    quality = evaluer_qualite_texte(result)
    print(f"Variante: {result['variant']} | "
          f"Note: {quality['note_globale']}/10")

print("\nStatistiques :")
print(json.dumps(experiment.get_stats(), indent=2))

Significativité statistique

Ne tirez pas de conclusions trop vite. Avec peu de données, les résultats sont bruités :

from scipy import stats

def test_significativite(scores_a: list[float],
                          scores_b: list[float]) -> dict:
    """Test t de Student pour comparer deux variantes."""
    t_stat, p_value = stats.ttest_ind(scores_a, scores_b)

    return {
        "moyenne_a": sum(scores_a) / len(scores_a),
        "moyenne_b": sum(scores_b) / len(scores_b),
        "p_value": p_value,
        "significatif": p_value < 0.05,
        "recommandation": (
            "Différence significative, déployez la variante gagnante"
            if p_value < 0.05
            else "Pas assez de données ou différence non significative"
        )
    }

Règle pratique : visez au moins 100 observations par variante avant de conclure.

Mise en pratique

  1. Créez un PromptExperiment avec deux variantes pour une tâche de votre choix
  2. Définissez 2-3 métriques d’évaluation
  3. Exécutez l’expérience sur 20 entrées de test
  4. Analysez les résultats et déterminez s’il y a un gagnant
  5. Documentez la décision et archivez les résultats

Points clés à retenir

  • L’A/B testing remplace l’intuition par des données mesurables
  • Assignez les variantes de manière déterministe par utilisateur
  • Mesurez les métriques métier, pas seulement la qualité perçue
  • Attendez la significativité statistique avant de conclure
  • Documentez chaque expérience et ses résultats