实时语音
实时语音模型直接听语音、说语音:你把用户说的话(音频)发过去,模型用语音回答,同时给出文字稿。整个对话走一条 WebSocket 连接,首段语音一般在 1~2 秒内开始返回,适合做语音助手、口语陪练、电话客服等。接口与 OpenAI Realtime API 兼容。想先听听效果,可以在控制台的 Playground 里点输入框旁的麦克风,直接和它语音对话。
| 参数 | 值 |
|---|---|
| 地址 | https://api.tokenjoy.org/v1/realtime?model=gpt-realtime |
| 认证 | 请求头 Authorization: Bearer <你的 API Key> |
| 音频格式 | 默认 24 kHz、16 位、单声道 PCM(小端),base64 编码后放进 JSON 事件 |
| 接口说明 | API 文档 · 实时语音 |
可用模型
| 模型 | 说明 |
|---|---|
gpt-realtime | 效果最好,适合对话质量要求高的场景 |
gpt-realtime-mini | 价格约为前者的三分之一,速度相近,适合大量调用 |
实时语音模型不会出现在 GET /v1/models 的列表里,直接在地址的 model 参数中填写即可。
第一次调用
连接后先用 session.update 设置回答方式和音色,再发一条文字消息,用 response.create 让模型回答。模型的语音以 response.output_audio.delta 分段返回,一轮回答以 response.done 结束,其中带有本轮的 token 用量。
需要 pip install websockets。把模型的回答保存为 reply.wav,同时打印文字稿。
import asyncio, base64, json, os, wave
import websockets
URL = "https://api.tokenjoy.org/v1/realtime?model=gpt-realtime"
async def main():
headers = {"Authorization": f"Bearer {os.environ['TOKENJOY_API_KEY']}"}
async with websockets.connect(URL, additional_headers=headers, max_size=None) as ws:
await ws.send(json.dumps({"type": "session.update", "session": {
"type": "realtime",
"output_modalities": ["audio"],
"instructions": "你是一个友好的中文语音助手,回答简洁。",
"audio": {"output": {"voice": "marin"}},
}}))
await ws.send(json.dumps({"type": "conversation.item.create", "item": {
"type": "message", "role": "user",
"content": [{"type": "input_text", "text": "你好,用一句话介绍一下你自己。"}],
}}))
await ws.send(json.dumps({"type": "response.create"}))
audio = bytearray()
async for message in ws:
event = json.loads(message)
if event["type"] == "response.output_audio.delta":
audio += base64.b64decode(event["delta"])
elif event["type"] == "response.output_audio_transcript.delta":
print(event["delta"], end="", flush=True)
elif event["type"] == "error":
raise RuntimeError(event["error"])
elif event["type"] == "response.done":
print("\nusage:", event["response"]["usage"])
break
# 输出是 24 kHz、16 位、单声道 PCM,加上 WAV 头就能直接播放
with wave.open("reply.wav", "wb") as f:
f.setnchannels(1)
f.setsampwidth(2)
f.setframerate(24000)
f.writeframes(audio)
asyncio.run(main())发送语音
用 input_audio_buffer.append 把音频分段发过去。默认开着服务端语音检测(server_vad):检测到用户停顿就自动结束这句话并开始回答,适合接麦克风实时说话。处理录好的音频文件时,可以像下面这样关掉检测,发完后自己 commit 并请求回答:
import asyncio, base64, json, os, wave
import websockets
URL = "https://api.tokenjoy.org/v1/realtime?model=gpt-realtime"
async def main():
with wave.open("question.wav") as f: # 24 kHz、16 位、单声道
pcm = f.readframes(f.getnframes())
headers = {"Authorization": f"Bearer {os.environ['TOKENJOY_API_KEY']}"}
async with websockets.connect(URL, additional_headers=headers, max_size=None) as ws:
# 关掉服务端语音检测,由我们自己决定一句话什么时候说完
await ws.send(json.dumps({"type": "session.update", "session": {
"type": "realtime",
"output_modalities": ["audio"],
"audio": {"input": {"turn_detection": None}, "output": {"voice": "marin"}},
}}))
for i in range(0, len(pcm), 48000): # 每次发 1 秒音频
chunk = base64.b64encode(pcm[i:i + 48000]).decode()
await ws.send(json.dumps({"type": "input_audio_buffer.append", "audio": chunk}))
await ws.send(json.dumps({"type": "input_audio_buffer.commit"}))
await ws.send(json.dumps({"type": "response.create"}))
audio = bytearray()
async for message in ws:
event = json.loads(message)
if event["type"] == "response.output_audio.delta":
audio += base64.b64decode(event["delta"])
elif event["type"] == "error":
raise RuntimeError(event["error"])
elif event["type"] == "response.done":
print("usage:", event["response"]["usage"])
break
with wave.open("answer.wav", "wb") as f:
f.setnchannels(1)
f.setsampwidth(2)
f.setframerate(24000)
f.writeframes(audio)
asyncio.run(main())多轮对话在同一条连接里继续发送即可,服务端会记住这条连接里之前说过的内容,不需要重复发送历史。
在网页里使用
浏览器自带的 WebSocket 不能设置 Authorization 请求头,而且 API Key 不能放进网页代码,否则任何访问者都能拿去使用。请在你自己的后端做一层转发:浏览器连你的后端,后端校验登录后带上 Key 连接 TokenJoy,再把消息原样双向转发。
// 需要 npm install ws。浏览器连到这个服务,由它带上 Key 转发到 TokenJoy。
import { WebSocketServer } from "ws";
const UPSTREAM = "https://api.tokenjoy.org/v1/realtime?model=gpt-realtime";
const server = new WebSocketServer({ port: Number(process.env.PORT ?? 8080) });
server.on("connection", (browser, request) => {
// 在这里校验你自己网站的登录状态,不通过就 browser.close(1008)
const upstream = new WebSocket(UPSTREAM, { headers: { Authorization: `Bearer ${process.env.TOKENJOY_API_KEY}` } });
const pending = [];
upstream.onopen = () => pending.splice(0).forEach((m) => upstream.send(m));
upstream.onmessage = (m) => browser.send(m.data);
upstream.onclose = () => browser.close();
browser.on("message", (m) => {
const text = m.toString();
if (upstream.readyState === WebSocket.OPEN) upstream.send(text);
else pending.push(text);
});
browser.on("close", () => upstream.close());
});计费
每分钟大约花多少
一来一回的正常对话(双方轮流说话),按通话时长估算每分钟的花费,单位美元:
| 模型 | 本站价(约) | 官方价(约) |
|---|---|---|
gpt-realtime | — | $0.09–0.11 / 分钟 |
gpt-realtime-mini | — | $0.020–0.030 / 分钟 |
这是本站 2026-10 在线上实测的范围,用户和模型各说一半左右。实际按 token 扣费:模型说得多、对话越长,每分钟越贵;只用文字回答会便宜很多。
按 token 的单价
与其他模型一样,按官方价计价、按实际 token 用量从余额扣费,本站折扣同样适用。每轮回答结束时按 response.done 中的用量结算,音频和文字分开计价(官方价,美元 / 百万 token):
| 模型 | 音频输入 | 音频输入(缓存) | 音频输出 | 文字输入 | 文字输入(缓存) | 文字输出 |
|---|---|---|---|---|---|---|
gpt-realtime | $32 | $0.40 | $64 | $4 | $0.40 | $16 |
gpt-realtime-mini | $10 | $0.30 | $20 | $0.60 | $0.06 | $2.40 |
- 换算到时长:用户说 1 分钟约 600 个音频输入 token,模型说 1 分钟约 1,200 个音频输出 token。
- 同一条连接里,之前的对话会在每轮作为输入再计一次,所以对话越长每分钟越贵;重复的部分大多按便宜很多的缓存价计。内容无关的长对话可以断开重连,从头开始。
- 用量在控制台「概览」页的「用量明细」里按模型列出,大约每 30 秒结算一次。见 Token 用量计算。
限制
- 目前只支持 WebSocket 连接,不支持 WebRTC。
- 单条连接有时长上限(默认 60 分钟),到时服务端以关闭码 1001 结束连接,需要时重新连接即可。
- 余额用完时无法建立新连接;通话中途用完时,连接会在你发送下一条消息时以关闭码 1008 断开。
- 关闭码和错误的含义见 API 文档 · 实时语音。价格之外的其他模型见模型价格页。