Monitoring et Observabilité IA : LangSmith, Phoenix, Helicone

tl;dr: Monitoring IA production : LangSmith (LangChain natif, $39/mois), Phoenix (open source Arize), Helicone (proxy API simple). Trackez latence, coûts par requête, qualité des réponses. Tracing complet, alertes, A/B testing. Essentiel pour production.

Votre chatbot RAG fonctionne en dev. Super ! Mais en production :

  • 💸 Combien ça coûte réellement par utilisateur ?
  • ⏱️ Quelle est la latence p95 ? Certains utilisateurs attendent 10 secondes ?
  • 🎯 La qualité se dégrade-t-elle au fil du temps ?
  • 🐛 Pourquoi cette requête a échoué à 3h du matin ?
  • 🔄 Quelle version du prompt performe le mieux ?

Sans monitoring, vous pilotez à l’aveugle. Les problèmes apparaissent en prod, vous ne savez pas quand ni pourquoi.

La solution : Monitoring et observabilité IA.

💡 🎯 Le monitoring IA va au-delà des métriques classiques (CPU, mémoire). Il faut tracker : latence par composant (retrieval, LLM), coûts par requête, qualité des réponses, et tracer chaque prompt end-to-end.

Dans ce guide :

  • Métriques clés à tracker en production
  • LangSmith : Solution officielle LangChain (payant)
  • Phoenix : Open source par Arize AI
  • Helicone : Proxy API simple et gratuit
  • Comparaison et recommandations
  • Alerting et détection d’anomalies
  • A/B testing de prompts

Guide pratique sur le monitoring et observabilité des systèmes IA pour déployer l’IA localement

Pourquoi le monitoring IA est différent

Monitoring classique vs IA

AspectMonitoring ClassiqueMonitoring IA
MétriquesCPU, RAM, requêtes/secLatence LLM, tokens, coûts, qualité
TracesHTTP requestsPrompts, retrievals, chains complets
Erreurs500, timeoutsHallucinations, mauvais retrieval, rate limits
CoûtsInfra fixeVariable (par token), imprévisible
QualitéUptimePertinence, exactitude, toxicité
DebugLogs, stack tracesPrompts, contexte, réponses LLM

Les défis spécifiques

Non-déterminisme

# Même prompt, réponses différentes
response1 = llm("Résume ce texte")  # "Le texte parle de..."
response2 = llm("Résume ce texte")  # "Ce document traite de..."
# → Comment comparer la qualité ?

Coûts variables

# Coût par requête varie selon input + output
query1 = "Bonjour"              # $0.0001
query2 = "Analyse ce livre..."  # $0.50 (avec long contexte RAG)
# → Besoin de tracking granulaire

Latence multi-composants

Total: 3.2s
├─ Embedding: 0.1s
├─ Vector search: 0.3s
├─ LLM: 2.5s (où est le bottleneck ?)
└─ Post-processing: 0.3s

Qualité subjective

Comment mesurer si "Réponse A" est meilleure que "Réponse B" ?
→ Besoin de métriques proxy + human eval
💡 📊 Les outils de monitoring IA résolvent ces défis en trackant tokens, coûts, latence par composant, et en capturant le contexte complet (prompt + réponse).

Métriques clés à tracker

Latence et performance

Métriques essentielles :

MétriqueDescriptionObjectif
TTFT (Time to First Token)Temps avant premier token en streaming<500ms
Latence totaleTemps total de réponse<3s (p95)
Latence par composantEmbedding, retrieval, LLM séparésIdentifier bottlenecks
Tokens/secondeVitesse génération>50 t/s
ThroughputRequêtes/seconde traitéesSelon charge

Exemple de tracking :

import time
from functools import wraps

