Évaluation et Observabilité des LLM en Production avec DeepEval et Langfuse

Mettez en place un système complet d'évaluation et d'observabilité pour vos LLM en production. DeepEval, Langfuse, métriques RAG, LLM-as-a-Judge et intégration CI/CD avec exemples Python.

Introduction : Pourquoi l'Évaluation des LLM est Devenue Non-Négociable

En 2024, la plupart des entreprises expérimentaient les grands modèles de langage dans des prototypes et des démos internes. Deux ans plus tard, la donne a complètement changé : les LLM sont au cœur de flux de travail critiques — support client automatisé, génération de documents juridiques, assistants de code, agents autonomes. Et ce passage du prototype à la production a mis en lumière un problème qu'on ne peut plus ignorer : sans évaluation rigoureuse, un LLM en production est une bombe à retardement.

Hallucinations, dérive de qualité, régressions silencieuses après un changement de prompt… ces problèmes ne sont plus anecdotiques. Selon Gartner, plus de 40 % des projets d'IA agentique risquent d'être abandonnés sans gouvernance et évaluation appropriées. Et honnêtement, quand on voit le coût potentiel d'une hallucination dans un contexte médical ou juridique (on parle en millions d'euros), on comprend vite pourquoi.

L'évaluation des LLM n'est plus optionnelle. C'est désormais une infrastructure critique, au même titre que les tests unitaires ou les pipelines CI/CD.

Dans ce guide, on va construire ensemble un système complet d'évaluation et d'observabilité avec deux outils open source incontournables : DeepEval pour l'évaluation automatisée et Langfuse pour l'observabilité en production.

Concrètement, vous apprendrez à :

  • Mettre en place des métriques d'évaluation automatisées (hallucination, fidélité, pertinence)
  • Créer des métriques personnalisées avec G-Eval
  • Évaluer spécifiquement un pipeline RAG
  • Intégrer les évaluations dans votre CI/CD
  • Tracer vos appels LLM en production avec Langfuse
  • Implémenter le pattern LLM-as-a-Judge
  • Concevoir une architecture complète du développement à la production

Les Trois Piliers : Évaluation, Observabilité et Monitoring

Avant de plonger dans le code, prenons un moment pour clarifier trois concepts qu'on confond souvent — mais qui sont fondamentalement distincts. Chacun répond à des besoins différents et intervient à des moments différents du cycle de vie d'une application LLM.

Évaluation (Evals)

L'évaluation mesure la qualité intrinsèque des sorties d'un LLM. En gros, elle répond à la question : « Mon modèle produit-il des réponses correctes, pertinentes et fidèles au contexte fourni ? »

Les métriques clés incluent :

  • Pertinence de la réponse (Answer Relevancy) : la réponse correspond-elle à la question posée ?
  • Fidélité (Faithfulness) : la réponse est-elle fidèle au contexte fourni, sans ajout d'informations inventées ?
  • Hallucination : le modèle génère-t-il des informations factuellement incorrectes ?
  • Pertinence contextuelle : dans un pipeline RAG, les documents récupérés sont-ils pertinents ?

Quand l'utiliser : pendant le développement, avant le déploiement, dans les pipelines CI/CD comme porte de qualité (quality gate).

Observabilité (Tracing)

L'observabilité capture le comportement détaillé de votre application LLM en temps réel. La question ici : « Que s'est-il passé exactement lors de cet appel ? »

Les données capturées incluent :

  • Traces et spans : le parcours complet d'une requête à travers votre système
  • Prompts et réponses : les entrées et sorties exactes de chaque appel LLM
  • Utilisation de tokens : nombre de tokens d'entrée et de sortie par appel
  • Latence : temps de réponse de chaque composant
  • Coût : coût monétaire de chaque appel API

Quand l'utiliser : en production pour le débogage, l'analyse des performances et la compréhension du comportement utilisateur.

Monitoring

Le monitoring, c'est ce qui détecte les anomalies et dégradations au fil du temps. Question centrale : « La qualité de mon système se maintient-elle ? »

Ce qu'il détecte :

  • Dérive de qualité (quality drift) : dégradation progressive des réponses
  • Anomalies : pics soudains d'erreurs ou de latence
  • Régressions : baisse de performance après un changement de prompt ou de modèle
  • Dépassements de coûts : augmentation inattendue de la consommation de tokens

Quand l'utiliser : en continu en production, avec des alertes automatisées.

Mettre en Place un Pipeline d'Évaluation avec DeepEval

