JavaRush /Курсы /ChatGPT Apps /Voice / Realtime‑контекст: поведение App при голосовом об...

Voice / Realtime‑контекст: поведение App при голосовом общении

ChatGPT Apps
8 уровень , 4 лекция
Открыта

1. Контекст: что означает «голосовой режим» для ChatGPT App

Для начала важно понять, что в рамках ChatGPT Apps SDK вы не пишете свой аудио‑клиент, не управляете микрофоном и не стримите аудио сами. Этим занимается клиент ChatGPT (веб или мобильное приложение).

Предполагаем, что у вас уже есть базовое представление о виджете, callTool и GiftGenius из прошлых модулей — здесь мы смотрим на те же элементы через призму голосового режима.

Из вашей перспективы как разработчика App всё выглядит так:

  • Пользователь говорит в микрофон. Клиент ChatGPT делает распознавание речи и отправляет в модель уже текст.
  • Вы в потоке «видите» то же самое, что если бы пользователь печатал сообщение, только приходят они быстрее и более «разговорными».
  • Модель отвечает текстом, который клиент озвучивает.
  • Одновременно модель может вызывать ваши инструменты (callTool), менять displayMode виджета, обновлять widgetState и предлагать follow‑up’ы — как в текстовом режиме.

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

Из этого вытекают два последствия:

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

Для нашего GiftGenius это сразу подсказка: сценарий «еду в машине, подбери подарок маме» — это не просто текстовый чат. Это мультимодальный диалог, где голос ведет, а UI подстраховывает.

2. Чем voice‑сценарий отличается от текстового

Чтобы не попадать в ловушку «ну это всё то же самое, только пользователь говорит голосом», полезно сравнить текстовый и голосовой режимы по нескольким осям.

Аспект Текстовый режим Голосовой режим
Внимание пользователя Смотрит на экран, читает, скроллит Может не смотреть вообще (hands‑free)
Форма запросов Более структурированная, человек редактирует Разговорная, обрывки фраз, «угу», «давай ещё»
Терпимость к паузам Нормально 1–2 секунды тишины Долгая тишина ощущается болезненно
Роль UI Основной носитель деталей Вспомогательный, «табло» с краткой визуальной опорой
Ошибки ввода Опечатки, но текст виден Неразборчивая речь, шум, «ложные да/нет»

Отсюда несколько важных выводов.

  • Нельзя полагаться на то, что пользователь «прочитает в карточке». Критичные вещи надо проговаривать: что вы поняли, что собираетесь сделать, какой результат получили.
  • UI должен выдерживать сценарий «глянул на 1 секунду». Статус, прогресс, главный выбор — всё должно быть на виду крупным шрифтом. Детали — вторичны.
  • Паузы надо заполнять. Пока ваш MCP‑сервер думает над тяжелым запросом, модель должна озвучить, что происходит, а виджет — показать прогресс, чтобы не было ощущения зависшего ассистента.

Можно думать о голосовом режиме как об аудиокниге с иллюстрациями: у вас есть голосовой рассказчик (GPT) и есть картинки (виджет). Нужно синхронизировать их так, чтобы они дополняли друг друга, а не дублировали и не спорили.

3. Роль виджета в голосовом режиме: от «панели управления» к «табло»

В текстовом сценарии виджет часто выступает как полноценный интерфейс: формы, таблицы, карусели с фильтрами, кнопки действий. В голосовом режиме его роль меняется. Рекомендации по мультимодальным интерфейсам и VUI показывают, что при голосовых сценариях UI становится скорее информационным табло (glanceable UI): он нужен для быстрых проверок и подтверждений, а не для плотной работы глазами.

Для GiftGenius это означает следующее.

Когда пользователь идет по голосовому мастеру, на экране в inline‑виджете или fullscreen показываем:

  • Крупный статус: «Шаг 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 или tool‑ответе.

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" показывает прогресс, а голос — что он «работает».

Простейшая схема такого server‑side инструмента, который отдаёт «заглушку» с job‑id, а не блокируется до полного результата, может выглядеть так:

// Псевдокод 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‑notification пришёл job.completed, виджет обновляется до списка подарков, а голос озвучивает их саммари.

Так мы получаем поведение, максимально близкое к realtime, даже если backend работает не мгновенно.

6. Безопасность и подтверждения в голосе

Голосовой интерфейс очень коварен, когда речь заходит о критичных действиях: оплате, удалении данных, изменении настроек. Распознавание речи не идеально, пользователи говорят «на ходу», и «угу» может легко превратиться в «да, покупай». Поэтому для голосовых сценариев особенно важны confirmation flows.

Есть два базовых паттерна.

  • Явное голосовое подтверждение (Explicit Voice Confirmation). Для опасных действий вы требуете конкретную фразу. Например: «Для подтверждения покупки скажите: “Подтверждаю покупку”» — и в system‑prompt’е запрещаете выполнять оплату по расплывчатым «ага», «окей», «давай».
  • Только визуальное подтверждение (Visual Confirmation Only). Модель голосом подводит пользователя к действию («Я подготовил заказ, на экране показана итоговая сумма и содержимое корзины»), но фактический триггер — это нажатие кнопки в виджете «Оплатить». Особенно это актуально в commerce‑сценариях, и мы еще вернемся к этому в модуле 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, не углубляясь пока в low‑level 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: Опасные действия без сильного подтверждения.
В текстовом режиме опасно делать «Оплатить» по одной кнопке, а в голосовом — ещё опаснее выполнять покупку по расплывчатому «угу». Игнорирование явных confirmation flows (голосовых и/или визуальных) ведёт к неправильным покупкам и проблемам с доверием к App. Продумывайте, какие действия требуют двойного подтверждения, и явно описывайте это в system‑prompt и UI.

Ошибка №4: Ориентация только на глаз, а не на ухо.
Иногда App проектируют так, будто пользователь всегда читает текст: слишком сложные формулировки, длинные кнопки, перегруженные описания. В голосовом режиме всё это ещё и надо проговорить — получается «словесный суп». Старайтесь, чтобы ключевой смысл укладывался в короткие, простые фразы, которые легко воспринять на слух.

Ошибка №5: Путаница между Apps SDK и собственным Voice‑клиентом.
Некоторые студенты начинают искать в Apps SDK события микрофона, аудио‑стриминг, WebRTC и т.д., как в Realtime API, и разочаровываются, что «ничего этого нет». Важно понимать: ChatGPT App живёт внутри клиента ChatGPT, а голосом управляет платформа. Вы работаете с текстом, tool‑вызовами и состоянием виджета и проектируете UX так, чтобы голосовой режим «просто работал хорошо». Если нужен полный контроль над голосом, это уже отдельный, более сложный проект с Realtime API.

Ошибка №6: Отсутствие стратегии работы с задержками.
Если не продумать, что модель говорит и что показывает виджет во время долгих операций, пользователь будет перебивать, задавать новые вопросы и ломать вам flow. Задержки в голосе ощущаются сильнее, чем в тексте. Используйте промежуточные статусы, фоновую обработку задач и голосовые «я думаю, расскажи пока…», чтобы тишина не превращалась в баг.

1
Задача
ChatGPT Apps, 8 уровень, 4 лекция
Недоступна
No‑Awkward‑Silence Loader — pending‑состояние + follow‑up при задержках
No‑Awkward‑Silence Loader — pending‑состояние + follow‑up при задержках
1
Опрос
UX и интерфейсы, 8 уровень, 4 лекция
Недоступен
UX и интерфейсы
UX и интерфейсы (Inline, Fullscreen, Voice)
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