ИИ для инженера: основы Промптинг: RCTF и STAR
0%

Промптинг: RCTF и STAR

Промптинг: RCTF и STAR

Фрагмент лекции: «Все что нужно знать про ИИ айтишнику», 43:32 — отсюда взято содержание этой главы.

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

Промпт — это ваш запрос к модели. Не заклинание и не секретная команда, а обычный текст, который дописывается к остальному контексту и превращается в начало последовательности токенов, продолжение которой модель и генерирует. Отсюда всё остальное: чем меньше в этом начале неоднозначности, тем уже множество правдоподобных продолжений и тем предсказуемее ответ. Центральная мысль главы формулируется одним предложением, и она не про технологию:

Шаблон запроса работает и с человеком, и с моделью, потому что снимает неоднозначность. Собеседник — живой или машинный — не читает ваши мысли; он достраивает недосказанное из своего опыта. Шаблон сокращает то, что приходится достраивать.

Из чего на самом деле состоит вход модели

Когда вы нажимаете «отправить», модель получает не ваше сообщение. Она получает склейку нескольких слоёв, и ваш текст — только последний из них.

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

Второе: системный слой сильнее пользовательского. Это не догадка, а спроектированное свойство. Современные модели обучают следовать иерархии инструкций: системные указания имеют приоритет над сообщениями пользователя, а сообщения пользователя — над содержимым документов и результатов инструментов. Иерархия описана и в публичных спецификациях поведения (OpenAI Model Spec), и в исследовательских работах (The Instruction Hierarchy). Практический смысл простой: правило, которое должно соблюдаться всегда, кладите в системный промпт, а не повторяйте в каждом сообщении.

Третье, и самое важное: приоритет — это не механизм безопасности. Иерархия инструкций — статистическое предпочтение, выученное на обучающих данных, а не проверка прав доступа, как в операционной системе. Достаточно правдоподобный текст в пользовательском слое или в подгруженном документе способен перебить системные указания. Как это эксплуатируется и что с этим делают в продакшне — отдельная большая тема: Безопасность и инъекции.

Отдельно про доверие. Системный промпт коммерческого продукта — закрытый код. Вы не видите его содержимого, не знаете, меняется ли он между запросами, и не можете проверить, как он учитывается в биллинге. Это не обвинение в чей-либо адрес, а соображение, из которого следует практический вывод: для задач, где важна воспроизводимость, работайте через API, где вход собираете вы сами. Сколько всё это стоит в токенах и почему длинный диалог дорожает — в главе про токены и контекст.

Заход через людей: почему шаблон вообще помогает

Автор лекции подходит к промптингу неожиданно — через человеческую коммуникацию. Ход сильный, потому что показывает: дело не в модели. Представьте, что вы пишете коллеге просьбу о помощи. Вариант первый, как пишут обычно:

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

Вариант второй, тот же запрос по шаблону STAR — Situation, Task, Action, Result:

Ситуация: готовлю полугодовой отчёт по клиентам, сдать нужно в пятницу.
Задача: свести шесть месячных выгрузок из CRM в один набор данных.
Действия: выгрузил файлы за январь–июнь, сверил по идентификатору клиента,
нашёл 340 записей, которые есть в майской выгрузке и отсутствуют в июньской.
Результат: не понимаю, это удаление в CRM или ошибка самой выгрузки.
Прошу: подскажи, где смотреть журнал изменений. Нужно минут пятнадцать.

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

Три круга уточнений против одного ответа. Умножьте на десяток таких переписок в день — получите разницу в рабочем дне, а не в стиле письма. Где STAR применим в обычной работе:

Ситуация Что даёт шаблон
Просьба о помощи в чате Коллега отвечает с первого раза, а не после нескольких кругов уточнений
Статус-отчёт по задаче Читателю видно, что сделано и что заблокировано, без чтения всего текста
Ответ на вопрос о прошлом опыте Рассказ не растекается: понятно, в чём была задача и чем всё кончилось
Описание бага для разработчика «Действия» и «Результат» напрямую превращаются в шаги воспроизведения