DeepEval est un framework open source d'évaluation de LLM qui s'intègre nativement avec Pytest. Il propose plus de 14 métriques prêtes à l'emploi et supporte le pattern LLM-as-a-Judge avec différents modèles, y compris Claude d'Anthropic. C'est probablement l'outil le plus complet dans cette catégorie à l'heure actuelle.

Installation et Configuration

Commençons par installer DeepEval et configurer l'environnement :

# Installation de DeepEval
pip install deepeval

# Si vous souhaitez utiliser Claude comme modèle juge
pip install deepeval[anthropic]

# Vérifier l'installation
deepeval --version

DeepEval peut utiliser différents modèles comme juge pour évaluer les sorties. Pour utiliser Claude, il suffit de configurer la clé API Anthropic :

import os

# Configuration de la clé API Anthropic pour le modèle juge
os.environ["ANTHROPIC_API_KEY"] = "votre-clé-api-anthropic"

# Ou via un fichier .env
# ANTHROPIC_API_KEY=votre-clé-api-anthropic

Pour utiliser Claude comme modèle juge dans DeepEval, on passe par la classe AnthropicModel :

from deepeval.models import AnthropicModel

# Initialiser Claude comme modèle juge
claude_judge = AnthropicModel(
    model="claude-sonnet-4-20250514",
    temperature=0
)

Créer Votre Premier Test d'Évaluation

DeepEval utilise le concept de LLMTestCase pour représenter un cas de test. Chaque cas contient une entrée (input), une sortie réelle (actual_output) et éventuellement une sortie attendue ou un contexte de récupération.

Voici un exemple complet avec trois métriques fondamentales — c'est vraiment le strict minimum pour démarrer :

from deepeval import evaluate
from deepeval.test_case import LLMTestCase
from deepeval.metrics import (
    AnswerRelevancyMetric,
    FaithfulnessMetric,
    HallucinationMetric
)
from deepeval.models import AnthropicModel

# Configurer le modèle juge
claude_judge = AnthropicModel(
    model="claude-sonnet-4-20250514",
    temperature=0
)

# Définir les métriques avec des seuils
relevancy_metric = AnswerRelevancyMetric(
    threshold=0.7,
    model=claude_judge
)

faithfulness_metric = FaithfulnessMetric(
    threshold=0.8,
    model=claude_judge
)

hallucination_metric = HallucinationMetric(
    threshold=0.5,  # Score en dessous duquel le test échoue
    model=claude_judge
)

# Créer un cas de test
test_case = LLMTestCase(
    input="Quels sont les avantages du RAG par rapport au fine-tuning ?",
    actual_output="""Le RAG (Retrieval-Augmented Generation) présente plusieurs
    avantages par rapport au fine-tuning : 1) Il permet d'accéder à des données
    actualisées sans réentraînement du modèle. 2) Il est moins coûteux car il
    ne nécessite pas de GPU pour l'entraînement. 3) Il offre une meilleure
    traçabilité grâce aux sources citées. 4) Il réduit les hallucinations en
    ancrant les réponses dans des documents vérifiés.""",
    expected_output="""Le RAG offre des données à jour, un coût réduit,
    une meilleure traçabilité et moins d'hallucinations.""",
    retrieval_context=[
        "Le RAG permet d'accéder à des informations actualisées sans réentraîner le modèle.",
        "Le fine-tuning nécessite des ressources GPU importantes et coûteuses.",
        "Le RAG améliore la traçabilité en citant les sources des réponses.",
        "Les systèmes RAG réduisent les hallucinations en s'appuyant sur des documents vérifiés."
    ]
)

# Exécuter l'évaluation
results = evaluate(
    test_cases=[test_case],
    metrics=[relevancy_metric, faithfulness_metric, hallucination_metric]
)

# Afficher les résultats
for result in results.test_results:
    print(f"Test: {result.success}")
    for metric_result in result.metrics_data:
        print(f"  {metric_result.name}: {metric_result.score:.2f} "
              f"(seuil: {metric_result.threshold})")
        if metric_result.reason:
            print(f"  Raison: {metric_result.reason}")

Ce code évalue trois aspects fondamentaux : la pertinence de la réponse par rapport à la question, la fidélité par rapport au contexte fourni, et la présence d'hallucinations. Chaque métrique utilise Claude comme juge pour produire un score entre 0 et 1.

G-Eval : Créer des Métriques Personnalisées

Les métriques intégrées ne couvrent pas toujours tous vos besoins — et c'est là que G-Eval devient vraiment intéressant. Cette fonctionnalité vous permet de créer des métriques personnalisées en définissant vos propres critères d'évaluation en langage naturel :

from deepeval.metrics import GEval
from deepeval.test_case import LLMTestCase, LLMTestCaseParams

