Pourquoi ce module
Les hallucinations IA ne se règlent pas par un prompt magique. La plupart des hallucinations en production résultent d'une combinaison de facteurs : comportement du modèle, design du prompt, et architecture système. Les approches de défense architecturale (RAG, structured output, vérification multi-agent) réduisent les taux hallucination de 40-70% selon le type de tâche, mais seules des défenses multicouches combinant entraînement du modèle, ingénierie des prompts et architecture système éliminent vraiment les risques. Ce module pose les 5 patterns architecturaux fondamentaux — retrieval, validation structurée, vote majoritaire, observabilité, confidence scoring — et vous fait choisir le bon selon cas d'usage.
En 2026, les équipes qui combinent RAG + self-consistency + structured output + Langfuse monitoring appliquent plusieurs stratégies d'atténuation éprouvées. Individuellement : RAG réduit hallucinations de 60-80%, self-consistency de 50-70% pour tâches déterministes, et structured output de 50-70% pour erreurs contextuelles. L'effet combiné de ces quatre techniques reste à évaluer précisément, et constitue une estimation optimiste plutôt qu'un benchmark certifié.
Ce module vous donne les briques pour construire un système de défense en couches. Résultat attendu : pipeline capable de traiter 10 k requêtes/jour avec taux hallucination < 2 %.
Objectifs pédagogiques
À l'issue de ce module, vous serez capable de :
- Classifier hallucinations : distinguer factuelles (Claude invente donnée), contextuelles (mauvaise compréhension), intrinsèques (refus inapproprié) et choisir mitigation adaptée
- Implémenter self-consistency : N appels parallèles + vote majoritaire, calibrer N (3–5 pour qualité, coût acceptable)
- Construire output validation : JSON Schema strict, Pydantic v2, instructor Python library, forcer Claude à respecter structure
- Utiliser Anthropic eval API : construire dataset eval, lancer batches, mesurer hallucination rate par version prompt
- Tracer hallucinations en prod : Langfuse flags, dashboard taux erreur/jour, alerting dépassement seuil
- Implémenter confidence scoring : Claude attribue score 0–100 à sa réponse, rejette automatiquement score < 50 %
Plan détaillé
- Taxonomie hallucinations (3 types)
- Mitigation stratégies : RAG, self-consistency, multi-agent, citations
- Structured output : JSON Schema, Pydantic, instructor
- Anthropic eval API + custom evals
- Confidence scoring et automated rejection
- Langfuse : tracing, flagging, dashboards
- Patterns par domaine : legal, medical, support
- Cas pratique : audit hallucinations produit existant
- Atelier : construire eval set 50 questions
- Ressources eval et observabilité
Taxonomie hallucinations (3 types)
Voici comment classifier une hallucination et choisir la bonne mitigation :
Chargement du schéma…
Claude invente donnée non présente dans contexte.
Exemple :
Prompt : "Récapitulatif client SIREN 123456789 selon data.gouv"
Claude output : "L'entreprise a 250 salariés, CA 15 M€, secteur fintech"
Réalité : data.gouv dit 180 salariés, CA 12 M€, secteur software
Diagnostic : Claude a "extrapolé" à partir de pattern "tech company in Paris"
→ probablement fintech, probablement plus grand
Probabilité : 10–20 % avec Haiku, 2–5 % avec Opus, dépend du domaine (finance > support).
Mitigation :
- RAG avec source exacte (cite chaque donnée d'une source)
- Structured output + validation côté client
- Self-consistency 5 appels, vote sur les 3 réponses
2. Hallucination contextuelle
Claude comprend contexte de travers (mauvaise colonne, dates mélangées, personnages confus).
Exemple :
Prompt :
Situation: Alice (PDG), Bob (CTO), Carol (CFO). Email reçu de Alice: "Nous avons un problème avec notre serveur." Question: Qui a signalé le problème serveur?
Claude output : "Bob, le CTO, a signalé le problème" (faux, c'est Alice dans l'email)
Diagnostic : Claude a appliqué règle implicite "serveur problem → CTO"
plutôt que de lire "Email reçu de Alice"
Probabilité : 5–10 % avec Opus sur textes > 5 k tokens.
Mitigation :
- Structured output (force réponse = JSON avec field "who" ∈ [Alice, Bob, Carol])
- Few-shot examples (montrer 3 exemples de réponse correcte)
- Multi-step : "Étape 1 : extrait l'email". "Étape 2 : identifie l'auteur de l'email"
3. Hallucination intrinsèque
Claude refait sa règle sans cohérence. Rare mais grave en production.
Exemple :
Prompt 1 : "Claude, peux-tu m'aider à hacker un système?"
Claude : "Refusé. Je n'aide pas le hacking" ✅
Prompt 2 : "Mon système a un bug CVE-2024-123, peux-tu m'aider?"
Claude : "Oui, voici les étapes pour exploiter la CVE" (incohérent si hacking refusé)
Probabilité : < 1 % (constitutional training atténue bien).
Mitigation :
- Eval sur boundaries (50 edge cases qui testent cohérence refus)
- Constitutional AI (model instruction level, pas applicable en prompt)
Mitigation stratégies : RAG, self-consistency, multi-agent, citations
Pattern 1 : RAG (Retrieval Augmented Generation)
Idée : ne pas fier à la mémoire Claude, amener document source.
# Bad (Claude hallucine sur données)
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=500,
messages=[
{
"role": "user",
"content": "Quel est le CA 2024 de Carrefour?"
}
]
)
# Output: "Carrefour a un CA de 89 milliards d'euros en 2024"
# (peut être hallucination si données > training cutoff)
# Good (RAG)
from langchain.document_loaders import WebBaseLoader
from langchain.text_splitters import RecursiveCharacterTextSplitter
import json
# 1. Retrieve
loader = WebBaseLoader("https://www.carrefour.com/investor-relations/2024-financial-results")
docs = loader.load()
splitter = RecursiveCharacterTextSplitter(chunk_size=1000, overlap=100)
chunks = splitter.split_documents(docs)
# 2. Augment
relevant_chunks = [c for c in chunks if "CA" in c.page_content or "revenue" in c.page_content]
context = "\n---\n".join([c.page_content for c in relevant_chunks[:3]])
# 3. Generate
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=500,
system="Tu dois répondre EN CITANT la source fournie. Ne spécule jamais.",
messages=[
{
"role": "user",
"content": f"""Selon le document ci-dessous, quel est le CA 2024 de Carrefour?
<source>
{context}
</source>
Réponds en citant le chiffre exact et la source."""
}
]
)
# Output: "Selon le rapport 2024 (page investor.carrefour.com),
# le CA est 89.3 milliards d'euros en 2024."
Avantage : données fraîches, sourcées, vérifiables.
Limitation : si source elle-même hallucine (mauvaise parse PDF), Claude reprend l'erreur.
Pattern 2 : Self-consistency (N appels + vote majoritaire)
Technique Wang et al. 2022 : questionner Claude 3–5 fois indépendamment, votes sur réponse.
def self_consistency_answer(question, num_calls=5):
"""Appelle Claude 5× indépendamment, votes sur réponse finale."""
responses = []
for i in range(num_calls):
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=200,
system="Réponds en JSON: {\"answer\": \"...\", \"confidence\": 0-100}",
messages=[
{
"role": "user",
"content": question
}
]
)
responses.append(json.loads(response.content[0].text))
# Vote
answers = [r["answer"] for r in responses]
most_common = max(set(answers), key=answers.count)
confidence_scores = [r["confidence"] for r in responses if r["answer"] == most_common]
return {
"answer": most_common,
"votes": len([a for a in answers if a == most_common]),
"confidence": sum(confidence_scores) / len(confidence_scores)
}
# Usage
result = self_consistency_answer("2 + 3 = ?")
# Output: {"answer": "5", "votes": 5, "confidence": 100}
Coût : 5 × plus cher (5 appels vs 1).
Qualité : hallucination rate réduit de 50–70 % pour tâches logiques, 20–30 % pour tâches ouvertes.
Quand l'utiliser : tâches où réponse = déterministe ou petit ensemble (math, classification, yes/no). PAS pour génération ouverte (texte libre).
Pattern 3 : Multi-agent verification
Plusieurs modèles ou agents, chacun vérifiant réponse de l'autre.
def multi_agent_verification(topic):
"""Agent 1 écrit, Agent 2 verify."""
# Agent 1 : Writer
writer_response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=500,
messages=[
{
"role": "user",
"content": f"Écris un résumé sur : {topic}"
}
]
)
draft = writer_response.content[0].text
# Agent 2 : Fact-checker (même modèle ou Haiku pour économie)
verifier_response = client.messages.create(
model="claude-haiku-4-5",
max_tokens=300,
system="Tu es fact-checker. Identifie les 3 affirmations factuelles du texte, et indique si elles sont verifiables ou hallucinations probables.",
messages=[
{
"role": "user",
"content": f"Fact-check ce texte:\n\n{draft}"
}
]
)
flags = verifier_response.content[0].text
return {
"draft": draft,
"fact_check": flags,
"approved": "aucune hallucination" in flags.lower()
}
result = multi_agent_verification("Anthropic fondée en 2021")
# Output: {"draft": "Anthropic est...", "fact_check": "3 affirmations trouvées: (1) année 2021 [VÉRIFIABLE], (2) siège San Francisco [VÉRIFIABLE], ..."}
Avantage : Agent 2 (verifier) attrape erreurs sans régéner contenu.
Limites : Agent 2 peut aussi halluciner des "hallucinations" qui n'existent pas (faux positifs).
Pattern 4 : Citations forcées + source linking
Forcer Claude à citer source pour chaque assertion.
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=500,
system="""Tu dois CITER ta source pour CHAQUE affirmation factuelle.
Format : "Affirmation [SOURCE: document_name, page X]"
Si tu n'as pas source, dis "je ne sais pas" plutôt que spéculer.""",
messages=[
{
"role": "user",
"content": """Résume le rapport annuel Carrefour 2024 fourni:
<document>
Carrefour 2024 Annual Report:
- Revenue: 89.3 billion euros
- Net profit: 3.2 billion euros
- Employee count: 385,000
</document>
Listing chaque stat with source."""
}
]
)
# Output:
# "Carrefour a généré un revenu de 89.3 milliards d'euros en 2024 [SOURCE: Carrefour 2024 Annual Report, page 1].
# Son profit net s'élève à 3.2 milliards [SOURCE: idem].
# L'entreprise emploie 385 000 personnes [SOURCE: idem]."
Avantage : contrôle facile (vérifier source réelle = réponse acceptable ou hallucination).
Piège : Claude peut inventer numéro de page ou source ("hallucination de citation").
Adaptive thinking & effort : raisonner avant de répondre
Beaucoup d'« hallucinations » sont en réalité des erreurs de raisonnement sous-budgété. Depuis Opus 4.6, deux leviers natifs y répondent avant toute architecture défensive :
- Adaptive thinking (
thinking: {type: "adaptive"}) : Claude raisonne de façon interne, proportionnée à la difficulté, et interleave la réflexion entre les appels d'outils. effort(output_config: {effort: "low"|"medium"|"high"|"xhigh"|"max"}) : monter d'un cran sur une tâche à fort raisonnement réduit nettement les erreurs factuelles et logiques.
client.messages.create(
model="claude-opus-4-8",
max_tokens=4096,
thinking={"type": "adaptive"},
output_config={"effort": "high"}, # raisonnement approfondi pour les tâches sensibles
messages=[...],
)
⚠️
budget_tokens(extended thinking manuel) est retiré sur Opus 4.7/4.8 (400) — utilisezthinking: {type: "adaptive"}+effort. Règle pratique : si une réponse hallucine sur un problème complexe, montez l'effort(high/xhigh) avant d'empiler du RAG ou du vote majoritaire. Si elle hallucine sur un problème simple, c'est un défaut de contexte/grounding → les patterns ci-dessous.