Честная оговорка, которую делает и автор лекции: шаблон не бесплатен и требует общей конвенции. Человеку, который STAR не знает, структурированное сообщение может показаться сухим и формальным; ему привычнее разговорный текст. Выигрыш появляется, когда обе стороны привыкли к форме — тогда сообщение читается как заполненная анкета. С моделью этой проблемы нет, и это ключевой переход главы. Модель «знакома» со всеми распространёнными шаблонами, потому что они многократно встречались в обучающих данных. Структурированный запрос попадает в область текстов, где за разметкой «Задача:» обычно следует конкретное решение, а не разговор вокруг. Вы не столько объясняете модели задачу, сколько помещаете её в ту часть распределения, где живут хорошие ответы на такие задачи. Модель ответит и на неряшливый запрос — просто менее надёжно.

RCTF: четыре слота промпта

Для работы с моделью удобнее другой шаблон — RCTF: Role, Context, Task, Format. Роль, контекст, задача, формат ответа.

Role — роль

Кем модель должна себя считать: «аналитик службы поддержки», «технический редактор», «ревьюер кода на Python». Роль не наделяет модель знаниями, которых нет в весах, — на этот счёт стоит быть трезвым. Она делает другое: сдвигает распределение. Текст от лица аналитика поддержки статистически отличается лексикой, уровнем детализации и структурой от текста без роли — и модель начинает генерировать именно такой текст. Отсюда правило: роль должна быть функциональной, а не рекламной. «Ты — аналитик службы поддержки SaaS-сервиса» работает. «Ты — лучший в мире эксперт с двадцатилетним опытом» не работает: корпуса текстов «лучших в мире экспертов» в обучающих данных нет, зато есть корпус рекламных текстов — и стиль ответа может съехать туда.

Context — контекст

Факты, необходимые для решения и отсутствующие у модели: сами данные, домен, ограничения, принятые в команде соглашения, что уже пробовали и почему это не сработало. Контекст — единственный слот, где вы даёте модели то, чего она объективно знать не может: содержимое ваших таблиц, названия ваших категорий, требования вашего заказчика. Здесь же самая частая ошибка новичка — контекст «на всякий случай»: лишние абзацы не помогают, а разбавляют сигнал и стоят токенов на каждом запросе.

Task — задача

Что именно сделать. Три требования: одно действие, повелительное наклонение, измеримый признак готовности. «Проанализируй ситуацию с обращениями» — не задача, потому что непонятно, когда она выполнена. «Отнеси каждое обращение ровно к одной категории из списка и оцени срочность» — задача: результат либо есть для каждой строки, либо нет. Если действий несколько и они разнородные — это, скорее всего, не один промпт, а два: промпт, который делает пять дел сразу, невозможно отлаживать, потому что правка ради одного пункта ломает другой.

Format — формат ответа

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

Формат ответа — это контракт между моделью и программой. Как только вы собираетесь распарсить ответ, любое «модель добавила вежливое вступление перед JSON» превращается в исключение в проде. Промптом формат просят; гарантируют его другими средствами — схемами, режимом структурированного вывода, валидацией с повторной попыткой. Как это делается по-инженерному, разобрано в Структурированном выводе; здесь достаточно запомнить, что просьба и гарантия — разные вещи.

Tone — необязательный пятый слот

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

Собранный промпт целиком

Нейтральный рабочий пример: разобрать список обращений в поддержку и вернуть машиночитаемый результат.

Роль: ты — аналитик службы поддержки SaaS-сервиса для выставления счетов.

Контекст: ниже 20 обращений из чата поддержки за сутки. Пользователи продукта —
бухгалтеры малого бизнеса. Категории заведены заранее и расширять их нельзя:
billing, bug, feature_request, account, other. Тексты сырые: часть написана
транслитом, часть содержит номера договоров.

Задача: отнеси каждое обращение ровно к одной категории, оцени срочность по шкале
low / medium / high и выпиши одну цитату из текста обращения, на которой основано
решение. Если данных для уверенной классификации не хватает, ставь категорию other
и признак needs_human = true.

