Exécuter le même prompt cinq fois produit cinq copies de la même erreur
La programmation N-version part du principe que la diversité vient d’auteurs différents. Avec les LLM, cela signifie des modèles différents, des fournisseurs différents, peut-être des entraînements différents. Mais ce principe est faux. Vous pouvez obtenir une diversité significative à partir du même modèle en changeant la façon dont vous demandez, pas ce que vous demandez.
Le problème : augmenter la température à 1,0 et exécuter votre prompt cinq fois n’est pas une stratégie. Vous obtiendrez une variation superficielle. Les noms de variables changent. Les commentaires se mélangent. La structure reste identique, et les bugs restent identiques aussi.
Si vous voulez des implémentations qui échouent indépendamment, vous devez demander des modes de pensée différents, pas des sorties différentes.
Ce dont la programmation N-version a réellement besoin des LLM
La programmation N-version est une technique de tolérance aux pannes où plusieurs implémentations indépendantes de la même spécification sont exécutées en parallèle. Les sorties sont comparées, et un vote majoritaire détermine le résultat correct. L’idée est que différents développeurs, travaillant indépendamment, introduiront différents bugs. Les bugs ne seront pas corrélés, donc un vote majoritaire les supprime.
C’est une vieille idée. C’est aussi coûteux. Vous payez N équipes pour construire la même chose.
Les LLM le rendent assez bon marché pour essayer. Au lieu de N équipes, vous avez N appels API. Le problème est que N appels API au même modèle avec le même prompt produisent N implémentations presque identiques. Les bugs sont parfaitement corrélés. Votre vote majoritaire est inutile.
La solution est de traiter le prompt comme le développeur, pas le modèle. Différents prompts produisent différents développeurs.
Pourquoi la température seule produit une diversité cosmétique
La température contrôle la distribution de probabilité sur les tokens. À haute température, le modèle choisit des tokens suivants moins probables. Cela crée une variation dans le phrasé, le nommage des variables et la structure superficielle.
Cela ne crée pas de variation dans l’approche algorithmique. Si vous demandez une fonction pour trouver la plus longue sous-chaîne palindromique, la température change si vous utilisez left et right ou l et r. Cela ne change pas si vous optez pour expand-around-center ou la programmation dynamique.
Pour la programmation N-version, c’est inutile. Vous avez besoin d’implémentations qui résolvent le problème différemment, pas d’implémentations qui ont l’air différentes tout en le résolvant de la même manière.
Quatre stratégies de prompt qui imposent une diversité algorithmique
Voici quatre approches qui changent la façon dont le modèle pense au problème.
Faire varier le cadrage du problème
La même tâche présentée comme « écrire un parser » par rapport à « écrire une state machine qui reconnaît cette grammaire » produira du code différent. L’un pourrait utiliser une descente récursive. L’autre pourrait utiliser une approche dirigée par table.
Vous pouvez automatiser cela en demandant au modèle d’adopter un cadrage spécifique avant de résoudre :
import os
from openai import OpenAI
client = OpenAI(api_key=os.environ["OPENAI_API_KEY"])
def generate_with_framing(task: str, framing: str) -> str:
prompt = f"""{framing}
Task: {task}
Write a complete, correct implementation. Do not explain your approach."""
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": prompt}],
temperature=0.7,
)
return response.choices[0].message.content
task = "Parse a CSV string into a list of dictionaries, handling quoted fields and newlines within quotes."
framings = [
"Approach this as a finite state machine with explicit state transitions.",
"Approach this using recursive descent parsing with a lexer and parser.",
"Approach this by splitting on delimiters and post-processing edge cases.",
]
for framing in framings:
print(f"=== {framing} ===")
print(generate_with_framing(task, framing))
En l’exécutant avec GPT-4o, le cadrage state machine produit systématiquement un parser caractère par caractère avec un enum d’état explicite. Le cadrage descente récursive produit un lexer et des fonctions parser séparées. Le cadrage split-and-fix produit une solution plus compacte mais fragile.
Changer de personas
Différents personas activent différentes connaissances. Un programmeur système écrit du code différent d’un data scientist ou d’un programmeur compétitif.
personas = [
"You are a systems programmer who prioritizes memory efficiency and avoids unnecessary allocations.",
"You are a Pythonic developer who prefers concise, idiomatic code using standard library features.",
"You are an algorithms researcher who reaches for theoretically optimal solutions even if the code is longer.",
]
Le prompt de persona est étonnamment efficace pour la diversité structurelle. Le programmeur système opte pour des tableaux et des indices. Le développeur pythonique opte pour itertools et les comprehensions. Le chercheur en algorithmes pourrait importer une bibliothèque ou écrire une solution plus formelle.
Contraindre les outils disponibles
Restreindre ou étendre la boîte à outils disponible impose différentes approches.
constraints = [
"You may only use the Python standard library. No external dependencies.",
"You may use numpy and pandas. Optimize for vectorized operations.",
"You must implement this without using regular expressions.",
]
C’est particulièrement utile quand vous savez qu’une approche a un point aveugle. Si vos parsers basés sur regex continuent de mal gérer les guillemets imbriqués, forcez une version qui n’utilise pas de regex.
Chain-of-Thought avec raisonnement divergent
Au lieu de demander du code directement, demandez au modèle de générer plusieurs stratégies de solution et de choisir la moins évidente.
cot_prompt = f"""Task: {task}
First, list three different algorithms or approaches to solve this problem.
Then, pick the one that is most different from the others and implement it.
Do not pick the most obvious approach."""
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": cot_prompt}],
temperature=0.7,
)
Le chain-of-thought force le modèle à expliciter son raisonnement. La contrainte « most different » le pousse loin de la solution par défaut. En pratique, cela produit la plus grande diversité structurelle de toute technique individuelle.
Où cela échoue : le plafond de diversité
La diversité même-modèle a des limites, et vous les atteindrez.
Les lacunes fondamentales de connaissances sont partagées. Si les données d’entraînement contiennent une incompréhension systématique sur la comparaison de nombres à virgule flottante, chaque cadrage et chaque persona reproduiront cette incompréhension. Le modèle a un seul ensemble de poids. Vous ne contournez pas cela avec des prompts.
Il y a aussi des rendements décroissants. Les trois premiers cadrages pourraient vous donner une state machine, un parser récursif et une approche basée sur le split. Le quatrième cadrage pourrait vous donner une state machine avec des noms de variables différents. Après trois à cinq approches réellement différentes, vous grattez le fond du baril.
Certaines techniques dégradent la qualité. La contrainte « most different » produit parfois des solutions qui sont différentes parce qu’elles sont erronées. La divergence pour elle-même n’est pas utile. Vous avez besoin d’un mécanisme de vote ou de test pour filtrer les mauvaises idées.
Une configuration pratique que vous pouvez déployer aujourd’hui
Si vous intégrez cela dans un système, ne randomisez pas. Concevez votre diversité.
Choisissez trois à cinq techniques de la liste ci-dessus. Générez une implémentation par technique. Exécutez votre suite de tests ou des tests basés sur les propriétés contre toutes. Gardez celles qui passent. Utilisez un simple vote majoritaire pour la sortie finale.
from collections import Counter
def majority_vote(outputs: list[str], test_fn) -> str:
passing = [o for o in outputs if test_fn(o)]
if not passing:
raise RuntimeError("No implementation passed tests")
# Exact match voting; swap for AST comparison if needed
return Counter(passing).most_common(1)[0][0]
L’étape de filtrage par les tests est non négociable. La diversité sans correction n’est que du bruit.
FAQ
Cela fonctionne-t-il avec des modèles plus petits ?
Oui, mais le plafond de diversité est plus bas. Les modèles plus petits ont moins de stratégies de solution distinctes dans leurs données d’entraînement. Vous pourriez obtenir deux approches réellement différentes au lieu de quatre. Les techniques fonctionnent toujours ; elles produisent juste moins de variation.
De combien d’implémentations ai-je réellement besoin ?
Trois est le minimum pratique pour le vote majoritaire. Cinq vous donne une meilleure couverture mais avec un coût croissant de manière linéaire. Après cinq, la diversité même-modèle se dégrade en variation cosmétique. Si vous avez besoin de plus de cinq, passez à la diversité inter-modèles.
La diversité même-modèle est-elle aussi bonne que la diversité inter-modèles ?
Non. Différents modèles ont différentes données d’entraînement, architectures et fine-tuning. Ils échouent de manières réellement différentes. La diversité même-modèle est un compromis entre coût et commodité opérationnelle. Utilisez-la quand vous avez besoin d’une bonne tolérance aux pannes rapidement, pas quand vous avez besoin d’une tolérance aux pannes parfaite.
Puis-je combiner ces techniques ?
Absolument. Un prompt de persona combiné avec une contrainte d’outil et une étape de chain-of-thought produira plus de diversité que n’importe quelle technique seule. Le coût est un prompt plus long et plus de tokens par génération. Pour les chemins de code critiques, les tokens supplémentaires en valent la peine.
Par quoi commencer
Commencez par la variation de cadrage. C’est la plus facile à implémenter et elle produit la diversité structurelle la plus cohérente. Ajoutez le changement de persona si vous avez besoin de plus. Réservez la diversité inter-modèles pour les cas où la diversité même-modèle atteint son plafond.
Faites passer vos implémentations dans la même suite de tests avant de les laisser voter. Une implémentation diverse non testée n’est qu’une implémentation buggée que vous n’avez pas encore rencontrée.