Пять запусков одного и того же промпта дают пять копий одной и той же ошибки

N-version programming предполагает, что разнообразие приходит от разных авторов. С LLM это означает разные модели, разных провайдеров, возможно, разные обучающие запуски. Но это предположение неверно. Можно получить осмысленное разнообразие от той же самой модели, меняя то, как вы спрашиваете, а не что вы спрашиваете.

Проблема в том, что выкручивание temperature на 1.0 и пять запусков вашего промпта — это не стратегия. Вы получите вариацию на поверхностном уровне. Меняются имена переменных. Комментарии перетасовываются. Структура остается идентичной, и баги тоже остаются идентичными.

Если вам нужны реализации, которые падают независимо, нужно запрашивать разные паттерны мышления, а не разные выходы.

Что N-version programming на самом деле нужно от LLM

N-version programming — это техника fault tolerance, где несколько независимых реализаций одной спецификации выполняются параллельно. Выходы сравниваются, и majority vote определяет правильный результат. Идея в том, что разные разработчики, работая независимо, вносят разные баги. Баги не коррелируют, поэтому majority vote подавляет их.

Это старая идея. Она также дорогая. Вы платите N командам за создание одного и того же.

LLM делают это достаточно дешевым, чтобы попробовать. Вместо N команд у вас N API calls. Проблема в том, что N API calls к той же модели с тем же промптом дают N почти идентичных реализаций. Баги коррелируют идеально. Ваш majority vote бесполезен.

Решение — рассматривать промпт как разработчика, а не модель. Разные промпты дают разных разработчиков.

Почему temperature alone дает только косметическое разнообразие

Temperature контролирует распределение вероятностей по токенам. При высоком temperature модель выбирает менее вероятные следующие токены. Это создает вариацию в формулировках, именовании переменных и поверхностной структуре.

Это не создает вариации в алгоритмическом подходе. Если вы просите функцию для поиска самой длинной палиндромной подстроки, temperature меняет, используете ли вы left и right или l и r. Это не меняет, выберете ли вы expand-around-center или dynamic programming.

Для N-version programming это бесполезно. Вам нужны реализации, которые решают проблему по-разному, а не реализации, которые выглядят по-разному, но решают ее одинаково.

Четыре стратегии промптинга, которые заставляют алгоритмическое разнообразие

Вот четыре подхода, которые меняют то, как модель думает о проблеме.

Варьируйте framing проблемы

Одна и та же задача, представленная как “напишите parser” против “напишите state machine, который распознает эту grammar”, даст разный код. Один может использовать recursive descent. Другой может использовать table-driven approach.

Можно автоматизировать это, попросив модель принять конкретный framing перед решением:

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))

Запуская это на GPT-4o, framing с state machine последовательно дает посимвольный parser с явным state enum. Framing с recursive descent дает lexer и отдельные parser functions. Framing split-and-fix дает более компактное, но хрупкое решение.

Меняйте персоны

Разные персоны активируют разные знания. Systems programmer пишет другой код, чем data scientist или competitive programmer.

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.",
]

Persona prompting удивительно эффективен для структурного разнообразия. Systems programmer выбирает arrays и indices. Pythonic developer выбирает itertools и comprehensions. Algorithms researcher может подключить библиотеку или написать более формальное решение.

Ограничивайте доступные инструменты

Ограничение или расширение доступного toolkit заставляет использовать разные подходы.

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.",
]

Это особенно полезно, когда вы знаете, что у одного подхода есть blind spot. Если ваши regex-based parsers постоянно неправильно обрабатывают вложенные кавычки, заставьте версию не использовать regex.

Chain-of-Thought с расходящимся рассуждением

Вместо того чтобы сразу просить код, попросите модель сгенерировать несколько solution strategies и выбрать наименее очевидную.

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,
)

Chain-of-Thought заставляет модель вывести свои reasoning наружу. Ограничение “most different” толкает ее away от решения по умолчанию. На практике это дает наибольшее структурное разнообразие из любой отдельной техники.

Где это ломается: diversity ceiling

Same-model diversity имеет пределы, и вы в них упретесь.

Фундаментальные пробелы в знаниях общие. Если в обучающих данных содержится systematic misunderstanding о floating-point comparison, каждый framing и каждая персона воспроизведут это misunderstanding. У модели один набор weights. Вы не обойдете это с помощью промптов.

Также есть diminishing returns. Первые три framing могут дать вам state machine, recursive parser и split-based approach. Четвертый framing может дать вам state machine с другими именами переменных. После трех-пяти по-настоящему разных подходов вы дошли до дна бочки.

Некоторые техники снижают качество. Ограничение “most different” иногда дает решения, которые отличаются тем, что они неправильные. Divergence ради divergence бесполезна. Вам нужен механизм voting или testing, чтобы отфильтровать плохие идеи.

Практическая настройка, которую можно развернуть сегодня

Если вы встраиваете это в систему, не рандомизируйте. Продумывайте свое разнообразие.

Выберите три-пять техник из списка выше. Сгенерируйте одну реализацию на технику. Запустите ваш test suite или property-based tests против всех них. Оставьте те, что проходят. Используйте простой majority vote для финального выхода.

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]

Шаг фильтрации через тесты non-negotiable. Разнообразие без корректности — это просто шум.

FAQ

Работает ли это с меньшими моделями?

Да, но diversity ceiling ниже. Меньшие модели имеют меньше отдельных solution strategies в своих обучающих данных. Вы можете получить два по-настоящему разных подхода вместо четырех. Техники все еще работают; они просто дают меньше вариации.

Сколько реализаций мне на самом деле нужно?

Три — это практический минимум для majority voting. Пять дают лучшее покрытие, но с линейно растущей стоимостью. После пяти same-model diversity деградирует в косметическую вариацию. Если вам нужно больше пяти, переключайтесь на cross-model diversity.

Является ли same-model diversity такой же хорошей, как cross-model diversity?

Нет. Разные модели имеют разные обучающие данные, архитектуры и fine-tuning. Они падают по-настоящему разными способами. Same-model diversity — это компромисс стоимости и операционного удобства. Используйте его, когда вам нужен хороший fault tolerance быстро, а не когда вам нужен идеальный fault tolerance.

Могу ли я комбинировать эти техники?

Абсолютно. Persona prompt в сочетании с ограничением на инструменты и шагом chain-of-thought даст больше разнообразия, чем любая отдельная техника. Цена — более длинный промпт и больше токенов на генерацию. Для критических code paths лишние токены стоят того.

С чего начать

Начните с вариации framing. Это проще всего внедрить и дает наиболее последовательное структурное разнообразие. Добавьте переключение персон, если нужно больше. Оставьте cross-model diversity для случаев, где same-model diversity упирается в свой потолок.

Пропустите ваши реализации через один и тот же test suite, прежде чем позволить им голосовать. Нетестированная разнообразная реализация — это просто багованная реализация, с которой вы еще не познакомились.