def track_latency(component_name):
    """Decorator pour tracker latence"""
    def decorator(func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            start = time.time()
            result = func(*args, **kwargs)
            duration = time.time() - start

            # Log vers monitoring tool
            logger.info({
                "component": component_name,
                "duration_ms": duration * 1000,
                "timestamp": time.time()
            })

            return result
        return wrapper
    return decorator

# Usage
@track_latency("embedding")
def create_embedding(text):
    return embeddings.embed_query(text)

@track_latency("vector_search")
def search_docs(query_vector):
    return vectorstore.similarity_search(query_vector, k=5)

@track_latency("llm_generation")
def generate_response(prompt):
    return llm.invoke(prompt)

Coûts et tokens

Métriques financières :

MétriqueDescriptionUtilité
Coût par requête$ par appel APIROI, pricing
Coût par utilisateur$ par user/moisLTV vs CAC
Tokens input/outputComptage par typeOptimisation prompts
Coût par composantEmbedding vs LLM vs toolsIdentifier coûts cachés
Burn rate$/jour, $/moisBudget tracking

Calcul de coûts :

from tiktoken import get_encoding

class CostTracker:
    """Track coûts OpenAI en temps réel"""

    PRICES = {
        "gpt-4o": {"input": 2.50, "output": 10.00},  # $/1M tokens
        "gpt-4o-mini": {"input": 0.15, "output": 0.60},
        "text-embedding-3-small": {"input": 0.02, "output": 0}
    }

    def __init__(self):
        self.enc = get_encoding("cl100k_base")
        self.daily_cost = 0

    def calculate_cost(self, model: str, input_text: str, output_text: str) -> float:
        """Calcule coût d'un appel"""

        input_tokens = len(self.enc.encode(input_text))
        output_tokens = len(self.enc.encode(output_text))

        prices = self.PRICES.get(model, {"input": 0, "output": 0})

        cost = (
            (input_tokens * prices["input"] / 1_000_000) +
            (output_tokens * prices["output"] / 1_000_000)
        )

        self.daily_cost += cost

        return cost

    def log_cost(self, model: str, input_text: str, output_text: str, user_id: str):
        """Log coût vers monitoring"""

        cost = self.calculate_cost(model, input_text, output_text)

        logger.info({
            "event": "llm_call",
            "model": model,
            "user_id": user_id,
            "cost_usd": cost,
            "daily_total": self.daily_cost,
            "timestamp": time.time()
        })

        # Alerter si dépassement
        if self.daily_cost > 100:  # $100/jour
            send_alert(f"⚠️ Daily cost: ${self.daily_cost:.2f}")

# Usage
tracker = CostTracker()
tracker.log_cost("gpt-4o", prompt, response, user_id="user_123")

Qualité et exactitude

Métriques de qualité :

MétriqueDescriptionMéthode
RelevanceRéponse pertinente à la questionLLM-as-judge, human eval
FaithfulnessRéponse basée sur contexte fourniCitation checking
GroundednessPas d’hallucinationsFact verification
ToxicityContenu inappropriéPerspective API
User satisfaction👍/👎 utilisateursFeedback buttons

Exemple d’évaluation automatique :

from langchain.evaluation import load_evaluator

# Évaluateur de pertinence
relevance_evaluator = load_evaluator("qa")

def evaluate_response(question: str, answer: str, context: str):
    """Évalue qualité d'une réponse RAG"""

    # 1. Pertinence (LLM-as-judge)
    relevance_score = relevance_evaluator.evaluate_strings(
        prediction=answer,
        input=question,
        reference=context
    )

    # 2. Groundedness (réponse basée sur contexte)
    grounding_prompt = f"""
    Contexte: {context}
    Réponse: {answer}

    La réponse est-elle entièrement basée sur le contexte ?
    Réponds uniquement par "oui" ou "non".
    """

    grounding = llm.invoke(grounding_prompt).content.lower()
    grounded = "oui" in grounding

    # 3. Log métriques
    logger.info({
        "question": question,
        "relevance_score": relevance_score["score"],
        "grounded": grounded,
        "answer_length": len(answer),
        "timestamp": time.time()
    })

    return {
        "relevance": relevance_score["score"],
        "grounded": grounded
    }

Erreurs et fiabilité

Métriques de fiabilité :

MétriqueDescriptionObjectif
Error rate% requêtes en erreur<1%
Rate limit hitsFréquence 429 errors0
Timeout rate% timeouts<0.5%
Fallback usage% utilisation backup provider<5%
Retry attemptsNombre de retries moyen<1.2
⚠️ Warning
⚠️ Un taux d’erreur >5% indique un problème sérieux : rate limits dépassés, provider instable, ou prompts mal formés. Alertez immédiatement.

LangSmith : solution officielle LangChain

Présentation

LangSmith est la plateforme de monitoring officielle de LangChain, par les créateurs de LangChain.

Points forts :

  • ✅ Intégration native LangChain (1 ligne de config)
  • ✅ Tracing complet des chains/agents
  • ✅ Playground pour tester prompts
  • ✅ Datasets et évaluation
  • ✅ Production monitoring

Lien : https://smith.langchain.com/ & notre article dédié LangSmith

🔎 Tip
Alternative open source : Découvrez aussi Langfuse, une alternative open source à LangSmith avec self-hosting possible et fonctionnalités similaires.

Configuration

1. Inscription et API key :

# Créer compte sur smith.langchain.com
# Générer API key

# .env
LANGCHAIN_TRACING_V2=true
LANGCHAIN_API_KEY=ls__...
LANGCHAIN_PROJECT=my-chatbot-prod

2. C’est tout ! Toutes vos chains LangChain sont automatiquement tracées.

Interface et features

Traces :

Run: user_query_abc123
├─ Retriever
│  ├─ Embedding (100ms, $0.00002)
│  └─ Vector Search (250ms)
│     → 5 documents retrieved
├─ Prompt Template (5ms)
│  → Rendered prompt (2,341 tokens)
└─ LLM (ChatOpenAI)
   ├─ Input: 2,341 tokens ($0.0059)
   ├─ Output: 287 tokens ($0.0029)
   ├─ Duration: 2,150ms
   └─ Response: "Voici la réponse..."

Total: 2.5s, $0.0088

Dashboard :

  • 📊 Latency p50/p95/p99 over time
  • 💰 Costs par jour/semaine/mois
  • 🔢 Tokens input/output trends
  • ❌ Error rate et types d’erreurs
  • 👥 Usage par utilisateur

Playground :

  • Tester prompts interactivement
  • Comparer versions côte à côte
  • Voir traces en temps réel

Évaluation avec datasets

Créer un dataset :

from langsmith import Client

client = Client()

# Créer dataset d'évaluation
dataset = client.create_dataset("rag-eval-v1")

# Ajouter exemples
client.create_examples(
    dataset_id=dataset.id,
    inputs=[
        {"question": "Qu'est-ce que le RAG ?"},
        {"question": "Comment fonctionne l'embedding ?"}
    ],
    outputs=[
        {"answer": "Le RAG combine recherche et génération..."},
        {"answer": "L'embedding transforme texte en vecteurs..."}
    ]
)

Évaluer automatiquement :

from langsmith.evaluation import evaluate

def my_evaluator(run, example):
    """Évaluateur custom"""
    # run.outputs["answer"] = réponse du modèle
    # example.outputs["answer"] = réponse attendue

    # Calculer similarité
    from difflib import SequenceMatcher
    similarity = SequenceMatcher(
        None,
        run.outputs["answer"],
        example.outputs["answer"]
    ).ratio()

    return {"score": similarity}

# Lancer évaluation
results = evaluate(
    lambda inputs: my_chain.invoke(inputs),
    data=dataset.name,
    evaluators=[my_evaluator]
)

# Résultats disponibles dans dashboard

Alerting

Configurer alertes :

# Via UI LangSmith :
# 1. Settings → Automations
# 2. Create Alert
#    - Condition: error_rate > 5%
#    - Notification: Email, Slack, Webhook

# Ou via code
client.create_rule(
    project="my-chatbot-prod",
    rule_type="threshold",
    conditions={
        "metric": "error_rate",
        "operator": ">",
        "value": 0.05
    },
    actions=[
        {"type": "email", "recipients": ["[email protected]"]},
        {"type": "slack", "webhook": "https://hooks.slack.com/..."}
    ]
)

Tarification

PlanPrixLimitesUse Case
DeveloperGratuit5K traces/moisPOC, dev
Plus$39/mois100K traces/moisPetite prod
EnterpriseSur devisIllimité, SSO, SLAGrande prod

Calcul : 1 requête RAG = ~4 traces (embedding, retrieval, prompt, LLM) → 100K traces = ~25K requêtes utilisateurs

💡 ✨ LangSmith est le choix idéal si vous utilisez LangChain : setup en 1 ligne, tracing automatique, excellent UX. Le tier gratuit suffit pour démarrer.

Phoenix : Open source par Arize AI

Présentation

Phoenix est une plateforme de monitoring IA open source par Arize AI.

Points forts :

  • ✅ Complètement gratuit et open source
  • ✅ Self-hosted (contrôle total des données)
  • ✅ Supporte LangChain, LlamaIndex, OpenAI SDK
  • ✅ Tracing, évaluation, embedding analysis
  • ✅ Interface moderne

Lien : https://github.com/Arize-ai/phoenix

Installation

Docker (recommandé) :

docker run -p 6006:6006 arizephoenix/phoenix:latest

Python :

pip install arize-phoenix

# Lancer serveur
python -m phoenix.server.main serve

Interface disponible sur http://localhost:6006

Configuration avec LangChain

from phoenix.trace.langchain import LangChainInstrumentor

# Instrumenter LangChain
LangChainInstrumentor().instrument()

# Vos chains fonctionnent normalement, traces automatiques !
from langchain.chains import RetrievalQA

qa_chain = RetrievalQA.from_chain_type(...)
response = qa_chain.invoke({"query": "Qu'est-ce que le RAG ?"})
# → Tracé automatiquement dans Phoenix

Configuration avec OpenAI SDK

from openinference.instrumentation.openai import OpenAIInstrumentor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import SimpleSpanProcessor

# Setup OpenTelemetry
tracer_provider = TracerProvider()
tracer_provider.add_span_processor(
    SimpleSpanProcessor(OTLPSpanExporter("http://localhost:6006/v1/traces"))
)

# Instrumenter OpenAI
OpenAIInstrumentor().instrument(tracer_provider=tracer_provider)

# Utiliser OpenAI normalement
from openai import OpenAI
client = OpenAI()

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Hello"}]
)
# → Tracé dans Phoenix