# Métrique personnalisée : qualité technique du contenu
technical_quality_metric = GEval(
    name="Qualité Technique",
    criteria="""Évaluer la qualité technique de la réponse selon les critères suivants :
    1. Exactitude des informations techniques (concepts, terminologie, code)
    2. Complétude de la réponse par rapport à la question posée
    3. Clarté des explications et progression logique
    4. Pertinence et correction des exemples de code fournis""",
    evaluation_params=[
        LLMTestCaseParams.INPUT,
        LLMTestCaseParams.ACTUAL_OUTPUT
    ],
    threshold=0.7,
    model=claude_judge
)

# Métrique personnalisée : ton professionnel en français
professional_tone_metric = GEval(
    name="Ton Professionnel",
    criteria="""Évaluer si la réponse maintient un ton professionnel et pédagogique :
    1. Le registre de langue est-il approprié pour un contexte technique ?
    2. Les termes techniques anglais sont-ils correctement intégrés dans le texte français ?
    3. La réponse évite-t-elle le jargon inutile tout en restant précise ?
    4. Le ton est-il engageant sans être familier ?""",
    evaluation_params=[
        LLMTestCaseParams.INPUT,
        LLMTestCaseParams.ACTUAL_OUTPUT
    ],
    threshold=0.6,
    model=claude_judge
)

# Créer et évaluer un cas de test
test_case = LLMTestCase(
    input="Explique comment fonctionne l'attention multi-tête dans un Transformer.",
    actual_output="""L'attention multi-tête (Multi-Head Attention) est un mécanisme
    central des architectures Transformer. Au lieu d'appliquer une seule fonction
    d'attention, le modèle projette les queries, keys et values en h sous-espaces
    différents via des projections linéaires apprises. Chaque "tête" calcule
    l'attention indépendamment, capturant ainsi différents types de relations
    dans la séquence. Les résultats des h têtes sont ensuite concaténés et
    projetés une dernière fois. Formellement :
    MultiHead(Q,K,V) = Concat(head_1,...,head_h) × W_O
    où head_i = Attention(Q×W_Q_i, K×W_K_i, V×W_V_i)."""
)

results = evaluate(
    test_cases=[test_case],
    metrics=[technical_quality_metric, professional_tone_metric]
)

print(f"Qualité technique : {results.test_results[0].metrics_data[0].score:.2f}")
print(f"Ton professionnel : {results.test_results[0].metrics_data[1].score:.2f}")

Évaluer un Pipeline RAG

Bon, passons aux choses sérieuses. L'évaluation d'un pipeline RAG nécessite des métriques spécifiques qui mesurent à la fois la qualité de la récupération et la qualité de la génération. DeepEval propose des métriques dédiées pour chaque dimension :

from deepeval import evaluate
from deepeval.test_case import LLMTestCase
from deepeval.metrics import (
    ContextualRelevancyMetric,
    ContextualRecallMetric,
    ContextualPrecisionMetric,
    FaithfulnessMetric,
    AnswerRelevancyMetric
)
from deepeval.models import AnthropicModel

claude_judge = AnthropicModel(model="claude-sonnet-4-20250514", temperature=0)

# Métriques de récupération (retrieval)
contextual_relevancy = ContextualRelevancyMetric(
    threshold=0.7,
    model=claude_judge
)

contextual_recall = ContextualRecallMetric(
    threshold=0.7,
    model=claude_judge
)

contextual_precision = ContextualPrecisionMetric(
    threshold=0.7,
    model=claude_judge
)

# Métriques de génération
faithfulness = FaithfulnessMetric(threshold=0.8, model=claude_judge)
answer_relevancy = AnswerRelevancyMetric(threshold=0.7, model=claude_judge)

# Simuler un cas de test RAG
rag_test_case = LLMTestCase(
    input="Quelle est la politique de remboursement pour les abonnements annuels ?",
    actual_output="""Les abonnements annuels peuvent être remboursés intégralement
    dans les 30 premiers jours suivant la souscription. Après cette période,
    un remboursement au prorata est possible pour les mois restants, avec des
    frais administratifs de 50€. Les demandes doivent être soumises via le
    portail client ou par email à [email protected].""",
    expected_output="""Remboursement intégral sous 30 jours, puis au prorata
    avec 50€ de frais administratifs.""",
    retrieval_context=[
        "Politique de remboursement : Les abonnements annuels bénéficient d'une "
        "garantie satisfait ou remboursé de 30 jours. Le remboursement est intégral "
        "si la demande est faite dans ce délai.",
        "Après la période de 30 jours, les clients peuvent demander un remboursement "
        "au prorata des mois non utilisés. Des frais administratifs de 50€ "
        "s'appliquent à toute demande de remboursement tardive.",
        "Les demandes de remboursement doivent être soumises via le portail client "
        "en ligne ou par email à [email protected]. Le traitement prend 5 à 10 "
        "jours ouvrés.",
        "Les réductions promotionnelles ne sont pas remboursables. Seul le montant "
        "effectivement payé est pris en compte pour le calcul du remboursement."
    ]
)

