1. Контекст: що означає «голосовий режим» для ChatGPT App
Насамперед важливо розуміти: у межах ChatGPT Apps SDK ви не пишете власний аудіоклієнт, не керуєте мікрофоном і не стримите аудіо самостійно. За це відповідає клієнт ChatGPT (веб або мобільний застосунок).
Припустімо, що ви вже маєте базове уявлення про віджет, callTool і GiftGenius із попередніх модулів. Тут ми дивимося на ті самі елементи, але крізь призму голосового режиму.
З вашого погляду як розробника App усе виглядає так:
- Користувач говорить у мікрофон. Клієнт ChatGPT розпізнає мовлення й надсилає до моделі вже текст.
- У стрічці повідомлень ви «бачите» те саме, що й тоді, коли користувач друкує. Просто повідомлення надходять швидше й звучать розмовніше.
- Модель відповідає текстом, а клієнт озвучує відповідь.
- Паралельно модель може викликати ваші інструменти (callTool), змінювати displayMode віджета, оновлювати widgetState і пропонувати підказки для продовження — так само, як у текстовому режимі.
Ключова відмінність у тому, що користувач може майже не дивитися на екран або лише час від часу кидати на нього погляд. Тобто UI перестає бути основним каналом взаємодії й перетворюється на доповнення до голосу, а не навпаки.
Звідси випливають два наслідки:
- Усе, що справді важливо, має бути зрозуміло «на слух» — через репліки GPT.
- Віджет має бути зручним для побіжного погляду: з першої ж миті видно статус і ключові варіанти, без читання дрібного тексту.
Для нашого GiftGenius це одразу підказка: сценарій «їду в авто, підбери подарунок мамі» — це не просто текстовий чат. Це мультимодальний діалог, у якому голос веде, а UI підстраховує.
2. Чим голосовий сценарій відрізняється від текстового
Щоб не потрапити в пастку «це все те саме, просто користувач говорить голосом», корисно порівняти текстовий і голосовий режими за кількома вимірами.
| Аспект | Текстовий режим | Голосовий режим |
|---|---|---|
| Увага користувача | Дивиться на екран, читає, прокручує | Може взагалі не дивитися (hands‑free) |
| Форма запитів | Більш структурована, людина редагує | Розмовна, уривки фраз, «угу», «давай ще» |
| Толерантність до пауз | Нормально 1–2 с тиші | Тривала тиша відчувається болісно |
| Роль UI | Основний носій деталей | Допоміжний: «табло» з короткою візуальною опорою |
| Помилки введення | Описки, але текст видно | Нерозбірливе мовлення, шум, «хибні так/ні» |
Звідси — кілька важливих висновків.
- Не можна покладатися на те, що користувач «прочитає в картці». Критичні речі треба проговорювати: що ви зрозуміли, що збираєтеся зробити, який результат отримали.
- UI має витримувати сценарій «глянув на 1 с». Статус, прогрес, головний вибір — усе має бути перед очима великим шрифтом. Деталі — другорядні.
- Паузи потрібно заповнювати. Поки ваш MCP‑сервер обробляє складний запит, модель має озвучити, що відбувається, а віджет — показати прогрес. Так не виникатиме відчуття «завислого» асистента.
Про голосовий режим можна думати як про аудіокнигу з ілюстраціями: у вас є голосовий оповідач (GPT) і є «картинки» (віджет). Їх потрібно синхронізувати так, щоб вони доповнювали одне одного, а не дублювали й не конфліктували.
3. Роль віджета в голосовому режимі: від «панелі керування» до «табло»
У текстовому сценарії віджет часто працює як повноцінний інтерфейс: форми, таблиці, каруселі з фільтрами, кнопки дій. У голосовому режимі його роль змінюється. Рекомендації щодо мультимодальних інтерфейсів і VUI підказують: у голосових сценаріях UI стає радше інформаційним табло (glanceable UI). Воно потрібне для швидких перевірок і підтверджень, а не для «щільної» роботи очима.
Для GiftGenius це означає таке.
Коли користувач рухається за голосовим помічником, на екрані в inline‑віджеті або в повноекранному режимі показуємо:
- Великий статус: «Крок 2 із 3: бюджет і тип подарунка».
- Мінімум тексту, але чіткі підписи: «Бюджет до 50 $», «Надає перевагу цифровому подарунку».
- Кілька великих CTA‑кнопок, якщо голосовий сценарій допускає кліки: «Змінити бюджет», «Продовжити».
- Один простий progress‑bar або stepper замість десяти дрібних індикаторів.
Приклад простого «табло» в inline‑віджеті для голосового сценарію (TypeScript + React, сильно спрощено):
type VoiceUiMode = "default" | "voiceGlance";
interface GiftStepProps {
step: number;
totalSteps: number;
summary: string; // короткий опис того, що вже зібрано
uiMode: VoiceUiMode;
}
export function GiftVoiceStep(props: GiftStepProps) {
const fontSize = props.uiMode === "voiceGlance" ? "text-lg" : "text-sm";
return (
<div className="rounded-xl border p-3 flex flex-col gap-2">
<div className={`${fontSize} font-semibold`}>
Крок {props.step} з {props.totalSteps}
</div>
<div className={`${fontSize} text-muted-foreground`}>
{props.summary}
</div>
</div>
);
}
Тут немає нічого «голосового» як такого, але ідея очевидна: коли uiMode === "voiceGlance", ми робимо все більшим і простішим. Сигнал про те, що зараз увімкнено голосовий режим, може надходити з різних джерел: від непрямих ознак до явного прапорця, який модель проставляє у widgetState або у відповіді інструмента.
4. Синхронізація модальностей: що говорить GPT і що показує App
Ключовий принцип voice‑UX для Apps — синхронізація модальностей: голос і візуальний UI мають розповідати одну й ту саму історію, але на різних рівнях деталізації.
Типова помилка — змусити модель уголос зачитувати все, що показано у віджеті: довгі списки подарунків, JSON‑структури з фільтрами тощо. У голосовому режимі це перетворюється на катування. Краще так: голос дає коротке резюме, а UI показує деталі.
Приклад вдалої синхронізації для GiftGenius.
Користувач: «Підбери подарунок мамі, вона любить садівництво, бюджет до 50 доларів».
Модель (голосом): «Я підібрала кілька варіантів. Найкращий, на мою думку, — набір інструментів для саду за 45 доларів. На екрані я показала ще два схожі варіанти. Хочете, розповім детальніше, чи відразу перейдемо до вибору?»
Віджет (inline): показує три картки з подарунками, коротким описом і CTA‑кнопками «Вибрати» / «Показати схожі».
Діалогово‑JSON‑подібне представлення одного кроку (це не реальний протокол, а ілюстрація мислення):
{
"user": "Підбери подарунок мамі...",
"assistant_text": "Я підібрала кілька варіантів...",
"widget": {
"displayMode": "inline",
"state": {
"view": "gift_list",
"items": [
{ "id": "g1", "title": "Набір садових інструментів", "price": 45 },
{ "id": "g2", "title": "Фартух садівника", "price": 30 },
{ "id": "g3", "title": "Набір насіння квітів", "price": 20 }
]
}
}
}
Важлива деталь: у system‑promptʼі ви можете прямо прописати, як модель має говорити про UI, щоб не «зачитувати JSON». Наприклад: «Якщо ти показуєш список варіантів у віджеті, не зачитуй кожен повністю. Коротко опиши найкращий варіант і скажи, що решту видно на екрані».
У майбутньому, коли ви працюватимете з Realtime API і власними voice‑клієнтами, принцип залишиться тим самим: UI й аудіопотік мають бути узгоджені. Просто там ви вже матимете прямий контроль над стримінгом.
5. Realtime і latency: як уникнути ніякової тиші
Технічно tool_calls у голосовому режимі такі самі, як у текстовому: модель вирішує викликати ваш інструмент, ви віддаєте відповідь, віджет оновлюється. Але в голосі зʼявляється нова UX‑проблема — затримки. Поки ваш MCP‑сервер звертається до зовнішніх API або рахує складний звіт, користувач чує… нічого. І це сприймається значно гірше, ніж звичайне очікування тексту в чаті.
Тут є два рівні «захисту»: голосовий і візуальний.
- На голосовому рівні system‑prompt має дозволяти (і заохочувати) моделі проговорювати «я працюю» та ставити додаткові запитання, поки tool ще обробляється. Наприклад: «Зараз я підберу подарунки, це займе кілька секунд. Поки розкажіть, чи є ще обмеження».
- На візуальному рівні віджет має дуже явно показувати прогрес: лоадер, статус «Шукаю варіанти…», поточний крок. Без цього користувач вирішить, що все зависло, і почне говорити знову, збиваючи голосовий потік.
На практиці це зручно розвʼязувати через відкладене завдання: інструмент одразу повертає статус "pending" і jobId, а сам підбір іде у фоні. Віджет за "pending" показує прогрес, а голос — що він «працює».
Найпростіша схема такого інструмента на боці сервера, який віддає «заглушку» з jobId, а не блокується до повного результату, може виглядати так:
// Псевдокод server-side інструмента GiftGenius
export async function startGiftSearch(params: SearchParams) {
const jobId = await createBackgroundJob(params); // кладемо задачу в чергу
return {
status: "pending",
jobId,
message: "Пошук подарунків запущено"
};
}
Віджет, побачивши status: "pending", може перемкнути UI в режим прогресу:
if (toolOutput.status === "pending") {
return (
<div className="p-4 rounded-xl border flex items-center gap-3">
<Spinner />
<div className="text-base">
Підбираю подарунки… Це займе кілька секунд.
</div>
</div>
);
}
А модель у відповідь на цей самий tool‑output, згідно з інструкціями, уголос скаже приблизно те саме й, можливо, поставить додаткове уточнювальне запитання. Пізніше, коли фонове завдання завершиться і, скажімо, через MCP‑сповіщення прийде job.completed, віджет оновиться до списку подарунків. А голос озвучить їхнє резюме.
Так ми отримуємо поведінку, максимально наближену до realtime, навіть якщо бекенд працює не миттєво.
6. Безпека і підтвердження в голосі
Голосовий інтерфейс буває підступним, коли йдеться про критичні дії: оплату, видалення даних, зміну налаштувань. Розпізнавання мовлення не ідеальне, користувачі говорять «на ходу», і «угу» легко може перетворитися на «так, купуй». Тому для голосових сценаріїв особливо важливі сценарії підтвердження.
Є два базові патерни.
- Явне голосове підтвердження (Explicit Voice Confirmation). Для небезпечних дій ви вимагаєте конкретну фразу. Наприклад: «Для підтвердження покупки скажіть: «Підтверджую покупку»» — і в system‑prompt забороняєте виконувати оплату за розмитими «ага», «окей», «давай».
- Лише візуальне підтвердження (Visual Confirmation Only). Модель голосом підводить користувача до дії («Я підготувала замовлення, на екрані показано підсумкову суму і вміст кошика»), але фактичний тригер — натискання кнопки у віджеті «Сплатити». Особливо актуально це в комерційних сценаріях — і ми ще повернемося до цього в модулі 14.
Для GiftGenius це може виглядати так.
Модель: «Я підібрала чудовий набір для садівництва за 45 доларів. Я можу оформити покупку через ChatGPT. На екрані показано підсумкову ціну й адресу доставки. Щоб підтвердити голосом, скажіть «Підтверджую покупку» або натисніть кнопку «Сплатити» на екрані».
Віджет (fullscreen): показує підсумкове замовлення, жирним виділяє суму й адресу та дві помітні кнопки: «Сплатити» і «Скасувати».
Усередині віджета ви можете показувати статус підтвердження:
type CheckoutState = "review" | "waiting_voice_confirm" | "confirmed";
if (state.phase === "waiting_voice_confirm") {
return (
<div className="space-y-3">
<h2 className="text-xl font-semibold">Майже готово</h2>
<p className="text-base">
Підтвердьте покупку голосом фразою
«Підтверджую покупку» або натисніть кнопку «Сплатити».
</p>
<Button variant="primary">Сплатити</Button>
<Button variant="ghost">Скасувати</Button>
</div>
);
}
Так, навіть якщо модель помилково інтерпретує щось у голосі, у користувача лишається візуальний «страхувальний» шар.
7. Прості голосові команди і дизайн інструментів
У голосовому режимі користувач не формулюватиме команди так точно, як названі змінні вашого інструмента. Він скаже: «вибери перший», «покажи ще дешевші», «давай без техніки». Завдання розробника — так спроєктувати інструменти й system‑prompt, щоб модель легко співвідносила такі фрази з викликами ваших інструментів (callTool).
Для GiftGenius можна закласти, наприклад, такі дії:
- Вибір одного з показаних варіантів за індексом або id.
- Уточнення бюджету: «дешевше», «до 30 доларів».
- Фільтрація за типом: «лише цифрові подарунки», «нічого, що треба доставляти поштою».
Це зручно виразити через інструмент із простим параметром‑enum action і додатковими полями:
// Псевдосхема інструмента в TypeScript
type VoiceActionInput =
| { action: "select_item"; itemId: string }
| { action: "refine_budget"; maxPrice: number }
| { action: "filter_type"; type: "digital" | "physical" };
export function handleVoiceAction(input: VoiceActionInput) {
switch (input.action) {
case "select_item":
// позначаємо подарунок як обраний
break;
case "refine_budget":
// перераховуємо добірку під новий бюджет
break;
case "filter_type":
// фільтруємо наявний список
break;
}
}
У system‑prompt ви описуєте, як ці дії співвідносяться з голосовими командами: «Якщо користувач каже “вибери перший варіант”, виклич інструмент gift.voiceAction з action="select_item" і ідентифікатором першого подарунка на екрані» тощо.
З погляду UX це зменшує когнітивне навантаження: користувачу не потрібно вигадувати точні формулювання на кшталт «Уточни фільтри так, щоб були лише цифрові подарунки до 30 $». Він говорить по‑людськи, а модель «перекладає» це у структуру даних.
8. Голосовий сценарій GiftGenius: три кроки
Зберімо все докупи й спроєктуймо повний голосовий сценарій для GiftGenius, поки що не заглиблюючись у низькорівневий Realtime API.
Уявімо користувача: він їде в авто та вмикає голосовий режим ChatGPT. Він каже: «Підбери, будь ласка, подарунок мамі, вона любить сад, бюджет до 50 доларів».
Крок 1. Збір інформації голосом
Модель: «Чудово, давайте підберемо подарунок. Уточню кілька моментів: коли потрібен подарунок — у найближчі дні чи пізніше? І чи є обмеження, наприклад нічого важкого або громіздкого?»
Віджет (inline): поки що лише невелика панель зі статусом «Підбираємо подарунок для: мама, садівництво, до 50 $». Шрифти трохи більші, ніж зазвичай, щоб їх можна було розгледіти одним поглядом.
Код стану віджета може виглядати так:
interface GiftSessionState {
mode: "voice" | "text";
step: 1 | 2 | 3;
recipientSummary: string;
budget?: number;
}
const [state, setState] = useState<GiftSessionState>({
mode: "voice",
step: 1,
recipientSummary: "Мама, любить садівництво"
});
Серверна частина в міру відповідей користувача оновлює recipientSummary і budget, а віджет реагує.
Крок 2. Пошук і очікування
Після того як модель зібрала достатньо інформації, вона викликає ваш інструмент пошуку подарунків. Він, своєю чергою, може запустити фонове завдання, якщо добірка складна, і повернути status: "pending". Поки фон працює, модель каже: «Зараз я пошукаю відповідні варіанти — це займе кілька секунд. Поки можете розповісти, чи вона віддає перевагу фізичним подарункам, чи їй підійдуть цифрові сертифікати».
Віджет перемикається у «PiP‑подібний» режим, якщо користувач переходить в іншу частину інтерфейсу, або лишається inline з прогресом: «Шукаю подарунки…» і невеликим індикатором.
Крок 3. Результати і вибір
Коли результати готові, модель: «Я знайшла три варіанти. Перший — набір інструментів для саду за 45 доларів. Другий — фартух садівника за 30 доларів. Я показала їх на екрані. Скажіть “вибери перший” або “покажи ще дешевші”».
Віджет показує три великі картки з цінами й короткими описами. Кожна картка має CTA «Вибрати» і «Схожі». Плюс окрема кнопка «Показати ще варіанти».
Якщо користувач каже: «Вибери другий», модель викликає ваш інструмент voiceAction із action="select_item" і id другого подарунка. Віджет підсвічує його як обраний, а модель озвучує: «Чудово, обрали фартух садівника за 30 доларів».
Необов’язковий крок 4. Оформлення
Якщо App інтегрований з оплатами (у майбутньому модулі 14), починається checkout. Модель проговорює умови й просить підтвердження голосом або кнопкою. Віджет переходить у fullscreen‑майстер із кроками «Перевірка замовлення» → «Адреса доставки» → «Підтвердження».
Важливо, щоб на кожному кроці все ключове проговорювалося голосом, а віджет слугував візуальною опорою — особливо якщо користувач уже зупинився й дивиться на екран.
9. Практичні нотатки щодо реалізації і межі Apps SDK
Усі описані кроки для GiftGenius реалізуються всередині звичайного ChatGPT App — без власного аудіоклієнта й WebRTC. Тут важливо памʼятати про межі стека.
Дуже легко «зануритися» в теми Realtime API, WebRTC, стримінгу аудіо й почати будувати в голові власну голосову платформу. Для цього в курсі є окремий модуль 20. У цій лекції важливо тримати фокус на межах саме ChatGPT App усередині клієнта ChatGPT.
У поточній архітектурі:
- Аудіопотоком керує клієнт ChatGPT. Ви не надсилаєте й не отримуєте аудіобайти у віджеті.
- На бекенді ви все ще бачите звичайні tool‑виклики й текстові повідомлення, але модель може бути в голосовому режимі. Її відповіді при цьому будуть озвучені.
- Платформа може передавати непрямі ознаки того, що зараз voice‑режим (через user-agent або поля оточення). Але на цьому не можна будувати жорстку залежність: API може змінюватися, а ваш App має залишатися корисним і в суто текстовому режимі також.
Тому хороша стратегія така: спочатку ви проєктуєте UX, який нормально працює і для тексту, і для голосу. Це короткі статуси, чіткі CTA, зрозумілі стадії прогресу. Далі додаєте кілька поліпшень під голос: трохи більші шрифти в режимі "voiceGlance", виразніший прогрес, акцент на статусах на кшталт «Крок 2 із 3» і очевидні стани на кшталт «Чекаю підтвердження».
Додатково в system‑prompt ви описуєте голосову поведінку моделі: як вона коментує стан віджета, які фрази використовує для підтверджень, яких слів уникає (наприклад, не читає JSON, не озвучує кожну дрібницю у списку).
Якщо згодом ви робитимете свій Custom Voice Client на Realtime API, усі ці UX‑рішення спокійно «переїдуть» туди. Різниця буде лише в рівні доступу до подій і стримінгу, а не в принципах.
10. Типові помилки під час роботи з Voice / Realtime‑контекстом
Помилка №1: «Читання UI вголос» замість резюме.
Іноді розробники пишуть інструменти так, що модель починає зачитувати вголос увесь вміст JSON‑відповіді або повний список карток. У голосовому режимі це руйнує UX: користувач втрачає нитку, а ви витрачаєте токени. Краще, щоб голос озвучував коротке резюме й фокусувався на одному‑двох варіантах, а решту лишав на екрані.
Помилка №2: Повна відсутність візуального зворотного зв’язку в голосовому сценарії.
Є спокуса думати: «Раз користувач говорить, значить, він слухає — UI не потрібен». На практиці користувач часто поглядає на екран або повертається до нього за хвилину. Якщо в цей момент там немає ні статусу, ні прогресу, ні зрозумілого підсумку, він вирішить, що App завис або нічого не зробив. Обов’язково показуйте «Я думаю», «Крок 2 із 3», «Результати готові» тощо.
Помилка №3: Небезпечні дії без надійного підтвердження.
У текстовому режимі вже ризиковано робити «Сплатити» по одній кнопці, а в голосовому — ще ризикованіше виконувати покупку за розмитим «угу». Ігнорування явних сценаріїв підтвердження (голосових і/або візуальних) веде до помилкових покупок і проблем із довірою до App. Продумуйте, які дії потребують подвійного підтвердження, і явно описуйте це в system‑prompt та UI.
Помилка №4: Орієнтація лише на око, а не на вухо.
Іноді App проєктують так, ніби користувач завжди читає текст: надто складні формулювання, довгі кнопки, перевантажені описи. У голосовому режимі все це ще й треба проговорити — виходить «словесний суп». Намагайтеся, щоб ключовий зміст укладався в короткі, прості фрази, які легко сприймаються на слух.
Помилка №5: Плутанина між Apps SDK і власним Voice‑клієнтом.
Деякі студенти починають шукати в Apps SDK події мікрофона, аудіостримінг, WebRTC тощо, як у Realtime API, і розчаровуються, що «нічого цього немає». Важливо розуміти: ChatGPT App живе всередині клієнта ChatGPT, а голосом керує платформа. Ви працюєте з текстом, tool‑викликами і станом віджета та проєктуєте UX так, щоб голосовий режим «просто добре працював». Якщо потрібен повний контроль над голосом, це вже окремий, складніший проєкт із Realtime API.
Помилка №6: Відсутність стратегії роботи із затримками.
Якщо не продумати, що модель говорить і що показує віджет під час тривалих операцій, користувач перебиватиме, ставитиме нові запитання й ламатиме вам flow. Затримки в голосі відчуваються сильніше, ніж у тексті. Використовуйте проміжні статуси, фонову обробку завдань і голосові «я думаю, розкажіть поки…», щоб тиша не перетворювалася на баг.