1. 上下文:对 ChatGPT App 而言,“语音模式”意味着什么
首先要明确:在 ChatGPT Apps SDK 的范畴内,你并不自己编写音频客户端,不控制麦克风,也不自行流式传输音频。这些都由 ChatGPT 客户端(网页或移动应用)完成。
假设你已经了解小部件、callTool 与 GiftGenius(在前面的模块中讲过)——这里我们从语音模式的视角重新审视相同的要素。
从你作为 App 开发者的视角,一切大致如下:
- 用户对着麦克风说话。ChatGPT 客户端做语音识别,向模型发送的已经是文本。
- 你在流中“看到”的与用户键入消息相同,只是到达更快、更口语化。
- 模型以文本作答,客户端将其合成语音播放。
- 同时,模型可以调用你的工具(callTool)、切换小部件的 displayMode、更新 widgetState,并像文本模式一样给出后续建议(follow‑ups)。
关键区别在于,用户可能几乎不看屏幕,或者只是“扫一眼”手机。也就是说,你的 UI 不再是主要交互通道,而是对语音的补充,而不是反过来。
由此带来两个后果:
- 所有真正重要的信息,都必须“听得懂”,通过 GPT 的语音回复传达。
- 小部件应在好的意义上“光滑利落”:匆匆一瞥就能看到状态和关键选项,而无需阅读小字。
对我们的 GiftGenius,这立即给出提示:场景“我在开车,帮我给妈妈挑礼物”并不是普通的文本聊天。这是一个多模态对话,语音主导,UI 兜底。
2. voice 场景与文本场景有何不同
为了避免陷入“其实一模一样,只是换成说话”的陷阱,按几条轴线对比文本与语音模式会更有帮助。
| 方面 | 文本模式 | 语音模式 |
|---|---|---|
| 用户注意力 | 看屏幕、阅读、滚动 | 可能完全不看(免手持) |
| 请求形态 | 更结构化,用户会修改润色 | 更口语化,片段、填充词,“嗯”“再来点” |
| 对停顿的容忍 | 1–2 秒安静可以接受 | 长时间安静会让人不适 |
| UI 的角色 | 细节的主要载体 | 辅助,“公告牌”式的简短视觉锚点 |
| 输入错误 | 有错别字,但文本可见 | 语音不清、噪音、“误判的 是/否” |
由此得到几个重要结论。
- 不能指望用户“会读卡片里的内容”。关键事项要说出来:你理解了什么、要做什么、得到了什么结果。
- UI 必须经受“一眼一秒”的场景。状态、进度、主要选择——都要用大字号放在显眼处。细节其次。
- 需要填补停顿。当你的 MCP 服务器在处理重任务时,模型应该口头说明正在进行的事情,而小部件要显示进度,避免“助理卡住”的感觉。
可以把语音模式类比为“有插图的有声书”:你有一个讲述者(GPT),还有“插图”(小部件)。需要把它们同步成互补,而不是重复或互相冲突。
3. 语音模式下小部件的角色:从“控制面板”到“信息公告牌”
在文本场景中,小部件常常是完整的界面:表单、表格、带筛选的轮播、操作按钮。在语音场景中,它的角色会发生改变。多模态界面与 VUI 的经验显示,语音场景下 UI 更像是信息公告牌(glanceable UI):用于快速查看与确认,而不是依赖眼睛的密集操作。
对 GiftGenius,这意味着:
当用户通过语音向导前进时,在内联(inline)小部件或全屏中展示:
- 大号状态:“第 2 步,共 3 步:预算与礼物类型”。
- 极简文本但明确标签:“预算至 50 美元”“偏好数字礼物”。
- 如果语音场景允许点击,提供几个大的 CTA 按钮:“修改预算”“继续”。
- 一个简单的进度条或步骤条,而不是十个小指示器。
下面是一个用于语音场景的内联小部件“公告牌”示例(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 显示什么
针对 Apps 的语音 UX 核心原则是模态同步:语音与可视化 UI 要讲同一个故事,但细节层级不同。
常见错误是让模型把小部件里显示的内容原样念出来:冗长的礼物列表、带筛选条件的 JSON 结构等——这会变成折磨。建议做法:语音给出简短摘要(summary),UI 呈现细节。
GiftGenius 的合理同步示例。
用户:“给妈妈挑个礼物,她喜欢园艺,预算 50 美元以内。”
模型(语音):“我找到了几个选项。我认为最合适的是一套园艺工具,45 美元。我还在屏幕上显示了另外两个类似选项。要不要我详细介绍,还是直接开始选择?”
小部件(内联):展示三张礼物卡片,附简短描述和 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 与自有语音客户端时,原则仍然相同:UI 与音频流要一致。只是届时你将对流式传输有直接控制权。
5. Realtime 与延迟:如何避免尴尬的沉默
在语音模式下,技术上 tool_calls 与文本模式一致:模型决定调用你的工具,你返回响应,小部件更新。但语音引入了一个新的 UX 问题——延迟。当你的 MCP 服务器在调用外部 API 或计算复杂报告时,用户听到的……是一片寂静。这比在聊天里等待文本要糟糕得多。
这里有两个层面的“防护”:语音与视觉。
- 语音层面上,system prompt 应允许并鼓励模型说“我在处理”,并在工具仍在计算时提出补充问题。例如:“我现在在挑选礼物,大约需要几秒钟。期间如果还有其他限制,请告诉我。”
- 视觉层面上,小部件要非常清晰地展示进度:loading、状态“正在寻找选项……”、当前步骤。否则用户会以为卡住了,开始继续说话,打乱语音流。
实践中可以通过延迟任务来处理:工具立即返回 "pending" 状态与 jobId,而真实的挑选在后台进行。小部件根据 "pending" 显示进度,语音也同步说明“正在处理”。
一个在服务端返回“占位” job‑id 而非阻塞到完整结果的简单工具示意如下:
// GiftGenius 的服务端工具伪代码
export async function startGiftSearch(params: SearchParams) {
const jobId = await createBackgroundJob(params); // 放入队列
return {
status: "pending",
jobId,
message: "已开始搜索礼物"
};
}
小部件在看到 status 为 "pending" 后,可以切换到进度模式:
if (toolOutput.status === "pending") {
return (
<div className="p-4 rounded-xl border flex items-center gap-3">
<Spinner />
<div className="text-base">
正在挑选礼物……这需要几秒钟。
</div>
</div>
);
}
而模型根据相同的工具输出,按指令口头说出类似内容,并可能提出进一步澄清问题。稍后当后台任务完成,并通过 MCP 通知收到 job.completed 时,小部件更新为礼物列表,语音播报摘要。
这样,即便后端不是即时返回,也能获得尽可能接近实时(realtime)的体验。
6. 语音中的安全与确认
当涉及关键动作(支付、删除数据、变更设置)时,语音界面尤其“狡猾”。语音识别并不完美,用户常在“路上”说话,“嗯嗯”很容易被当成“好,购买”。因此,语音场景下尤其需要确认流程(confirmation flows)。
有两种基本模式。
- 显式语音确认(Explicit Voice Confirmation)。 对危险动作要求明确语句。例如:“要确认购买,请说:‘确认购买’”,并在 system prompt 中禁止对含糊的“嗯哼”“好吧”“可以”执行支付。
- 仅可视化确认(Visual Confirmation Only)。 模型用语音引导用户到动作(“我已准备好订单,屏幕上显示了总金额和购物车内容”),但实际触发是在小部件中点击“支付”按钮。尤其在电商场景中适用,我们在第 14 模块会再谈。
对 GiftGenius,可以这样设计。
模型:“我挑到了一套不错的园艺套装,45 美元。我可以通过 ChatGPT 完成购买。屏幕上显示了最终价格与送货地址。若要通过语音确认,请说‘确认购买’,或点击屏幕上的‘支付’按钮。”
小部件(全屏):显示最终订单,粗体标注金额与地址,并有两个显眼按钮:“支付”“取消”。
在小部件中,你可以反映确认状态:
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:语音收集信息
模型:“好的,一起挑礼物。我再确认两点:礼物是这几天内需要,还是可以晚些?还有没有限制,比如不要太沉或太占地方?”
小部件(内联):此时只是一个小面板,状态为“正在为:妈妈、园艺,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 的模式;否则保持内联并显示进度:“正在寻找礼物……”与一个小指示器。
步骤 3:结果与选择
当结果就绪,模型:“我找到了三个选项。第一——一套园艺工具,45 美元。第二——园艺围裙,30 美元。我已在屏幕上显示。你可以说‘选第一个’,或‘再便宜点’。”
小部件显示三张大卡片,附价格和简述。每张卡片有“选择”“相似”CTA,另有“显示更多选项”按钮。
若用户说:“选第二个”,模型调用你的 voiceAction 工具,传入 action="select_item" 与第二个礼物的 id。小部件高亮该礼物,模型播报:“好的,已选择园艺围裙,价格 30 美元。”
可选步骤 4:下单
如果 App 集成了支付(见未来的第 14 模块),则进入结账流程。模型口头说明条款并请求语音或按钮确认。小部件进入全屏向导:“订单确认” → “配送地址” → “确认”。
重要的是,每一步的关键信息都通过语音说明,小部件作为视觉锚点,尤其是在用户停下来看屏幕时。
9. 实现要点与 Apps SDK 的边界
上述 GiftGenius 的步骤都可以在普通 ChatGPT App 内完成——无需自建音频客户端或 WebRTC。关键是牢记栈的边界。
很容易“一路飙”到 Realtime API、WebRTC、音频流等话题,脑中开始搭建自有语音平台。课程中另有第 20 模块专门讨论这些。本讲请牢记:我们讨论的是ChatGPT 客户端内的 ChatGPT App。
在当前架构下:
- 音频流由 ChatGPT 客户端管理。你不会在小部件中发送或接收音频字节。
- 在后端,你仍然看到普通的工具调用与文本消息,但模型可能处于语音模式,其回答会被播报。
- 平台可能通过 user-agent 或环境字段传递语音模式的间接信号。但不要对此建立强依赖:API 可能变化,而你的 App 也应在纯文本模式下保持可用。
因此,一个不错的实现策略是:先设计同时适配文本与语音的 UX:精炼状态、明确 CTA、清晰的进度阶段。然后为语音加几处增强:在 "voiceGlance" 模式下稍微放大字号、更显眼的进度、强调“第 2/3 步”等状态与“等待确认”这类明确阶段。
同时在 system prompt 中描述模型的语音行为:如何评论小部件状态、用于确认的措辞、应避免的说法(例如不读 JSON,不逐项念清单里的每个细节)。
如果未来你要在 Realtime API 上构建自定义语音客户端,上述 UX 方案也能顺利“迁移”过去。差别只在于你对事件与流的控制层级,而非原则本身。
10. 使用 Voice / Realtime 上下文时的常见错误
错误 1:“把 UI 念出来”而非做摘要。
有时开发者设计的工具会让模型把整个 JSON 响应或完整卡片列表都念出来。在语音模式下,这是对 UX 的致命打击:用户很快迷失,你也浪费 token。更好的方式是让语音给出简短摘要,聚焦一两个选项,其余内容交给屏幕展示。
错误 2:语音场景下完全没有视觉反馈。
有人会想:“既然用户在说话,说明他在听,就不需要 UI 了。”但实际上,用户常会看一眼屏幕,或过一分钟再回来。若此时既没有状态也没有进度、没有清晰的结果,他会以为 App 卡住或啥也没做。务必显示“我在思考”“第 2/3 步”“结果已就绪”等。
错误 3:对危险动作缺少强确认。
在文本模式下一键“支付”已然有风险,在语音模式下更危险的是对模糊的“嗯嗯”直接执行购买。忽视明确的确认流程(语音和/或视觉)会导致错误购买与 App 信任问题。请规划哪些动作需要双重确认,并在 system prompt 与 UI 中明确。
错误 4:只为“眼睛”,而非为“耳朵”设计。
有时 App 的设计默认用户总是在阅读:措辞复杂、按钮文案冗长、描述信息过载。在语音模式下,这些还得“念出来”——就成了“话语浓汤”。尽量让关键信息落入简短、易于听懂的句子。
错误 5:混淆 Apps SDK 与自建语音客户端。
一些同学会在 Apps SDK 里寻找麦克风事件、音频流、WebRTC 等(就像在 Realtime API 中那样),然后失望于“没有这些”。请理解:ChatGPT App 运行在 ChatGPT 客户端内,语音由平台管理。你处理的是文本、工具调用与小部件状态,目标是让语音模式“自然而然地好用”。若需要对语音的完全控制,那是另一个更复杂的 Realtime API 项目。
错误 6:缺少处理延迟的策略。
如果不设计在长耗时期间模型要说什么、小部件要显示什么,用户会不断打断、提出新问题,从而打乱流程。语音中的延迟感受比文本更强。请使用中间状态、后台任务处理与“我在思考,你先说说……”这类语音填补,让沉默不再像是 bug。
GO TO FULL VERSION