# Évaluation complète du pipeline RAG
results = evaluate(
    test_cases=[rag_test_case],
    metrics=[
        contextual_relevancy,
        contextual_recall,
        contextual_precision,
        faithfulness,
        answer_relevancy
    ]
)

# Rapport détaillé
print("=== Rapport d'Évaluation RAG ===")
print("\n--- Métriques de Récupération ---")
for metric_data in results.test_results[0].metrics_data[:3]:
    status = "PASS" if metric_data.success else "FAIL"
    print(f"  [{status}] {metric_data.name}: {metric_data.score:.2f}")
print("\n--- Métriques de Génération ---")
for metric_data in results.test_results[0].metrics_data[3:]:
    status = "PASS" if metric_data.success else "FAIL"
    print(f"  [{status}] {metric_data.name}: {metric_data.score:.2f}")

Ces cinq métriques couvrent l'ensemble du pipeline. Les trois premières évaluent la récupération (les documents retournés sont-ils pertinents, complets et bien classés ?), les deux dernières évaluent la génération (la réponse est-elle fidèle et pertinente ?). En pratique, c'est un excellent point de départ pour tout pipeline RAG.

Intégration CI/CD avec Pytest

Un des gros avantages de DeepEval, c'est son intégration native avec Pytest. Vous pouvez ajouter des tests d'évaluation LLM directement dans votre pipeline CI/CD, exactement comme des tests unitaires classiques :

# tests/test_llm_evals.py
import pytest
from deepeval import assert_test
from deepeval.test_case import LLMTestCase
from deepeval.metrics import (
    AnswerRelevancyMetric,
    FaithfulnessMetric,
    HallucinationMetric
)
from deepeval.models import AnthropicModel
from deepeval.dataset import EvaluationDataset

claude_judge = AnthropicModel(model="claude-sonnet-4-20250514", temperature=0)

# Définir les métriques globalement
relevancy = AnswerRelevancyMetric(threshold=0.7, model=claude_judge)
faithfulness = FaithfulnessMetric(threshold=0.8, model=claude_judge)
hallucination = HallucinationMetric(threshold=0.5, model=claude_judge)

# Charger les cas de test depuis un fichier JSON ou les définir ici
test_cases = [
    LLMTestCase(
        input="Qu'est-ce que le machine learning ?",
        actual_output="Le machine learning est une branche de l'intelligence "
                      "artificielle qui permet aux systèmes d'apprendre à "
                      "partir de données sans être explicitement programmés.",
        retrieval_context=[
            "Le machine learning (apprentissage automatique) est un sous-domaine "
            "de l'IA qui développe des algorithmes capables d'apprendre et de "
            "s'améliorer à partir de l'expérience (données)."
        ]
    ),
    LLMTestCase(
        input="Comment fonctionne un réseau de neurones ?",
        actual_output="Un réseau de neurones est composé de couches de neurones "
                      "artificiels interconnectés. Chaque neurone reçoit des "
                      "entrées pondérées, applique une fonction d'activation, "
                      "et transmet le résultat aux neurones de la couche suivante.",
        retrieval_context=[
            "Les réseaux de neurones artificiels sont composés de couches "
            "interconnectées de nœuds (neurones) qui traitent les données "
            "via des poids et des fonctions d'activation."
        ]
    )
]

dataset = EvaluationDataset(test_cases=test_cases)


@pytest.mark.parametrize("test_case", dataset, ids=lambda x: x.input[:50])
def test_llm_answer_relevancy(test_case: LLMTestCase):
    """Vérifie que les réponses du LLM sont pertinentes."""
    assert_test(test_case, [relevancy])


@pytest.mark.parametrize("test_case", dataset, ids=lambda x: x.input[:50])
def test_llm_faithfulness(test_case: LLMTestCase):
    """Vérifie que les réponses sont fidèles au contexte."""
    assert_test(test_case, [faithfulness])


@pytest.mark.parametrize("test_case", dataset, ids=lambda x: x.input[:50])
def test_llm_hallucination(test_case: LLMTestCase):
    """Vérifie l'absence d'hallucinations."""
    assert_test(test_case, [hallucination])

Pour exécuter les tests dans votre pipeline CI/CD :

# Exécuter les tests d'évaluation
deepeval test run tests/test_llm_evals.py

