COMRAD404 / HOWTO

Как писать промпты для генерации кода

Практическое руководство по промптам для генерации кода: как формулировать задачу, задавать ограничения, требовать тесты и проверять результат без самообмана.

Хороший промпт для генерации кода — это не «напиши мне сервис», а короткая техническая спецификация: роль модели, контекст проекта, точная задача, входы и выходы, ограничения по стеку, требования к качеству и формат ответа. Такой подход ускоряет работу на типовых задачах, но плохо подходит для неясных требований, критичных по безопасности модулей, сложной доменной логики без тестов и унаследованного кода, в котором модель не видит полного контекста.

Короткий ответ

Чтобы модель писала полезный код, промпт должен отвечать на шесть вопросов:

  1. Что нужно сделать: одна конкретная задача, без расплывчатых формулировок.
  2. Где это будет работать: язык, версия, фреймворк, окружение, ограничения проекта.
  3. Что подать на вход: структура данных, интерфейсы, примеры вызова.
  4. Что ожидается на выходе: формат ответа, сигнатуры функций, поведение, ошибки.
  5. По каким правилам писать: стиль, запреты, требования к производительности, безопасности, тестам.
  6. Как проверить результат: набор кейсов, ожидаемые результаты, критерии готовности.

Минимально рабочая структура промпта выглядит так: Контекст проекта → Задача → Ограничения → Интерфейс → Критерии качества → Формат ответа. Если промпт не задает хотя бы интерфейс и критерии проверки, модель почти наверняка заполнит пробелы своими допущениями.

Метод особенно полезен для генерации небольших функций, тестов, рефакторинга, SQL-запросов, миграций, обвязки API и документации к коду. Он менее надежен для криптографии, авторизации, конкурентных алгоритмов, биллинга, медицинских и юридически значимых сценариев, а также для задач, где ошибка проявится только под нагрузкой или на редких данных.

Что понадобится

  • Четкая постановка задачи. Если вы сами не можете описать поведение функции в двух-трех абзацах, модель тоже не угадает.
  • Контекст проекта. Язык, версии, фреймворки, правила кодстайла, запрещенные зависимости, целевая база данных, ОС, ограничения рантайма.
  • Минимальный фрагмент существующего кода. Сигнатуры, типы, контракты, пример похожего модуля.
  • Набор проверок. Примеры входных данных, граничные случаи, ожидаемые ошибки, тестовые сценарии.
  • Право на итерации. Один промпт редко дает финальный результат; обычно нужен цикл из 2–5 уточнений.

Если в проекте есть внутренние стандарты, полезно включить их в промпт кратко и буквально: например, используй только стандартную библиотеку, не меняй публичный интерфейс, покрой pytest-тестами happy path и edge cases. Не стоит отправлять модели целый регламент на десять страниц: лучше вынести 5–8 правил, влияющих именно на текущую задачу.

Пошаговый план

1. Сузьте задачу до одного результата

Плохой запрос: сделай авторизацию в приложении. Хороший: напиши middleware для проверки JWT в FastAPI, которое извлекает Bearer-токен из заголовка Authorization, валидирует подпись HS256, отклоняет истекший токен и кладет user_id в request.state.

Если в одном промпте смешаны архитектура, код, тесты, деплой и документация, модель начнет усреднять ответ. Разбивайте работу на отдельные шаги: сначала интерфейс и поведение, потом реализация, затем тесты, затем рефакторинг.

2. Дайте проектный контекст, без которого код будет неверным

Минимальный контекст обычно включает: язык, версии, фреймворк, ограничения по зависимостям, стиль типизации, используемую БД и формат логирования. Пример:

Проект на Python 3.12, FastAPI, Pydantic v2, SQLAlchemy 2.0. Используем PostgreSQL 15. Внешние зависимости добавлять нельзя. Код должен проходить mypy strict и pytest. Логи через стандартный logging. Асинхронный стиль обязателен там, где есть I/O.

Такой блок сильно снижает вероятность того, что модель предложит несовместимый API, устаревший синтаксис или лишнюю библиотеку.