Features clés

1. Tracing :

  • Visualisation tree des appels
  • Latence par span
  • Inputs/outputs capturés
  • Métadonnées et tags

2. Embedding Analysis :

import phoenix as px

# Analyser embeddings
df = px.load_dataset("your_embeddings.parquet")

# Visualiser clusters
px.launch_app(
    primary=px.Dataset(df, "embeddings_column"),
    schema=px.Schema(
        embedding_feature_column_names=["embedding"],
        prediction_label_column_name="category"
    )
)
# → Interface interactive pour explorer embeddings

3. Évaluation :

from phoenix.experimental.evals import (
    HallucinationEvaluator,
    RelevanceEvaluator,
    run_evals
)

# Évaluer hallucinations
hallucination_eval = HallucinationEvaluator()
relevance_eval = RelevanceEvaluator()

results = run_evals(
    dataframe=traces_df,
    evaluators=[hallucination_eval, relevance_eval],
    provide_explanation=True
)

# Résultats : scores + explications
print(results[["hallucination_score", "hallucination_explanation"]])

Avantages vs LangSmith

AspectPhoenixLangSmith
PrixGratuit$39+/mois
Self-hosted✅ Oui❌ Cloud only
Data privacy✅ Total⚠️ Données chez LangChain
Setup⚠️ Plus complexe✅ 1 ligne
Embedding viz✅✅ Excellent❌ Non
LangChain integration✅ Bon✅✅ Natif
UI✅ Moderne✅✅ Excellent
💡 🔧 Phoenix est idéal si vous voulez self-host (compliance, privacy) ou besoin d’analyse avancée des embeddings. LangSmith meilleur pour simplicité.