# Ou avec pytest directement
pytest tests/test_llm_evals.py -v

# Dans un fichier GitHub Actions (.github/workflows/llm-evals.yml)
# name: LLM Evaluation
# on: [push, pull_request]
# jobs:
#   eval:
#     runs-on: ubuntu-latest
#     steps:
#       - uses: actions/checkout@v4
#       - name: Install dependencies
#         run: pip install deepeval[anthropic]
#       - name: Run LLM evaluations
#         env:
#           ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
#         run: deepeval test run tests/test_llm_evals.py --verbose

Observabilité en Production avec Langfuse

Si DeepEval est votre outil d'évaluation en phase de développement, Langfuse est son complément naturel pour la production. C'est une plateforme open source qui capture, analyse et visualise l'ensemble des interactions de votre application avec les LLM. Et franchement, une fois qu'on l'a mis en place, on se demande comment on faisait avant.

Architecture et Installation

Langfuse s'appuie sur OpenTelemetry et propose deux modes de déploiement : en cloud (géré par Langfuse) ou en auto-hébergé via Docker. L'architecture repose sur la collecte de traces composées de spans, de générations et d'événements.

# Installation du SDK Python Langfuse
pip install langfuse

# Variables d'environnement requises
# Pour Langfuse Cloud :
export LANGFUSE_PUBLIC_KEY="pk-lf-..."
export LANGFUSE_SECRET_KEY="sk-lf-..."
export LANGFUSE_HOST="https://cloud.langfuse.com"

# Pour Langfuse self-hosted :
# export LANGFUSE_HOST="http://localhost:3000"

Configuration initiale dans votre code Python :

import os
from langfuse import Langfuse

# Le client Langfuse lit automatiquement les variables d'environnement
langfuse = Langfuse()

# Vérifier la connexion
langfuse.auth_check()
print("Connexion à Langfuse établie avec succès.")

Tracer Vos Appels LLM

Langfuse utilise le concept de traces pour capturer le parcours complet d'une requête. Chaque trace contient des spans (opérations) et des générations (appels LLM). Depuis la version 2.x, l'approche recommandée utilise le décorateur @observe — et c'est vraiment élégant :

from langfuse import Langfuse
from langfuse.decorators import observe, langfuse_context
import anthropic

langfuse = Langfuse()
client = anthropic.Anthropic()


@observe()
def recuperer_contexte(question: str) -> list[str]:
    """Simule la récupération de documents pertinents."""
    # Dans un vrai système, ceci interrogerait une base vectorielle
    documents = [
        "Le RAG améliore la précision des réponses en s'appuyant sur des sources vérifiées.",
        "Les bases vectorielles comme ChromaDB ou Qdrant stockent les embeddings des documents."
    ]

    # Ajouter des métadonnées au span courant
    langfuse_context.update_current_observation(
        metadata={"num_documents": len(documents), "source": "vector_db"}
    )

    return documents


@observe()
def generer_reponse(question: str, contexte: list[str]) -> str:
    """Génère une réponse en utilisant Claude avec le contexte récupéré."""
    contexte_formate = "\n".join(f"- {doc}" for doc in contexte)
    prompt = f"""Contexte :\n{contexte_formate}\n\nQuestion : {question}\n\nRéponds de manière précise et concise."""

    response = client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=1024,
        messages=[{"role": "user", "content": prompt}]
    )

    reponse_texte = response.content[0].text

    # Enregistrer la génération avec les détails du modèle
    langfuse_context.update_current_observation(
        model="claude-sonnet-4-20250514",
        input=prompt,
        output=reponse_texte,
        usage={
            "input": response.usage.input_tokens,
            "output": response.usage.output_tokens
        }
    )

    return reponse_texte


@observe()
def pipeline_rag(question: str) -> str:
    """Pipeline RAG complet avec traçage automatique."""
    contexte = recuperer_contexte(question)
    reponse = generer_reponse(question, contexte)

    # Ajouter un score de qualité à la trace
    langfuse_context.update_current_trace(
        user_id="user-123",
        tags=["rag", "production"],
        metadata={"version": "1.0.0"}
    )

    return reponse


# Exécuter le pipeline
reponse = pipeline_rag("Comment fonctionne le RAG ?")
print(reponse)

# S'assurer que toutes les données sont envoyées à Langfuse
langfuse.flush()

Ce code crée automatiquement une trace hiérarchique dans Langfuse : le span pipeline_rag contient deux sous-spans (recuperer_contexte et generer_reponse), chacun avec ses propres métadonnées. C'est puissant et ça prend très peu de lignes de code à mettre en place.

Intégration avec LangChain et les Agents

