1. Что такое follow‑up’ы в ChatGPT App и зачем они нужны
Начнём с определения на человеческом, а не маркетинговом языке. Follow‑up в контексте ChatGPT App — это программно инициированный следующий шаг в диалоге. Обычно это короткая подсказка, оформленная как кнопка: при нажатии она превращается в новое текстовое сообщение от пользователя в чате и запускает продолжение сценария общения человека и ИИ.
С точки зрения модели follow‑up — это просто ещё одно пользовательское сообщение. Никакой магии: когда пользователь кликает по вашей кнопке «Показать варианты дешевле», в историю чата попадает что‑то вроде «Покажи подарки дешевле». Модель видит это как обычную фразу пользователя, применяет system‑prompt и описания tools, решает, какой инструмент вызвать, и, возможно, снова рендерит ваш виджет уже с другими данными.
Почему это важно:
- Модель думает в терминах текста. Если вы по клику просто вызываете tool напрямую (без сообщения), вы обходите основной «мозг» системы и теряете часть контекста. Follow‑up сохраняет прогресс беседы в истории и помогает модели понимать, что именно происходит.
- Пользователь остаётся в привычной парадигме чата: он либо пишет сообщения, либо нажимает подсказки. Follow‑up’ы — это такие же «быстрые ответы» (quick replies), как в современных мессенджерах.
- Это главный мост между вашим UI и текстовой частью ChatGPT. Без follow‑up’ов виджет превращается в немой остров: пользователь пощёлкал и не совсем понимает, что делать дальше.
Можно смотреть на follow‑up’ы как на кнопку «Следующий вопрос к ИИ», только сформулированную вами. Дальше мы будем смотреть на follow‑up’ы не как на «ещё одну UI‑фичу», а как на основной способ выстраивать диалог вокруг виджета.
2. «Диалог вокруг виджета»: conversational sandwich
Чтобы было проще увидеть, как follow‑up’ы помогают строить «диалог вокруг виджета», полезно представить диалог как бутерброд из трёх слоёв:
- сверху текст ChatGPT до виджета (pre‑text),
- в середине ваш виджет (UI),
- снизу follow‑up’ы и последующие сообщения (post‑interaction).
Схематично:
sequenceDiagram
participant U as Пользователь
participant G as ChatGPT
participant W as Виджет
U->>G: "Подбери подарок сестре до 100$"
G->>G: Решает вызвать App
G->>U: Pre-text: "Сейчас открою GiftGenius и подберу идеи"
G->>W: Передаёт toolOutput для рендера
W-->>U: Карточки подарков + кнопки follow-up
U->>W: Клик по "Показать варианты дешевле"
W->>G: sendFollowUpMessage("Покажи подарки дешевле, до 50$")
G->>G: Новый прогон модели, вызов tools
G->>W: Новый toolOutput, обновлённый виджет
Pre‑text обычно полностью генерирует модель на основе system‑prompt и контекста: там она «объясняет», что сейчас произойдёт («Сейчас открою приложение, которое поможет выбрать подарок»). Управлять этим слоем мы будем позже, в модуле про инструкции.
Виджет — это ваш привычный React‑компонент: отрисовывает toolOutput, использует widgetState, даёт кнопки и выборы.
Post‑interaction слой — это то, чем мы занимаемся сегодня. Вы через follow‑up’ы и отправку сообщений задаёте понятный маршрут дальше: «Показать дороже», «Изменить бюджет», «Начать подбор заново», «Перейти к оформлению».
Если упростить, follow‑up — это управляющая реплика, которая замыкает цикл: UI → текст → новый UI.
3. Техническая модель: как клик по follow‑up превращается в новый tool‑call
Давайте внимательно посмотрим на цепочку событий «под капотом». Удобно думать об этом как о гибридном цикле взаимодействия: клик → API → текст в истории → новое решение модели → новый вызов инструмента → обновлённый UI.
Последовательность примерно такая:
- Пользователь нажимает кнопку в вашем виджете.
- Виджет вызывает API window.openai.sendFollowUpMessage или, в React‑слое, удобный хук‑обёртку useSendMessage (в примерах ниже мы будем использовать именно хук). Аргумент — обычная строка: текст, который как будто написал пользователь.
- ChatGPT добавляет это сообщение в историю как новый user_message.
- Модель выполняет новый проход: учитывает всю историю, включая предыдущее toolOutput, и решает, вызывать ли инструмент, какой именно и с какими аргументами.
- Ваш backend/MCP исполняет tool‑call и возвращает toolOutput.
- ChatGPT отображает новый ответ: возможно, снова виджет (с новыми данными), возможно, текст, возможно, комбинацию.
Важно, что вы почти никогда не хотите напрямую из виджета дергать тот же инструмент, что вызывала модель, в обход этого цикла. Иначе модель «теряет» шаг: в истории нет того запроса, который инициировал повторный вызов. Позже при длинных сценариях это аукается запутанным контекстом.
Можно свести это к короткой формуле:
User Click → useSendMessage("...") → ChatGPT (LLM) → Tool Call → New toolOutput → Widget rerender
4. Виды follow‑up’ов и кто их придумывает
Мы разобрались, что происходит «под капотом» после клика по follow‑up. Теперь посмотрим, какие вообще follow‑up’ы имеет смысл давать пользователю и какие роли они могут играть в сценарии.
В реальном App существует несколько «семейств» follow‑up’ов. Я делю их по разным осям: статические vs динамические, по роли (drill‑down, pivot, commit и т.д.).
Для практики удобнее табличка.
| Тип | Что делает | Пример текста/кнопки |
|---|---|---|
| Подсказывающий (Suggestive) | Помогает, если пользователь не знает, что спросить дальше | «Показать ещё идеи», «Сузить по интересам» |
| Уточняющий (Drill-down / Parametric) | Сужает предыдущий запрос по параметрам | «Дешевле», «Только цифровые подарки», «Только Nike» |
| Поворот (Pivot) | Меняет ветку сценария | «Начать подбор заново», «Показать подарки для ребёнка» |
| Навигационный (Navigation) | Переводит на другой шаг процесса | «Перейти к оформлению», «Вернуться к выбору» |
| Завершающий (Commit) | Подтверждает действие | «Заказать этот подарок», «Сохранить подбор» |
С точки зрения источника идей есть две большие группы.
Во‑первых, follow‑up’ы, которые предлагает само приложение. Это наши кнопки в виджете, завязанные на логику UI и данные: например «Показать похожие на [Название подарка]» или «Фильтровать только по интересу travel». Такие подсказки можно жёстко закодировать (static) или формировать динамически на основе toolOutput (dynamic).
Во‑вторых, «родные» подсказки ChatGPT — маленькие чипсы под сообщением, которые модель придумывает сама. Вы их напрямую не контролируете; относитесь к ним как к бесплатному бонусу, а не надёжному механизму. Ваше приложение всё равно должно работать даже без них.
В этой лекции нас больше интересует первая группа: кнопки и подсказки, которые вы рисуете в своём React‑компоненте и по клику отправляете sendFollowUpMessage.
5. Реализация follow‑up’ов в React: GiftGenius как пример
Продолжим наш условный App GiftGenius, который подбирает подарки. После вызова инструмента get_gift_ideas виджет получает toolOutput со списком подарков. В прошлых темах мы уже делали сетку карточек. Теперь добавим секцию follow‑up’ов.
Предположим, в SDK у нас есть хуки useWidgetProps и useSendMessage. Названия здесь условные, но концепция совпадает с тем, как это устроено в референсной реализации:
import { useWidgetProps, useSendMessage } from '@/openai-apps';
export const GiftSuggestions: React.FC = () => {
const { toolOutput } = useWidgetProps();
const sendMessage = useSendMessage();
const gifts = toolOutput?.data?.gifts ?? [];
if (gifts.length === 0) {
return <div>Подарков не найдено. Попробуйте изменить запрос.</div>;
}
const handleCheaper = () => {
sendMessage('Покажи подарки дешевле, до 50$');
};
const handleDigital = () => {
sendMessage('Покажи только цифровые подарки: сертификаты, подписки и т.п.');
};
return (
<div className="flex flex-col gap-4">
<div className="grid grid-cols-2 gap-2">
{gifts.map((gift: any) => (
<GiftCard key={gift.id} item={gift} />
))}
</div>
<div className="border-t pt-3 text-sm">
<div className="text-xs text-gray-500 mb-2">Что сделать дальше?</div>
<div className="flex flex-wrap gap-2">
<button
onClick={handleCheaper}
className="px-3 py-1 rounded-full bg-gray-100 hover:bg-gray-200"
>
Дешевле
</button>
<button
onClick={handleDigital}
className="px-3 py-1 rounded-full bg-gray-100 hover:bg-gray-200"
>
Только цифровые
</button>
</div>
</div>
</div>
);
};
Здесь важно несколько моментов.
Во‑первых, sendMessage получает строку — этот текст появится в чате, как будто его ввёл пользователь. Ваш App не должен имитировать «внутренний» системный протокол; просто формулируйте фразы так, чтобы модель их поняла.
Во‑вторых, секция follow‑up’ов визуально отделена (бордер сверху, мелкий текст «Что сделать дальше?»), чтобы пользователь понимал: это скорее продолжение диалога, чем элементы самой карточки.
В‑третьих, кнопок немного — две. UX‑рекомендации советуют держаться диапазона примерно от двух до четырёх вариантов: этого достаточно, чтобы помочь, но не перегрузить.
Динамический follow‑up на основе данных
Предположим, вы хотите позволить пользователю углубиться в конкретный подарок: «Показать похожие на вот этот». Тогда текст follow‑up должен упоминать нужный объект, и вы можете сгенерировать текст на лету.
const handleShowSimilar = (giftTitle: string) => {
sendMessage(
`Покажи похожие подарки на "${giftTitle}", можно в том же бюджете или чуть дороже`
);
};
И в карточке:
<button
onClick={() => handleShowSimilar(gift.title)}
className="mt-2 text-xs text-blue-600 underline"
>
Показать похожие
</button>
Так вы реализуете динамический follow‑up, завязанный на конкретные данные из toolOutput. Именно такие кнопки делают диалог «умным», а не просто набором универсальных «Далее / Назад».
6. Почему мы отправляем текст, а не напрямую вызываем tool
Типичная мысль фронтенд‑разработчика: «Раз у меня есть useCallTool, зачем мне отправлять текст? Я могу сразу дернуть get_gift_ideas с другими параметрами». Иногда это действительно нужно (об этом будет больше в модуле про вызов инструментов (tools) из виджета), но по умолчанию лучше идти через текстовый follow‑up. Причины довольно практичные.
Во‑первых, история чата остаётся целостной. Снаружи всё выглядит так, будто пользователь сам написал: «Покажи подарки дешевле». Через неделю он откроет историю и поймёт, что происходило. Если вы делали всё через прямые tool‑вызовы, между сообщениями пользователя будут внезапно появляться разные виджеты без видимых причин.
Во‑вторых, модель может принять дополнительные решения. Например, понять, что вместо повторного вызова того же инструмента лучше сначала уточнить бюджет: «Вы уверены, что хотите снизить бюджет до 5$? Может быть, оставим хотя бы 20$?» Такие гибкие сценарии становятся невозможными, если вы жёстко кодируете «клик → тот же tool с другими аргументами».
В‑третьих, модель может вызвать совсем другой инструмент. Допустим, пользователь нажал «Связаться с поддержкой», а ваш system‑prompt обучает модель, что в таком случае нужно вызвать create_support_ticket, а не get_gift_ideas. Follow‑up как текст даёт модели свободу переключиться на другой инструмент.
Поэтому практическое правило модуля 3: по клику в UI в большинстве случаев отправляем текстовый follow‑up, а не напрямую tool. Прямые tool‑вызовы из виджета оставляем для специфических случаев, когда нам точно не нужен новый пользовательский шаг в истории.
7. Связка follow‑up’ов и состояний: не рассинхронизировать UI и текст
Интересная проблема: UI «живёт» своей жизнью, а текст в чате — своей. Допустим, вы по клику меняете фильтр внутри виджета и одновременно отправляете follow‑up. Если вы обновили только UI, но не зафиксировали новое состояние через widgetState, при следующем рендере ChatGPT восстановит старый widgetState. В результате виджет снова покажет старые фильтры, хотя в истории чата уже есть шаг «про дешевле». Это довольно странный опыт.
Поэтому хороший паттерн: при клике по follow‑up одновременно:
- обновлять widgetState,
- отправлять follow‑up‑сообщение.
Пример:
import { useWidgetState, useSendMessage } from '@/openai-apps';
type GiftWidgetState = {
priceFilter?: 'any' | 'cheap' | 'premium';
};
export const GiftFollowups: React.FC = () => {
const [widgetState, setWidgetState] = useWidgetState<GiftWidgetState>();
const sendMessage = useSendMessage();
const handleCheaper = () => {
setWidgetState({ ...widgetState, priceFilter: 'cheap' });
sendMessage('Покажи подарки дешевле, примерно до 50$');
};
// ...
};
Теперь и ChatGPT, и ваш UI знают, что фильтр сменился. Если модель пересоберёт виджет позже, она увидит обновлённый widgetState и сможет, например, сформировать новый pre‑text вроде «Вот идеи в более бюджетном сегменте».
8. Проектирование хороших follow‑up’ов
Хороший follow‑up — это половина UX‑успеха. Он не просто «красивый», он экономит пользователю мыслительную энергию.
Есть несколько практических принципов, которые стоило бы повесить себе над монитором.
Во-первых, краткость. Follow‑up — не место для поэм. Одна короткая фраза, понятная без контекста UI, обычно идеальна: «Дешевле», «Только премиум», «Изменить получателя». Если нужно что‑то длиннее, подумайте, не должен ли это быть обычный текст GPT, а не кнопка.
Во‑вторых, ориентированность на действие. Формулируйте так, чтобы было ясно, что произойдёт. «Ещё идеи» — нормально. «Подробнее про пункт 2» — лучше заменить на «Расскажи подробнее про второй вариант» (так модель поймёт, о чём речь, даже без UI).
В‑третьих, продолжение сценария, а не его повтор. Вместо «Ещё раз подобрать подарок» лучше «Изменить бюджет» или «Поменять хобби получателя». Follow‑up должен сдвигать пользователя вперёд или в сторону, а не возвращать в исходную точку без необходимости.
В‑четвёртых, ограниченное количество. Две–четыре кнопки внизу виджета — почти всегда достаточно. Плашка из десяти вариантов превращается в экзамен по выбору судьбы: пользователь теряется и не нажимает ничего.
Наконец, учитывайте тональность. Если всё приложение общается дружелюбно, странно воткнуть кнопку «ПОДТВЕРДИТЬ ЗАКАЗ» капсом. Follow‑up — часть того же диалога, что и текст модели; стиль должен совпадать.
9. «Диалоги вокруг виджета» в целом: кто за что отвечает
Важно не воспринимать виджет как «главного героя», а ChatGPT — как «рамку вокруг него». Всё наоборот: модель продолжает вести диалог, а виджет — лишь один из способов показать и скорректировать данные.
Типичный сценарий для GiftGenius выглядит так:
- Пользователь: «Нужен подарок сестре‑айтишнице до 100$».
- Модель: текстовое вступление (pre‑text) — объясняет, что сейчас откроет GiftGenius и что он делает.
- Виджет: показывает подборку идей, даёт follow‑up’ы.
- Пользователь: либо сам пишет сообщение, либо жмёт кнопку follow‑up (например «Показать только цифровые подарки»).
- Модель: интерпретирует это как текст, вызывает нужный tool, при необходимости комментирует результат (post‑text) или снова показывает виджет.
- И так по кругу, пока задача не будет решена — вплоть до подтверждения выбора, заказа и т.д.
Follow‑up’ы тут — клей, который связывает каждый виток этого цикла. Без них пользователь после виджета оказывается в подвешенном состоянии: всё красиво, но «что дальше» — непонятно.
В более сложных сценариях (workflows, агенты) follow‑up’ы помогают моделировать многошаговые воронки: «Сначала выбери получателя», «Теперь уточни бюджет», «Теперь подтвердим выбор». Но в этом модуле нам важно просто увидеть: даже самый простой одношаговый App сильно выигрывает от пары продуманных подсказок.
10. Практика: что стоит сделать прямо сейчас
Хорошее упражнение на закрепление — доработать ваш текущий учебный виджет.
Если у вас уже есть список результатов (например, тех же подарков, отелей или документов), добавьте под ним маленький блок «Что дальше?» с двумя–тремя кнопками. Постарайтесь, чтобы эти кнопки соответствовали типам из таблицы выше: одна должна быть уточняющей (drill‑down, например, «Дешевле»), другая — поворотом (pivot, «Изменить получателя»), третья —, если нужно, навигационной (navigation, «Перейти к оформлению»).
Внутри обработчиков кликов вызовите useSendMessage с осмысленным текстом на естественном языке, не забудьте при необходимости обновить widgetState. Затем перезапустите сценарий в ChatGPT и посмотрите, как выглядит диалог: насколько стало понятнее, что делать после виджета.
Попробуйте также намеренно сделать плохие follow‑up’ы: длинные, расплывчатые, с десятком вариантов — и сравните ощущения. Это быстрый способ почувствовать разницу на себе.
11. Типичные ошибки при работе с follow‑up’ами
Ошибка №1: «Молчаливый» виджет.
Разработчик делает отличный UI, но вообще не даёт follow‑up’ов. Пользователь видит карточки, думает «ну прикольно», а дальше должен сам догадаться, что можно, например, попросить: «Покажи дешевле» или «Поменяй получателя». Большинство людей просто не догадываются и уходят. Минимум одна–две подсказки «что дальше» под виджетом решают эту проблему.
Ошибка №2: Слишком много кнопок.
Обратная крайность — заспамить пользователя десятком вариантов: «Изменить бюджет», «Изменить интересы», «Сменить валюту», «Сохранить подбор», «Поделиться с другом», «Показать похожие», «Спросить у поддержки» и так далее. Получается психологический «шведский стол», на котором выбирать тяжело. Лучше начать с двух–трёх самых частых действий, а остальное оставить модели и обычному тексту.
Ошибка №3: Логика диалога только во фронтенде.
Иногда пытаются «оптимизировать» и вместо отправки follow‑up через useSendMessage (или низкоуровневый sendFollowUpMessage) напрямую дергают тот же tool из виджета, обновляя UI. В истории чата при этом нет ни слова о том, что произошло. Через пару шагов модель начинает путаться, а вы — вместе с ней. Правильный путь: держать логику диалога в уровне текста и инструментов, а виджет — как тонкий UI‑слой.
Ошибка №4: Неочевидные или двусмысленные формулировки.
Кнопка «Ещё» без контекста может означать всё, что угодно: ещё подарков, ещё текста, ещё денег? Точно так же формулировки типа «Пересчитать» или «Пересобрать» неясны и модели, и пользователю. Лучшие follow‑up’ы конкретны: «Показать больше вариантов в этом бюджете», «Показать только цифровые подарки».
Ошибка №5: Несинхронизированный UI и текст.
Классика: по клику по «Дешевле» вы обновили UI‑фильтр, но не отправили follow‑up в чат или не обновили widgetState. В результате в истории нет шагов про изменение бюджета, а при следующем рендере виджета фильтр «откатился» назад. Появляется ощущение «ломаного» интерфейса. Используйте комбинацию setWidgetState + sendMessage, чтобы текст и UI шагали в ногу.
Ошибка №6: Попытка контролировать «нативные» подсказки ChatGPT.
Иногда разработчики рассчитывают, что ChatGPT сам сгенерирует нужные follow‑up‑чипсы под сообщением, и не добавляют свои. Но эти подсказки не гарантированы и не управляются приложением. Относитесь к ним как к приятному бонусу, но всегда давайте свои собственные, критически важные follow‑up‑кнопки в виджете.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