Helicone : Proxy API simple

Présentation

Helicone est un proxy pour APIs LLM (OpenAI, Anthropic, etc.) avec monitoring intégré.

Concept :

Votre app → Helicone Proxy → OpenAI
         Monitoring

Points forts :

  • ✅ Setup ultra-simple (changer URL)
  • ✅ Agnostique du framework (pas besoin LangChain)
  • ✅ Gratuit jusqu’à 100K requêtes/mois
  • ✅ Caching intégré
  • ✅ Rate limiting

Lien : https://www.helicone.ai/

Configuration

1. Créer compte sur helicone.ai et obtenir API key

2. Modifier code (changer base URL) :

from openai import OpenAI

client = OpenAI(
    api_key="sk-...",  # Votre clé OpenAI
    base_url="https://oai.helicone.ai/v1",  # Proxy Helicone
    default_headers={
        "Helicone-Auth": "Bearer <HELICONE_API_KEY>"
    }
)

# Utiliser normalement
response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Hello"}]
)
# → Requête passe par Helicone → loggée → envoyée à OpenAI

Avec LangChain :

from langchain.chat_models import ChatOpenAI

llm = ChatOpenAI(
    model="gpt-4o",
    openai_api_key="sk-...",
    openai_api_base="https://oai.helicone.ai/v1",
    model_kwargs={
        "extra_headers": {
            "Helicone-Auth": "Bearer <HELICONE_API_KEY>"
        }
    }
)