Si vous utilisez LangChain ou des agents, Langfuse propose un CallbackHandler qui s'intègre directement dans la chaîne d'exécution :

from langfuse.callback import CallbackHandler
from langchain_anthropic import ChatAnthropic
from langchain.agents import AgentExecutor, create_tool_calling_agent
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.tools import tool

# Créer le handler Langfuse pour LangChain
langfuse_handler = CallbackHandler(
    public_key=os.environ["LANGFUSE_PUBLIC_KEY"],
    secret_key=os.environ["LANGFUSE_SECRET_KEY"],
    host=os.environ.get("LANGFUSE_HOST", "https://cloud.langfuse.com"),
    trace_name="agent-support-client"
)


@tool
def rechercher_faq(question: str) -> str:
    """Recherche dans la FAQ les réponses aux questions courantes."""
    faq = {
        "remboursement": "Les remboursements sont possibles sous 30 jours.",
        "livraison": "La livraison standard prend 3 à 5 jours ouvrés.",
    }
    for cle, reponse in faq.items():
        if cle in question.lower():
            return reponse
    return "Aucune réponse trouvée dans la FAQ."


@tool
def consulter_compte(user_id: str) -> str:
    """Consulte les informations du compte client."""
    return f"Compte {user_id}: Abonnement Premium, actif depuis le 15/01/2026."


# Configurer l'agent
llm = ChatAnthropic(model="claude-sonnet-4-20250514", temperature=0)
prompt = ChatPromptTemplate.from_messages([
    ("system", "Tu es un assistant de support client professionnel et serviable."),
    ("human", "{input}"),
    ("placeholder", "{agent_scratchpad}")
])

agent = create_tool_calling_agent(llm, [rechercher_faq, consulter_compte], prompt)
agent_executor = AgentExecutor(agent=agent, tools=[rechercher_faq, consulter_compte])

# Exécuter avec le traçage Langfuse
result = agent_executor.invoke(
    {"input": "Je voudrais un remboursement pour ma commande."},
    config={"callbacks": [langfuse_handler]}
)

print(result["output"])

Chaque appel d'outil, chaque décision de l'agent et chaque génération du LLM apparaîtront comme des spans distincts dans la trace Langfuse. Très pratique pour comprendre exactement le raisonnement de l'agent quand quelque chose ne se passe pas comme prévu.

Tableaux de Bord et Alertes

Une fois vos traces collectées, Langfuse fournit des tableaux de bord complets pour analyser vos données en production :

  • Suivi des coûts : visualisez le coût par trace, par utilisateur, par fonctionnalité. Idéal pour identifier les prompts les plus gourmands et les optimiser.
  • Monitoring de la latence : suivez les temps de réponse par modèle et par type de requête. Vous pouvez détecter les dégradations avant qu'elles n'impactent les utilisateurs.
  • Scores de qualité : associez des scores manuels ou automatisés (via LLM-as-a-Judge) à vos traces pour suivre la qualité dans le temps.
  • Échantillonnage pour volumes élevés : pour les applications à fort trafic, configurez un taux d'échantillonnage afin de limiter les coûts tout en conservant une visibilité statistiquement significative.
# Configurer l'échantillonnage pour les environnements à fort volume
from langfuse import Langfuse

langfuse = Langfuse(
    sample_rate=0.3  # Tracer 30% des requêtes en production
)

LLM-as-a-Judge : Le Pattern Clé pour l'Évaluation Automatisée

Le pattern LLM-as-a-Judge consiste à utiliser un LLM performant pour évaluer automatiquement les sorties d'un autre LLM. C'est devenu la pierre angulaire de l'évaluation automatisée, et pour cause : il permet de mesurer des dimensions qualitatives (pertinence, cohérence, ton) qui échappent complètement aux métriques traditionnelles basées sur le chevauchement lexical comme BLEU ou ROUGE.

Principe et Fonctionnement

Le principe est assez simple en fait : on fournit au modèle juge un prompt d'évaluation structuré contenant la question originale, la réponse à évaluer, et éventuellement une réponse de référence. Le juge produit alors un score et une justification.

Mais pour que ça marche vraiment bien, il y a quelques bonnes pratiques à respecter :

  • Prompting Chain-of-Thought : demander au juge de raisonner étape par étape avant de donner son score. Ça améliore significativement la cohérence des évaluations.
  • Sorties JSON structurées : imposer un format de sortie JSON avec un score numérique et un champ de justification. Ça facilite grandement l'automatisation.
  • Critères explicites : définir précisément les critères d'évaluation et l'échelle de notation (par exemple 1-5 avec description de chaque niveau).
  • Calibration : tester le juge sur un ensemble d'exemples annotés manuellement pour vérifier la corrélation avec le jugement humain. C'est une étape qu'on oublie trop souvent.