3. Зафиксируйте интерфейс и формат ответа

Одна из частых причин бесполезного кода — отсутствие явной сигнатуры. Лучше сразу задать форму результата:

Нужна функция normalize_phone(value: str) -> str. Она должна принимать строку с номером телефона, удалять пробелы, скобки и дефисы, приводить российские номера к формату +7XXXXXXXXXX и выбрасывать ValueError для невалидных значений.

Если нужен не просто код, а, например, патч, так и скажите: верни только измененные функции, покажи diff по файлу, сначала кратко опиши план, потом код. Формат ответа влияет на полезность не меньше, чем сама задача.

4. Задайте ограничения и запреты

Модель склонна оптимистично дополнять недостающие детали. Поэтому ограничения лучше формулировать явно:

  • Не менять публичные сигнатуры.
  • Не использовать внешние пакеты.
  • Не делать сетевых вызовов внутри функции.
  • Не хранить секреты в коде.
  • Не использовать рекурсию из-за ограничений по стеку.
  • Учитывать сложность не хуже O(n log n).

Запреты полезны, когда есть риск «красивого», но неприемлемого решения. Для продакшн-кода они часто важнее пожеланий по стилю.

5. Требуйте обработку ошибок и тесты сразу

Если попросить просто «написать функцию», модель часто вернет happy path. Для практической работы лучше сразу требовать негативные сценарии:

Добавь unit-тесты для валидных и невалидных входов. Отдельно проверь пустую строку, лишние символы, формат с кодом 8 и уже нормализованный формат. Укажи ожидаемые исключения.

Хорошее правило: если функция работает с вводом пользователя, сетью, временем, файлами, БД или деньгами, тесты должны быть частью запроса, а не последующей мыслью.

6. Добавьте примеры входа и выхода

Примеры уменьшают пространство догадок. Вместо абстрактного описания лучше дать 3–5 кейсов:

  • 8 (999) 123-45-67 -> +79991234567
  • +7 999 1234567 -> +79991234567
  • 9991234567 -> ValueError

Особенно это важно для парсеров, сериализации, SQL, генерации конфигов и работы с датами. Там одно пропущенное допущение меняет логику целиком.

7. Просите сначала план, потом код, если задача нетривиальна

Для простых функций это не нужно. Но если задача затрагивает несколько файлов, состояние приложения или миграции, полезно сначала получить короткий план:

Сначала перечисли шаги реализации и возможные риски. Не пиши код, пока не зафиксируешь архитектурное решение по кэшированию и обработке ошибок.

Это уменьшает количество дорогих переделок и помогает заметить неправильные допущения до генерации кода.

8. Используйте шаблон промпта

Практичный шаблон:

Контекст: проект на [язык/версия/фреймворк]. Ограничения: [зависимости, стиль, производительность, безопасность].
Задача: [что именно сделать].
Интерфейс: [сигнатуры, модели данных, файлы, которые можно менять].
Поведение: [правила, edge cases, ошибки].
Проверка: [тесты, примеры входа/выхода, критерии приемки].
Формат ответа: [только код / код + пояснение / diff / список изменений].

Этот шаблон полезен тем, что заставляет вас заполнить пробелы до того, как их заполнит модель.

9. Уточняйте итеративно, а не переписывайте с нуля

Если ответ почти верный, лучше дать адресную корректировку: не меняй API, убери pandas, перепиши на итеративный обход, добавь тест на пустой список. Итеративный режим экономит время и сохраняет полезный контекст.

Но если базовое допущение неверно — например, модель выбрала не ту схему данных или неправильно поняла бизнес-правило, — проще сбросить контекст и начать с более строгой постановки.

10. Отделяйте генерацию от принятия решения

Не просите модель одновременно придумать архитектуру, выбрать компромисс и написать финальный код, если цена ошибки высока. Сначала получите варианты, затем вручную выберите подход, и только после этого просите реализацию. Иначе вы рискуете принять неявное решение, которое модель приняла за вас без знания системы целиком.

