mcpField
O
openai-realtime-voice

openai-realtime-voice

github.com/calesthio开发219108 次安装15 周前更新
代码仓库

关于 openai-realtime-voice

面向实时语音产品构建者的 OpenAI Realtime 开发技能,适用于需要持续会话、轮流对话和工具调用的语音 Agent。它涵盖架构选择、传输方式、会话配置与打断处理,并区分实时交互和文件式音频任务。

主要功能

  1. 01规划语音对话架构与传输方式
  2. 02配置实时会话和语音参数
  3. 03设计语音检测与轮次管理
  4. 04处理打断、工具调用和交接
  5. 05考虑隐私、安全及成本

适用场景

  1. 01构建浏览器实时语音助手
  2. 02实现电话式语音支持 Agent
  3. 03设计可被用户打断的语音交互
  4. 04为实时语音会话接入工具

MCP 相关

Realtime 会话可使用远程 MCP 服务提供的工具,也可使用应用执行的函数工具和内置连接器。技能说明如何规划工具配置与执行责任。

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/calesthio/generative-media-skills --skill openai-realtime-voice

安装后在 Claude Code 中输入/openai-realtime-voice确认已加载。

附带文件EVAL.md

详细介绍

OpenAI Realtime 语音

当交付内容是实时语音交互时,请使用此技能:例如浏览器语音助手、电话式 Agent、低延迟语音到语音支持流程、引导式面试、辅导对话、实时自助终端,或能够聆听、推理、说话、打断对话并可能调用工具的实时语音 UI。

不要将此技能用于批量语音转文本、离线 TTS、配音生成、音频编辑、配音、音乐或基于文件的音频任务。这些任务应使用单独的 OpenAI 音频/请求 API 或其他语音/音频技能。界限很简单:如果持久实时会话和轮流发言行为是产品的一部分,请使用此技能;如果任务是“将此文件/文本转换为文本/音频”,则不要使用。

以下所有 OpenAI API 事实均已于 2026-07-10 根据 OpenAI 第一方文档核实。由于 Realtime 功能变化很快,正式发布前请重新核对模型标识符、价格、会话限制、语音、API 对象字段、数据保留和政策要求。

每项设计都应考虑的来源依据事实

已记录的事实:

  • OpenAI 将 Realtime 会话定位为适用于需要低延迟的实时音频;基于请求的音频 API 则适用于文件、有明确边界的请求,或无需实时会话的语音生成。来源:Realtime 和音频概览。
  • 语音 Agent 会话适用于模型需要在 /v1/realtime 上响应用户、调用工具并管理对话状态的应用。翻译和转录会话是独立的 Realtime 会话类型。来源:Realtime 和音频概览。
  • OpenAI 的语音 Agent 指南区分了语音转语音实时音频会话与串联式 STT -> 文本推理 -> TTS 工作流。实时音频是实现插话、低首段音频延迟、自然轮流对话和实时工具调用的起点。来源:语音 Agent。
  • 文档说明的浏览器接入方式是 WebRTC,建议使用 Agents SDK 辅助工具,例如 RealtimeAgent 和 RealtimeSession;服务器到服务器的集成则使用 WebSocket。来源:语音 Agent、WebRTC 指南、WebSocket 指南。
  • Realtime 语音转语音会话包含一个 Session 对象、一个 Conversation 和多个 Responses。Session 控制模型、语音和配置;Conversation 包含用户和模型 Items;Responses 是模型生成的音频或文本 Items。来源:Realtime 对话。
  • 服务器发送 session.created 后,客户端可以使用 session.update。大多数会话属性都可以随时更新,但在模型于会话中首次以音频作出响应后,便无法更改语音。文档规定的 Realtime 会话最长时长为 60 分钟。来源:Realtime 对话。
  • 使用 WebSocket 传输音频时,客户端需通过输入音频缓冲区事件手动发送 base64 编码的音频;WebRTC 会处理大部分媒体传输。来源:Realtime 对话。
  • 语音转语音 Realtime 会话默认启用 VAD,可在 session.audio.input.turn_detection 中进行配置。文档列出的 VAD 模式为 server_vad 和 semantic_vad;服务器 VAD 可调整阈值、前缀填充和静音时长。来源:语音活动检测。
  • Realtime 会话可以使用由应用执行的函数工具、由远程 MCP 服务提供的 MCP 工具,以及内置连接器。工具可以附加到会话级别,也可以仅用于一次响应。来源:使用工具的 Realtime。
  • Realtime 语音 Agent 会话会对文本、音频和图像模态的输入与输出令牌计费;翻译和转录流式会话则按时长计费。后续对话轮次的费用更高,因为每次 Response 的输入都包含整个服务器端对话。OpenAI 说明的音频令牌计算方式为:用户消息音频每 100 毫秒计 1 个令牌,助手消息音频每 50 毫秒计 1 个令牌。来源:管理成本。
  • 输入音频转录是独立于 Realtime 模型原生音频理解的 ASR 过程;OpenAI 提醒,转录文本可能与模型的理解不同。应将转录文本视为可观测性和无障碍辅助材料,而非精确的推理输入。来源:Realtime API 参考。
  • OpenAI 声明,除非组织明确选择加入,否则不会使用发送至 API 的数据来训练或改进模型。来源:OpenAI 平台的数据控制。
  • OpenAI 的使用政策和安全最佳实践仍适用于 Realtime 语音体验,包括有害内容、冒充、隐私、高影响决策和用户保护方面的限制。来源:使用政策、安全最佳实践。 本技能中的生产实践启发并非 OpenAI 的承诺,而是为基于已记录的 API 功能构建语音产品的 Agent 提供的实用设计指导。

