Промпт-паттерны и structured outputs
Как формулировать инструкции, чтобы LLM отвечала предсказуемо: роль и критерии, few-shot примеры, декомпозиция, chain-of-thought — и как constrained decoding гарантирует валидный JSON на уровне сэмплирования, а не просьбы.
Интуиция
Контекст-инжиниринг отвечает на вопрос, что и в каком порядке модель видит на входе. Эта тема — про следующий слой: как формулировать сами инструкции и как получать выход, который можно парсить кодом, а не глазами. Промпт — это интерфейс к вероятностной машине: модель не «понимает задание», а продолжает текст самым правдоподобным способом. Хорошие паттерны работают не магией, а тем, что делают желаемое продолжение статистически самым вероятным.
Паттерны, которые работают (и почему)
- Роль и критерии вместо «сделай хорошо». «Хорошо» не имеет распределения, а «ты — редактор технической документации; критерии: без пассивного залога, абзацы до 4 строк, термины по глоссарию» — имеет. Явные критерии превращают вкусовщину в проверяемые условия.
- Few-shot примеры. Пара «вход → эталонный выход» задаёт формат и стиль мощнее любых слов: показ, а не рассказ. Модель — машина продолжения паттернов, и три примера — это уже паттерн. Обязательно включай краевые случаи: пустое поле, два значения вместо одного, мусорный вход — иначе на них модель импровизирует.
- Декомпозиция. Одна задача — один промпт. Цепочка «извлеки → нормализуй → проверь» из трёх маленьких промптов отлаживается и оценивается по частям, а промпт-монстр на десять задач падает целиком, и непонятно где.
- «Дай подумать» (chain-of-thought). Для сложных задач попроси рассуждение перед ответом — промежуточные шаги дают модели дополнительные вычисления. Важно: у reasoning-моделей это уже встроено обучением, и промпт «думай пошагово» им почти ничего не добавляет.
- Явный формат выхода. Опиши структуру ответа и покажи её в примерах — «верни JSON с полями name, date, amount» плюс образец. Всё, что не задано, модель заполнит по своему вкусу, и каждый раз по-разному.
Structured outputs: от просьбы к гарантии
Просьба «верни JSON» — это лестница надёжности. Уровень 1: просто попросить — модель иногда добавит «Конечно, вот ваш JSON:» или забудет кавычку. Уровень 2: few-shot с образцами — заметно лучше. Уровень 3: JSON mode — провайдер обучил модель и настроил инференс так, чтобы выход был валидным JSON. Уровень 4: schema-guided / constrained decoding — по JSON-схеме строится грамматика, и на каждом шаге генерации маскируются все токены, нарушающие её: невалидный токен физически не может быть выбран при сэмплировании. Валидность становится свойством декодера, а не воспитанности модели.
Гарантируется только форма: JSON распарсится, поля и типы совпадут со схемой. Правильность значений полей — по-прежнему на совести модели: в поле amount может лежать не та сумма, и никакая грамматика этого не поймает.
Поиграй с порядком включения: первый few-shot пример даёт огромный скачок за умеренные токены, три примера — ещё точнее, но втрое дороже, а constrained decoding добивает валидность до 100% почти бесплатно — но не двигает точность ни на пункт. Это и есть главный вывод про structured outputs.
Анти-паттерны
Противоречивые инструкции. «Отвечай кратко» в начале и «объясни развёрнуто» в середине — модель не выберет «правильную», она будет метаться между обеими. Отрицания. «Не упоминай конкурентов» подсаживает конкурентов в контекст — токены «конкуренты» уже там и повышают вероятность соответствующего продолжения; надёжнее позитивная формулировка: «говори только о нашем продукте». Мегапромпт на десять задач. Инструкции конкурируют за внимание, отладка превращается в гадание — декомпозируй. Примеры расходятся с инструкцией. Если инструкция требует одного формата, а few-shot показывает другой, модель поверит примерам: показ сильнее рассказа работает и против тебя.
Промпт — это код
Промпт в проде меняет поведение системы так же, как код, — и заслуживает той же дисциплины: храни в репозитории и версионируй, а не правь в админке; на каждое изменение прогоняй eval-сет (см. оценку LLM) — «стало лучше на моих трёх примерах» регулярно означает «сломалось на тридцати чужих»; фиксируй, для какой модели промпт писался: смена модели — это смена рантайма.
- «Как добиться валидного JSON от LLM?» — лестница: просьба в промпте → few-shot с образцами → JSON mode провайдера → constrained decoding по схеме; только последний уровень даёт гарантию, потому что маскирует невалидные токены при сэмплировании.
- «Почему few-shot сильнее словесной инструкции?» — модель продолжает паттерны: примеры задают формат в том же виде, в каком его нужно воспроизвести, без перевода «слова → поведение».
- «Чем плох гигантский промпт на много задач?» — инструкции конкурируют и противоречат друг другу, ошибку нельзя локализовать, eval неинформативен; лечится декомпозицией на цепочку промптов.
- «Гарантирует ли схема правильные данные?» — нет: она гарантирует форму (парсинг, типы полей), а правильность значений проверяется только оценкой на эталонном наборе.