Типичные ошибки

  • Слишком общий запрос. Чем шире задача, тем больше вымышленных допущений.
  • Нет версии стека. Ответ может опираться на устаревший или несовместимый API.
  • Нет запрета на лишние зависимости. Модель легко добавляет библиотеку ради удобства.
  • Нет формата ответа. Вместо готового фрагмента вы получите длинное объяснение или неполный шаблон.
  • Нет edge cases. Код работает только на счастливом пути.
  • Смешение нескольких задач. Архитектура, код и тесты в одном запросе часто дают поверхностный результат.
  • Слепое доверие. Даже аккуратный промпт не заменяет ревью, линтеры, типизацию и тесты.
  • Секреты в контексте. Нельзя вставлять в промпты токены, пароли, приватные ключи и реальные персональные данные.

Отдельная ошибка — просить «оптимальный» код без критерия оптимальности. Для модели это пустое слово. Уточняйте, что именно важно: скорость, память, читаемость, отсутствие зависимостей, совместимость с текущим интерфейсом или простота тестирования.

Как проверить результат

Проверка должна идти в четыре слоя.

  1. Сравнение с постановкой. Код решает ровно ту задачу, которая была сформулирована? Не изменился ли публичный интерфейс? Нет ли лишнего поведения?
  2. Статическая проверка. Линтеры, форматтеры, типизация, анализ импортов, проверка совместимости версий.
  3. Тесты. Нужны happy path, негативные сценарии и граничные случаи. Если код работает с БД или сетью, отдельно проверьте таймауты, пустые ответы и невалидные данные.
  4. Ручной технический просмотр. Ищите скрытые допущения: часовые пояса, локали, порядок сортировки, Unicode, SQL-инъекции, работу с None, гонки, обработку исключений.

Удобный чек-лист приемки:

  • Все требования из промпта покрыты явно.
  • Нет новых зависимостей без необходимости.
  • Ошибки обрабатываются предсказуемо.
  • Тесты воспроизводят критические кейсы.
  • Код соответствует стилю проекта.
  • Нет утечек секретов и небезопасных практик.

Если результат выглядит слишком хорошим при слабом промпте, это повод проверить его вдвойне. Генерация кода часто создает правдоподобный, но не полностью корректный ответ.

FAQ

Нужно ли писать промпт на английском?

Не обязательно. Для большинства практических задач русский подходит, если формулировки точные. Английский может помочь, когда вы используете названия API, сообщения об ошибках или терминологию экосистемы в оригинале.

Насколько длинным должен быть промпт?

Достаточно длинным, чтобы убрать неоднозначность, и достаточно коротким, чтобы не утонуть в нерелевантных деталях. Для небольшой функции обычно хватает 8–15 строк структурированного текста.

Стоит ли просить модель объяснять код?

Да, если вы проверяете решение или обучаете команду. Нет, если вам нужен только чистый патч и вы уже понимаете предметную область. В этом случае лучше требовать короткий список допущений и рисков, а не длинное объяснение.

Можно ли давать модели существующий файл целиком?

Можно, если это действительно нужно для контекста и если в файле нет секретов или чувствительных данных. Но часто полезнее дать только релевантные сигнатуры, типы и кусок кода вокруг точки изменения.

Как писать промпты для рефакторинга?

Фиксируйте инварианты: что нельзя менять, какие тесты уже должны проходить, какие метрики важны, какие участки кода трогать нельзя. Хороший запрос на рефакторинг почти всегда начинается с ограничений, а не с пожеланий.

Когда лучше не использовать генерацию кода?

Когда требования не определены, когда цена ошибки высока, когда нужно глубокое знание внутренней архитектуры, когда код затрагивает безопасность или соответствие нормам, и когда у вас нет способа быстро проверить результат автоматически.

Можно ли одним промптом получить сразу production-ready код?

Иногда для локальной утилиты или простой функции — да. Для серверной логики, интеграций, платежей, многопоточности и сложных миграций — обычно нет. Рассчитывайте на итерации, ревью и тестирование.

Читайте также

LINKS