架构选择

在编写提示词或代码之前,先选择架构。

在以下情况下,使用语音到语音的 Realtime 语音 Agent 会话:

  • 延迟和自然的对话流程是产品的核心;
  • 用户应能打断助手;
  • 助手需要在实时音频交流过程中调用工具;
  • 语音行为、时机和轮流发言方式都是质量的一部分;
  • 会话可以安全地承载对话状态。

在以下情况下,则使用串联的 STT -> 文本 Agent -> TTS 流程:

  • 每项工具操作都必须由应用明确安排顺序;
  • 与自然语音到语音的时序相比,可确定的转录文本更重要;
  • 现有产品已有文本 Agent,语音只是输入/输出层;
  • 合规要求在语音合成前存储、检查或审批文本;
  • 真正的任务是批处理、文件、长录音、旁白或配音。

只有在产品需要流式转录文本增量、且不需要模型生成语音回复时,才使用 Realtime 转录模式。只有在产品是口译工具时,才使用 Realtime 翻译。不要将这些模式混入语音 Agent 架构。

会话与传输模式

围绕三个边界设计每种实现:客户端媒体、可信服务器和 OpenAI Realtime 会话。

对于浏览器或移动设备:

  1. 仅在用户明确同意后,才在本地采集麦克风音频。
  2. 请求可信服务器启动或授权 Realtime 会话。
  3. 尽可能使用 WebRTC 传输媒体和数据通道事件。
  4. 将标准 OpenAI API 密钥保存在服务器上。如果使用临时密钥流程,仅为预期会话签发短期凭证。
  5. 呈现助手音频,并显示明确的说话/聆听状态和醒目的停止/静音控件。

对于服务器到服务器的连接:

  1. 将音频采集和播放保留在受控基础设施、电话桥接或后端进程中。
  2. 仅从安全服务器使用标准 API 密钥连接到 wss://api.openai.com/v1/realtime?model=...。
  3. 明确发送会话更新、输入音频缓冲区事件和工具结果。
  4. 如果应用会分配稳定且保护隐私的安全标识符,请按 OpenAI 对 WebSocket 连接的文档添加相应请求头。
  5. 记录事件 ID、项目 ID、响应 ID、工具调用和用户可见结果,但不要记录原始密钥或不必要的音频。

不要将标准 API 密钥放入浏览器、移动应用、桌面软件包或可下载客户端。除非有意接受相关身份验证和媒体方面的权衡,否则应将浏览器发起的 WebSocket 连接视为特殊情况下的原型方案。

模型、语音和会话配置

已于 2026-07-10 验证:OpenAI 当前的语音 Agent 示例使用 gpt-realtime-2.1。文档和模型页面中可能还存在较旧或其他实时模型名称;实现时,请从当前模型参考中选择模型,并在日志和产物中记录确切的模型名称。