Features

Dashboard :

  • 📊 Requests/day, coûts totaux
  • ⏱️ Latence moyenne, p95
  • 💰 Coût par requête, par user
  • 📈 Trends over time
  • 🔍 Recherche dans prompts/réponses

Caching :

# Activer cache (économiser coûts)
response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Hello"}],
    extra_headers={
        "Helicone-Auth": "Bearer ...",
        "Helicone-Cache-Enabled": "true"
    }
)
# Si même prompt déjà vu → réponse depuis cache (gratuit, <50ms)

User tracking :

# Associer requêtes à utilisateurs
response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Hello"}],
    extra_headers={
        "Helicone-Auth": "Bearer ...",
        "Helicone-User-Id": "user_12345"
    }
)
# → Dashboard : coûts par user, usage patterns

Custom properties :

# Ajouter métadonnées custom
response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "Hello"}],
    extra_headers={
        "Helicone-Auth": "Bearer ...",
        "Helicone-Property-Environment": "production",
        "Helicone-Property-Feature": "chatbot",
        "Helicone-Property-Version": "v2.1"
    }
)
# → Filtrer dashboard par propriétés

Tarification

PlanPrixLimitesFeatures
Free$0100K requests/moisDashboard, basic analytics
Pro$20/mois1M requestsCaching, alerting, webhooks
EnterpriseSur devisIllimitéSelf-hosted, SLA
💡 🚀 Helicone est parfait pour démarrer : setup en 2 minutes (changer URL), gratuit jusqu’à 100K requêtes. Idéal si vous n’utilisez pas LangChain.

Comparaison des solutions

Tableau récapitulatif

