实时语音
语音通话和其他会话一样——有 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_audio | audio:data(base64)、format(pcm16)、sample_rate | 用户的麦克风音频:原始 16 位小端 PCM,单声道。sample_rate 为 8000、16000(默认)、24000、44100 或 48000。请连续地分成小段发送。 |
input_image | image:data(base64)、format | 用户展示的图片,例如一帧摄像头画面:jpeg(默认)或 jpg、png、webp、gif、heic 或 heif。 |
input_text | text | 用户在通话中输入的文字:最多 32,000 个字符。 |
tool_result | tool_call_id、name、output | 你对 tool_call 的答复(见下)。 |
{ "type": "input_audio", "audio": { "data": "AAABAAIA…", "format": "pcm16", "sample_rate": 16000 } }
服务端 → 客户端
type | 字段 | 内容 |
|---|---|---|
session.ready | — | 通话已就绪。 |
output_audio | audio:data(base64)、format(pcm16)、sample_rate(24000) | 助手的语音:原始 16 位小端 PCM,单声道,24 kHz。收到即播放。 |
input_transcript | text | 识别出的用户所说内容。 |
output_transcript | text | 助手所说的内容。 |
turn_complete | — | 助手这一轮说完了。 |
interrupted | — | 用户打断了助手:停止播放已排队的音频。 |
tool_call | tool_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返回它的纪要。