启动前需要记录的最低限度会话决策:

  • model:确切的 Realtime 模型标识符和验证日期;
  • transport:WebRTC、WebSocket,或适用时使用的 SIP/电话桥接;
  • session_type:语音 Agent、转录或翻译;
  • voice:确切的语音标识符;记录会话收到第一个音频响应后便无法更改语音;
  • modalities:输出为音频、文本还是两者兼有;
  • 手动处理音频时的 input_audio_format 和 output_audio_format;
  • turn_detection:server_vad、semantic_vad 或禁用/手动;
  • input_audio_transcription:是否启用、使用哪个 ASR 模型,以及转录文本是否对用户可见;
  • tools:工具列表、架构、执行负责人、审批策略和副作用;
  • retention/logging:是否存储原始音频、转录文本、事件和工具载荷;存储时长;以及有权访问的人员。

经验法则:在创建会话时设置语音,而不是连接后再设置。通话中途收到更改语音的请求时,应将其视为“使用新语音启动新会话”,除非当前文档明确更改了关于首次音频后语音不可变的说明。

轮次交接和打断设计

如果系统没有明确显示轮次状态,语音 UX 就会失效。请明确实现以下状态机:

  • idle:麦克风权限未启用或通话尚未开始;
  • listening:正在传输用户音频;
  • user_speaking:VAD 检测到语音开始;
  • thinking:用户轮次已提交,正在生成响应;
  • assistant_speaking:正在传输音频输出;
  • interrupted:用户插话或 UI 停止操作已取消/截断助手输出;
  • tool_pending:模型请求执行外部操作;
  • handoff_or_escalation:需要人工介入或切换到其他工作流;
  • ended:会话已关闭,摘要和产物已封存。

在普通环境中,默认的对话式助手可使用 server_vad。请调整以下参数:

  • 当用户希望快速来回对话且经常打断时,降低 silence_duration_ms;
  • 对于会在思考时停顿的用户、无障碍场景、语言学习者或复杂的支持案例,提高 silence_duration_ms;
  • 在嘈杂环境中提高 threshold;
  • 设置足够的 prefix_padding_ms,避免截掉第一个音节。

当基于静音的分段会截断长时间停顿的用户发言,或短语完整性比速度更重要时,可以考虑使用 semantic_vad。请务必结合真实口音、背景噪声和领域词汇进行充分测试。

只有在 UI 采用有意设计的按住说话或操作员控制工作流时,才应禁用自动 VAD 并手动提交轮次。如果关闭 VAD,应用程序就要负责管理轮次边界,并避免将意外采集的背景语音作为用户输入发送。

打断规则:

  • 提供清晰可见的“停止发言”或“将助手静音”控件。
  • 除非产品有法律或安全方面的理由,否则应允许用户语音打断较长的助手音频。
  • 被打断后,只总结实际说出的内容;不要假设用户听到了后续内容。
  • 对于工具调用,要区分打断语音播放和取消具有副作用的工具操作。副作用需要单独的取消语义。

语音 Agent 的提示词与指令设计

实时语音提示词需要明确约束行为,而这些行为往往是文本提示词默认隐含的。

在相关情况下,请加入以下指令块:

  1. 角色与目标:Agent 能提供哪些帮助,以及哪些事情不能处理。
  2. 语音风格:亲和度、正式程度、语速、简洁程度和填充词使用策略。
  3. 轮次长度:每次回答的最大句数或时长。
  4. 对话修复:如何请求用户重复、澄清不确定的音频,以及如何应对背景噪音。
  5. 工具策略:何时调用工具、何时征求许可,以及如何描述等待状态。
  6. 安全边界:受监管领域的建议、紧急情况、未成年人、身份验证,以及财务/法律/医疗免责声明。
  7. 隐私行为:除非必要,否则绝不索取敏感信息;遮盖秘密信息或避免大声读出。
  8. 语言策略:支持的语言、语码转换、发音,以及用户切换语言时的应对方式。
  9. 升级与转接:何时联系人工人员或结束会话。
  10. 输出风格:不使用 Markdown;除非口头表达自然,否则不使用列表;不说“作为一个 AI 语言模型”;工具结果返回前,不得假装已完成工具操作。

