1. Kontekst: co oznacza „tryb głosowy” dla ChatGPT App
Na początek ważne jest zrozumienie, że w ramach ChatGPT Apps SDK nie piszesz własnego klienta audio, nie sterujesz mikrofonem i nie strumieniujesz dźwięku samodzielnie. Tym zajmuje się klient ChatGPT (aplikacja webowa lub mobilna).
Zakładamy, że masz już podstawowe pojęcie o widżecie, callTool i GiftGenius z poprzednich modułów — tutaj patrzymy na te same elementy przez pryzmat trybu głosowego.
Z twojej perspektywy jako dewelopera App wszystko wygląda tak:
- Użytkownik mówi do mikrofonu. Klient ChatGPT rozpoznaje mowę i wysyła do modelu już tekst.
- W strumieniu „widzisz” to samo, co gdyby użytkownik pisał wiadomość, tylko przychodzi to szybciej i jest bardziej „rozmowne”.
- Model odpowiada tekstem, który klient odczytuje na głos.
- Jednocześnie model może wywoływać twoje narzędzia (callTool), zmieniać displayMode widżetu, aktualizować widgetState i proponować follow‑upy — tak jak w trybie tekstowym.
Kluczowa różnica polega na tym, że użytkownik może prawie w ogóle nie patrzeć na ekran lub zerkać na telefon „kątem oka”. Twój UI przestaje więc być głównym kanałem interakcji i staje się uzupełnieniem głosu, a nie odwrotnie.
Z tego wynikają dwa konsekwencje:
- Wszystko, co naprawdę ważne, musi być zrozumiałe „ze słuchu”, poprzez kwestie GPT.
- Widżet powinien być „glanceable” w dobrym sensie: po szybkim rzucie oka natychmiast widać status i kluczowe opcje, bez czytania drobnego tekstu.
Dla naszego GiftGenius to od razu wskazówka: scenariusz „jadę samochodem, dobierz prezent dla mamy” to nie tylko czat tekstowy. To multimodalny dialog, w którym prym wiedzie głos, a UI asekuracyjnie wspiera.
2. Czym scenariusz voice różni się od tekstowego
Aby nie wpaść w pułapkę „to wszystko to samo, tylko użytkownik mówi”, warto porównać tryby tekstowy i głosowy wzdłuż kilku osi.
| Aspekt | Tryb tekstowy | Tryb głosowy |
|---|---|---|
| Uwaga użytkownika | Patrzy na ekran, czyta, przewija | Może w ogóle nie patrzeć (hands‑free) |
| Forma zapytań | Bardziej strukturalna, użytkownik edytuje | Rozmowna, urwane frazy, „mhm”, „dawaj jeszcze” |
| Tolerancja na pauzy | Normalne jest 1–2 sekundy ciszy | Dłuższa cisza jest dokuczliwa |
| Rola UI | Główny nośnik szczegółów | Pomocniczy, „tablica” z krótką wizualną podporą |
| Błędy wejścia | Literówki, ale tekst jest widoczny | Niewyraźna mowa, hałas, „fałszywe tak/nie” |
Stąd kilka ważnych wniosków.
- Nie można zakładać, że użytkownik „przeczyta to na karcie”. Rzeczy krytyczne trzeba wypowiedzieć: co zrozumiałeś, co zamierzasz zrobić, jaki wynik uzyskałeś.
- UI musi wytrzymać scenariusz „rzut oka na 1 sekundę”. Status, postęp, główny wybór — wszystko powinno być widoczne dużą czcionką. Detale są drugorzędne.
- Pauzy trzeba wypełniać. Gdy twój serwer MCP przetwarza ciężkie zapytanie, model powinien powiedzieć, co się dzieje, a widżet — pokazać postęp, aby nie było wrażenia zawieszonego asystenta.
Na tryb głosowy można patrzeć jak na audiobook z ilustracjami: masz lektora (GPT) oraz obrazki (widżet). Trzeba je tak zsynchronizować, aby się uzupełniały, a nie dublowały czy ze sobą konkurowały.
3. Rola widżetu w trybie głosowym: od „panelu sterowania” do „tablicy”
W scenariuszu tekstowym widżet często jest pełnoprawnym interfejsem: formularze, tabele, karuzele z filtrami, przyciski akcji. W trybie głosowym jego rola się zmienia. Rekomendacje dot. interfejsów multimodalnych i VUI pokazują, że przy scenariuszach głosowych UI staje się raczej tablicą informacyjną (glanceable UI): służy do szybkich sprawdzeń i potwierdzeń, a nie do intensywnej pracy wzrokiem.
Dla GiftGenius oznacza to następujące rzeczy.
Gdy użytkownik przechodzi głosowego „kreatora”, na ekranie w widżecie inline lub w fullscreen pokazujemy:
- Duży status: „Krok 2 z 3: budżet i typ prezentu”.
- Minimum tekstu, ale wyraźne etykiety: „Budżet do 50 $”, „Preferowany prezent cyfrowy”.
- Kilka dużych przycisków CTA, jeśli scenariusz głosowy dopuszcza kliki: „Zmień budżet”, „Kontynuuj”.
- Jeden prosty pasek postępu lub stepper, a nie dziesięć drobnych wskaźników.
Przykład prostej „tablicy” w widżecie inline dla scenariusza głosowego (TypeScript + React, mocno uproszczone):
type VoiceUiMode = "default" | "voiceGlance";
interface GiftStepProps {
step: number;
totalSteps: number;
summary: string; // krótkie podsumowanie tego, co już zebrano
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`}>
Krok {props.step} z {props.totalSteps}
</div>
<div className={`${fontSize} text-muted-foreground`}>
{props.summary}
</div>
</div>
);
}
Nie ma tu nic „głosowego” jako takiego, ale idea jest jasna: przy uiMode === "voiceGlance" robimy wszystko większe i prostsze. Sygnał, że aktualnie jest tryb głosowy, może przyjść z różnych miejsc: od przesłanek po jawny znacznik ustawiany przez model w widgetState lub odpowiedzi narzędzia.
4. Synchronizacja modalności: co mówi GPT i co pokazuje App
Kluczowa zasada voice‑UX dla Apps to synchronizacja modalności: głos i wizualny UI powinny opowiadać tę samą historię, ale na różnych poziomach szczegółowości.
Typowy błąd to zmuszanie modelu do czytania na głos wszystkiego, co pokazane w widżecie: długich list prezentów, struktur JSON z filtrami itp. To zamienia się w torturę. Zalecenie: głos daje krótkie podsumowanie (samary), a UI pokazuje detale.
Przykład poprawnej synchronizacji dla GiftGenius.
Użytkownik: „Dobierz prezent dla mamy, lubi ogrodnictwo, budżet do 50 dolarów”.
Model (głosem): „Zebrałem kilka opcji. Najlepsza moim zdaniem to zestaw narzędzi do ogrodu za 45 dolarów. Na ekranie pokazałem jeszcze dwie podobne opcje. Chcesz, opowiem więcej czy od razu przejdziemy do wyboru?”
Widżet (inline): pokazuje trzy karty z prezentami, krótkim opisem i przyciskami CTA „Wybierz” / „Pokaż podobne”.
Dialogowo‑JSON‑owe przedstawienie jednego kroku (to nie prawdziwy protokół, a ilustracja sposobu myślenia):
{
"user": "Dobierz prezent dla mamy...",
"assistant_text": "Znalazłem kilka opcji...",
"widget": {
"displayMode": "inline",
"state": {
"view": "gift_list",
"items": [
{ "id": "g1", "title": "Zestaw narzędzi ogrodniczych", "price": 45 },
{ "id": "g2", "title": "Fartuch ogrodnika", "price": 30 },
{ "id": "g3", "title": "Zestaw nasion kwiatów", "price": 20 }
]
}
}
}
Ważny detal: w system‑prompt możesz wprost określić, jak model ma mówić o UI, żeby nie „czytać JSON‑a”: „Jeśli pokazujesz listę opcji w widżecie, nie czytaj każdej w całości. Krótko opisz najlepszą opcję i powiedz, że reszta jest widoczna na ekranie”.
W przyszłości, gdy będziesz pracować z Realtime API i własnymi klientami voice, zasada pozostanie ta sama: UI i strumień audio muszą być spójne. Po prostu wtedy będziesz mieć bezpośrednią kontrolę nad strumieniem.
5. Realtime i opóźnienia: jak uniknąć niezręcznej ciszy
Technicznie tool_calls w trybie głosowym są takie same jak w tekstowym: model decyduje o wywołaniu narzędzia, ty zwracasz odpowiedź, widżet się aktualizuje. Ale w głosie pojawia się nowy problem UX — opóźnienia. Gdy twój serwer MCP idzie do zewnętrznych API lub liczy złożony raport, użytkownik słyszy… nic. Odbierane jest to znacznie gorzej niż samo oczekiwanie na tekst na czacie.
Są tu dwa poziomy zabezpieczeń: głosowy i wizualny.
- Na poziomie głosowym system‑prompt powinien pozwalać (i zachęcać) modelowi do komunikowania „pracuję” i zadawania dodatkowych pytań, gdy tool jeszcze liczy. Na przykład: „Zaraz dobiorę prezenty, to zajmie kilka sekund. W międzyczasie powiedz, jeśli są jeszcze jakieś ograniczenia”.
- Na poziomie wizualnym twój widżet powinien bardzo wyraźnie pokazywać postęp: loader, status „Szukam opcji…”, bieżący krok. Bez tego użytkownik uzna, że wszystko się zawiesiło, i zacznie znowu mówić, wybijając strumień głosowy z rytmu.
W praktyce wygodnie rozwiązać to przez zadanie odroczone: narzędzie od razu zwraca status "pending" i jobId, a samo wyszukiwanie trwa w tle. Widżet na podstawie "pending" pokazuje postęp, a głos informuje, że „pracuje”.
Najprostszy schemat takiego narzędzia po stronie serwera, które zwraca „zajawkę” z job‑id zamiast blokować się do pełnego wyniku, może wyglądać tak:
// Pseudokod narzędzia po stronie serwera GiftGenius
export async function startGiftSearch(params: SearchParams) {
const jobId = await createBackgroundJob(params); // wrzucamy zadanie do kolejki
return {
status: "pending",
jobId,
message: "Wyszukiwanie prezentów uruchomione"
};
}
Widżet, widząc status: "pending", może przełączyć UI w tryb postępu:
if (toolOutput.status === "pending") {
return (
<div className="p-4 rounded-xl border flex items-center gap-3">
<Spinner />
<div className="text-base">
Dobieram prezenty… To potrwa kilka sekund.
</div>
</div>
);
}
A model, w odpowiedzi na ten sam tool‑output, zgodnie z instrukcjami, powie na głos mniej więcej to samo i być może zada dodatkowe pytanie doprecyzowujące. Później, gdy zadanie w tle się zakończy i np. przez MCP‑notification nadejdzie job.completed, widżet zaktualizuje się do listy prezentów, a głos odczyta ich podsumowanie.
W ten sposób uzyskujemy zachowanie możliwie zbliżone do realtime, nawet jeśli backend nie działa natychmiast.
6. Bezpieczeństwo i potwierdzenia w głosie
Interfejs głosowy bywa podstępny przy działaniach krytycznych: płatnościach, usuwaniu danych, zmianach ustawień. Rozpoznawanie mowy nie jest idealne, użytkownicy mówią „w biegu”, a „mhm” może łatwo zamienić się w „tak, kupuj”. Dlatego dla scenariuszy głosowych szczególnie ważne są confirmation flows.
Są dwa podstawowe wzorce.
- Wyraźne potwierdzenie głosowe (Explicit Voice Confirmation). Dla działań niebezpiecznych wymagaj konkretnej frazy. Na przykład: „Aby potwierdzić zakup, powiedz: ‘Potwierdzam zakup’” — i w system‑prompt zabroń realizacji płatności po niejednoznacznych „aha”, „okej”, „tak”.
- Tylko potwierdzenie wizualne (Visual Confirmation Only). Model głosem prowadzi użytkownika do działania („Przygotowałem zamówienie, na ekranie pokazano kwotę końcową i zawartość koszyka”), ale faktyczny trigger to naciśnięcie przycisku „Zapłać” w widżecie. Szczególnie istotne w scenariuszach commerce — wrócimy do tego w module 14.
Dla GiftGenius może to wyglądać tak.
Model: „Dobrałem świetny zestaw dla ogrodnictwa za 45 dolarów. Mogę sfinalizować zakup przez ChatGPT. Na ekranie pokazano cenę końcową i adres dostawy. Aby potwierdzić głosem, powiedz ‘Potwierdzam zakup’, albo naciśnij przycisk ‘Zapłać’ na ekranie.”
Widżet (fullscreen): pokazuje zamówienie końcowe, pogrubia kwotę i adres oraz dwa wyraźne przyciski: „Zapłać” i „Anuluj”.
Wewnątrz widżetu możesz odzwierciedlać status potwierdzenia:
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">Prawie gotowe</h2>
<p className="text-base">
Potwierdź zakup głosem frazą
„Potwierdzam zakup” lub naciśnij przycisk „Zapłać”.
</p>
<Button variant="primary">Zapłać</Button>
<Button variant="ghost">Anuluj</Button>
</div>
);
}
Dzięki temu, jeśli model jednak błędnie zinterpretował coś w mowie, użytkownik ma wizualną „warstwę asekuracyjną”.
7. Proste komendy głosowe i projektowanie narzędzi
Użytkownik głosowy nie będzie formułować poleceń dokładnie jak zmienne twojego narzędzia. Powie „wybierz pierwszy”, „pokaż tańsze”, „bez elektroniki”. Zadanie dewelopera — tak zaprojektować narzędzia i system‑prompt, aby model łatwo mapował takie frazy na wywołania twoich narzędzi (callTool).
Dla GiftGenius można założyć np. takie działania:
- Wybór jednej z pokazanych opcji po indeksie lub id.
- Doprecyzowanie budżetu: „taniej”, „do 30 dolarów”.
- Filtrowanie po typie: „tylko prezenty cyfrowe”, „nic, co trzeba wysyłać pocztą”.
Wygodnie wyrazić to narzędziem z prostym parametrem enum action oraz dodatkowymi polami:
// Pseudowzorzec narzędzia w 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":
// oznaczamy prezent jako wybrany
break;
case "refine_budget":
// przeliczamy propozycje pod nowy budżet
break;
case "filter_type":
// filtrujemy istniejącą listę
break;
}
}
W system‑prompt opisujesz, jak te działania odnoszą się do komend głosowych: „Jeśli użytkownik mówi ‘wybierz pierwszy wariant’, wywołaj narzędzie gift.voiceAction z action="select_item" i identyfikatorem pierwszego prezentu na ekranie” itp.
Z punktu widzenia UX zmniejsza to obciążenie poznawcze: użytkownik nie musi wymyślać dokładnych sformułowań w rodzaju „Doprecyzuj filtry tak, aby zostały tylko prezenty cyfrowe do 30$”. Mówi po ludzku, a model przekłada to na strukturę danych.
8. Scenariusz głosowy GiftGenius: trzy kroki
Zbierzmy wszystko razem i zaprojektujmy pełny scenariusz głosowy dla GiftGenius, nie wchodząc na razie w low‑level Realtime API.
Wyobraźmy sobie użytkownika: jedzie samochodem i uruchamia tryb głosowy ChatGPT. Mówi: „Dobierz, proszę, prezent dla mamy, lubi ogród, budżet do 50 dolarów”.
Krok 1. Zbieranie informacji głosem
Model: „Super, dobierzmy prezent. Dopytam o kilka rzeczy: kiedy potrzebny jest prezent — w najbliższych dniach czy później? I czy są jakieś ograniczenia, np. nic ciężkiego albo nieporęcznego?”
Widżet (inline): na razie tylko mały panel ze statusem „Dobieramy prezent dla: mama, ogrodnictwo, do 50 $”. Czcionki nieco większe niż zwykle, aby dało się je ogarnąć jednym spojrzeniem.
Kod stanu widżetu może wyglądać tak:
interface GiftSessionState {
mode: "voice" | "text";
step: 1 | 2 | 3;
recipientSummary: string;
budget?: number;
}
const [state, setState] = useState<GiftSessionState>({
mode: "voice",
step: 1,
recipientSummary: "Mama, lubi ogrodnictwo"
});
Część serwerowa w miarę odpowiedzi użytkownika aktualizuje recipientSummary i budget, a widżet reaguje.
Krok 2. Wyszukiwanie i oczekiwanie
Gdy model zbierze wystarczająco informacji, wywołuje twoje narzędzie wyszukiwania prezentów. Ono z kolei może uruchomić zadanie w tle, jeśli dobór jest złożony, i zwrócić status: "pending". Gdy tło pracuje, model mówi: „Teraz poszukam odpowiednich opcji, to zajmie kilka sekund. W międzyczasie możesz powiedzieć, czy woli prezenty fizyczne, czy wystarczą jej cyfrowe vouchery”.
Widżet przełącza się w tryb podobny do PiP, jeśli użytkownik przechodzi do innej części interfejsu, lub zostaje inline z postępem: „Szukam prezentów…” i niewielkim wskaźnikiem.
Krok 3. Wyniki i wybór
Gdy wyniki są gotowe, model: „Znalazłem trzy opcje. Pierwsza — zestaw narzędzi do ogrodu za 45 dolarów. Druga — fartuch ogrodnika za 30 dolarów. Pokazałem je na ekranie. Powiedz ‘wybierz pierwszy’ albo ‘pokaż tańsze’”.
Widżet pokazuje trzy duże karty z cenami i krótkimi opisami. Każda karta ma CTA „Wybierz” i „Podobne”. Plus osobny przycisk „Pokaż więcej opcji”.
Jeśli użytkownik mówi: „Wybierz drugi”, model wywołuje twój voiceAction‑tool z action="select_item" i id drugiego prezentu. Widżet podświetla go jako wybrany, a model mówi: „Świetnie, wybraliśmy fartuch ogrodnika za 30 dolarów”.
Opcjonalny Krok 4. Finalizacja
Jeśli App jest zintegrowana z płatnościami (w przyszłym module 14), zaczyna się checkout. Model wypowiada warunki i prosi o potwierdzenie głosem lub przyciskiem. Widżet przechodzi w pełnoekranowy kreator ze stopniami „Weryfikacja zamówienia” → „Adres dostawy” → „Potwierdzenie”.
Ważne, aby na każdym kroku wszystko kluczowe było wypowiadane głosem, a widżet służył wizualną podporą — szczególnie jeśli użytkownik już się zatrzymał i patrzy na ekran.
9. Praktyczne uwagi wdrożeniowe i granice Apps SDK
Wszystkie opisane kroki dla GiftGenius realizuje się w ramach zwykłego ChatGPT App — bez własnego klienta audio i WebRTC. Ważne jest pamiętanie o granicach stosu.
Bardzo łatwo „odlecieć” w tematy Realtime API, WebRTC, strumieniowania audio i w głowie budować własną platformę głosową. Temu poświęcony jest osobny moduł 20. W tej lekcji pamiętaj o granicach właśnie ChatGPT App wewnątrz klienta ChatGPT.
W bieżącej architekturze:
- Strumieniem audio zarządza klient ChatGPT. Nie wysyłasz ani nie odbierasz bajtów audio w widżecie.
- Na backendzie nadal widzisz zwykłe wywołania narzędzi i wiadomości tekstowe, ale model może być w trybie głosowym, a jego odpowiedzi będą odczytywane.
- Platforma może przekazywać przesłanki, że teraz jest tryb voice (przez user-agent lub pola środowiskowe). Nie można jednak budować na tym twardej zależności: API może się zmieniać, a twój App powinien pozostać użyteczny także w czysto tekstowym trybie.
Dlatego dobra strategia wdrożenia jest taka. Najpierw projektujesz UX, który działa dobrze zarówno dla tekstu, jak i dla głosu: krótkie statusy, wyraźne CTA, czytelne etapy postępu. Potem dodajesz kilka ulepszeń pod głos: nieco większe fonty w trybie "voiceGlance", bardziej wyraźny postęp, nacisk na statusy typu „Krok 2 z 3” i oczywiste stany typu „Czekam na potwierdzenie”.
Dodatkowo w system‑prompt opisujesz zachowanie głosowe modelu: jak komentuje stan widżetu, jakich fraz używa do potwierdzeń, jakich sformułowań unika (np. nie czyta JSON‑a, nie odczytuje każdej drobnostki z listy).
Jeśli kiedyś później będziesz tworzyć własnego Custom Voice Clienta na Realtime API, wszystkie te decyzje UX bez problemu „przeniosą się” tam. Różnica będzie tylko w poziomie dostępu do zdarzeń i strumieniowania, a nie w zasadach.
10. Typowe błędy przy pracy z kontekstem Voice / Realtime
Błąd nr 1: „Czytanie UI na głos” zamiast streszczenia.
Czasem deweloperzy piszą narzędzia tak, że model zaczyna czytać na głos całą zawartość odpowiedzi JSON lub pełną listę kart. W trybie głosowym to zabija UX: użytkownik gubi wątek, a ty tracisz tokeny. Lepiej, by głos podawał krótkie podsumowanie i skupiał się na jednej‑dwóch opcjach, a resztę zostawiał na ekranie.
Błąd nr 2: Całkowity brak wizualnej informacji zwrotnej w głosie.
Może kusić myśl: „Skoro użytkownik mówi, to słucha, UI nie jest potrzebny”. W praktyce użytkownik często zerka na ekran lub wraca do niego po minucie. Jeśli w tym momencie nie ma tam statusu, postępu ani zrozumiałego efektu, uzna, że App się zawiesił lub nic nie zrobił. Koniecznie pokazuj „Myślę”, „Krok 2 z 3”, „Wyniki gotowe” itp.
Błąd nr 3: Niebezpieczne działania bez silnego potwierdzenia.
W trybie tekstowym niebezpieczne jest robić „Zapłać” jednym przyciskiem, a w głosowym jeszcze bardziej ryzykowne jest realizować zakup po niejednoznacznym „mhm”. Ignorowanie wyraźnych confirmation flows (głosowych i/lub wizualnych) prowadzi do błędnych zakupów i problemów z zaufaniem do App. Zaplanuj, które działania wymagają podwójnego potwierdzenia, i opisz to wyraźnie w system‑prompt i UI.
Błąd nr 4: Skupienie tylko na wzroku, a nie na słuchu.
Czasem App projektuje się tak, jakby użytkownik zawsze czytał tekst: zbyt skomplikowane sformułowania, długie przyciski, przeładowane opisy. W trybie głosowym to wszystko trzeba jeszcze wypowiedzieć — powstaje „werbalny bigos”. Staraj się, aby kluczowy sens mieścił się w krótkich, prostych zdaniach, łatwych do odsłuchu.
Błąd nr 5: Mieszanie Apps SDK z własnym klientem Voice.
Niektórzy studenci szukają w Apps SDK zdarzeń mikrofonu, strumieni audio, WebRTC itp., jak w Realtime API, i rozczarowują się, że „tego nie ma”. Trzeba rozumieć: ChatGPT App żyje wewnątrz klienta ChatGPT, a głosem zarządza platforma. Pracujesz z tekstem, wywołaniami narzędzi i stanem widżetu oraz projektujesz UX tak, aby tryb głosowy „po prostu działał dobrze”. Jeśli potrzebujesz pełnej kontroli nad głosem, to osobny, bardziej złożony projekt z Realtime API.
Błąd nr 6: Brak strategii pracy z opóźnieniami.
Jeśli nie przemyślisz, co model mówi i co widżet pokazuje podczas długich operacji, użytkownik będzie przerywać, zadawać nowe pytania i psuć flow. Opóźnienia w głosie są bardziej odczuwalne niż w tekście. Używaj stanów pośrednich, przetwarzania w tle i głosowych „myślę, opowiedz w międzyczasie…”, aby cisza nie zamieniała się w błąd.
GO TO FULL VERSION