Qwen-Omni-Realtime
Qwen-Omni-Realtime 是千問推出的即時音視訊交談模型。能同時理解流式的音頻與映像輸入(例如從視頻流中即時抽取的連續映像幀),並即時輸出高品質的文本與音頻。
支援的地區:新加坡、北京,需使用各地區的 API Key。
如何使用
1. 建立串連
Qwen-Omni-Realtime 模型支援 WebSocket 和 WebRTC 兩種協議接入。WebSocket 適合服務端整合和快速接入;WebRTC 適合瀏覽器端、低延遲語音情境,音頻通過 UDP 直接傳輸,內建回聲消除和降噪。
WebSocket
WebSocket 原生串連
串連時需要以下配置項:
|
配置項 |
說明 |
|
調用地址 |
華北2(北京)地區:wss://dashscope.aliyuncs.com/api-ws/v1/realtime 新加坡地區:wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/realtime,其中WorkspaceId需替換為真實的業務空間 ID。 |
|
查詢參數 |
查詢參數為model,需指定為訪問的模型名。樣本: |
|
要求標頭 |
使用 Bearer Token 鑒權:Authorization: Bearer DASHSCOPE_API_KEY DASHSCOPE_API_KEY 是您在百鍊上申請的API Key。 |
# pip install websocket-client
import json
import websocket
import os
API_KEY=os.getenv("DASHSCOPE_API_KEY")
API_URL = "wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/realtime?model=qwen3.5-omni-plus-realtime"
headers = [
"Authorization: Bearer " + API_KEY
]
def on_open(ws):
print(f"Connected to server: {API_URL}")
def on_message(ws, message):
data = json.loads(message)
print("Received event:", json.dumps(data, indent=2))
def on_error(ws, error):
print("Error:", error)
ws = websocket.WebSocketApp(
API_URL,
header=headers,
on_open=on_open,
on_message=on_message,
on_error=on_error
)
ws.run_forever()
DashScope Python SDK
# SDK 版本不低於1.23.9
import os
import json
from dashscope.audio.qwen_omni import OmniRealtimeConversation,OmniRealtimeCallback
import dashscope
# 新加坡和北京地區的API Key不同。擷取API Key:https://www.alibabacloud.com/help/zh/model-studio/get-api-key
# 若沒有配置 API Key,請將下行改為 dashscope.api_key = "sk-xxx"
dashscope.api_key = os.getenv("DASHSCOPE_API_KEY")
class PrintCallback(OmniRealtimeCallback):
def on_open(self) -> None:
print("Connected Successfully")
def on_event(self, response: dict) -> None:
print("Received event:")
print(json.dumps(response, indent=2, ensure_ascii=False))
def on_close(self, close_status_code: int, close_msg: str) -> None:
print(f"Connection closed (code={close_status_code}, msg={close_msg}).")
callback = PrintCallback()
conversation = OmniRealtimeConversation(
model="qwen3.5-omni-plus-realtime",
callback=callback,
# 以下為新加坡地區URL,調用時請將WorkspaceId替換為真實的業務空間ID,各地區的URL不同。
url="wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/realtime"
)
try:
conversation.connect()
print("Conversation started. Press Ctrl+C to exit.")
conversation.thread.join()
except KeyboardInterrupt:
conversation.close()
DashScope Java SDK
// SDK 版本不低於 2.20.9
import com.alibaba.dashscope.audio.omni.*;
import com.alibaba.dashscope.exception.NoApiKeyException;
import com.google.gson.JsonObject;
import java.util.concurrent.CountDownLatch;
public class Main {
public static void main(String[] args) throws InterruptedException, NoApiKeyException {
CountDownLatch latch = new CountDownLatch(1);
OmniRealtimeParam param = OmniRealtimeParam.builder()
.model("qwen3.5-omni-plus-realtime")
.apikey(System.getenv("DASHSCOPE_API_KEY"))
// 以下為新加坡地區URL,調用時請將WorkspaceId替換為真實的業務空間ID,各地區的URL不同。
.url("wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/realtime")
.build();
OmniRealtimeConversation conversation = new OmniRealtimeConversation(param, new OmniRealtimeCallback() {
@Override
public void onOpen() {
System.out.println("Connected Successfully");
}
@Override
public void onEvent(JsonObject message) {
System.out.println(message);
}
@Override
public void onClose(int code, String reason) {
System.out.println("connection closed code: " + code + ", reason: " + reason);
latch.countDown();
}
});
conversation.connect();
latch.await();
conversation.close(1000, "bye");
System.exit(0);
}
}
WebRTC
WebRTC 建立串連分為兩個階段:
-
SDP 交換(HTTP):用戶端先將自身的媒體能力和網路地址(Offer SDP)通過 HTTP POST 發送給服務端,服務端返回自身資訊(Answer SDP),雙方完成能力協商。
-
建連(自動):協商完成後,WebRTC 底層自動建立音頻傳輸通道。
SDP 交換的配置項:
|
配置項 |
說明 |
|
請求地址 |
POST https://{endpoint}/api/v1/webrtc/realtime WebRTC 功能目前為白名單開放,請聯絡商務經理擷取 Endpoint。 |
|
查詢參數 |
查詢參數為model,需指定為訪問的模型名。樣本: |
|
Content-Type |
application/sdp |
|
要求標頭 |
Authorization: Bearer DASHSCOPE_API_KEY |
|
請求體 |
用戶端產生的 Offer SDP 字串 |
|
響應 |
成功:HTTP 200,返回服務端 Answer SDP 字串。失敗:HTTP 4xx,返回 JSON 錯誤資訊。 |
建連範例程式碼:
# pip install aiortc aiohttp certifi
import asyncio, aiohttp, ssl, certifi
from aiortc import RTCPeerConnection, RTCConfiguration, RTCSessionDescription
from aiortc.mediastreams import AudioStreamTrack
API_KEY = "your-api-key"
MODEL = "qwen3.5-omni-plus-realtime"
SIGNALING_URL = f"https://{{endpoint}}/api/v1/webrtc/realtime?model={MODEL}"
async def connect():
pc = RTCPeerConnection(RTCConfiguration(iceServers=[]))
# 添加音頻軌道,確保 Offer SDP 包含 m=audio(服務端必需)
pc.addTrack(AudioStreamTrack())
# 建立 DataChannel 以觸發 SDP 協商(名稱可自訂,服務端會通過名為 "txt" 的通道推送事件)
pc.createDataChannel("oai-events")
# SDP 交換:建立 Offer 並發送到服務端
offer = await pc.createOffer()
await pc.setLocalDescription(offer)
async with aiohttp.ClientSession() as session:
async with session.post(
SIGNALING_URL,
ssl=ssl.create_default_context(cafile=certifi.where()),
data=offer.sdp.encode("utf-8"),
headers={
"Content-Type": "application/sdp",
"Authorization": f"Bearer {API_KEY}",
},
) as resp:
if not resp.ok:
raise Exception(f"SDP 交換失敗: {resp.status} {await resp.text()}")
answer_sdp = await resp.text()
print("=== Offer SDP ===")
print(offer.sdp)
print("=== Answer SDP ===")
print(answer_sdp)
# ICE 建連自動完成
await pc.setRemoteDescription(RTCSessionDescription(sdp=answer_sdp, type="answer"))
print("WebRTC 串連已建立")
return pcconst API_KEY = 'your-api-key';
const API_URL = 'https://{endpoint}/api/v1/webrtc/realtime?model=qwen3.5-omni-plus-realtime';
async function connect() {
const pc = new RTCPeerConnection({ iceServers: [] });
// 添加音頻軌道,確保 Offer SDP 包含 m=audio(服務端必需)
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
stream.getAudioTracks().forEach(t => pc.addTrack(t, stream));
// 建立 DataChannel 以觸發 SDP 協商(名稱可自訂,服務端會通過名為 "txt" 的通道推送事件)
pc.createDataChannel('oai-events');
// 等待 ICE 收集完成後發送 Offer 擷取 Answer
pc.onicegatheringstatechange = async () => {
if (pc.iceGatheringState !== 'complete') return;
const resp = await fetch(API_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/sdp',
'Authorization': `Bearer ${API_KEY}`,
},
body: pc.localDescription.sdp,
});
if (!resp.ok) throw new Error('SDP 交換失敗: ' + resp.status);
const answerSdp = await resp.text();
// ICE 建連自動完成
await pc.setRemoteDescription({ type: 'answer', sdp: answerSdp });
console.log('WebRTC 串連已建立');
};
// 建立 Offer
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
return pc;
}2. 配置會話
發送用戶端事件session.update:
{
// 該事件的id,由用戶端產生
"event_id": "event_ToPZqeobitzUJnt3QqtWg",
// 事件類型,固定為session.update
"type": "session.update",
// 會話配置
"session": {
// 輸出模態,支援設定為["text"](僅輸出文本)或["text","audio"](輸出文本與音頻)。
"modalities": [
"text",
"audio"
],
// 輸出音訊音色
"voice": "Ethan",
// 輸入音頻格式,當前僅支援設定為pcm。輸入音頻為16 kHz採樣率的PCM音頻流。
"input_audio_format": "pcm",
// 輸出音頻格式,當前僅支援設定為pcm。輸出音頻為24 kHz採樣率的PCM音頻流。
"output_audio_format": "pcm",
// 系統訊息,用於設定模型的目標或角色。
"instructions": "你是某五星級酒店的AI客服專員,請準確且友好地解答客戶關於房型、設施、價格、預訂政策的諮詢。請始終以專業和樂於助人的態度回應,杜絕提供未經證實或超出酒店服務涵蓋範圍的資訊。",
// 是否開啟語音活動檢測。若需啟用,需傳入一個設定物件,服務端將據此自動檢測語音起止。
// 設定為null表示由用戶端決定何時發起模型響應。
"turn_detection": {
// VAD類型,取值為server_vad或semantic_vad。使用qwen3.5-omni-realtime模型時推薦設為semantic_vad。
"type": "semantic_vad",
// VAD檢測閾值。建議在嘈雜的環境中增加,在安靜的環境中降低。
"threshold": 0.5,
// 檢測語音停止的靜音期間,超過此值後會觸發模型響應
"silence_duration_ms": 800
}
}
}
3. 輸入音頻與圖片
音頻輸入是必需的;圖片輸入是可選的。輸入方式取決於接入協議。
WebSocket
用戶端通過input_audio_buffer.append和 input_image_buffer.append 事件發送 Base 64 編碼的音頻和圖片資料到服務端緩衝區。
圖片可以來自本地檔案,或從視頻流中即時採集。
啟用服務端VAD時,服務端會在檢測到語音結束時自動認可資料並觸發響應。禁用VAD時(手動模式),用戶端必須在發送完資料後,主動調用input_audio_buffer.commit事件來提交。
WebRTC
建連時添加的音頻軌道和視頻軌道(即 RTP 媒體通道)會自動將資料轉送到服務端。
-
音頻:通過音頻軌道(RTP)直接傳輸,無需發送
input_audio_buffer.append事件。 -
圖片:通過視頻軌道(RTP)發送畫面幀,不支援
input_image_buffer.append事件。
WebRTC 僅支援服務端 VAD 模式(server_vad或semantic_vad),不支援手動模式。
4. 接收模型響應
模型的響應格式取決於配置的輸出模態。
WebSocket
-
僅輸出文本
通過response.text.delta事件接收流式文本,response.text.done事件擷取完整文本。
-
輸出文本+音頻
-
文本:通過response.audio_transcript.delta事件接收流式文本,response.audio_transcript.done事件擷取完整文本。
-
音頻:通過response.audio.delta事件擷取 Base 64 編碼的流式輸出音頻資料。response.audio.done事件標誌音頻資料產生完成。
-
WebRTC
-
僅輸出文本
通過 DataChannel 接收
response.text.delta和response.text.done等流式文本事件。 -
輸出文本+音頻
-
文本:通過 DataChannel 接收
response.text.delta和response.text.done等流式文本事件。 -
音頻:通過 RTP 軌道即時接收和播放,無需通過
response.audio.delta事件擷取音頻資料。
-
模型選型
Qwen3.5-Omni-Realtime 是千問最新推出的即時多模態模型,相比於上一代的 Qwen3-Omni-Flash-Realtime:
-
智能水平
模型智力大幅提升,與 Qwen3.5-Plus 智能水平相當。
-
連網搜尋
原生支援連網搜尋(WebSearch),模型可自主判斷是否需要搜尋來回應即時問題。詳見連網搜尋。
-
工具調用
支援 Function Calling,模型可自主判斷是否需要調用外部工具,實現與外部系統的互動。詳見Qwen-Omni-Realtime 系列。
-
語義打斷
自動識別對話意圖,避免附和聲和無意義背景音觸發打斷。
-
語音控制
通過語音指令控制聲音大小、語速和情緒,如“語速快一些”、“聲音大一些”、“用開心的語氣”等。
-
支援的語言
支援 113 種語種和方言的語音辨識,以及 36 種語種和方言的語音產生。
-
支援的音色
支援 55 種音色(47 種多語言 + 8 種方言),具體可查看音色列表。
-
聲音複刻
qwen3.5-omni-plus-realtime 和 qwen3.5-omni-flash-realtime 支援聲音複刻功能,可使用自訂音色進行即時對話。詳見聲音複刻。
模型的名稱、上下文、價格、快照版本等資訊請參見百鍊控制台;並發限流條件請參考限流。
使用限制
-
連網搜尋和工具調用不相容,不可同時開啟。
-
單次會話最長可持續 120 分鐘,達到此上限後服務將主動關閉串連。
-
模型會維護對話歷史上下文,當對話輪次或累計時間長度超過以下限制時,將自動丟棄更早的歷史資訊。最大時間長度指模型上下文中能保留的音頻或視頻(映像幀)累計時間長度上限。
由於視頻以抽幀方式輸入(建議 1 幀/秒),視頻最大時間長度即模型能保留的映像幀累計時間長度。例如 240 秒錶示模型最多保留最近 240 秒內收到的幀,超過後更早的幀將被丟棄。
qwen3-omni-flash-realtime 最大輪次為 8 輪,一般會先觸及輪次限制,時間長度限制為模型的上下文長度限制,不再單獨列出。
模型
音頻最大輪次
視頻最大輪次
音頻最大時間長度
視頻最大時間長度
qwen3.5-omni-plus-realtime
100輪
50輪
600秒
240秒
qwen3.5-omni-flash-realtime
80輪
50輪
480秒
120秒
qwen3-omni-flash-realtime
8輪
8輪
—
—
快速開始
請選擇您熟悉的程式設計語言,通過以下步驟快速體驗與 Realtime 模型即時對話的功能。
WebSocket
DashScope Python SDK
-
準備運行環境
您的 Python 版本需要不低於 3.10。
首先根據您的作業系統安裝 pyaudio。
macOS
brew install portaudio && pip install pyaudio
Debian/Ubuntu
-
若未使用虛擬環境,可直接通過系統包管理器安裝:
sudo apt-get install python3-pyaudio -
若使用虛擬環境,需先安裝編譯依賴:
sudo apt update sudo apt install -y python3-dev portaudio19-dev然後在已啟用的虛擬環境中使用 pip 安裝:
pip install pyaudio
CentOS
sudo yum install -y portaudio portaudio-devel && pip install pyaudio
Windows
pip install pyaudio
安裝完成後,通過 pip 安裝依賴:
pip install websocket-client dashscope
-
選擇互動模式
-
VAD 模式(Voice Activity Detection,自動檢測語音起止)
服務端自動判斷使用者何時開始與停止說話並作出回應。
-
Manual 模式(按下即說,鬆開即發送)
用戶端控制語音起止。使用者說話結束後,用戶端需主動發送訊息至服務端。
VAD 模式
建立一個 python 檔案,命名為vad_dash.py,並將以下代碼複製到檔案中:
運行
vad_dash.py,通過麥克風即可與 Qwen-Omni-Realtime 模型即時對話,系統會檢測您的音頻起始位置並自動發送到伺服器,無需您手動發送。Manual 模式
建立一個 python 檔案,命名為
manual_dash.py,並將以下代碼複製進檔案中:運行
manual_dash.py,按 Enter 鍵開始說話,再按一次擷取模型響應的音頻。 -
DashScope Java SDK
選擇互動模式
-
VAD 模式(Voice Activity Detection,自動檢測語音起止)
Realtime API 自動判斷使用者何時開始與停止說話並作出回應。
-
Manual 模式(按下即說,鬆開即發送)
用戶端控制語音起止。使用者說話結束後,用戶端需主動發送訊息至服務端。
VAD 模式
運行OmniServerVad.main()方法,通過麥克風即可與 Realtime 模型即時對話,系統會檢測您的音頻起始位置並自動發送到伺服器,無需您手動發送。
Manual 模式
運行OmniWithoutServerVad.main()方法,按 Enter 鍵開始錄音,錄音過程中再次按 Enter 鍵停止錄音並發送,隨後將接收並播放模型響應。
WebSocket(Python)
-
準備運行環境
您的 Python 版本需要不低於 3.10。
首先根據您的作業系統來安裝 pyaudio。
macOS
brew install portaudio && pip install pyaudioDebian/Ubuntu
sudo apt-get install python3-pyaudio 或者 pip install pyaudio推薦使用
pip install pyaudio。如果安裝失敗,請先根據您的作業系統安裝portaudio依賴。CentOS
sudo yum install -y portaudio portaudio-devel && pip install pyaudioWindows
pip install pyaudio安裝完成後,通過 pip 安裝 websocket 相關的依賴:
pip install websockets==15.0.1 -
建立用戶端
在本地建立一個 python 檔案,命名為
omni_realtime_client.py,並將以下代碼複製進檔案中: -
選擇互動模式
-
VAD 模式(Voice Activity Detection,自動檢測語音起止)
Realtime API 自動判斷使用者何時開始與停止說話並作出回應。
-
Manual 模式(按下即說,鬆開即發送)
用戶端控制語音起止。使用者說話結束後,用戶端需主動發送訊息至服務端。
VAD 模式
在
omni_realtime_client.py的同級目錄下建立另一個 python 檔案,命名為vad_mode.py,並將以下代碼複製進檔案中:運行
vad_mode.py,通過麥克風即可與 Realtime 模型即時對話,系統會檢測您的音頻起始位置並自動發送到伺服器,無需您手動發送。Manual 模式
在
omni_realtime_client.py的同級目錄下建立另一個 python 檔案,命名為manual_mode.py,並將以下代碼複製進檔案中:運行
manual_mode.py,按 Enter 鍵開始說話,再按一次擷取模型響應的音頻。 -
WebRTC
Python
-
準備運行環境
您的 Python 版本需要不低於 3.10。安裝以下依賴:
pip install aiortc aiohttp sounddevice numpy certifi av -
運行樣本
建立一個 Python 檔案,命名為
webrtc_demo.py,並將以下代碼複製到檔案中:運行
webrtc_demo.py,通過麥克風即可與 Qwen-Omni-Realtime 模型即時對話,系統會檢測您的音頻起始位置並自動發送到伺服器,無需您手動發送。
JavaScript
-
前提條件
-
使用支援 WebRTC 的現代瀏覽器(Chrome、Edge、Firefox、Safari 等)。
-
瀏覽器需要麥克風許可權。
-
瀏覽器無法直接向服務端發起建立串連的請求(受瀏覽器跨域安全性原則限制),因此需要通過終端執行 curl 命令來完成串連建立。
-
-
運行樣本
建立一個 HTML 檔案,命名為
webrtc_demo.html,並將以下代碼複製到檔案中:在瀏覽器中開啟此檔案,按以下步驟操作:
-
點擊開始會話,頁面會自動產生 Offer SDP 和對應的 curl 命令。
-
點擊複製 curl 命令,在終端中執行。命令返回的內容即為 Answer SDP。
-
將 Answer SDP 粘貼到頁面的 Answer SDP 文字框中,點擊設定 Answer即可建立串連並開始語音對話。
-
互動流程
VAD 模式
將session.update事件的session.turn_detection.type 設為"server_vad"或"semantic_vad"以啟用 VAD 模式。適用於語音通話情境。WebSocket 和 WebRTC 均支援 VAD 模式,兩者的服務端事件一致,區別在於音頻和圖片的傳輸方式不同。
WebRTC 僅支援 VAD 模式,不支援 Manual 模式。WebRTC 的音頻通過 RTP 直接傳輸,無需發送input_audio_buffer.append事件;圖片通過視頻軌道傳輸,不支援input_image_buffer.append事件。控制指令和服務端事件通過 DataChannel 傳輸,事件類型與 WebSocket 一致。
互動流程如下:
-
用戶端發送音頻資料。WebSocket 通過 input_audio_buffer.append 事件發送;WebRTC 通過音頻軌道(RTP)自動傳輸,無需手動發送事件。
-
服務端檢測到語音開始,通過 DataChannel(WebRTC)或 WebSocket 發送 input_audio_buffer.speech_started 事件。
-
服務端檢測到語音結束,發送input_audio_buffer.speech_stopped 事件。
-
服務端自動認可音頻緩衝區,發送input_audio_buffer.committed 事件。
-
服務端開始產生響應,依次發送 response.created、conversation.item.created 等事件。模型的音頻回複通過 WebSocket 的
response.audio.delta事件增量返回,或通過 WebRTC 的音頻軌道(RTP)直接傳輸。 -
響應過程中,服務端通過
response.audio_transcript.delta事件增量返迴文字轉錄,最終發送response.done事件標誌響應完成。
|
生命週期 |
用戶端事件 |
服務端事件 |
|
會話初始化 |
會話配置 |
會話已建立 會話配置已更新 |
|
使用者音頻輸入 |
WebSocket:通過此事件添加音頻到緩衝區 WebSocket:通過此事件添加圖片到緩衝區 WebRTC:音頻通過 RTP 音頻軌道自動傳輸,圖片通過視頻軌道傳輸,無需發送上述事件。 |
input_audio_buffer.speech_started 檢測到語音開始 input_audio_buffer.speech_stopped 檢測到語音結束 伺服器收到提交的音頻 |
|
伺服器音訊輸出 |
無 |
服務端開始產生響應 響應時有新的輸出內容 對話項被建立 新的輸出內容添加到assistant message response.audio_transcript.delta 增量產生的轉錄文字 WebSocket:模型增量產生的音頻通過此事件返回。WebRTC:音頻通過 RTP 音頻軌道直接傳輸,不返回此事件。 response.audio_transcript.done 文本轉錄完成 音頻產生完成 Assistant message 的文本或音頻內容流式輸出完成 Assistant message 的整個輸出項串流完成 響應完成
使用者語音輸入的文字流式轉錄(需在 session.update 中啟用 input_audio_transcription)
使用者語音輸入的文字轉錄完成(需在 session.update 中啟用 input_audio_transcription) |
Manual 模式
將session.update事件的session.turn_detection 設為 null 以啟用 Manual 模式。此模式下,用戶端通過顯式發送input_audio_buffer.commit 和response.create事件請求伺服器響應。適用於按下即說情境,如聊天軟體中的發送語音。
互動流程如下:
-
用戶端隨時發送 input_audio_buffer.append與input_image_buffer.append事件追加音頻與圖片至緩衝區。
發送 input_image_buffer.append 事件前,至少發送過一次 input_audio_buffer.append 事件。
-
用戶端發送input_audio_buffer.commit事件提交音頻緩衝區與映像緩衝區,告知服務端本輪的使用者輸入(音頻及圖片)已全部發送完畢。
-
服務端響應 input_audio_buffer.committed事件。
-
用戶端發送response.create事件,等待服務端返回模型的輸出。
-
服務端響應conversation.item.created事件。
|
生命週期 |
用戶端事件 |
服務端事件 |
|
會話初始化 |
會話配置 |
會話已建立 會話配置已更新 |
|
使用者音頻輸入 |
添加音頻到緩衝區 添加圖片到緩衝區 提交音頻與圖片到伺服器 建立模型響應 |
伺服器收到提交的音頻 |
|
伺服器音訊輸出 |
清除緩衝區的音頻 |
服務端開始產生響應 響應時有新的輸出內容 對話項被建立 新的輸出內容添加到assistant message 項 response.audio_transcript.delta 增量產生的轉錄文字 模型增量產生的音頻 response.audio_transcript.done 完成文本轉錄 完成音頻產生 Assistant message 的文本或音頻內容流式輸出完成 Assistant message 的整個輸出項串流完成 響應完成 |
連網搜尋
連網搜尋功能使模型能夠基於即時檢索資料進行回複,適用於股票價格、天氣預報等需要即時資訊的情境。模型可自主判斷是否需要搜尋來回應使用者的即時問題。
連網搜尋僅Qwen3.5-Omni-Realtime模型支援,且預設關閉,需通過session.update事件啟用。
計費請參考計費說明中的agent策略。
啟用方式
在 session.update 事件中添加以下參數:
-
enable_search:設定為true啟用連網搜尋功能。 -
search_options.enable_source:設定為true返回搜尋結果來源列表。
參數詳情請參見session.update。
響應格式
啟用連網搜尋後,response.done 事件中的 usage 會新增 plugins 欄位,用於記錄搜尋計量資訊:
{
"usage": {
"total_tokens": 2937,
"input_tokens": 2554,
"output_tokens": 383,
"input_tokens_details": {
"text_tokens": 2512,
"audio_tokens": 42
},
"output_tokens_details": {
"text_tokens": 90,
"audio_tokens": 293
},
"plugins": {
"search": {
"count": 1,
"strategy": "agent"
}
}
}
}
程式碼範例
以下樣本展示如何在即時對話中啟用連網搜尋功能。
DashScope Python SDK
在 update_session 調用中傳入 enable_search 和 search_options 參數:
import os
import base64
import time
import json
import pyaudio
from dashscope.audio.qwen_omni import MultiModality, AudioFormat, OmniRealtimeCallback, OmniRealtimeConversation
import dashscope
dashscope.api_key = os.getenv('DASHSCOPE_API_KEY')
url = 'wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/realtime'
model = 'qwen3.5-omni-plus-realtime'
voice = 'Tina'
class SearchCallback(OmniRealtimeCallback):
def __init__(self, pya):
self.pya = pya
self.out = None
def on_open(self):
self.out = self.pya.open(format=pyaudio.paInt16, channels=1, rate=24000, output=True)
def on_event(self, response):
if response['type'] == 'response.audio.delta':
self.out.write(base64.b64decode(response['delta']))
elif response['type'] == 'conversation.item.input_audio_transcription.delta':
preview = response.get('text', '') + response.get('stash', '')
print(f"\r[User] {preview}", end='', flush=True)
elif response['type'] == 'conversation.item.input_audio_transcription.completed':
print(f"\r[User] {response['transcript']}")
elif response['type'] == 'response.audio_transcript.done':
print(f"[LLM] {response['transcript']}")
elif response['type'] == 'response.done':
usage = response.get('response', {}).get('usage', {})
plugins = usage.get('plugins', {})
if plugins.get('search'):
print(f"[Search] count={plugins['search']['count']}, strategy={plugins['search']['strategy']}")
pya = pyaudio.PyAudio()
callback = SearchCallback(pya)
conv = OmniRealtimeConversation(model=model, callback=callback, url=url)
conv.connect()
conv.update_session(
output_modalities=[MultiModality.AUDIO, MultiModality.TEXT],
voice=voice,
instructions="你是個人助理小雲",
enable_search=True,
search_options={'enable_source': True}
)
mic = pya.open(format=pyaudio.paInt16, channels=1, rate=16000, input=True)
print("連網搜尋已啟用,對著麥克風說話 (Ctrl+C 退出)...")
try:
while True:
audio_data = mic.read(3200, exception_on_overflow=False)
conv.append_audio(base64.b64encode(audio_data).decode())
time.sleep(0.01)
except KeyboardInterrupt:
conv.close()
mic.close()
callback.out.close()
pya.terminate()
print("\n對話結束")
DashScope Java SDK
在 updateSession 中通過 parameters 傳入連網搜尋配置:
import com.alibaba.dashscope.audio.omni.*;
import com.alibaba.dashscope.exception.NoApiKeyException;
import com.google.gson.JsonObject;
import javax.sound.sampled.*;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicBoolean;
public class OmniSearch {
static class SequentialAudioPlayer {
private final SourceDataLine line;
private final Queue<byte[]> audioQueue = new ConcurrentLinkedQueue<>();
private final Thread playerThread;
private final AtomicBoolean shouldStop = new AtomicBoolean(false);
public SequentialAudioPlayer() throws LineUnavailableException {
AudioFormat format = new AudioFormat(24000, 16, 1, true, false);
line = AudioSystem.getSourceDataLine(format);
line.open(format);
line.start();
playerThread = new Thread(() -> {
while (!shouldStop.get()) {
byte[] audio = audioQueue.poll();
if (audio != null) {
line.write(audio, 0, audio.length);
} else {
try { Thread.sleep(10); } catch (InterruptedException ignored) {}
}
}
}, "AudioPlayer");
playerThread.start();
}
public void play(String base64Audio) {
audioQueue.add(Base64.getDecoder().decode(base64Audio));
}
public void close() {
shouldStop.set(true);
try { playerThread.join(1000); } catch (InterruptedException ignored) {}
line.drain();
line.close();
}
}
public static void main(String[] args) {
try {
SequentialAudioPlayer player = new SequentialAudioPlayer();
AtomicBoolean shouldStop = new AtomicBoolean(false);
OmniRealtimeParam param = OmniRealtimeParam.builder()
.model("qwen3.5-omni-plus-realtime")
.apikey(System.getenv("DASHSCOPE_API_KEY"))
.url("wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/realtime")
.build();
OmniRealtimeConversation conversation = new OmniRealtimeConversation(param, new OmniRealtimeCallback() {
@Override public void onOpen() {
System.out.println("串連已建立");
}
@Override public void onClose(int code, String reason) {
System.out.println("串連已關閉");
shouldStop.set(true);
}
@Override public void onEvent(JsonObject event) {
String type = event.get("type").getAsString();
if ("response.audio.delta".equals(type)) {
player.play(event.get("delta").getAsString());
} else if ("response.audio_transcript.done".equals(type)) {
System.out.println("[LLM] " + event.get("transcript").getAsString());
} else if ("response.done".equals(type)) {
JsonObject response = event.getAsJsonObject("response");
if (response != null && response.has("usage")) {
JsonObject usage = response.getAsJsonObject("usage");
if (usage.has("plugins")) {
JsonObject plugins = usage.getAsJsonObject("plugins");
if (plugins.has("search")) {
JsonObject search = plugins.getAsJsonObject("search");
System.out.println("[Search] count=" + search.get("count").getAsInt()
+ ", strategy=" + search.get("strategy").getAsString());
}
}
}
}
}
});
conversation.connect();
conversation.updateSession(OmniRealtimeConfig.builder()
.modalities(Arrays.asList(OmniRealtimeModality.AUDIO, OmniRealtimeModality.TEXT))
.voice("Tina")
.enableTurnDetection(true)
.enableInputAudioTranscription(true)
.parameters(Map.of(
"instructions", "你是個人助理小雲",
"enable_search", true,
"search_options", Map.of("enable_source", true)
))
.build()
);
System.out.println("連網搜尋已啟用,請開始說話(按Ctrl+C退出)...");
AudioFormat format = new AudioFormat(16000, 16, 1, true, false);
TargetDataLine mic = AudioSystem.getTargetDataLine(format);
mic.open(format);
mic.start();
ByteBuffer buffer = ByteBuffer.allocate(3200);
while (!shouldStop.get()) {
int bytesRead = mic.read(buffer.array(), 0, buffer.capacity());
if (bytesRead > 0) {
conversation.appendAudio(Base64.getEncoder().encodeToString(buffer.array()));
}
Thread.sleep(20);
}
conversation.close(1000, "正常結束");
player.close();
mic.close();
} catch (NoApiKeyException e) {
System.err.println("未找到API KEY: 請設定環境變數 DASHSCOPE_API_KEY");
} catch (Exception e) {
e.printStackTrace();
}
}
}
WebSocket(Python)
在 session.update 的 JSON 中添加 enable_search 和 search_options 欄位:
import json
import os
import websocket
import base64
import pyaudio
import threading
API_KEY = os.getenv("DASHSCOPE_API_KEY")
API_URL = "wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/realtime?model=qwen3.5-omni-plus-realtime"
pya = pyaudio.PyAudio()
out_stream = pya.open(format=pyaudio.paInt16, channels=1, rate=24000, output=True)
def on_open(ws):
ws.send(json.dumps({
"type": "session.update",
"session": {
"modalities": ["text", "audio"],
"voice": "Tina",
"instructions": "你是個人助理小雲",
"input_audio_format": "pcm",
"output_audio_format": "pcm",
"enable_search": True,
"search_options": {
"enable_source": True
}
}
}))
print("連網搜尋已啟用,對著麥克風說話...")
def send_audio():
mic = pya.open(format=pyaudio.paInt16, channels=1, rate=16000, input=True)
try:
while True:
audio = mic.read(3200, exception_on_overflow=False)
ws.send(json.dumps({
"type": "input_audio_buffer.append",
"audio": base64.b64encode(audio).decode()
}))
except Exception:
mic.close()
threading.Thread(target=send_audio, daemon=True).start()
def on_message(ws, message):
event = json.loads(message)
if event["type"] == "response.audio.delta":
out_stream.write(base64.b64decode(event["delta"]))
elif event["type"] == "response.audio_transcript.done":
print(f"[LLM] {event['transcript']}")
elif event["type"] == "response.done":
usage = event.get("response", {}).get("usage", {})
plugins = usage.get("plugins", {})
if plugins.get("search"):
print(f"[Search] count={plugins['search']['count']}, strategy={plugins['search']['strategy']}")
def on_error(ws, error):
print(f"Error: {error}")
headers = ["Authorization: Bearer " + API_KEY]
ws = websocket.WebSocketApp(API_URL, header=headers, on_open=on_open, on_message=on_message, on_error=on_error)
ws.run_forever()
API 參考
計費與限流
計費規則
Qwen-Omni-Realtime 模型根據不同模態(音頻、映像)對應的Token數計費。計費詳情請參見百鍊控制台。
限流
模型限流規則請參見限流。
錯誤碼
如果模型調用失敗並返回報錯資訊,請參見錯誤碼進行解決。
音色列表
Qwen-Omni-Realtime模型的音色列表可參見音色列表。