语音 Agent 启发式原则:

  • 优先给出简短回答:直接回答一个问题,再提出一个后续问题。
  • 避免密集罗列;如果答案包含三个以上步骤,请询问用户是否要继续。
  • 使用听起来自然的确认语:“我找到了这笔订单。它昨天已发货。”而不是“工具返回的状态等于已发货。”
  • 不要过度道歉;反复道歉会让语音对话显得拖沓。
  • 如果自动语音识别(ASR)结果不确定或名称有歧义,请确认关键信息:“您说的是订单 3917 吗?”
  • 面对情绪化的对话,先简短回应,再推进到有帮助的下一步。
  • 工具响应延迟时,只说明一次当前状态:“我现在查一下。”不要用猜测填补沉默。

工具和函数调用

只有在语音 Agent 需要实时数据或执行副作用时,才附加工具。工具不能替代提示词指令。

在以下情况下,请使用由应用执行的函数工具:

  • 应用拥有私有业务逻辑、凭据或策略检查;
  • 可能需要人工审批;
  • 操作会更改状态、花费资金、发送消息、预约、解锁门或修改记录;
  • 工具结果在播报前需要脱敏。

只有在组织批准了远程工具服务/连接器、授权边界明确,且日志记录了可能离开应用边界的数据时,才使用 MCP 或连接器工具。

工具设计规则:

  • 保持架构简洁明确。语音用户只需补充一个缺失字段,就能更快纠正错误。
  • 准确标记必填字段;不要强迫模型编造 ID。
  • 即使模型输出的 JSON 符合架构,也要在服务端验证参数。
  • 返回简洁、适合语音播报的结果,以及机器可读的状态字段。
  • 如果没有应用侧审批规则,绝不要让模型执行不可逆操作。
  • 对于支付、合同、医疗预约、账户变更或法律/财务决策,请在产生副作用前立即征求明确确认。
  • 如果工具失败,请说明发生了什么,并提供一个后续步骤;不要暴露堆栈跟踪信息。

工具审批策略示例:

Read-only tools may run automatically after user intent is clear.
Drafting tools may create but not send.
Side-effecting tools require explicit user confirmation in the same turn as execution.
High-impact tools require human operator approval outside the model.
The assistant must never claim completion until function_call_output confirms success.

延迟、成本和上下文控制

延迟预算是体验要求,而不只是工程指标。至少跟踪以下指标:

  • 麦克风权限授予到连接就绪;
  • 语音开始到 VAD speech_started;
  • 语音停止到创建响应;
  • 创建响应到助手首次输出音频;
  • 首段音频到最终音频;
  • 发出工具调用到返回工具结果;
  • 发出中断指令到音频停止。

成本控制:

  • 保持系统指令简洁而完整;冗长的指令会在会话上下文中反复出现。
  • 如果长会话不需要逐字保留历史记录,请总结或截断过时的对话 Items。
  • 避免为非语音或背景噪声创建 Responses;依靠 VAD 和 UI 静音。
  • 使用工具仅获取必要字段。
  • 及时结束空闲会话。
  • 判断是否需要转录文本;输入转录会按照 ASR 模型定价单独计费,而非按照 Realtime 模型定价计费。
  • 根据 OpenAI 文档中的经验法则估算音频 token 消耗:用户音频每 100 毫秒 1 个 token,助手音频每 50 毫秒 1 个 token,另加特殊 token 的差异。

经验法则:在生产仪表板中,按会话、用户轮次、助手音频时长、工具耗时以及放弃/空闲时间拆分成本。语音团队经常发现,助手长篇独白既是成本最高的部分,也是 UX 中最不实用的部分。

同意、权利、隐私和安全

采集语音前:

  • 明确说明使用麦克风的相关事宜,并在适用时说明是否会录音或存储转录文本。
  • 说明是否可能由人工审核音频/转录文本。
  • 适当时提供静音、停止、删除/结束会话和联系人工支持的途径。
  • 除非已落实法律/合规控制措施,否则避免收集儿童个人数据。
  • 除非产品针对特定用途已获得明确同意、具备法律依据,并得到服务提供方支持,否则避免通过语音识别生物特征。

对于生成的语音:

  • 除非确实是真人且已明确披露,否则不要将 Agent 说成真人。
  • 不要冒充他人、名人、员工、临床医生或公职人员。
  • 除非已依据适用的语音政策和当地法律记录相关权利和同意,否则不要克隆或模仿他人的声音。
  • 如果 Agent 代表某个品牌,请明确说明其品牌角色。