CritèreLangSmithPhoenixHelicone
Prix$39/mois (100K traces)Gratuit (open source)Gratuit (100K req)
Setup⭐⭐⭐⭐⭐ 1 ligne⭐⭐⭐ Docker required⭐⭐⭐⭐⭐ Change URL
LangChain⭐⭐⭐⭐⭐ Natif⭐⭐⭐⭐ Bon⭐⭐⭐ Basique
Non-LangChain⭐⭐⭐ OpenAI SDK⭐⭐⭐⭐ Multi-framework⭐⭐⭐⭐⭐ Agnostic
Tracing⭐⭐⭐⭐⭐ Complet⭐⭐⭐⭐⭐ Excellent⭐⭐⭐ Basique
Évaluation⭐⭐⭐⭐⭐ Datasets⭐⭐⭐⭐ Evals auto⭐⭐ Limité
Embedding viz❌ Non⭐⭐⭐⭐⭐ Excellent❌ Non
Caching❌ Non❌ Non⭐⭐⭐⭐ Intégré
Self-hosted❌ Non⭐⭐⭐⭐⭐ Oui⭐⭐⭐ Enterprise
Data privacy⚠️ Cloud⭐⭐⭐⭐⭐ Total⚠️ Cloud

Recommandations

Choisir LangSmith si :

  • ✅ Vous utilisez LangChain massivement
  • ✅ Besoin de playground interactif
  • ✅ Budget OK ($39+/mois)
  • ✅ Datasets d’évaluation importants

Choisir Phoenix si :

  • ✅ Budget limité (gratuit)
  • ✅ Besoin self-hosted (compliance, privacy)
  • ✅ Analyse d’embeddings importante
  • ✅ OK avec self-manage infrastructure

Choisir Helicone si :

  • ✅ Pas de framework (OpenAI SDK direct)
  • ✅ Setup ultra-simple requis
  • ✅ Besoin de caching pour économiser
  • ✅ Tracking par user essentiel

Combiner plusieurs :

# LangSmith pour LangChain chains
LANGCHAIN_TRACING_V2=true

# Helicone pour appels OpenAI directs
openai_client = OpenAI(base_url="https://oai.helicone.ai/v1")

Alerting et détection d’anomalies

Configurer des alertes critiques

Alertes recommandées :

# 1. Coûts excessifs
if daily_cost > budget_daily * 1.5:
    send_alert(f"🚨 Daily cost: ${daily_cost:.2f} (budget: ${budget_daily})")

# 2. Latence dégradée
if latency_p95 > 5000:  # 5s
    send_alert(f"⚠️ Latency p95: {latency_p95}ms (SLA: 3000ms)")

# 3. Taux d'erreur élevé
if error_rate > 0.05:  # 5%
    send_alert(f"❌ Error rate: {error_rate:.1%}")

# 4. Rate limits atteints
if rate_limit_errors > 10:
    send_alert(f"⏱️ Rate limit hit {rate_limit_errors} times in 1h")

# 5. Qualité dégradée
if avg_relevance_score < 0.7:
    send_alert(f"📉 Relevance score dropped to {avg_relevance_score:.2f}")

Intégration Slack

import requests

def send_slack_alert(message: str, channel: str = "#alerts"):
    """Envoie alerte Slack"""

    webhook_url = os.getenv("SLACK_WEBHOOK_URL")

    payload = {
        "channel": channel,
        "username": "AI Monitoring",
        "icon_emoji": ":robot_face:",
        "text": message
    }

    response = requests.post(webhook_url, json=payload)
    return response.status_code == 200

# Usage
send_slack_alert("🚨 Daily AI cost exceeded $100: $127.50")

Détection d’anomalies

Méthode simple : écarts types :

import numpy as np
from collections import deque

class AnomalyDetector:
    """Détecte anomalies par écart à la moyenne"""

    def __init__(self, window_size=100, threshold=3):
        self.window = deque(maxlen=window_size)
        self.threshold = threshold  # Nombre d'écarts types

    def is_anomaly(self, value: float) -> bool:
        """Retourne True si anomalie détectée"""

        if len(self.window) < 10:
            self.window.append(value)
            return False

        mean = np.mean(self.window)
        std = np.std(self.window)

        # Z-score
        z_score = abs((value - mean) / std) if std > 0 else 0

        self.window.append(value)

        return z_score > self.threshold

