跳到主要内容

实时语音

语音通话和其他会话一样——有 id,消息列表里有转写内容,结束后有纪要——只是对话通过 WebSocket 进行:你的客户端把用户的麦克风音频上传,同时播放助手传下来的语音。

1. 开启会话​

curl -s "$NEWEYES_API_BASE/v1/realtime/sessions" -X POST \
-H "Authorization: Bearer $NEWEYES_USER_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'
{
"object": "realtime_session",
"id": "5e7a…",
"client_secret": { "value": "…", "expires_at": 1760861190 },
"websocket_url": "wss://…",
"model": "neweyes-voice-1"
}
  • client_secret.value 只能打开一次连接,而且只在 expires_at(Unix 秒,生成后 90 秒)之前有效。请立即连接。
  • 开启语音会话需要项目的计费账户有可用余额。
  • 请求体可以用 capabilities 或 exclude_capabilities 选择这个会话的能力,和文字会话一样(为会话选择能力)。语音助手会被告知会话没开哪些能力,因此不会提议、也不会委托这些能力。

2. 连接​

打开到 websocket_url 的 WebSocket,并在 Authorization 请求头里带上密钥:

Authorization: Bearer <client_secret.value>

浏览器无法在 WebSocket 上设置这个请求头,所以改为把密钥作为子协议,与 neweyes-realtime 一起提供:

const socket = new WebSocket(session.websocket_url, [
'neweyes-realtime',
`neweyes-secret.${session.client_secret.value}`,
]);

通话准备就绪时,服务端会发送 {"type": "session.ready"}。

3. 帧​

每一帧都是一条带 type 的 JSON 文本消息。

客户端 → 服务端

type字段内容
input_audioaudio:data(base64)、format(pcm16)、sample_rate用户的麦克风音频:原始 16 位小端 PCM,单声道。sample_rate 为 8000、16000(默认)、24000、44100 或 48000。请连续地分成小段发送。
input_imageimage:data(base64)、format用户展示的图片,例如一帧摄像头画面:jpeg(默认)或 jpg、png、webp、gif、heic 或 heif。
input_texttext用户在通话中输入的文字:最多 32,000 个字符。
tool_resulttool_call_id、name、output你对 tool_call 的答复(见下)。
{ "type": "input_audio", "audio": { "data": "AAABAAIA…", "format": "pcm16", "sample_rate": 16000 } }

服务端 → 客户端

type字段内容
session.ready—通话已就绪。
output_audioaudio:data(base64)、format(pcm16)、sample_rate(24000)助手的语音:原始 16 位小端 PCM,单声道,24 kHz。收到即播放。
input_transcripttext识别出的用户所说内容。
output_transcripttext助手所说的内容。
turn_complete—助手这一轮说完了。
interrupted—用户打断了助手:停止播放已排队的音频。
tool_calltool_call_id、name、arguments助手请你的客户端执行一个工具(见下)。

帧类型可能新增:遇到不认识的类型请忽略。

客户端发送的帧在以下情况会被丢弃,且没有任何回复:超过 4 MiB;音频或图片的 data 不是 base64;sample_rate 或图片 format 不在上面列出的范围内;input_text 超过 32,000 个字符或含有 NUL 字符;tool_result 的 output 以 JSON 计超过 32,000 个字符。

4. 工具​

语音助手通过两个工具把耗时较长的工作交给完整的助手处理。工具由你的客户端执行,并用带相同 tool_call_id 的 tool_result 答复:

  • create_task:arguments.task 描述了要做的事。把这段文字作为消息发到一个文字会话——POST /v1/sessions,或在你为这次通话保留的会话上 POST /v1/sessions/{session_id}/messages——并用返回的 task_id 答复。它的进度和回复照常在该会话的事件流上流式发送。创建这个文字会话时,请带上你为这次通话传的同样的 capabilities 或 exclude_capabilities:通话的选择不会自动带到文字会话上,不带的文字会话会打开全部能力。
  • get_task_status:arguments.task_id 指定一个任务(不填则为最近一个)。用事件流上该任务的情况答复:仍在运行,或已经结束——完成(附回复)、已停止或失败。
{ "type": "tool_result", "tool_call_id": "call_1", "name": "create_task", "output": { "task_id": "exec-5a2c…" } }

5. 关闭码​

WebSocket 没有响应体,所以连接因何结束由关闭码说明:

关闭码含义应对
1000正常关闭,或会话已结束。无需处理。
1001离开:一方正在结束会话。无需处理。
1011服务端错误,或语音服务暂时不可用。稍后重试。
1012服务端正在按计划重启更新。稍后重连(第 6 步)。
4001密钥缺失、无效、过期或已被使用;会话已被删除或已结束;或调用方的访问已被撤销。获取新的密钥——会话未结束时用同一个 session_id。
4009更新的连接接管了这个会话。不要从这个连接重连,否则两个连接会轮流抢占会话。
4401鉴权失败,或账号不是有效状态。检查用户及其令牌。
4429计费账户积分不足,或同时进行的语音会话太多。项目加上积分、或另一个会话结束后再试。

6. 断线后重新加入​

连接关闭并不会结束会话。要继续通话,带上它的 session_id 再次创建实时会话以获得新的密钥,然后重新连接:对话会从中断的地方继续。

curl -s "$NEWEYES_API_BASE/v1/realtime/sessions" -X POST \
-H "Authorization: Bearer $NEWEYES_USER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"session_id": "5e7a…"}'

重新加入的会话沿用它创建时的能力:不要传 capabilities 或 exclude_capabilities,否则请求会被拒绝,返回 422 validation_failed。

一个会话同一时间只由一条连接持有:再次连接会接管会话,旧连接以 4009 关闭。

7. 结束通话​

curl -s "$NEWEYES_API_BASE/v1/realtime/sessions/$SESSION_ID/end" -X POST \
-H "Authorization: Bearer $NEWEYES_USER_TOKEN"

持有会话的连接以 1000 关闭,会话的纪要开始生成。结束一个已经结束的会话,会原样返回它。

没有连接的会话会自行结束,但绝不会早于其最后一条连接关闭后的 10 分钟——这个时间可能变长,但不会缩短。在那之后重新加入会被拒绝,返回 409 session_ended:请开启新的会话。

通话之后​

  • GET /v1/sessions/{session_id} 读取语音会话(type 为 realtime)。
  • GET /v1/sessions/{session_id}/messages 列出它的转写内容。
  • GET /v1/sessions/{session_id}/brief 返回它的纪要。