对于隐私:

  • 尽量减少原始音频的保留时间。如果事件元数据和经过脱敏的转录文本足以用于 QA,请优先使用这些数据。
  • 切勿记录 API 密钥、临时客户端密钥、Authorization 标头、连接器 token 或用户说出的未经脱敏的机密信息。
  • 将转录文本视为敏感信息,因为即使不存储音频,其中也可能包含 PII。
  • 如果使用工具/连接器,请记录从用户音频中提取的数据会发送到哪些外部系统。
  • 在应用程序中提供数据保留和删除机制,而不只是写在内部文档中。

对于安全:

  • 针对医疗、法律、金融、就业、住房、教育、紧急情况和自我伤害相关情境,添加特定领域的拒绝和升级处理机制。
  • 使用 OpenAI 建议的稳定且保护隐私的用户标识符作为安全标识符。
  • 使用对抗性音频进行评估:背景人声、第三方说出的提示词注入、附近有儿童、侮辱性言论、情绪困扰以及滥用工具的尝试。

审批、日志记录与产物保管

对于任何生产环境中的语音 Agent,都应创建可审计的运行记录。至少存储以下内容:

  • 模型/API 验证日期;
  • 模型、语音、传输方式、会话类型和会话配置;
  • 提示词/指令版本;
  • VAD 配置;
  • 工具架构、工具审批策略和连接器清单;
  • UI 中显示的用户同意文案;
  • 原始音频、转录文本、事件、工具载荷和摘要的保留策略;
  • 测试矩阵和结果;
  • 已知限制和升级处理途径。

每个会话都应记录:

  • 会话 ID 或应用程序关联 ID;
  • 用户安全标识符或内部用户 ID 哈希;尽可能避免记录原始个人身份信息;
  • 连接、语音事件、响应、中断、工具调用和关闭的时间戳;
  • API 事件中的令牌/时长用量;
  • 错误和恢复操作;
  • 产物保管信息:音频/转录文本/事件日志的存储位置、保留期限和访问级别。

切勿将评估或评分产物存储在生产 Agent 上下文中。产品提示词、测试夹具和评分材料应分开存放。

完整示例:浏览器客户支持语音 Agent

示例意图:已登录的电商用户可以通过语音查询订单状态、中断助手并请求转接人工客服。助手可以查询订单,但未经明确确认和服务器审批,不得取消订单或退款。

已记录并于 2026-07-10 验证的 API 选择:

  • 会话类型:语音 Agent Realtime 会话;
  • 模型:如果构建时的模型文档仍显示其为当前型号,则使用 gpt-realtime-2.1;
  • 传输方式:通过 Agents SDK 辅助工具使用浏览器 WebRTC;
  • VAD:server_vad,经嘈杂环境测试后调整;
  • 工具:由应用程序执行的 lookup_order、create_refund_draft、request_human_handoff;
  • 日志记录:保留事件元数据和经脱敏的转录文本;默认不保留原始音频,除非用户选择参与 QA 审核。

语音 Agent 指令:

# Role & objective
You are Acme Retail's voice support assistant. Help authenticated customers understand orders, shipping, returns, and simple account questions.

# Spoken style
Use a warm, calm retail-support voice. Keep answers to 1-2 short sentences, then ask one useful follow-up question. Do not use markdown or numbered lists unless the user asks for steps.

# Audio uncertainty
If a name, order number, address, or amount is uncertain, ask the user to confirm it before using a tool. If background noise may have changed intent, ask a clarifying question.

# Tools
Use lookup_order for order status. It is read-only and may run automatically after intent is clear.
Use create_refund_draft only after explaining the refund reason you heard. It creates a draft, not a refund.
Before any cancellation, refund, message send, or account change, ask for explicit confirmation. Never say an action is complete until the tool result says success.

# Privacy
Do not read full addresses, payment details, tokens, or secrets aloud. Mask sensitive fields. If the user starts sharing a card number or password, stop them and explain that you do not need it.

# Boundaries
For threats, self-harm, legal claims, medical issues, or harassment, move to the approved escalation route. If the user wants a human, call request_human_handoff.

服务器端临时会话创建模式:

// Example only. Re-check current OpenAI SDK and REST shapes before production.
import express from "express";
import OpenAI from "openai";

const app = express();
app.use(express.json());
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

app.post("/voice/session", requireLoggedInUser, async (req, res) => {
  const realtimeSession = await openai.beta.realtime.sessions.create({
    model: "gpt-realtime-2.1",
    voice: "alloy",
    instructions: SUPPORT_VOICE_INSTRUCTIONS,
    audio: {
      input: {
        turn_detection: {
          type: "server_vad",
          threshold: 0.55,
          prefix_padding_ms: 300,
          silence_duration_ms: 650
        }
      }
    },
    tools: [
      {
        type: "function",
        name: "lookup_order",
        description: "Look up an authenticated customer's order status.",
        parameters: {
          type: "object",
          properties: {
            order_number: { type: "string" }
          },
          required: ["order_number"]
        }
      }
    ]
  });

  audit.log("realtime_session_created", {
    user_hash: hashUser(req.user.id),
    model: "gpt-realtime-2.1",
    voice: "alloy",
    raw_audio_retention: "off"
  });

  res.json({ client_secret: realtimeSession.client_secret });
});

浏览器连接模式:

// Example only. Prefer current Agents SDK docs at implementation time.
import { RealtimeAgent, RealtimeSession } from "@openai/agents/realtime";

const agent = new RealtimeAgent({
  name: "Acme Support",
  instructions: SUPPORT_VOICE_INSTRUCTIONS,
});

const session = new RealtimeSession(agent, {
  model: "gpt-realtime-2.1",
});

async function startVoice() {
  await showConsentAndGetMicPermission();
  const auth = await fetch("/voice/session", { method: "POST" }).then(r => r.json());
  await session.connect({ apiKey: auth.client_secret.value });
  ui.setState("listening");
}

session.on("transport_event", (event) => {
  auditClientEvent(event);
  updateVoiceUI(event);
});

此示例采用这种结构的原因:

  • 浏览器不会接触标准 API 密钥。
  • 助手的角色范围有限,语音回复简短。
  • 工具副作用与只读查询分开处理。
  • 同意和数据保留规则都明确规定。
  • 虽然配置了 VAD,但仍需使用真实音频进行测试。

可能的故障模式:

  • 助手大声读出敏感的订单详情。
  • VAD 将犹豫用户报出的订单号截断。
  • 工具返回过多 JSON,导致助手逐字读出。
  • 前端记录了临时密钥。
  • 模型声称已完成退款,但实际上只创建了退款草稿。

完整示例:服务端访谈筛选器

示例意图:一个研究团队进行一场经同意的语音访谈,收集产品反馈。Agent 提出准备好的问题,自然地追问,为回答添加标签,并在每个环节结束后调用 save_answer。除非后端确认用户符合资格,否则不得声称用户会获得报酬。

架构:

  • 服务端到服务端的 WebSocket,因为研究平台通过受控基础设施录制访谈;
  • VAD:如果当前文档和模型支持所选会话,则使用 semantic_vad;否则使用调优后的 server_vad;
  • 输出:音频,以及供主持人仪表板使用的可选文本摘要;
  • 仅在用户同意的情况下,将原始音频保留 14 天;脱敏后的文字稿保留用于研究分析。

指令:

# Role & objective
You are a voice research moderator. Run a 12-minute interview about a beta productivity app. Ask one question at a time, listen actively, and save concise answer notes after each topic.

# Interview method
Use the approved topic order: onboarding, first useful moment, confusing moments, trust concerns, pricing reaction, final recommendation.
Ask one follow-up when an answer is vague. Do not argue, persuade, or defend the product.

# Spoken style
Sound curious and neutral. Keep your turns under 12 seconds. Leave space for the participant to think.

# Consent and privacy
If the participant asks about recording, say: "We record only if you opted in on the consent screen. You can stop at any time." Do not request passwords, payment details, or medical information.

# Tools
After each topic, call save_answer with topic, concise_summary, notable_quote_if_any, and sentiment. Do not invent quotes. If you are uncertain, omit the quote.
Call flag_for_human_review if the participant withdraws consent, reports a safety issue, or asks to delete data.

服务端 WebSocket 模式:

// Example only. Match event names and fields to the current Realtime API reference.
import WebSocket from "ws";

const ws = new WebSocket("wss://api.openai.com/v1/realtime?model=gpt-realtime-2.1", {
  headers: {
    Authorization: `Bearer ${process.env.OPENAI_API_KEY}`,
    "OpenAI-Safety-Identifier": hashParticipant(participantId),
  },
});

