Aller au contenu principal

Métriques et benchmarks

Mesurer la qualité avec des métriques

Au-delà des scores LLM-as-Judge, il existe des métriques quantitatives pour évaluer différents aspects des sorties LLM. Cette leçon couvre les métriques essentielles et comment les implémenter pour créer vos propres benchmarks.

Métriques de qualité textuelle

Similarité sémantique

Comparez le sens de deux textes plutôt que les mots exacts :

import openai
import numpy as np

client = openai.OpenAI()

def similarite_cosinus(vec_a: list[float], vec_b: list[float]) -> float:
    """Calcule la similarité cosinus entre deux vecteurs."""
    a = np.array(vec_a)
    b = np.array(vec_b)
    return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)))

def similarite_semantique(texte_a: str, texte_b: str) -> float:
    """Mesure la similarité sémantique entre deux textes."""
    response = client.embeddings.create(
        model="text-embedding-3-large",
        input=[texte_a, texte_b],
    )

    vec_a = response.data[0].embedding
    vec_b = response.data[1].embedding

    return similarite_cosinus(vec_a, vec_b)

# Exemple
score = similarite_semantique(
    "Le chat est sur le tapis.",
    "Un félin se repose sur la moquette.",
)
print(f"Similarité : {score:.3f}")  # ~0.85+

Score de pertinence

def score_pertinence(
    question: str,
    reponse: str,
    contexte: str | None = None,
) -> dict:
    """Évalue si la réponse est pertinente par rapport à la question."""
    prompt = (
        "Évaluez la pertinence de cette réponse.\n\n"
        f"Question : {question}\n"
    )
    if contexte:
        prompt += f"Contexte fourni : {contexte[:1000]}\n"
    prompt += (
        f"Réponse : {reponse}\n\n"
        "Critères :\n"
        "1. La réponse répond-elle directement à la question ? (0-5)\n"
        "2. La réponse contient-elle des informations non demandées ? (0-5, 5=aucune)\n"
        "3. La réponse est-elle complète ? (0-5)\n\n"
        "Répondez en JSON avec les clés : directe, focus, completude."
    )

    response = client.responses.create(
        model="gpt-5.3",
        input=prompt,
        temperature=0.0,
    )

    import json
    try:
        scores = json.loads(response.output_text)
        moyenne = sum(scores.values()) / (len(scores) * 5)
        return {"score": moyenne, "details": scores}
    except (json.JSONDecodeError, TypeError):
        return {"score": 0, "erreur": "Parsing échoué"}

Métriques de sécurité

Détection d’hallucinations

def detecter_hallucinations(
    contexte: str,
    reponse: str,
) -> dict:
    """Détecte les affirmations non supportées par le contexte."""
    prompt = (
        "Analysez cette réponse et identifiez toute affirmation "
        "qui n'est PAS supportée par le contexte fourni.\n\n"
        f"Contexte : {contexte[:3000]}\n\n"
        f"Réponse : {reponse}\n\n"
        "Pour chaque affirmation de la réponse, indiquez :\n"
        "- supportee : l'affirmation est dans le contexte\n"
        "- non_supportee : l'affirmation n'est pas dans le contexte\n"
        "- inventee : l'affirmation contredit le contexte\n\n"
        "Répondez en JSON avec la clé affirmations (liste) et "
        "score_fidelite (0 à 1)."
    )

    response = client.responses.create(
        model="gpt-5.3",
        input=prompt,
        temperature=0.0,
    )

    import json
    try:
        return json.loads(response.output_text)
    except json.JSONDecodeError:
        return {"score_fidelite": 0, "erreur": "Parsing échoué"}

Créer un benchmark personnalisé

Structure d’un benchmark

from dataclasses import dataclass, field
import time

@dataclass
class ResultatBenchmark:
    modele: str
    prompt_version: str
    scores: dict
    latence_moyenne: float
    cout_total: float
    date: str = field(default_factory=lambda: time.strftime("%Y-%m-%d"))

class Benchmark:
    """Benchmark réutilisable pour comparer modèles et prompts."""

    def __init__(self, nom: str, dataset: list[dict]):
        self.nom = nom
        self.dataset = dataset
        self.resultats: list[ResultatBenchmark] = []

    def executer(
        self,
        modele: str,
        prompt_systeme: str,
        prompt_version: str,
    ) -> ResultatBenchmark:
        """Exécute le benchmark avec un modèle et prompt donnés."""
        scores_pertinence = []
        scores_hallucination = []
        latences = []
        cout = 0

        for cas in self.dataset:
            debut = time.perf_counter()
            response = client.responses.create(
                model=modele,
                instructions=prompt_systeme,
                input=cas["input"],
            )
            latence = time.perf_counter() - debut
            latences.append(latence)

            # Évaluer
            pertinence = score_pertinence(
                cas["input"], response.output_text
            )
            scores_pertinence.append(pertinence["score"])

            if cas.get("contexte"):
                halluc = detecter_hallucinations(
                    cas["contexte"], response.output_text
                )
                scores_hallucination.append(
                    halluc.get("score_fidelite", 0)
                )

        resultat = ResultatBenchmark(
            modele=modele,
            prompt_version=prompt_version,
            scores={
                "pertinence": sum(scores_pertinence) / len(scores_pertinence),
                "fidelite": (
                    sum(scores_hallucination) / len(scores_hallucination)
                    if scores_hallucination else None
                ),
            },
            latence_moyenne=sum(latences) / len(latences),
            cout_total=cout,
        )

        self.resultats.append(resultat)
        return resultat

    def comparer(self) -> str:
        """Compare tous les résultats du benchmark."""
        lignes = [f"Benchmark: {self.nom}\n"]
        for r in self.resultats:
            lignes.append(
                f"  {r.modele} (prompt {r.prompt_version}) | "
                f"Pertinence: {r.scores[pertinence]:.2f} | "
                f"Latence: {r.latence_moyenne:.2f}s"
            )
        return "\n".join(lignes)

Points clés à retenir

  • La similarité sémantique (embeddings) mesure le sens, pas les mots
  • Détectez les hallucinations en confrontant la réponse au contexte source
  • Créez des benchmarks reproductibles pour comparer modèles et versions de prompt
  • Combinez métriques automatiques et LLM-as-Judge pour une évaluation complète