# Usage
latency_detector = AnomalyDetector()
cost_detector = AnomalyDetector()

# Pour chaque requête
if latency_detector.is_anomaly(current_latency):
    send_alert(f"⚠️ Latency spike: {current_latency}ms")

if cost_detector.is_anomaly(current_cost):
    send_alert(f"💸 Cost anomaly: ${current_cost:.4f}")
⚠️ Warning
🔔 Configurez au minimum 3 alertes : coûts quotidiens, latence p95, et taux d’erreur. Slack/email pour notifs temps réel. PagerDuty pour incidents critiques.

A/B testing de prompts

Pourquoi A/B tester ?

# Version A
prompt_a = "Résume ce texte en 2 phrases."

# Version B
prompt_b = "Tu es un expert en résumés. Résume ce texte en 2 phrases concises."

# Laquelle performe mieux ?
# → A/B test pour le savoir

Implémentation simple

import random

class PromptABTest:
    """A/B testing de prompts"""

    def __init__(self):
        self.variants = {}
        self.results = {}

    def add_variant(self, name: str, prompt_template: str, weight: float = 0.5):
        """Ajoute variant"""
        self.variants[name] = {
            "template": prompt_template,
            "weight": weight
        }
        self.results[name] = {
            "count": 0,
            "total_latency": 0,
            "total_cost": 0,
            "thumbs_up": 0,
            "thumbs_down": 0
        }

    def get_variant(self):
        """Sélectionne variant (weighted random)"""
        variants = list(self.variants.keys())
        weights = [self.variants[v]["weight"] for v in variants]
        return random.choices(variants, weights=weights)[0]

    def log_result(self, variant: str, latency: float, cost: float, user_feedback: int):
        """Log résultat (user_feedback: 1 = 👍, -1 = 👎, 0 = none)"""

        self.results[variant]["count"] += 1
        self.results[variant]["total_latency"] += latency
        self.results[variant]["total_cost"] += cost

        if user_feedback == 1:
            self.results[variant]["thumbs_up"] += 1
        elif user_feedback == -1:
            self.results[variant]["thumbs_down"] += 1

    def get_stats(self):
        """Retourne statistiques"""
        stats = {}

        for variant, data in self.results.items():
            if data["count"] == 0:
                continue

            stats[variant] = {
                "requests": data["count"],
                "avg_latency": data["total_latency"] / data["count"],
                "avg_cost": data["total_cost"] / data["count"],
                "satisfaction": (
                    data["thumbs_up"] /
                    (data["thumbs_up"] + data["thumbs_down"])
                    if (data["thumbs_up"] + data["thumbs_down"]) > 0
                    else 0
                )
            }

        return stats

# Usage
ab_test = PromptABTest()

ab_test.add_variant(
    "control",
    "Résume ce texte en 2 phrases: {text}",
    weight=0.5
)

ab_test.add_variant(
    "expert",
    "Tu es un expert en résumés. Résume ce texte en 2 phrases concises: {text}",
    weight=0.5
)

# Pour chaque requête
variant = ab_test.get_variant()
prompt = ab_test.variants[variant]["template"].format(text=user_text)

# Appeler LLM, mesurer latence/coût
response, latency, cost = call_llm(prompt)

# Logger (user_feedback ajouté plus tard via thumbs up/down)
ab_test.log_result(variant, latency, cost, user_feedback=0)

# Après 1000 requêtes, analyser
print(ab_test.get_stats())
# {
#   "control": {"requests": 497, "avg_latency": 2.3s, "satisfaction": 0.72},
#   "expert": {"requests": 503, "avg_latency": 2.5s, "satisfaction": 0.81}
# }
# → "expert" gagne en satisfaction, déployer à 100%