Формат: верни JSON-массив. Каждый элемент — объект с полями: id (целое),
category (строка из списка выше), severity (строка из шкалы), quote (строка из
текста обращения), needs_human (логическое). Никакого текста вне JSON.

Тон: нейтральный деловой.

Обращения:
1. ...

Обратите внимание на две детали, которые отличают рабочий промпт от учебного. Во-первых, явный путь отказа: «не хватает данных → other + needs_human». Без него модель будет угадывать категорию и делать это уверенно, а неуверенность превратится в невидимую ошибку вместо наблюдаемого сигнала. Во-вторых, требование цитаты: оно заставляет привязать решение к тексту обращения и заодно даёт вам дешёвый способ проверить разметку глазами.

Позитивные формулировки вместо запретов

В лекции это звучит категорично: «негативные промпты не работают, пишите позитивные». Проверяемая версия утверждения мягче, но полезнее:

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

Причина не мистическая. Запрет очерчивает границу, но ничего не говорит о том, что делать внутри неё. «Не пиши длинно» оставляет открытыми оба главных вопроса: что считается длинным и как тогда писать. Пространство желаемого поведения остаётся неопределённым, и модель заполняет его по своему усмотрению. Утвердительная инструкция «ответь одним абзацем не длиннее 60 слов» задаёт цель, а не только ограничение.

Запрет Утвердительная переформулировка
«Не пиши длинно» «Ответь одним абзацем, максимум 60 слов»
«Не используй сложные термины» «Объясняй так, чтобы понял человек без технического образования»
«Не выдумывай факты» «Используй только сведения из блока Контекст; если нужного там нет — напиши “недостаточно данных”»
«Не добавляй пояснений» «Верни ровно JSON-массив; первый символ ответа — [, последний — ]»
«Не отвечай грубо» «Тон нейтральный деловой»

Важная оговорка, без которой правило превращается в суеверие: это не означает полного отказа от ограничений. Запрет прекрасно работает как дополнение к описанию желаемого — особенно для узких повторяющихся ошибок, которые вы уже наблюдали. Строка «никакого текста вне JSON» в примере выше — именно такой случай: сначала описано, что вернуть, потом точечно закрыта типовая ошибка. Плохо не «запрет», плохо «запрет вместо описания».

Мета-промптинг: попросить модель написать промпт

Приём звучит парадоксально, но работает: вместо того чтобы писать промпт самому, вы описываете задачу в двух предложениях и просите модель составить подробный промпт по RCTF, а потом выполняете его — возможно, уже на другой модели. Что здесь правда:

  • Это экономит время. Черновик из пяти абзацев появляется за секунды.
  • Это дешевле. Черновик прекрасно пишет младшая модель, а исполнять готовый промпт можно старшей. Разбиение «дешёвая модель сочиняет — дорогая исполняет» — нормальная практика.
  • Это хороший способ вспомнить забытые слоты. Модель почти всегда добавит формат ответа и критерии, о которых вы не подумали.

Что здесь неправда — и это существенно:

Модель не знает вашу задачу. Она угадывает её по короткому описанию.

Она не знает ваших категорий, вашего критерия приёмки, ваших ограничений и того, какая ошибка для вас дорогая, а какая безобидная. Всё это она заполнит правдоподобными умолчаниями — и промпт получится складным, общим и хуже написанного человеком, который задачу понимает. Поэтому честная формулировка: мета-промптинг — хороший генератор черновика и плохой источник финального промпта. Черновик обязательно вычитывает тот, кто отвечает за результат.

Комбинирование: RCTF не конкурирует с другими техниками

RCTF задаёт каркас запроса. Внутрь этого каркаса свободно вставляются техники, которым посвящена следующая глава: примеры правильных ответов (few-shot) кладутся в контекст, требование рассуждать по шагам — в задачу или формат.

