CodeGym /课程 /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‑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: "妈妈,喜欢园艺"
});

随着用户回答,服务端更新 recipientSummarybudget,小部件随之响应。

步骤 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。

1
调查/小测验
UX 与界面第 8 级,课程 4
不可用
UX 与界面
UX 与界面(Inline、Fullscreen、Voice)
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION