登录注册
API 指南实时语音

实时语音

实时语音模型直接听语音、说语音:你把用户说的话(音频)发过去,模型用语音回答,同时给出文字稿。整个对话走一条 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 文档 · 实时语音。价格之外的其他模型见模型价格页。