Implémenter un Judge avec DeepEval et Langfuse

La combinaison DeepEval + Langfuse permet de créer un système d'évaluation automatisée directement en production. DeepEval fournit les métriques et la logique d'évaluation, Langfuse stocke les scores au niveau de chaque observation :

from langfuse import Langfuse
from langfuse.decorators import observe, langfuse_context
from deepeval.test_case import LLMTestCase
from deepeval.metrics import AnswerRelevancyMetric, FaithfulnessMetric
from deepeval.models import AnthropicModel
import anthropic

langfuse = Langfuse()
client = anthropic.Anthropic()
claude_judge = AnthropicModel(model="claude-sonnet-4-20250514", temperature=0)


def evaluer_et_scorer(question: str, reponse: str, contexte: list[str],
                      trace_id: str) -> dict:
    """Evalue une réponse LLM et envoie les scores à Langfuse."""
    # Créer le cas de test DeepEval
    test_case = LLMTestCase(
        input=question,
        actual_output=reponse,
        retrieval_context=contexte
    )

    # Évaluer avec DeepEval
    relevancy = AnswerRelevancyMetric(threshold=0.7, model=claude_judge)
    faithfulness = FaithfulnessMetric(threshold=0.8, model=claude_judge)

    relevancy.measure(test_case)
    faithfulness.measure(test_case)

    scores = {
        "relevancy": relevancy.score,
        "faithfulness": faithfulness.score
    }

    # Envoyer les scores à Langfuse
    for metric_name, score in scores.items():
        langfuse.score(
            trace_id=trace_id,
            name=metric_name,
            value=score,
            comment=f"Évaluation automatique via DeepEval - {metric_name}"
        )

    return scores


@observe()
def pipeline_rag_avec_evaluation(question: str) -> dict:
    """Pipeline RAG complet avec évaluation automatique intégrée."""
    # Étape 1 : Récupération
    contexte = [
        "Les embeddings sont des représentations vectorielles denses de texte.",
        "Les bases vectorielles permettent une recherche sémantique efficace."
    ]

    # Étape 2 : Génération
    ctx_text = "\n".join(contexte)
    prompt = f"Contexte : {ctx_text}\n\nQuestion : {question}"
    response = client.messages.create(
        model="claude-sonnet-4-20250514",
        max_tokens=512,
        messages=[{"role": "user", "content": prompt}]
    )
    reponse = response.content[0].text

    # Étape 3 : Évaluation automatique (asynchrone en production)
    trace_id = langfuse_context.get_current_trace_id()
    scores = evaluer_et_scorer(question, reponse, contexte, trace_id)

    return {
        "reponse": reponse,
        "scores": scores,
        "trace_id": trace_id
    }


# Utilisation
resultat = pipeline_rag_avec_evaluation("Qu'est-ce qu'un embedding ?")
print(f"Réponse : {resultat['reponse']}")
print(f"Scores : {resultat['scores']}")
print(f"Trace Langfuse : {resultat['trace_id']}")

langfuse.flush()

Pièges et Biais à Éviter

Le pattern LLM-as-a-Judge n'est pas parfait — loin de là. Voici les principaux pièges à connaître :

  • Biais de position : des études montrent que GPT-4, utilisé comme juge, peut présenter jusqu'à 40 % d'incohérence dans ses évaluations selon l'ordre de présentation des réponses. La parade ? Randomiser l'ordre et moyenner sur plusieurs évaluations.
  • Biais de verbosité : les modèles juges tendent à favoriser les réponses plus longues, même si elles ne sont pas plus pertinentes. Incluez dans votre prompt d'évaluation un critère explicite de concision.
  • Angles morts intra-famille : un modèle juge peut être plus indulgent envers les réponses produites par un modèle de la même famille. Solution : utilisez un juge d'une famille différente, ou combinez plusieurs juges.
  • Astuces lexicales en RAG : un modèle peut « tricher » en reprenant mot pour mot le contexte fourni, obtenant un score élevé de fidélité sans réelle compréhension. Ajoutez des métriques de reformulation pour contrer ça.
  • Manque de calibration : sans benchmark humain, impossible de savoir si le juge est fiable. Calibrez toujours sur un ensemble de 50 à 100 exemples annotés manuellement avant de déployer.

Architecture Complète : Du Développement à la Production

Récapitulons tout ce qu'on a vu dans une architecture complète couvrant le cycle de vie complet de votre application LLM.

