用 OpenAI Realtime API 做一个能开口对话的语音智能体
打字太慢,对话才自然。传统语音 AI 是"听(STT)→ 想(LLM)→ 说(TTS)"三段接力,光 glue 就堆了约 600ms 延迟,还容易丢语气。OpenAI Realtime API(GPT-Realtime-2)直接在一个连接里"语音进、语音出",实测首字延迟能压到 1 秒出头。本教程用官方 OpenAI Agents SDK,带你搭一个会开口对话、能调工具、还能被中途打断的语音智能体。
先搞懂:Realtime 和普通语音 AI 差在哪?
一句话:它把"听→想→说"合成了一条直通隧道,没有中间的转写再生成环节。对比一下:
| 方案 | 延迟 | 体验 |
|---|---|---|
| STT→LLM→TTS 接力 | ~600ms+ 额外 glue | 机械、容易断语气 |
| Realtime 单连接 | p50 ~1.1s 往返 | 自然、支持打断 |
安全红线:浏览器端绝不能直接放你的标准 API Key。Realtime 用"临时密钥(ephemeral key)",由你的后端签发、约一分钟过期,前端只拿临时密钥连。这一步不能省。
Step 1:装好 Agents SDK
本教程用浏览器 + WebRTC 跑语音(最省事)。新建项目并安装推荐的包(需要 Zod v4):
# 用 Vite 起个空项目(或直接进你现有前端项目)
npm create vite@latest my-voice-agent -- --template vanilla-ts
cd my-voice-agent
# 安装 Agents SDK 与 Zod
npm install @openai/agents zod
openai-agents Python 包,from agents.realtime import RealtimeAgent 用法类似。本教程以 JS 为主线,思路互通。Step 2:后端签发临时密钥
在你的后端(Node/Python 都行)加一个接口,用真实 Key 换一个短命临时密钥:
// 后端示例(Node)
export async function mintEphemeralKey() {
const res = await fetch("https://api.openai.com/v1/realtime/client_secrets", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.OPENAI_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
session: { type: "realtime", model: "gpt-realtime-2" },
}),
});
const data = await res.json();
return data.value; // 以 ek_ 开头,约 1 分钟有效
}
真实 Key 永远只在后端:临时密钥 ek_... 前端才能用,过期即废。千万别把 OPENAI_API_KEY 写进前端代码或提交到仓库。
Step 3:创建你的语音 Agent
在前端用 RealtimeAgent 定义人设,和定义普通 Agent 很像,只是它输出的是语音:
import { RealtimeAgent } from "@openai/agents/realtime";
const agent = new RealtimeAgent({
name: "语音小助手",
instructions: "你是一个简洁友好的中文语音助手,回答要短、口语化,一次不超过三句话。",
// voice 可选:alloy / echo / shimmer 等
});
Step 4:建立连接并开口
用 RealtimeSession 通过 WebRTC 连接(自动处理麦克风采集和扬声器播放),传入 Step 2 的临时密钥:
import { RealtimeSession } from "@openai/agents/realtime";
const session = new RealtimeSession(agent, {
model: "gpt-realtime-2",
});
// 用后端换来的临时密钥连接
await session.connect({ apiKey: ephemeralKey });
// 连上后,直接对麦克风说话即可;
// 想用代码触发首句,也可以:
// session.sendMessage("你好,介绍一下你自己");
Step 5:给它"动手能力"(工具调用)
纯聊天不够,给它挂个工具(比如查订单),对话中它就能自动调用:
const lookupOrder = {
name: "lookup_order",
description: "根据用户说的订单号查询状态",
parameters: { type: "object", properties: { orderId: { type: "string" } } },
async execute({ orderId }) {
// 这里调你自己的后端 / 数据库
return { orderId, status: "已发货", eta: "今天 18:00 前" };
},
};
const agent = new RealtimeAgent({
name: "语音小助手",
instructions: "你是客服助手,需要查订单时调用 lookup_order 工具。",
tools: [lookupOrder],
});
工具调用要设审批:涉及"下单、退款、发消息"等写操作,开启 async_tool_calls 并在前端用 session.approve_tool_call() 让用户确认,防止 AI 自作主张。
Step 6:处理"打断"(Barge-in)
真人对话会打断。Realtime 原生支持:用户一开口,AI 正在说的语音会自动停止(通过 response.cancel)。WebSocket 模式下你要自己暂停采集;WebRTC 基本自动处理。关键是开启语义级语音活动检测(semantic VAD)让打断更自然:
const session = new RealtimeSession(agent, {
model: "gpt-realtime-2",
config: {
audio: {
input: { turnDetection: { type: "semantic_vad" } },
output: { modality: "audio" },
},
},
});
Step 7:调延迟与上线加固
想接到电话?用 Twilio 媒体流桥接(8kHz μ-law 需重采样到 24kHz PCM16)。上线前加固清单:
加固清单:
✅ 临时密钥只在后端签发,前端绝不出现标准 Key
✅ 写操作工具开启用户审批(approve_tool_call)
✅ 设置推理强度 reasoning.effort,平衡延迟与质量
✅ 监控 Token 用量,设预算上限
✅ 异常处理:断线自动重连 + 静音检测
自己搭还是买托管?若只是做个 Demo,Realtime API 很香;若要大规模接电话、做号码管理、合规录音,Retell / Vapi / Bland 这类托管平台能省很多运维。按团队精力取舍。
常见问题速查
| 你遇到的现象 | 大概率原因 & 解决 |
|---|---|
| 报 401 / 连接被拒 | 前端误用了标准 Key;改用后端签发的临时密钥 |
| 说话没反应 | 麦克风权限没授权,或 turnDetection 配置缺失 |
| 延迟明显偏高 | 关掉不必要的工具、降低 reasoning.effort、确认 semantic_vad |
| WebSocket 模式静音失效 | WS 不自动 mute,需自己暂停 sendAudio() |