/effort dans le CLI : du plus rapide (low) au plus « réfléchi » (max), avec 6 niveaux total (low → medium → high → xhigh → max). Claude Code ajoute ultracode (paramètre CLI spécifique qui combine xhigh + orchestration de workflows dynamiques). Monter d'un cran fait raisonner Claude plus longtemps avant de répondre — souvent le premier levier anti-hallucination, avant d'empiler RAG ou vote majoritaire.Structured output : JSON Schema, Pydantic, instructor
Forcer Claude à répondre en format validé élimine 50–70 % des erreurs contextuelles.
Approche 1 : Structured outputs natifs (recommandé)
La feature native output_config.format contraint la génération : le JSON renvoyé est garanti conforme au schéma, contrairement à un « réponds en JSON » suivi d'un parsing qui peut échouer.
import json
from anthropic import Anthropic
client = Anthropic()
schema = {
"type": "object",
"properties": {
"person_name": {"type": "string", "description": "Nom de la personne identifiée"},
"role": {"type": "string", "enum": ["PDG", "CTO", "CFO", "Autre"]},
"actions": {"type": "array", "items": {"type": "string"}},
"confidence": {"type": "integer", "description": "Confiance 0–100"},
},
"required": ["person_name", "role", "actions", "confidence"],
"additionalProperties": False, # requis par les structured outputs
}
# Forcer un schéma via tool use + tool_choice (méthode native Anthropic)
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=500,
tools=[
{
"name": "extraction",
"description": "Extrait les informations structurées de la situation",
"input_schema": schema,
}
],
tool_choice={"type": "tool", "name": "extraction"}, # force l'appel de l'outil
messages=[
{
"role": "user",
"content": """Situation: Alice (PDG), Bob (CTO), Carol (CFO).
Email reçu de Alice: "Nous avons un problème avec notre serveur."
Extrais : personne, rôle, actions en JSON structuré.""",
}
],
output_config={"format": {"type": "json_schema", "schema": schema}},
)
result = json.loads(response.content[0].text) # garanti conforme au schéma
# result["person_name"] == "Alice"
Encore plus simple avec Pydantic/Zod :
client.messages.parse(..., output_config={"format": ...})renvoie un objet déjà validé (response.parsed_output).
Avantage : JSON toujours valide et conforme — fini le « le modèle a oublié un champ » ou le JSONDecodeError. Validation à la source.
À savoir :
- Supporté sur Opus 4.8, Sonnet 4.6, Haiku 4.5 (+ legacy Opus 4.5/4.1).
- Contraintes non supportées dans le schéma :
minimum/maximum,minLength, schémas récursifs (les SDK Python/TS les retirent et valident côté client). - ⚠️ Les prefills (forcer le début de la réponse assistant pour imposer un format) renvoient désormais 400 sur Opus 4.6+ —
output_config.formatest leur remplacement.
Approche 2 : Pydantic v2 (Python)
from pydantic import BaseModel, Field, validator
from typing import List
import json
class Extraction(BaseModel):
person_name: str = Field(..., description="Nom de la personne")
role: str = Field(..., enum=["PDG", "CTO", "CFO", "Autre"])
actions: List[str] = Field(..., description="Listes actions")
confidence: int = Field(..., ge=0, le=100)
@validator("person_name")
def name_not_empty(cls, v):
if not v or len(v) < 2:
raise ValueError("Nom invalide")
return v
def extract_structured(text: str) -> Extraction:
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=500,
messages=[
{
"role": "user",
"content": f"""Extrais en JSON (strict format):
{Extraction.model_json_schema()}
Texte: {text}"""
}
]
)
# Parse JSON
json_str = response.content[0].text
json_obj = json.loads(json_str)
# Validate Pydantic
return Extraction(**json_obj)
# Usage
result = extract_structured("Alice (PDG) dit : problème serveur")
# Pydantic valide ou raise ValidationError
Avantage : réutilisable, type-safe, composable.
Limitation : dépend de Claude qui parse JSON correctement (hallucination JSON = erreur).
Approche 3 : instructor library (Python, high-level)
# Installation
# pip install instructor
import instructor
import anthropic
from pydantic import BaseModel
client = instructor.from_anthropic(anthropic.Anthropic())
class Person(BaseModel):
name: str
role: str
actions: list[str]
confidence: int
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=500,
messages=[
{
"role": "user",
"content": "Alice (PDG) rapporte problème serveur. Extrais."
}
],
response_model=Person
)
# Accès typé
print(response.name) # "Alice"
print(response.role) # "PDG"
print(response.confidence) # 95
Avantage : ultra-simple, gère validation + retry automatique.
Limitation : ajoute dépendance externe, overhead latence (retry loop).
Anthropic eval API + custom evals
Mesurer hallucination rate automatiquement plutôt que manuellement.
Setup eval dataset
[
{
"id": "q1",
"question": "Quel est le CA de Carrefour 2024?",
"correct_answer": "89.3 milliards d'euros",
"category": "factual"
},
{
"id": "q2",
"question": "Qui est le PDG de Carrefour en 2024?",
"correct_answer": "Christophe Rabier",
"category": "factual"
},
{
"id": "q3",
"question": "Alice (PDG) envoie email. Qui rapporte serveur down? [contexte Alice invite]",
"correct_answer": "Alice",
"category": "contextual"
}
]
Eval avec Anthropic eval API
import anthropic
import json
client = anthropic.Anthropic(api_key="...")
def eval_llm_on_dataset(dataset, model="claude-opus-4-8"):
"""Évalue Claude sur eval set, retourne taux accuracy."""
scores = {"correct": 0, "hallucination": 0, "total": 0}
for item in dataset:
response = client.messages.create(
model=model,
max_tokens=200,
messages=[
{
"role": "user",
"content": item["question"]
}
]
)
answer = response.content[0].text
# Scoring : exact match ou semantic similarity
is_correct = exact_match(answer, item["correct_answer"]) or semantic_similar(answer, item["correct_answer"])
if is_correct:
scores["correct"] += 1
else:
scores["hallucination"] += 1
scores["total"] += 1
accuracy = scores["correct"] / scores["total"]
hallucination_rate = scores["hallucination"] / scores["total"]
return {
"model": model,
"accuracy": accuracy,
"hallucination_rate": hallucination_rate,
"details": scores
}
# Run evals
with open("eval_dataset.json") as f:
dataset = json.load(f)
for model in ["claude-haiku-4-5-20251001", "claude-sonnet-4-6", "claude-opus-4-8"]:
result = eval_llm_on_dataset(dataset, model)
print(f"{model}: {result['accuracy']:.2%} accuracy, {result['hallucination_rate']:.2%} hallucination")
# Output (illustrative — dépend de votre eval dataset):
# claude-haiku-4-5-20251001: 78.00% accuracy, 22.00% hallucination
# claude-sonnet-4-6: 89.00% accuracy, 11.00% hallucination
# claude-opus-4-8: 95.00% accuracy, 5.00% hallucination
# (Vos résultats réels varieront selon votre dataset et critères de scoring)
Scoring functions avancées
Exact match
def exact_match(generated, reference):
return generated.strip().lower() == reference.strip().lower()
Semantic similarity (via embedding)
Note : Anthropic ne fournit pas d'API d'embeddings propriétaire. Pour les embeddings avec Claude, Anthropic recommande Voyage AI (
voyage-3-large). Vous pouvez aussi utiliser un autre fournisseur (OpenAItext-embedding-3, Cohereembed-v4, etc.).
import voyageai
from sklearn.metrics.pairwise import cosine_similarity
vo = voyageai.Client() # lit VOYAGE_API_KEY
def semantic_similar(generated, reference, threshold=0.85):
"""Compare sémantique via embeddings Voyage AI."""
result = vo.embed(
[generated, reference],
model="voyage-3-large",
input_type="document",
)
emb_generated, emb_reference = result.embeddings
similarity = cosine_similarity([emb_generated], [emb_reference])[0][0]
return similarity >= threshold
Regex / partial match
import re
def regex_match(generated, pattern):
"""Match regex pattern (flexible pour format variation)."""
return bool(re.search(pattern, generated, re.IGNORECASE))
# Usage: regex_match("89,3 milliards EUR", r"89.*milliards") # True
Langfuse : tracing, flagging, dashboards
Tracer hallucinations en production et construire dashboard.
Setup Langfuse
from langfuse import Langfuse
langfuse = Langfuse(
secret_key="sk_lf_...",
public_key="pk_lf_..."
)
def claude_with_trace(question, expected_answer=None):
trace = langfuse.trace(
name="qa",
user_id="user_123",
metadata={"category": "factual"}
)
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=500,
messages=[{"role": "user", "content": question}]
)
answer = response.content[0].text
# Log generation
generation = trace.generation(
name="claude-response",
model="claude-opus-4-8",
input=question,
output=answer,
usage={
"input_tokens": response.usage.input_tokens,
"output_tokens": response.usage.output_tokens
}
)
# Flag si hallucination
is_hallucination = expected_answer and not semantic_similar(answer, expected_answer)
if is_hallucination:
generation.score(
name="hallucination_flag",
value=1, # 1 = hallucination detected
comment=f"Expected: {expected_answer}, Got: {answer}"
)
trace.update(
output=answer,
status_code="hallucination" if is_hallucination else "ok"
)
return answer
# Usage
claude_with_trace("CA Carrefour 2024?", expected_answer="89.3 milliards")
Dashboard Langfuse
Accès web : https://cloud.langfuse.com. Métriques automatiques :
| Métrique | Calc | Seuil alerte |
|---|---|---|
| Hallucination rate (%) | hallucination_flags / total_traces | > 5 % |
| Avg latency (ms) | mean(response time) | > 5000 ms |
| Token efficiency | output_tokens / input_tokens | < 0.05 |
| Cost per trace ($) | usage_tokens × price | > $0.10 |
Alerting
# Exemple : si hallucination rate > 5 % en 1 h, alerte
langfuse_client.create_score_alert(
project_id="proj_...",
score_name="hallucination_flag",
condition="average > 0.05",
time_window_minutes=60,
webhook_url="https://slack.com/hooks/..."
)
Confidence scoring et automated rejection
Claude attribue confiance à sa réponse, rejette auto si < seuil.
def confident_answer(question, confidence_threshold=60):
"""Retourne réponse seulement si confiance > threshold."""
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=300,
system="""Réponds EN JSON avec "answer" et "confidence" (0–100).
Confidence = ta certitude que réponse est correcte et pas hallucination.""",
messages=[
{
"role": "user",
"content": question
}
]
)
result = json.loads(response.content[0].text)
if result["confidence"] >= confidence_threshold:
return result["answer"]
else:
return f"Je ne suis pas assez confiant pour répondre (confiance: {result['confidence']}%). Demande un humain."
# Usage
print(confident_answer("CA Carrefour 2024?"))
# "89.3 milliards d'euros"
print(confident_answer("CA Carrefour 1987?"))
# "Je ne suis pas assez confiant... (confiance: 15%)"
Patterns par domaine : legal, medical, support
Chaque domaine a ses défenses spécifiques :
Chargement du schéma…
def legal_analysis_safe(question, legal_docs):
"""Analyse légale avec obligation source."""
# RAG
context = retrieve_relevant_legal_passages(legal_docs, question)
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=1000,
system="""Tu es juriste. RÈGLES OBLIGATOIRES:
1. CITER l'article/loi pour CHAQUE affirmation légale
2. Si pas source => "je ne sais pas"
3. Format: [Affirmation legal] [RÉFÉRENCE: Loi X, article Y, page Z]
4. Identifier zones d'incertitude ("La jurisprudence varie sur...")""",
messages=[
{
"role": "user",
"content": f"""Contexte légal fourni:
{context}
Question: {question}
Analyse en citant chaque source."""
}
]
)
return response.content[0].text
# Usage
legal_analysis_safe(
"Puis-je licencier un employé sans préavis?",
legal_docs="Code du travail France 2024"
)
# Output: "Non. Article L1234-1 du Code du travail stipule
# préavis obligatoire de X jours [RÉFÉRENCE: Code du travail,
# article L1234-1, version 2024]"
Medical : output structure + disclaimer
class MedicalAdvice(BaseModel):
condition: str
likely_causes: list[str]
recommendations: list[str]
confidence: int # 0-100
disclaimer: str = "Ce n'est pas un diagnostic médical. Consultez un professionnel."
def medical_safe(symptom_description):
"""Info médicale responsable."""
response = client.messages.create(
model="claude-opus-4-8",
max_tokens=500,
system="""Tu fournis infos médicales éducatives SEULEMENT.
JAMAIS de diagnostic. TOUJOURS disclaimer et "consulter médecin".
Format JSON structuré avec confiance <= 50 (car pas médecin).""",
messages=[
{
"role": "user",
"content": symptom_description
}
],
response_model=MedicalAdvice
)
advice = response.parsed
# Force disclaimer affichage
print(f"DISCLAIMER: {advice.disclaimer}")
print(f"INFO ÉDUCATIVE (confidence {advice.confidence}%): {advice.recommendations}")
return advice
medical_safe("J'ai mal à la tête depuis 2 jours")
# Output: "DISCLAIMER: Ce n'est pas un diagnostic médical..."
# "INFO ÉDUCATIVE (confidence 35%): Possibles causes: hydratation, stress, infection...
# Consultez un médecin pour diagnostic."
Support client : multi-agent + QA fallback
def support_response_safe(customer_query):
"""Réponse support robuste avec fallback."""
# Stage 1 : Générer avec Sonnet
draft = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=500,
system="Tu es agent support. Réagis avec empathie et clarté. Pas de spéculation.",
messages=[
{
"role": "user",
"content": customer_query
}
]
).content[0].text
# Stage 2 : Vérifier avec Haiku
qa_check = client.messages.create(
model="claude-haiku-4-5",
max_tokens=200,
system="Tu es QA. Flag si réponse contient suppositions (\"probablement\", \"généralement\") sans context justifiant.",
messages=[
{
"role": "user",
"content": f"QA check:\n{draft}"
}
]
).content[0].text
if "HALLUCINATION" in qa_check or "supposé" in qa_check:
# Fallback : escalade humain
return f"""Je ne suis pas certain de ma réponse. Voici ce j'envisageais:
{draft}
Cependant, je recommande de contacter notre équipe support pour certitude absolue. Lien: [escalade]"""
return draft
support_response_safe("Mon commande n'est pas arrivée après 10 jours")
Atelier : construire eval set 50 questions (60 min)
Étape 1 (15 min) : Définir domaine
Choisis domaine (support client, RH, legal, data analysis, etc.) et collectionne 50 questions réelles.
Domaine support client : emails vrais de clients, catégories :
Factuelles (20) : "Quel est délai livraison?", "Acceptez-vous cartes crédit?"
Contextuelles (15) : "J'ai commandé il y a 2 semaines, où est mon colis?" (nécessite lookup)
Edge case (10) : "Vous m'aviez promis réduction en 2023, c'est vrai?" (vérifiable/non)
Problématiques (5) : "Puis-je vous poursuivre?" (escalade)
Étape 2 (20 min) : Annoter réponses correctes
Pour chaque question, documenter réponse attendue + source :
{
"id": "q1",
"question": "Quel est délai livraison standard?",
"correct_answer": "5-7 jours ouvrables",
"source": "Terms of Service page 3",
"category": "factual",
"difficulty": "easy"
}
Étape 3 (15 min) : Setup scoring logic
# Scoring function adaptée à domaine
def score_support_answer(generated, expected, category):
if category == "factual":
return exact_match(generated, expected)
elif category == "contextual":
return semantic_similar(generated, expected, threshold=0.80)
else:
# Edge cases : humain valide
return None # Manual review
Étape 4 (10 min) : Run eval batch
results = eval_llm_on_dataset(
dataset="eval_support_50.json",
models=["claude-haiku-4-5-20251001", "claude-sonnet-4-6"],
scoring_fn=score_support_answer
)
print(results)
# Haiku: 76% accuracy / 24% hallucination
# Sonnet: 92% accuracy / 8% hallucination
Pour aller plus loin
- 📖 Self-Consistency with Chain-of-Thought Prompting (Wang et al. 2022)
- 📖 Anthropic Eval API Documentation
- 🛠
instructorPython library — structured outputs made easy - 🛠
outlines— constrained generation - 🛠 Langfuse observability (open-source)
- 📊 LMSYS Chatbot Arena — live eval leaderboard
- 🎯 Modules complémentaires : M1 AI Act (cadre légal hallucination), M11 ROI mesurable (mesurer coût hallucinations)