Phase 1 — Développement (Evals locaux) :

  1. Écriture des prompts et de la logique applicative
  2. Création de datasets de test avec des cas représentatifs
  3. Exécution des métriques DeepEval en local (pertinence, fidélité, hallucination)
  4. Itération rapide sur les prompts en fonction des scores

Phase 2 — CI/CD (Quality Gates) :

  1. Exécution automatique des tests DeepEval via deepeval test run
  2. Blocage du déploiement si les scores tombent sous les seuils définis
  3. Versionnement des datasets d'évaluation avec le code
  4. Rapport de régression entre la version actuelle et la précédente

Phase 3 — Production (Tracing) :

  1. Traçage de toutes les requêtes avec Langfuse (avec échantillonnage si nécessaire)
  2. Capture des prompts, réponses, tokens, latence et coûts
  3. Association de scores de qualité automatisés (LLM-as-a-Judge) aux traces
  4. Collecte de feedback utilisateur (pouce haut/bas, corrections)

Phase 4 — Monitoring et Boucle de Rétroaction :

  1. Tableaux de bord Langfuse pour le suivi des tendances
  2. Alertes automatisées en cas de dégradation des scores ou d'anomalies de coût
  3. Identification des cas d'échec récurrents pour enrichir les datasets d'évaluation
  4. Retour en Phase 1 avec les nouvelles données pour améliorer les prompts

Et pour finir, quelques bonnes pratiques qui valent pour l'ensemble du pipeline :

  • Limitez-vous à 5 métriques maximum : trop de métriques diluent l'attention. Choisissez celles qui correspondent vraiment à vos objectifs business.
  • Versionnez vos datasets d'évaluation : ils doivent évoluer avec votre application et être traités comme du code. Pas comme un fichier Excel qui traîne quelque part.
  • Loguez tout, filtrez après : il est toujours plus facile de filtrer des données abondantes que de regretter des données manquantes.
  • Suivez les distributions, pas les moyennes : une moyenne de 0.85 en fidélité peut masquer 10 % de réponses catastrophiques. Surveillez les percentiles (p50, p95, p99).
  • Définissez des politiques d'alerte claires : qui est notifié, quel est le seuil, quelle est la procédure d'escalade. Sans ça, le monitoring ne sert à rien.

FAQ — Questions Fréquentes sur l'Évaluation des LLM

Quelle est la différence entre évaluation et observabilité des LLM ?

L'évaluation (evals) mesure la qualité des sorties d'un LLM — pertinence, fidélité, absence d'hallucinations — et s'exécute principalement en développement et CI/CD. L'observabilité capture le comportement opérationnel en production — traces, latence, coûts, tokens — pour le débogage et l'analyse. Les deux sont complémentaires : l'évaluation garantit la qualité avant le déploiement, l'observabilité la maintient après.

Comment évaluer un pipeline RAG en production ?

Il faut mesurer deux dimensions. Côté récupération : Contextual Relevancy (les documents récupérés sont-ils pertinents ?), Contextual Recall (toutes les informations ont-elles été récupérées ?) et Contextual Precision (les documents pertinents sont-ils bien classés ?). Côté génération : Faithfulness (la réponse est-elle fidèle au contexte ?) et Answer Relevancy (la réponse répond-elle à la question ?). DeepEval propose toutes ces métriques nativement.

DeepEval est-il gratuit et open source ?

Oui, DeepEval est un projet open source sous licence Apache 2.0. Le framework, toutes les métriques et l'intégration Pytest sont entièrement gratuits. Confident AI (la société derrière DeepEval) propose une plateforme cloud optionnelle pour le suivi des évaluations et la collaboration en équipe, mais ce n'est pas nécessaire pour utiliser le framework.

Peut-on utiliser Claude comme modèle juge dans DeepEval ?

Absolument. DeepEval supporte Claude via la classe AnthropicModel. Il suffit de configurer votre clé API Anthropic et d'instancier le modèle avec AnthropicModel(model="claude-sonnet-4-20250514"). Claude est un excellent choix comme juge grâce à ses capacités de raisonnement et son suivi rigoureux des instructions. Vous pouvez passer cette instance à n'importe quelle métrique via le paramètre model.

Combien de métriques faut-il suivre pour un LLM en production ?

La recommandation générale : pas plus de 5 métriques clés. Commencez par les trois fondamentales — pertinence, fidélité au contexte et taux d'hallucination. Ajoutez ensuite des métriques spécifiques à votre cas d'usage (ton, conformité, complétude). Trop de métriques rendent les décisions difficiles. Concentrez-vous sur celles qui ont un impact direct sur vos objectifs business.

À propos de l'auteur Editorial Team

Our team of expert writers and editors.