Qwen-Omni-Realtime は、ストリーミング音声および画像入力(ビデオフレームを含む)を処理し、リアルタイムでテキストおよび音声の応答を生成します。
対応リージョン: シンガポール、中国 (北京)。各リージョンには独自のAPI キーが必要です。
使用方法
1. 接続の確立
Qwen-Omni-Realtime は WebSocket と WebRTC の両方をサポートしています。WebSocket はサーバーサイド統合に適しており、クイックセットアップが可能です。一方、WebRTC はブラウザベースの低遅延音声シナリオ向けで、UDP 上で音声を転送し、エコーキャンセリングやノイズリダクションを内蔵しています。
WebSocket
ネイティブ WebSocket
接続パラメーター:
|
パラメーター |
説明 |
|
エンドポイント |
中国 (北京) リージョン: wss://dashscope.aliyuncs.com/api-ws/v1/realtime シンガポールリージョン: wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/realtime。{WorkspaceId} は実際のワークスペース ID に置き換えてください。 |
|
クエリパラメーター |
|
|
リクエストヘッダー |
Bearer トークンを使用して認証を行います:
|
# 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 version 1.23.9 or later is required.
import os
import json
from dashscope.audio.qwen_omni import OmniRealtimeConversation,OmniRealtimeCallback
import dashscope
# API keys for the Singapore and China (Beijing) regions are different. To get an API key, see https://www.alibabacloud.com/help/en/model-studio/get-api-key.
# If you have not configured an API key, change the following line to 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,
# The following URL is for the Singapore region. When calling, replace WorkspaceId with your actual workspace ID. URLs vary by region.
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 version 2.20.9 or later is required.
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"))
// The following URL is for the Singapore region. When calling, replace WorkspaceId with your actual workspace ID. URLs vary by region.
.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 接続の確立には次の 2 段階があります。
-
SDP 交換 (HTTP):クライアントは HTTP POST を介してメディア機能とネットワークアドレス (Offer SDP) をサーバーに送信します。サーバーはその情報を返信 (Answer SDP) し、機能ネゴシエーションを完了します。
-
接続 (自動):ネゴシエーション後、WebRTC レイヤーが自動的に音声トランスポートチャネルを確立します。
SDP 交換の設定:
|
パラメーター |
説明 |
|
リクエスト URL |
POST https://{endpoint}/api/v1/webrtc/realtime WebRTC 機能は現在、ホワイトリストによるアクセス制限付きで提供されています。エンドポイントについては、アカウントマネージャーにお問い合わせください。 |
|
クエリパラメーター |
|
|
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=[]))
# Add an audio track to ensure the Offer SDP contains m=audio (required by the server)
pc.addTrack(AudioStreamTrack())
# Create a DataChannel to trigger SDP negotiation (name is customizable; the server pushes events through a channel named "txt")
pc.createDataChannel("oai-events")
# SDP exchange: create an Offer and send it to the server
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 exchange failed: {resp.status} {await resp.text()}")
answer_sdp = await resp.text()
print("=== Offer SDP ===")
print(offer.sdp)
print("=== Answer SDP ===")
print(answer_sdp)
# ICE connection is established automatically
await pc.setRemoteDescription(RTCSessionDescription(sdp=answer_sdp, type="answer"))
print("WebRTC connection established")
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: [] });
// Add an audio track to ensure the Offer SDP contains m=audio (required by the server)
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
stream.getAudioTracks().forEach(t => pc.addTrack(t, stream));
// Create a DataChannel to trigger SDP negotiation (name is customizable; the server pushes events through a channel named "txt")
pc.createDataChannel('oai-events');
// Wait for ICE gathering to complete before sending the Offer to get the 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 exchange failed: ' + resp.status);
const answerSdp = await resp.text();
// ICE connection is established automatically
await pc.setRemoteDescription({ type: 'answer', sdp: answerSdp });
console.log('WebRTC connection established');
};
// Create the Offer
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
return pc;
}2. セッションの構成
session.update クライアントイベントを送信します。
{
// A client-generated event ID.
"event_id": "event_ToPZqeobitzUJnt3QqtWg",
// The event type. Must be "session.update".
"type": "session.update",
// The session configuration.
"session": {
// The output modality. Set this to ["text"] for text-only output, or ["text", "audio"] for both text and audio output.
"modalities": [
"text",
"audio"
],
// The voice for the audio output.
"voice": "Ethan",
// The input audio format. Only "pcm" is supported. The input audio must be a PCM audio stream at a 16 kHz sample rate.
"input_audio_format": "pcm",
// The output audio format. Only "pcm" is supported. The output audio is a PCM audio stream at a 24 kHz sample rate.
"output_audio_format": "pcm",
// A system instruction to define the model's goal or role.
"instructions": "You are an AI customer service agent for a five-star hotel. Answer customer inquiries about room types, facilities, prices, and booking policies accurately and in a friendly manner. Always respond with a professional and helpful attitude. Do not provide unconfirmed information or information beyond the scope of the hotel's services.",
// Enables server-side voice activity detection (VAD). If enabled, the server automatically detects the start and end of speech.
// If null, the client controls when to trigger model responses.
"turn_detection": {
// The VAD type. Valid values: "server_vad" and "semantic_vad". We recommend "semantic_vad" for the qwen3.5-omni-realtime model.
"type": "semantic_vad",
// The VAD detection threshold. We recommend increasing this value in noisy environments and decreasing it in quiet environments.
"threshold": 0.5,
// The silence duration in milliseconds (ms) that signals the end of an utterance. The model triggers a response if this duration is exceeded.
"silence_duration_ms": 800
}
}
}
3. 音声および画像の入力
音声入力は必須です。画像入力は任意です。入力方法はプロトコルによって異なります。
WebSocket
input_audio_buffer.append および input_image_buffer.append イベントを使用して、Base64 エンコードされた音声および画像データをサーバーバッファーに送信します。
画像はローカルファイルまたはリアルタイムビデオストリームキャプチャから取得できます。
サーバー側 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 イベントで Base64 エンコードされたストリーミング音声を受信します。response.audio.done イベントは音声生成が完了したことを示します。
-
WebRTC
-
テキストのみ
WebSocket と同じです。ストリーミングテキストイベントは DataChannel を通じて受信します。
-
テキストと音声
-
テキスト:WebSocket と同じく、DataChannel を通じてストリーミングテキストイベントとして受信します。
-
音声:RTP トラックを通じてリアルタイムで受信および再生されます。
response.audio.deltaイベントは不要です。
-
モデル選択
Qwen3.5-Omni-Realtime は、Qwen3-Omni-Flash-Realtime に対して以下の点で改善されています。
-
インテリジェンスレベル
Qwen3.5-Plus と同等です。
-
Web 検索
内蔵の Web 検索機能により、モデルはリアルタイムの質問に答えるために自動的に検索を行います。詳細については、「Web 検索」をご参照ください。
-
ツール呼び出し
関数呼び出し機能により、モデルは外部ツールを自動的に呼び出します。詳細については、「Qwen-Omni-Realtime シリーズ」をご参照ください。
-
セマンティック割り込み
会話の意図を識別し、相づちやバックグラウンドノイズによる割り込みを防止します。
-
音声制御
音声コマンド (例:「もっと早く話して」「もっと大きな声で」「ハッピーなトーンで」) でボリューム、話速、感情を制御できます。
-
サポート言語
113 言語および方言の音声認識と、36 言語および方言の音声生成をサポートしています。
-
サポート音声
55 音声をサポートしており、そのうち 47 は多言語音声、8 は方言音声です。完全な一覧については、「音声一覧」をご参照ください。
-
音声クローン
カスタムクローン音声をリアルタイム会話に使用できます (Qwen3.5-omni-plus-realtime および Qwen3.5-omni-flash-realtime)。詳細については、「音声クローン」をご参照ください。
モデル名、コンテキスト、価格、スナップショットバージョンについては、Model Studio コンソールをご確認ください。同時実行数のレート制限については、「レート制限」をご参照ください。
制限事項
-
Web 検索とツール呼び出しは相互排他です。
-
単一の WebSocket セッションは最大 120 分間持続できます。この制限に達すると、接続は自動的に閉じられます。
-
モデルは、以下のターン数および期間の制限まで会話履歴を保持します。制限を超えた場合、最も古い履歴が破棄されます。最大期間とは、コンテキストに保持される音声またはビデオ (画像フレーム) の累積期間を指します。
ビデオは抽出されたフレームとして入力されます (推奨:1 fps)。ビデオの最大期間とは、保持される累積フレーム期間を意味します。たとえば、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 ターン
—
—
クイックスタート
プログラミング言語を選択し、以下の手順に従ってリアルタイムチャットを開始します。
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 install websocket-client dashscope
-
インタラクションモード
-
VAD モード (音声区間検出、発話の開始と終了を自動検出)
ユーザーの発話終了を検出した後、サーバーが応答します。
-
手動モード (押して話す、離して送信)
クライアントが発話の開始と終了を制御します。発話後、アプリケーションがサーバーに通知する必要があります。
VAD モード
vad_dash.py という名前の Python ファイルを作成し、以下のコードをコピーします。
vad_dash.pyを実行して、マイクを通じてリアルタイム会話を開始します。システムは発話を検出し、音声をサーバーにストリーミングします。手動モード
manual_dash.pyという名前の Python ファイルを作成し、以下のコードをコピーします。manual_dash.pyを実行します。Enter キーを押して録音を開始し、再度 Enter キーを押して停止および送信します。モデルの音声応答は自動的に再生されます。 -
DashScope Java SDK
インタラクションモードの選択
-
VAD モード (音声区間検出、発話の開始と終了を自動検出)
Realtime API はユーザーの発話開始と終了を検出し、応答します。
-
手動モード (押して話す、離して送信)
クライアントが発話の開始と終了を制御します。発話後、クライアントがサーバーにメッセージを送信する必要があります。
VAD モード
OmniServerVad.main() を実行して、マイクを通じてリアルタイム会話を開始します。システムは発話を検出し、音声をサーバーに送信します。
手動モード
OmniWithoutServerVad.main() を実行します。Enter キーを押して録音を開始し、再度 Enter キーを押して停止および送信します。モデルの応答は自動的に再生されます。
WebSocket (Python)
-
実行環境の準備
Python 3.10 以降がインストールされていることを確認してください。
オペレーティングシステム用の pyaudio をインストールします。
macOS
brew install portaudio && pip install pyaudioDebian/Ubuntu
sudo apt-get install python3-pyaudio or pip install pyaudiopip install pyaudioの使用を推奨します。インストールに失敗した場合は、オペレーティングシステム用のportaudio依存関係を先にインストールしてください。CentOS
sudo yum install -y portaudio portaudio-devel && pip install pyaudioWindows
pip install pyaudioWebSocket 依存関係をインストールします。
pip install websockets==15.0.1 -
クライアントの作成
omni_realtime_client.pyという名前のファイルを作成し、以下のコードをコピーします。 -
インタラクションモードの選択
-
VAD モード (音声区間検出、発話の開始と終了を自動検出)
Realtime API はユーザーの発話開始と終了を検出し、応答を生成します。
-
手動モード (押して話す、離して送信)
ユーザーが音声送信の開始と終了を制御します。発話後、クライアントがサーバーに応答生成を要求するメッセージを送信する必要があります。
VAD モード
omni_realtime_client.pyと同じディレクトリにvad_mode.pyという名前のファイルを作成し、以下のコードをコピーします。vad_mode.pyを実行して、マイクを通じてリアルタイム会話を開始します。システムは発話を検出し、音声をサーバーにストリーミングします。手動モード
omni_realtime_client.pyと同じディレクトリにmanual_mode.pyという名前のファイルを作成し、以下のコードをコピーします。manual_mode.pyを実行します。Enter キーを押して録音を開始し、再度 Enter キーを押して停止および送信します。 -
WebRTC
Python
-
実行環境
Python 3.10 以降が必要です。以下の依存関係をインストールします。
pip install aiortc aiohttp sounddevice numpy certifi av -
デモの実行
webrtc_demo.pyという名前の Python ファイルを作成し、以下のコードを貼り付けます。webrtc_demo.pyを実行して、マイクを通じて Qwen-Omni-Realtime モデルとのリアルタイム会話を開始します。システムはユーザーの発話開始を検出し、音声を自動的にサーバーに送信します。
JavaScript
-
前提条件
-
WebRTC をサポートする最新のブラウザ (Chrome、Edge、Firefox、Safari など) を使用します。
-
ブラウザはマイクへのアクセス許可を必要とします。
-
ブラウザのクロスオリジンセキュリティポリシーにより、ブラウザはサーバーへの直接接続リクエストを送信できません。ターミナルで curl コマンドを実行して、接続設定を完了する必要があります。
-
-
デモの実行
webrtc_demo.htmlという名前の HTML ファイルを作成し、以下のコードを貼り付けます。このファイルをブラウザで開き、以下の手順に従います。
-
Start Session をクリックします。ページが自動的に Offer SDP と対応する curl コマンドを生成します。
-
Copy curl Command をクリックし、ターミナルで実行します。出力結果が Answer SDP になります。
-
Answer SDP を Answer SDP テキストボックスに貼り付け、Set Answer をクリックして接続を確立し、音声チャットを開始します。
-
インタラクションフロー
VAD モード
session.update の session.turn_detection.type を "server_vad" または "semantic_vad" に設定して、VAD モードを有効にします。音声通話シナリオに適しています。WebSocket と WebRTC の両方が VAD モードをサポートしており、サーバーイベントは同じですが、音声および画像の送信方法が異なります。
WebRTC は VAD モードのみをサポートしており、手動モードはサポートしていません。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 イベントを送信します。
-
サーバーが応答の生成を開始し、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 発話終了を検出しました。 音声バッファーがコミットされました。 |
|
サーバー音声出力 |
なし |
応答生成が開始されました。 応答に新しい出力アイテムが追加されました。 会話アイテムが作成されました。 アシスタントメッセージに新しいコンテンツ部分が追加されました。 response.audio_transcript.delta 増分的に生成された転写テキスト。 WebSocket:モデルから増分的に生成された音声は、このイベント経由で返されます。WebRTC:音声は RTP 音声トラック経由で直接送信され、このイベントは返されません。 response.audio_transcript.done テキスト転写が完了しました。 音声生成が完了しました。 アシスタントのテキストまたは音声コンテンツのストリーミングが完了しました。 アシスタントの全出力アイテムのストリーミングが完了しました。 応答が完了しました。
ユーザー音声入力の転写が完了しました (session.update で input_audio_transcription を有効にする必要があります)。 |
手動モード
session.update の session.turn_detection を null に設定して手動モードを使用します。クライアントは input_audio_buffer.commit および response.create を送信して応答を要求します。チャットアプリの音声メッセージなどのプッシュトゥトークシナリオに適しています。
インタラクションフローは以下のとおりです。
-
クライアントは、任意のタイミングで input_audio_buffer.append および input_image_buffer.append イベントを送信して、音声および画像をバッファーに追加できます。
input_audio_buffer.appendイベントを少なくとも 1 回送信した後に、input_image_buffer.appendイベントを送信する必要があります。 -
クライアントは input_audio_buffer.commit イベントを送信して、音声および画像バッファーをコミットし、現在のターンのユーザー入力 (音声および画像) がすべて送信されたことをサーバーに通知します。
-
サーバーは input_audio_buffer.committed イベントで応答します。
-
クライアントは response.create イベントを送信し、サーバーがモデルの出力を返すのを待ちます。
-
サーバーは conversation.item.created イベントで応答します。
|
ライフサイクル |
クライアントイベント |
サーバーイベント |
|
セッション初期化 |
セッション構成 |
セッションが作成されました。 セッション構成が更新されました。 |
|
ユーザー音声入力 |
音声をバッファーに追加します。 画像をバッファーに追加します。 音声および画像バッファーをコミットします。 モデル応答を要求します。 |
音声バッファーがコミットされました。 |
|
サーバー音声出力 |
バッファーから音声をクリアします。 |
応答生成が開始されました。 応答に新しい出力アイテムが追加されました。 会話アイテムが作成されました。 アシスタントメッセージアイテムに新しいコンテンツ部分が追加されました。 response.audio_transcript.delta 増分的に生成された転写テキスト。 モデルから増分的に生成された音声。 response.audio_transcript.done テキスト転写が完了しました。 音声生成が完了しました。 アシスタントのテキストまたは音声コンテンツのストリーミングが完了しました。 アシスタントの全出力アイテムのストリーミングが完了しました。 応答が完了しました。 |
Web 検索
Web 検索機能により、モデルは株価や天気などのタイムリーな情報に関する質問に答えるためにリアルタイムデータを使用できます。モデルは検索が必要かどうかを自動的に判断します。
qwen3.5-omni-plus-realtimeのみが Web 検索をサポートしています。デフォルトでは無効になっており、session.updateで有効にする必要があります。
課金については、課金ルールのエージェント
Web 検索の有効化
session.update イベントに以下のパラメーターを追加します。
-
enable_search:Web 検索機能を有効にするにはtrueに設定します。 -
search_options.enable_source:検索結果のソースを応答に含めるにはtrueに設定します。
その他のパラメーターについては、「session.update」をご参照ください。
応答形式
Web 検索が有効になっている場合、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"
}
}
}
}
コード例
リアルタイム会話で Web 検索を有効にします。
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="You are Xiaoyun, a personal assistant.",
enable_search=True,
search_options={'enable_source': True}
)
mic = pya.open(format=pyaudio.paInt16, channels=1, rate=16000, input=True)
print("Web search is enabled. Speak into the microphone (Ctrl+C to exit)...")
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("\nConversation ended.")
DashScope Java SDK
updateSession メソッドで、parameters 引数に Web 検索の構成を渡します。
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("Connection established.");
}
@Override public void onClose(int code, String reason) {
System.out.println("Connection closed.");
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", "You are Xiaoyun, a personal assistant.",
"enable_search", true,
"search_options", Map.of("enable_source", true)
))
.build()
);
System.out.println("Web search is enabled. Start speaking (press Ctrl+C to exit)...");
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, "Normal termination");
player.close();
mic.close();
} catch (NoApiKeyException e) {
System.err.println("API key not found: Set the DASHSCOPE_API_KEY environment variable.");
} 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": "You are Xiaoyun, a personal assistant.",
"input_audio_format": "pcm",
"output_audio_format": "pcm",
"enable_search": True,
"search_options": {
"enable_source": True
}
}
}))
print("Web search is enabled. Speak into the microphone...")
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
課金とレート制限
課金
課金はトークン単位で行われ、モダリティ (音声、画像、テキスト) ごとに計測されます。価格については、Model Studio コンソールをご確認ください。
レート制限
モデルのレート制限については、「レート制限」をご参照ください。
エラーコード
モデル呼び出しが失敗し、エラーメッセージが返された場合は、「エラーコード」を参照して解決してください。
音声一覧
Qwen-Omni-Realtime モデルで利用可能な音声の一覧については、「音声」をご参照ください。