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