Self-consistency : multiples raisonnements
Mis à jour le 28 juillet 2026
Self-consistency : multiples raisonnements
La self-consistency consiste à générer plusieurs réponses indépendantes au même prompt, puis à retenir la plus fréquente ou la plus cohérente. Le principe est celui du vote : un appel unique vous expose au hasard d’un mauvais tirage, cinq appels vous donnent une distribution que vous pouvez interroger. La technique améliore sensiblement la fiabilité des réponses, en particulier sur les tâches de raisonnement logique et mathématique.
Le problème qu’elle résout
Un LLM peut donner une réponse différente à chaque appel, même avec un prompt strictement identique. Certaines de ces réponses sont correctes, d’autres non, et rien dans la sortie ne vous signale laquelle vous êtes en train de lire : une réponse fausse est formulée avec le même aplomb qu’une réponse juste. La self-consistency retourne cette variabilité à votre avantage. Si le modèle aboutit à la même conclusion en empruntant plusieurs chemins de raisonnement différents, cette conclusion est probablement correcte ; une erreur, elle, se reproduit rarement à l’identique d’un tirage à l’autre, parce qu’elle dépend justement du chemin emprunté.
Implémentation de base
La mécanique tient en trois temps : générer, extraire la réponse finale, voter. Le marqueur >>> RÉPONSE : imposé dans les instructions n’a rien de cosmétique — c’est lui qui rend la sortie parsable sans avoir à interpréter le raisonnement qui la précède. Sans ce point d’ancrage, vous devriez écrire une heuristique fragile pour deviner où s’arrête la réflexion et où commence la conclusion.
from openai import OpenAI
from collections import Counter
client = OpenAI()
def self_consistency(prompt: str, system: str = "",
n: int = 5, model: str = "gpt-5.6-terra") -> dict:
"""Génère n réponses et retourne la plus fréquente."""
responses = []
for _ in range(n):
response = client.responses.create(
model=model,
instructions=system + "\nRaisonne étape par étape, puis donne "
"ta réponse finale après '>>> RÉPONSE :'",
input=prompt,
temperature=0.7 # Température > 0 pour la diversité
)
responses.append(response.output_text)
# Extraire les réponses finales
answers = []
for r in responses:
if ">>> RÉPONSE :" in r:
answer = r.split(">>> RÉPONSE :")[-1].strip()
answers.append(answer)
# Vote majoritaire
if answers:
counter = Counter(answers)
best_answer, count = counter.most_common(1)[0]
return {
"answer": best_answer,
"confidence": count / len(answers),
"all_answers": dict(counter),
"total_responses": len(answers)
}
return {"answer": None, "confidence": 0}
Ce que le vote vous apprend
Appliquée à une classification de sentiment, la fonction ne renvoie pas seulement une étiquette : elle renvoie une distribution, et cette distribution vaut souvent plus que l’étiquette elle-même. Sur l’avis ci-dessous, réellement ambigu — le produit convient, la livraison a été catastrophique —, quatre appels votent « négatif » et trois « neutre ». La confiance de 57 % vous dit que le cas mérite peut-être un traitement particulier : mise en file de relecture, demande de précision à l’utilisateur, ou simple exclusion des statistiques agrégées. Un appel unique vous aurait affiché « négatif » sans nuance, et vous auriez traité ce ticket exactement comme un avis unanimement furieux.
system = """Tu es un classificateur de sentiment.
Analyse le texte et raisonne étape par étape.
Donne ta réponse finale après '>>> RÉPONSE :' parmi :
positif, négatif, neutre"""
texte = "Le produit est correct mais la livraison a pris 3 semaines."
result = self_consistency(texte, system, n=7)
print(f"Sentiment : {result['answer']}")
print(f"Confiance : {result['confidence']:.0%}")
print(f"Distribution : {result['all_answers']}")
# Exemple de sortie :
# Sentiment : négatif
# Confiance : 57%
# Distribution : {'négatif': 4, 'neutre': 3}
Version parallèle avec asyncio
Le coût de la technique n’est pas seulement financier : cinq appels séquentiels, ce sont aussi cinq fois la latence, ce qui condamne l’approche dans toute interface où quelqu’un attend devant son écran. Or les appels sont parfaitement indépendants les uns des autres, rien ne justifie de les attendre l’un après l’autre. asyncio.gather ramène le temps total à celui de l’appel le plus lent et rend la self-consistency compatible avec un usage en ligne, pour un surcoût qui redevient purement monétaire.
import asyncio
from openai import AsyncOpenAI
async_client = AsyncOpenAI()
async def self_consistency_async(prompt: str, system: str = "",
n: int = 5) -> dict:
"""Version asynchrone de la self-consistency."""
async def single_call():
response = await async_client.responses.create(
model="gpt-5.6-terra",
instructions=system + "\nRaisonne puis donne ta réponse "
"après '>>> RÉPONSE :'",
input=prompt,
temperature=0.7
)
return response.output_text
# Lancer toutes les requêtes en parallèle
tasks = [single_call() for _ in range(n)]
results = await asyncio.gather(*tasks)
# Extraire et voter
answers = []
for r in results:
if ">>> RÉPONSE :" in r:
answers.append(r.split(">>> RÉPONSE :")[-1].strip())
counter = Counter(answers)
best, count = counter.most_common(1)[0] if answers else (None, 0)
return {
"answer": best,
"confidence": count / len(answers) if answers else 0,
"distribution": dict(counter)
}
Quand utiliser la self-consistency
Le critère de décision est simple à énoncer : le vote n’a de sens que si la réponse est discrète et comparable. Deux résumés bien écrits ne se comptabilisent pas, puisqu’ils ne seront jamais identiques au caractère près ; deux étiquettes de catégorie, si.
| Cas d'usage | Utile ? | Pourquoi |
|---|---|---|
| Classification | Oui | Réponse discrète, vote simple |
| Raisonnement mathématique | Oui | Multiples chemins vers la bonne réponse |
| Extraction d'entités | Oui | Consensus sur les entités détectées |
| Rédaction créative | Non | Pas de "bonne" réponse unique |
| Résumé de texte | Partiel | Utile pour les faits clés, pas le style |
Régler le rapport coût/fiabilité
Trois appels constituent le minimum viable pour qu’un vote majoritaire signifie quelque chose ; cinq offrent le meilleur compromis dans la plupart des applications ; de sept à onze se justifient sur les décisions critiques, celles où une erreur coûte bien plus cher que quelques appels supplémentaires. Le vrai gain vient toutefois de l’usage que vous faites du score de confiance plutôt que du nombre d’échantillons. En dessous de 60 %, considérez qu’il n’y a pas de consensus et escaladez, soit vers un opérateur humain, soit vers un modèle plus puissant avec davantage d’échantillons, comme ci-dessous.
result = self_consistency(question, system, n=5)
if result["confidence"] < 0.6:
# Pas de consensus clair, escalader
result = self_consistency(question, system, n=9, model="gpt-5.6-sol")
Reste à savoir si le jeu en vaut la chandelle chez vous, et cela se mesure au lieu de se supposer. Implémentez la version asynchrone, faites-la tourner sur une dizaine de questions de classification de sentiment dont vous connaissez la réponse attendue, puis comparez la précision obtenue avec un appel unique et avec cinq appels. Le chiffre que vous obtiendrez tranchera la question du surcoût pour votre cas précis, ce qu’aucune recommandation générale ne peut faire à votre place.
Points clés à retenir
- La self-consistency génère plusieurs réponses et vote pour la meilleure
- Utilisez une température > 0 pour obtenir de la diversité
- Efficace pour les tâches avec une réponse discrète (classification, calcul)
- Inutile pour les tâches créatives sans bonne réponse unique
- Parallélisez les appels avec asyncio pour limiter la latence