ws.on("open", () => {
  ws.send(JSON.stringify({
    type: "session.update",
    session: {
      type: "realtime",
      model: "gpt-realtime-2.1",
      voice: "alloy",
      instructions: INTERVIEW_INSTRUCTIONS,
      audio: {
        input: {
          turn_detection: {
            type: "server_vad",
            threshold: 0.5,
            prefix_padding_ms: 400,
            silence_duration_ms: 900
          }
        }
      },
      tools: [{
        type: "function",
        name: "save_answer",
        description: "Save a concise research note for the current interview topic.",
        parameters: {
          type: "object",
          properties: {
            topic: { type: "string" },
            concise_summary: { type: "string" },
            notable_quote_if_any: { type: "string" },
            sentiment: { type: "string", enum: ["positive", "neutral", "negative", "mixed"] }
          },
          required: ["topic", "concise_summary", "sentiment"]
        }
      }]
    }
  }));
});

ws.on("message", async (raw) => {
  const event = JSON.parse(raw.toString());
  logRealtimeEventMetadata(event);

  if (event.type === "response.function_call_arguments.done") {
    const result = await executeApprovedTool(event.name, event.arguments);
    ws.send(JSON.stringify({
      type: "conversation.item.create",
      item: {
        type: "function_call_output",
        call_id: event.call_id,
        output: JSON.stringify(result)
      }
    }));
    ws.send(JSON.stringify({ type: "response.create" }));
  }
});

此示例采用这种结构的原因:

  • 提示词强调研究方法,而不是客服角色设定。
  • 较长的 silence_duration_ms 给参与者留出思考时间。
  • save_answer 仅用于补充,且可审计,不是隐藏的决策引擎。
  • 撤回同意有专门的工具调用路径。

可能的失败模式:

  • 助手过快地填补沉默,影响访谈结果。
  • 将意译内容保存为引语。
  • 因未测试相关路径而漏掉撤回同意的请求。
  • 生成了精炼的摘要,却没有附带证据链接的研究记录。

发布前检查清单

只有满足以下条件,才能发布:

  • 已根据 OpenAI 文档重新核实模型、API 字段、价格假设、会话时长、语音列表、传输路径和政策限制,并注明日期。
  • 已记录架构选择,并将其与基于文件的 OpenAI 音频 API 区分开。
  • 标准 API 密钥仍保留在服务端;客户端凭证是临时的,或已通过其他方式限定权限。
  • 音频采集前,用户会看到有关麦克风使用和录音/转录的同意说明。
  • UI 提供静音/停止/结束控件,并清晰显示正在聆听/说话状态。
  • 已使用真实麦克风测试 VAD,涵盖不同口音、噪声、串音、停顿和打断情况。
  • 工具架构简洁,并在服务端验证;具有副作用的操作设有审批关卡。
  • 日志记录足够的信息以便调试、滥用审查和成本分析,同时不保留不必要的原始音频或机密信息。
  • 已通过对抗性语音场景测试安全、升级处理和人工接管行为。
  • 成本仪表板显示每个会话和每轮交互的用量,包括音频令牌数/时长和转录成本。
  • 团队已为会话失败、模型不可用、工具失败、音频卡住、用户撤回同意和数据删除请求准备运行手册。

快速故障排查

如果 Agent 抢在用户说话前开口:

  • 增大 silence_duration_ms,考虑使用语义 VAD,缩短助手的回答,并确保抢话时会取消音频播放。

如果开头的音节被截掉:

  • 增大 prefix_padding_ms,检查客户端麦克风缓冲,并使用轻声说话者进行测试。

如果 Agent 响应缓慢:

  • 分别测量首段音频延迟和回答总时长,精简冗长的提示词,缩短口头回答,减少不必要的工具,并在浏览器/移动设备上使用 WebRTC。

如果转录内容与模型的实际行为不一致:

  • 请记住,输入转录是独立的 ASR 流程;转录内容用于可观测性,而不能证明模型对音频的确切理解。

如果会话中的成本不断上升:

  • 检查对话内容是否不断增长、助手音频是否过长、是否有不必要的 Responses、是否存在背景噪声,以及转录设置。

如果工具操作不安全:

  • 将审批移至模型之外,要求明确确认,缩小架构范围,并返回适合语音播报的工具结果。

如果用户不信任语音:

  • 说明这是 AI 语音 Agent,告知用户录制或存储的内容,避免伪装成人类,并提供联系人工人员的途径。

评论

支持 Markdown