Qwen Audio 实时语音
qwen-audio-3.0-realtime-plus 和 qwen-audio-3.1-realtime-plus 支持通过 WebSocket 持续发送文本或音频,并接收流式语音、文本和工具调用事件。实际可用性以账户权限和平台配置为准。
连接与鉴权
wss://cubicspaces.cloud/v1/realtime?model=qwen-audio-3.0-realtime-plus
wss://cubicspaces.cloud/v1/realtime?model=qwen-audio-3.1-realtime-plus握手时携带 Authorization: Bearer <CUBICSPACES_API_KEY>。这是 WebSocket 接口,请勿发送普通的 Chat Completions 请求。
Python 示例在服务端运行。浏览器原生 WebSocket 无法设置任意握手请求头;网页语音应用可由自己的后端建立连接并转发事件,平台 API Key 保存在后端。
可运行的完整示例
下载 qwen_realtime.py,安装 pip install websocket-client,然后设置环境变量。PowerShell 示例:
$env:CUBICSPACES_API_KEY = "你的平台 API Key"
python qwen_realtime.py --text "你好,请简短介绍自己。"
python qwen_realtime.py --model qwen-audio-3.1-realtime-plus --text "你好,请简短介绍自己。"
python qwen_realtime.py --mode audio --input question.wav --output answer.wav
python qwen_realtime.py --mode search --text "请联网查询杭州今天的天气,并给出来源。"
python qwen_realtime.py --mode functionmacOS / Linux 使用 export CUBICSPACES_API_KEY='你的平台 API Key' 设置变量。脚本支持文本转语音、音频文件转语音、联网搜索、真实客户端函数调用四种模式,输出为可直接播放的 24 kHz WAV。函数示例实际执行 17 + 25,把 42 回传给模型后接收语音回答;搜索和函数模式使用独立连接。
音频文件模式要求 16 kHz、单声道、16 位 PCM WAV。可先转换录音:
ffmpeg -i recording.wav -ar 16000 -ac 1 -c:a pcm_s16le question.wav脚本会读取 WAV 中的 PCM 数据,按每 20 ms 一块发送,不发送 WAV 文件头。它是文件输入示例;实时麦克风应用需要持续采集音频,并同时接收事件、播放音频。
文本输入与语音输出示例
两个版本使用相同的 WebSocket 事件协议。3.0 Plus 的默认音色为 longanqian,3.1 Plus 为 longanqian_v3.1。脚本的 --model 参数会选择相应默认音色,也可以用 --voice 指定音色。voice 只能在首次 session.update 中设置,后续修改会被忽略。
将下面的 3.0 示例迁移到 3.1 时,把 URL 中的模型改为 qwen-audio-3.1-realtime-plus,并把音色改为 longanqian_v3.1。输入仍为 16 kHz PCM,输出仍为 24 kHz PCM。
安装 websocket-client,在环境变量 CUBICSPACES_API_KEY 中设置平台 API Key:
pip install websocket-clientimport base64
import json
import os
import websocket
url = "wss://cubicspaces.cloud/v1/realtime?model=qwen-audio-3.0-realtime-plus"
ws = websocket.create_connection(
url,
header={"Authorization": "Bearer " + os.environ["CUBICSPACES_API_KEY"]},
timeout=60,
)
def send(event):
ws.send(json.dumps(event))
try:
with open("reply.pcm", "wb") as audio:
while True:
event = json.loads(ws.recv())
kind = event.get("type")
if kind == "session.created":
send({"type": "session.update", "session": {
"modalities": ["text", "audio"],
"voice": "longanqian",
"instructions": "请简短回答。",
"turn_detection": {"type": "smart_turn"}
}})
elif kind == "session.updated":
send({"type": "conversation.item.create", "item": {
"type": "message", "role": "user",
"content": [{"type": "input_text", "text": "你好"}]
}})
send({"type": "response.create"})
elif kind == "response.audio.delta":
audio.write(base64.b64decode(event["delta"]))
elif kind in ("response.text.delta", "response.audio_transcript.delta"):
print(event.get("delta", ""), end="", flush=True)
elif kind == "response.done":
print("\nResponse status:", event["response"].get("status"))
break
elif kind == "error":
raise RuntimeError(event.get("error"))
finally:
ws.close()输出文件是 24 kHz、16 位、单声道裸 PCM,不包含 WAV 文件头。播放示例:
ffplay -f s16le -ar 24000 -ac 1 reply.pcm音频输入
输入必须是 16 kHz、16 位、单声道 PCM,每块音频以 Base64 编码,通过 input_audio_buffer.append 发送。输入和输出采样率不同,客户端需按方向分别处理。
{"type":"input_audio_buffer.append","audio":"<Base64 PCM>"}使用 server_vad 或 smart_turn 时,由服务自动检测说话结束。在手动模式下,首次 session.update 设置 turn_detection: null;发送音频后依次发送:
{"type":"input_audio_buffer.commit"}
{"type":"response.create"}上述两行是两条独立的 WebSocket 消息。turn_detection 应在发送第一块音频前设置。
事件与工具调用
session.created/session.updated:会话创建或配置完成。response.audio.delta:Base64 音频分块。response.audio_transcript.delta/response.text.delta:增量文本。response.function_call_arguments.delta/.done:工具参数增量或完成。客户端执行工具后,用conversation.item.create提交function_call_output,再发送response.create。response.done:本轮结束;检查response.status,区分completed、cancelled和failed。error:错误详情位于error对象中。
音频、文本、VAD、工具调用和自定义事件字段保持原始格式;显式的 false、0 和 null 不会在转发时被移除。response.cancel 用于取消正在进行的回复。收到新的用户语音或取消事件后,客户端应清空尚未播放的旧音频。
此接口提供 WebSocket 接入,不提供 WebRTC 或 AOQ 连接。
打断与继续对话
回复进行中发送 response.cancel,会收到 status: cancelled、status_details.reason: client_cancelled 的 response.done。连接保持可用,可以提交下一条消息并发送 response.create。
在 server_vad 模式下,回复过程中检测到新的用户语音也会取消当前回复,原因为 turn_detected;随后继续处理新语音并自动生成下一轮回复。客户端应在检测到插话时立即停止播放并清空旧音频队列。
取消事件可能不含 usage,客户端不要将其误判为连接失败,也不要假设每个 response.done 都有该字段。
主动取消并继续对话的消息顺序如下。先发送第一条,等待当前回复的 response.done 确认取消,再发送后两条:
{"type":"response.cancel"}
{"type":"conversation.item.create","item":{"type":"message","role":"user","content":[{"type":"input_text","text":"换个话题,请只回答好的。"}]}}
{"type":"response.create"}自动插话模式在首次配置中设置:
{"type":"session.update","session":{"modalities":["text","audio"],"voice":"longanqian","turn_detection":{"type":"server_vad","silence_duration_ms":500}}}持续发送麦克风音频;收到 input_audio_buffer.speech_started 时立即停止当前播放,清空待播放队列,并丢弃已经取消的旧回复后续音频。可按音频事件的 response_id 管理每轮队列,避免取消后迟到的旧音频混入新回复。自动模式不需要每次发送 input_audio_buffer.commit 和 response.create;不要在模型正在回复时重复触发生成。
联网搜索与 Function Calling
通过 session.update 中的 enable_search: true 启用搜索;需要来源时,同时设置 search_options: {"enable_source": true}。模型自行决定是否搜索,可检查最终响应的 search_info.search_results。搜索结果不保证回答事实准确。
tools 与 enable_search 不能同时启用。工具定义使用 {"type":"function","function":{"name":"...","parameters":{...}}}。客户端执行函数后,通过同一个 call_id 的 function_call_output 回传结果,再发送 response.create。
搜索会话配置示例,等待 session.updated 后再发送问题:
{"type":"session.update","session":{"modalities":["text","audio"],"voice":"longanqian","enable_search":true,"search_options":{"enable_source":true}}}完整 Python 示例中的 --mode function 包含工具声明、完整参数接收、参数检查、函数执行、结果回传和后续语音接收。工具结果中的 output 是 JSON 字符串,call_id 必须取自该次工具调用,不能自行生成。客户端只应执行自己声明并允许的函数。
常见问题
| 现象 | 检查方法 |
|---|---|
| 握手未返回 HTTP 101 | 检查 WebSocket 地址、平台 API Key 和模型使用权限。 |
| 音频变速或噪声 | 分别检查输入 16 kHz、输出 24 kHz;均为单声道、16 位小端 PCM,发送时不包含 WAV 文件头。 |
| 发送音频后没有回复 | 手动模式需依次 commit 和 response.create;自动模式需发送完整语音并留出静音结束段。 |
| 已取消仍播放旧回复 | 取消生成不会清空客户端播放器;停止播放、清空队列并按 response_id 丢弃旧分块。 |
| 工具调用后没有最终回答 | 等参数接收完整后执行函数,回传相同 call_id 的 function_call_output,再触发 response.create。 |
收到 error 时读取错误对象;收到 response.done 时检查状态。连接关闭后重新建立连接会创建新会话,需要重新发送配置;不要将重连当作旧会话自动恢复。