JavaRush /Курси /ChatGPT Apps /Взаємодія з ChatGPT: follow‑up-и та «діалоги навколо відж...

Взаємодія з ChatGPT: follow‑up-и та «діалоги навколо віджета»

ChatGPT Apps
Рівень 3 , Лекція 3
Відкрита

1. Що таке follow‑up-и в ChatGPT App і навіщо вони потрібні

Почнімо з визначення — людською, а не маркетинговою мовою. Follow‑up у контексті ChatGPT App — це програмно ініційований наступний крок у діалозі. Зазвичай це коротка підказка у вигляді кнопки: після натискання вона перетворюється на нове текстове повідомлення від користувача в чаті й запускає продовження сценарію спілкування людини з ШІ.

З погляду моделі follow‑up — це просто ще одне повідомлення користувача. Жодної магії: коли користувач натискає вашу кнопку «Показати варіанти дешевше», в історію чату потрапляє щось на кшталт «Покажи подарунки дешевше». Модель сприймає це як звичайну фразу користувача, застосовує system‑prompt і описи інструментів (tools), вирішує, який інструмент викликати, і, можливо, знову показує ваш віджет — уже з іншими даними.

Чому це важливо:

  • Модель мислить у термінах тексту. Якщо після натискання ви просто викликаєте інструмент напряму (без повідомлення), то обходите основний «мозок» системи й втрачаєте частину контексту. 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: Новий прогін моделі, виклик інструментів
    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.

Послідовність приблизно така:

  1. Користувач натискає кнопку у вашому віджеті.
  2. Віджет викликає API window.openai.sendFollowUpMessage або, на рівні React, зручний хук-обгортку useSendMessage (у прикладах нижче ми використовуватимемо саме цей хук). Аргумент — звичайний рядок: текст, який ніби написав користувач.
  3. ChatGPT додає це повідомлення в історію як новий user_message.
  4. Модель виконує новий прохід: враховує всю історію, включно з попереднім toolOutput, і вирішує, чи викликати інструмент, який саме та з якими аргументами.
  5. Ваш бекенд або MCP виконує tool‑call і повертає toolOutput.
  6. ChatGPT показує нову відповідь: можливо, знову віджет (із новими даними), можливо, текст, а можливо, комбінацію.

Важливо: майже ніколи не варто безпосередньо з віджета викликати той самий інструмент, який викликала модель, — в обхід цього циклу. Інакше модель «втрачає» крок: в історії немає запиту, який ініціював повторний виклик. У довгих сценаріях згодом це обертається заплутаним контекстом.

Можна звести це до короткої формули:

User Click → useSendMessage("...") → ChatGPT (LLM) → Tool Call → New toolOutput → Widget rerender

4. Види follow‑up-ів і хто їх вигадує

Ми розібралися, що відбувається «за лаштунками» після натискання follow‑up. Тепер подивімося, які follow‑up-и взагалі має сенс показувати користувачеві та які ролі вони можуть відігравати в сценарії.

У реальному застосунку є кілька «сімейств» follow‑up-ів. Я поділяю їх за різними вимірами: статичні й динамічні, а також за роллю (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 як приклад

Продовжимо наш умовний застосунок 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 отримує рядок — цей текст зʼявиться в чаті так, ніби його ввів користувач. Ваш застосунок не має імітувати «внутрішній» системний протокол: просто формулюйте фрази так, щоб модель їх правильно зрозуміла.

По‑друге, секція 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. Причини доволі практичні.

По‑перше, історія чату залишається цілісною. Ззовні все виглядає так, ніби користувач сам написав: «Покажи подарунки дешевше». За тиждень він відкриє історію й легко зрозуміє, що відбувалося. Якщо ж ви робили все через прямі виклики інструментів, між повідомленнями користувача раптово зʼявлятимуться різні віджети — без видимих причин.

По‑друге, модель може ухвалювати додаткові рішення. Наприклад, зрозуміти, що замість повторного виклику того ж інструмента краще спершу уточнити бюджет: «Ви впевнені, що хочете знизити бюджет до 5 $? Можливо, залишимо хоча б 20 $?» Такі гнучкі сценарії стають неможливими, якщо ви жорстко кодуєте «натискання → той самий tool з іншими аргументами».

По‑третє, модель може викликати зовсім інший інструмент. Припустімо, користувач натиснув «Звʼязатися з підтримкою», а ваш system‑prompt «вчить» модель, що в такому разі слід викликати create_support_ticket, а не get_gift_ideas. Follow‑up як текст дає моделі свободу перемкнутися на інший інструмент.

Тому практичне правило модуля 3: після дії в UI у більшості випадків надсилаємо текстовий follow‑up, а не викликаємо інструмент напряму. Прямі виклики інструментів із віджета залишаємо для специфічних ситуацій, коли нам точно не потрібен новий користувацький крок в історії.

7. Звʼязка follow‑up-ів і станів: не розсинхронізувати UI і текст

Є цікава проблема: UI «живе» своїм життям, а текст у чаті — своїм. Припустімо, після натискання ви змінюєте фільтр у віджеті й одночасно надсилаєте follow‑up. Якщо ви оновили лише UI, але не зафіксували новий стан через widgetState, під час наступного рендера ChatGPT відновить старий widgetState. У результаті віджет знову покаже старі фільтри, хоча в історії чату вже є крок «про дешевше». Досвід виходить доволі дивний.

Тому хороший підхід такий: після натискання follow‑up одночасно:

  1. оновлювати widgetState,
  2. надсилати 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 виглядає так:

  1. Користувач: «Потрібен подарунок сестрі‑айтішниці до 100 $».
  2. Модель: вступне повідомлення (pre‑text) — пояснює, що зараз відкриє GiftGenius і що він робить.
  3. Віджет: показує добірку ідей, дає follow‑up-и.
  4. Користувач: або сам пише повідомлення, або тисне кнопку follow‑up (наприклад, «Показати лише цифрові подарунки»).
  5. Модель: інтерпретує це як текст, викликає потрібний tool і, за потреби, коментує результат (post‑text) або знову показує віджет.
  6. І так по колу, доки завдання не буде розвʼязано — аж до підтвердження вибору, замовлення тощо.

Follow‑up-и тут — клей, що поєднує кожен виток цього циклу. Без них користувач після віджета опиняється у підвішеному стані: усе красиво, але «що далі» — незрозуміло.

У складніших сценаріях (workflows, агенти) follow‑up-и допомагають моделювати багатокрокові воронки: «Спочатку обери отримувача», «Тепер уточни бюджет», «Тепер підтвердимо вибір». Але в цьому модулі нам важливо просто побачити: навіть найпростіший однокроковий застосунок дуже виграє від пари продуманих підказок.

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-кнопки у віджеті.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