A/B testing avec LangSmith

from langsmith import Client

client = Client()

# Tag chaque run avec variant
def run_with_variant(variant: str, input_data: dict):
    # Ajouter tag
    with client.trace(
        name="rag_chain",
        tags=[f"variant:{variant}"],
        metadata={"variant": variant}
    ):
        response = chain.invoke(input_data)

    return response

# Analyser dans LangSmith UI :
# Filter runs by tag "variant:A" vs "variant:B"
# Comparer latency, cost, user feedback
💡 📊 A/B testez systématiquement : température, system prompts, chunking strategies. Même 5% d’amélioration = ROI significatif en satisfaction utilisateur.

Checklist de production

Setup initial

Monitoring :

  • Outil de monitoring choisi et configuré
  • Tracing activé (LangSmith, Phoenix, ou custom)
  • Dashboard créé avec métriques clés
  • Coûts trackés par requête et par user

Métriques :

  • Latence (p50, p95, p99) loggée
  • Taux d’erreur tracké
  • Tokens input/output comptés
  • Qualité évaluée (relevance, groundedness)

Alerting :

  • Alerte coûts quotidiens (>$X)
  • Alerte latence (p95 >Xms)
  • Alerte taux d’erreur (>X%)
  • Alerte rate limits
  • Canal de notification configuré (Slack/email)

En production

Quotidien :

  • Vérifier dashboard (5 min)
  • Checker alertes
  • Review erreurs récentes

Hebdomadaire :

  • Analyser trends latence
  • Revoir coûts (budget vs réel)
  • Identifier requêtes coûteuses
  • Review feedback utilisateurs

Mensuel :

  • A/B test nouveau prompt
  • Optimiser prompts (réduire tokens)
  • Évaluation qualité sur dataset
  • Audit sécurité (PII leaked ?)
  • ROI analysis
💡 📈 Le monitoring n’est pas optionnel en production. Sans métriques, vous ne pouvez ni optimiser ni détecter les problèmes avant qu’ils impactent users.

Conclusion

Le monitoring d’applications IA est fondamentalement différent du monitoring classique. Les métriques clés sont : latence par composant, coûts par requête, tokens utilisés, et qualité des réponses.

Points clés à retenir :

  1. Trois piliers : Latence, Coûts, Qualité (+ fiabilité)
  2. Outils existants : LangSmith (LangChain natif), Phoenix (open source), Helicone (proxy simple)
  3. Alerting essentiel : Budget, latence p95, taux d’erreur
  4. A/B testing : Optimiser prompts/params en continu
  5. Tracing complet : Capturer contexte (prompts, retrievals, réponses) pour debug

Stratégie recommandée :

  1. Démarrer : Helicone (gratuit, 2 min setup) ou LangSmith si LangChain
  2. Tracker : Latence, coûts, erreurs dès jour 1
  3. Alerter : Configurer alertes budget et latence
  4. Itérer : A/B test prompts, analyser top requêtes coûteuses
  5. Scaler : Migrer vers Phoenix (self-hosted) ou LangSmith Enterprise si besoin

Le monitoring transforme une application IA d’une boîte noire imprévisible en un système observable, optimisable et fiable. Investissez dans le monitoring dès le début - c’est 10x plus dur de l’ajouter après coup.

Tendances 2025 :

  • 📊 LLM-as-judge pour évaluation automatique qualité
  • 🤖 Anomaly detection IA-powered
  • 🔍 Embedding visualization pour debug retrieval
  • 💰 FinOps IA : optimisation coûts proactive
💡 🎯 Commencez aujourd’hui : activez LangSmith (1 ligne) ou Helicone (change URL). Vous découvrirez immédiatement des insights précieux sur votre app IA.

Pour aller plus loin

Articles connexes :

Ressources externes :

Outils mentionnés :