Ценность диаграммы — в её левой части: каждый типовой дефект ответа чинится своим слотом. Не парсится — правьте Format. Придумала факты — не хватило Context. Сделала не то — виновата Task. Это превращает промптинг из перебора формулировок в адресную отладку. Сами техники, которые вставляются в этот каркас, — zero-shot, few-shot, цепочка рассуждений и проверки — разобраны в следующей главе.

Практика: было и стало

Плохой запрос Хороший запрос Что именно изменилось
«Напиши про наш продукт» «Роль: технический редактор. Контекст: продукт — сервис выставления счетов для малого бизнеса, аудитория — бухгалтеры. Задача: напиши описание для главной страницы. Формат: три абзаца по 40–50 слов, без списков» Появились аудитория, объём и структура — сняты три источника неоднозначности
«Проанализируй эти логи» «Задача: найди в логе строки уровня ERROR, сгруппируй по тексту исключения, для каждой группы дай количество и первую строку. Формат: таблица с колонками exception, count, first_seen» Определено, что считать результатом и как его читать машиной
«Не пиши слишком формально» «Тон: разговорный, обращение на “вы”, без канцелярита» Запрет заменён описанием желаемого
«Помоги с SQL» «Контекст: PostgreSQL 16, таблицы orders(id, user_id, created_at, total) и users(id, country). Задача: напиши запрос, считающий выручку по странам за последние 30 дней. Формат: только SQL в блоке кода, без пояснений» Дана схема — модели больше не нужно выдумывать имена колонок
«Сделай хорошо и не ошибись» «Задача: … Если в контексте нет данных для ответа, верни строку “недостаточно данных” вместо предположения» Появился явный путь отказа вместо пожелания «не ошибаться»

Сборка промпта из шаблона в коде

Как только промпт перестаёт быть разовым, его пора собирать из шаблона, а не писать заново. Минимальная реализация без внешних зависимостей:

"""Сборка RCTF-промпта из шаблона. Обычные f-строки, без внешних зависимостей."""

# Порядок слотов фиксирован: роль и контекст идут раньше задачи, потому что
# к моменту чтения задачи модель должна уже знать, кем она является и с
# какими данными работает. Тон опционален — он нужен, если ответ читает человек.
SLOTS = ("role", "context", "task", "format", "tone")
TITLES = {"role": "Роль", "context": "Контекст", "task": "Задача",
          "format": "Формат ответа", "tone": "Тон"}
REQUIRED = ("role", "context", "task", "format")


def build_prompt(role, context, task, output_format, tone=None, examples=None):
    """Собирает промпт по RCTF.

    examples — пары «вход, эталонный ответ» (few-shot, следующая глава):
    они дополняют контекст, а не заменяют его.
    """
    values = {"role": role, "context": context, "task": task,
              "format": output_format, "tone": tone or ""}
    values = {name: text.strip() for name, text in values.items()}

    # Пустой обязательный слот — ошибка шаблона, а не «модель сама догадается».
    missing = [name for name in REQUIRED if not values[name]]
    if missing:
        raise ValueError("Не заполнены слоты: " + ", ".join(missing))

    blocks = [f"{TITLES[name]}: {values[name]}" for name in SLOTS if values[name]]

    if examples:
        # Примеры размечаем единообразно: разнобой в оформлении модель тоже
        # считает частью образца и начинает его воспроизводить в ответе.
        blocks.append("Примеры:\n" + "\n\n".join(
            f"Пример {i}.\nВход:\n{src.strip()}\nОжидаемый ответ:\n{dst.strip()}"
            for i, (src, dst) in enumerate(examples, start=1)))

    # Пустая строка между блоками: границы слотов модель различает по разметке.
    return "\n\n".join(blocks)


print(build_prompt(
    role="ты — аналитик службы поддержки SaaS-сервиса для выставления счетов",
    context="категории заведены заранее: billing, bug, feature_request, account, other",
    task=("отнеси каждое обращение ровно к одной категории и оцени срочность "
          "low / medium / high; если данных не хватает — other и needs_human = true"),
    output_format="JSON-массив объектов с полями id, category, severity, needs_human"))

Сборка линейна: O(n) по времени и памяти относительно длины итогового текста, скрытых затрат здесь нет. Реальная цена другая — длина промпта умножается на количество запросов и превращается в счёт за входные токены. И один архитектурный момент, который окупается быстро: храните слоты отдельно, а не склеенную строку. Тогда можно менять формат, не трогая роль, вести разные варианты задачи при общем контексте и видеть в истории изменений, какой именно слот вы поправили перед тем, как качество упало.

Чек-лист самопроверки промпта

  • Роль названа функционально — профессия и предметная область, а не превосходные степени.
  • Контекст содержит всё, чего модель знать не может: данные, названия, ограничения, принятые соглашения. И не содержит абзацев «на всякий случай».
  • Задача — одно действие в повелительном наклонении, с признаком, по которому видно, выполнена она или нет.
  • Формат описан настолько, чтобы ответ можно было проверить механически: поля, типы, длина, что запрещено вокруг результата.
  • Есть явный путь отказа: что делать, если данных для ответа не хватает.
  • Ограничения сформулированы утвердительно, а точечные запреты стоят после описания желаемого, а не вместо него.
  • Ничего лишнего: каждый абзац вы можете объяснить — зачем он здесь и что сломается, если его убрать; и сам промпт лежит в файле или шаблоне, а не набран заново в окне чата.

Куда идти за продакшн-деталями

Эта глава — вводная: она даёт каркас запроса и словарь. Как только промпт становится частью работающего продукта, появляется второй слой вопросов: как хранить и версионировать промпты, как прогонять регрессионный набор перед каждым изменением, как ловить деградацию после обновления модели у провайдера, как разложить один длинный промпт на композицию коротких. Всё это разобрано в статье Основы промптинга — читайте её, когда дойдёте до практики, здесь мы это сознательно не дублируем.

Источники

Мини-итог

  • Промпт — обычный текст, который дописывается к контексту; чем меньше в нём неоднозначности, тем уже множество правдоподобных продолжений и предсказуемее ответ.
  • Вход модели состоит из слоёв: системный промпт провайдера, ваш системный промпт, история диалога, текущий запрос. В чат-интерфейсе первый слой вам не подконтролен — через API его нет.
  • Системные инструкции имеют приоритет над пользовательскими, и это документированное поведение, но приоритет — статистическое предпочтение, а не защита: инъекции остаются отдельной инженерной проблемой.
  • STAR (Situation, Task, Action, Result) экономит время читателя, потому что превращает текст в заполненную форму; выигрыш появляется, когда форма знакома обеим сторонам, а модель знакома со всеми распространёнными формами по обучающим данным.
  • RCTF (Role, Context, Task, Format) — тот же приём для модели: роль сдвигает стиль, контекст даёт недостающие факты, задача задаёт критерий готовности, формат превращает ответ в контракт с вашим кодом.
  • Конкретная утвердительная инструкция надёжнее запрета, потому что описывает желаемое поведение, а не только границу; точечные запреты остаются полезными как дополнение, а не как замена.
  • Мета-промптинг экономит время и деньги на черновике, но модель угадывает вашу задачу, а не знает её, — финальный промпт вычитывает человек, который за результат отвечает.

Что дальше

RCTF даёт каркас, но не отвечает на вопрос, что делать, когда задача требует рассуждения: посчитать, сравнить варианты, перепроверить собственный вывод. Для этого существует набор техник, каждая из которых дороже предыдущей по токенам и времени, — от простого запроса без примеров до перебора альтернативных цепочек рассуждения. Следующая глава разбирает пять таких техник и, что важнее, критерий выбора между ними.

Продвинутый промптинг: ZS, FS, CoT, CoV, ToT

Нашли неточность? Выделите фрагмент текста — рядом появится жучок.

Нужен разбор именно вашей ситуации?

Статья описывает общий случай. Если у вас частный — можно разобрать его отдельно, платно. А если не хватает целого материала, предложите тему: её оплачивают вскладчину, и она выходит открытой для всех.

Доска запросов