Skip to main content
Omni-modal

Realtime audio and video understanding

Live audio and video chat

How to use

1. Establish a connection

Qwen-Omni-Realtime supports two protocols: WebSocket and WebRTC. WebSocket is suitable for server-side integration and quick setup. WebRTC is designed for browser-based and low-latency voice scenarios, with audio transmitted directly over UDP and built-in echo cancellation and noise reduction. For session duration and conversation history limits, see Limitations.
  • WebSocket
  • WebRTC
  • Native WebSocket connection
  • DashScope SDK
The following configuration items are required:
Configuration itemDescription
Endpointwss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime
Query parameterThe query parameter is model. Set it to the name of the model you want to access. See Model selection. Example: ?model=qwen3.5-omni-plus-realtime
Request headerUse Bearer Token for authentication: Authorization: Bearer DASHSCOPE_API_KEY. DASHSCOPE_API_KEY is the API Key that you requested on QwenCloud.
# pip install websocket-client
import json
import websocket
import os

API_KEY=os.getenv("DASHSCOPE_API_KEY")
API_URL = "wss://dashscope-intl.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()

2. Configure the session

Send the session.update client event:
{
  // The ID of this event, generated by the client.
  "event_id": "event_ToPZqeobitzUJnt3QqtWg",
  // The event type. This is fixed to session.update.
  "type": "session.update",
  // Session configuration.
  "session": {
    // The output modalities. Supported values are ["text"] (text only) or ["text", "audio"] (text and audio).
    "modalities": [
      "text",
      "audio"
    ],
    // The voice for the output audio.
    "voice": "Tina",
    // 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",
    // The system message. It is used to set 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 friendly. Always respond with a professional and helpful attitude. Do not provide unconfirmed information or information beyond the scope of the hotel's services.",
    // Specifies whether to enable voice activity detection. To enable it, pass a configuration object. The server will automatically detect the start and end of speech based on this object.
    // Set to null to let the client decide when to initiate a model response.
    "turn_detection": {
      // The VAD type. Valid values: server_vad and semantic_vad. We recommend that you set this parameter to semantic_vad for models like qwen3.5-omni-realtime.
      "type": "semantic_vad",
      // The VAD detection threshold. Increase this value in noisy environments and decrease it in quiet environments.
      "threshold": 0.5,
      // The duration of silence to detect the end of speech. If this value is exceeded, a model response is triggered.
      "silence_duration_ms": 800
    }
  }
}
Qwen3.5-Omni-Realtime models support cloned voices in addition to preset voices. See Voice cloning to create a custom voice from an audio sample.

3. Input audio and images

  • WebSocket
  • WebRTC
The client sends Base64-encoded audio and image data to the server buffer using the input_audio_buffer.append and input_image_buffer.append events. Audio input is required. Image input is optional and can come from local files or a real-time video stream.
When server-side Voice Activity Detection (VAD) is enabled, the server automatically submits the data and triggers a response when it detects the end of speech. When VAD is disabled (manual mode), the client must call the input_audio_buffer.commit event to submit the data.

4. Receive model responses

The format of the model response depends on the configured output modalities.
  • WebSocket
  • WebRTC

Limitations

  • Mutually exclusive features: Web search and tool calling cannot be enabled at the same time.
  • Session duration: A single WebSocket session can last up to 120 minutes. The connection closes automatically at this limit.

Conversation history limits

The model retains conversation history up to the following turn and duration limits. When exceeded, the oldest history is discarded. Max duration is the cumulative audio or video (image frame) duration retained in context.
ModelAudio max turnsVideo max turnsAudio max durationVideo max duration
qwen3.5-omni-plus-realtime100 turns50 turns600 seconds240 seconds
qwen3.5-omni-flash-realtime80 turns50 turns480 seconds120 seconds
qwen3-omni-flash-realtime8 turns8 turns
  • Video is input as extracted frames (recommended: 1 fps). Video max duration is the cumulative frame duration retained — for example, 240 s means only frames from the last 240 seconds are kept.
  • The qwen3-omni-flash-realtime model has a limit of 8 dialog turns (typically reached first). Its duration limit depends on the model's context length and is not listed separately.

Getting started

Get an API key and set it as an environment variable.
  • DashScope Python SDK
  • DashScope Java SDK
  • WebSocket (Python)
Prepare the runtime environmentYour Python version must be 3.10 or later.First, install PyAudio based on your operating system.
  • macOS
  • Debian/Ubuntu
  • CentOS
  • Windows
brew install portaudio && pip install pyaudio
After installation completes, install the remaining dependencies using pip:
pip install websocket-client dashscope
Choose an interaction mode
  • VAD mode (automatically detects the start and end of speech) The server automatically determines when the user starts and stops speaking and responds accordingly.
  • Manual mode (press to talk, release to send) The client controls the start and end of speech. After the user finishes speaking, the client must actively send a message to the server.
  • VAD mode
  • Manual mode
Create a new Python file named vad_dash.py and copy the following code into the file:
# Dependencies: dashscope >= 1.23.9, pyaudio
import os
import base64
import time
import pyaudio
from dashscope.audio.qwen_omni import MultiModality, AudioFormat,OmniRealtimeCallback,OmniRealtimeConversation
import dashscope

# Configuration parameters: URL, API key, voice, model, model role
url = 'wss://dashscope-intl.aliyuncs.com/api-ws/v1/realtime'
# Configure the API key. If you have not set an environment variable, replace the following line with dashscope.api_key = "sk-xxx"
dashscope.api_key = os.getenv('DASHSCOPE_API_KEY')
# Specify the voice
voice = 'Tina'
# Specify the model
model = 'qwen3.5-omni-plus-realtime'
# Specify the model role
instructions = "You are Xiaoyun, a personal assistant. Please answer the user's questions in a humorous and witty way."
class SimpleCallback(OmniRealtimeCallback):
  def __init__(self, pya):
    self.pya = pya
    self.out = None
  def on_open(self):
    # Initialize the audio output stream
    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':
      # Play the audio
      self.out.write(base64.b64decode(response['delta']))
    elif response['type'] == 'conversation.item.input_audio_transcription.delta':
      # Streaming preview: text is the confirmed prefix, stash is the unconfirmed suffix.
      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 the transcribed text
      print(f"\r[User] {response['transcript']}")
    elif response['type'] == 'response.audio_transcript.done':
      # Print the assistant's reply text
      print(f"[LLM] {response['transcript']}")

# 1. Initialize the audio device
pya = pyaudio.PyAudio()
# 2. Create the callback function and session
callback = SimpleCallback(pya)
conv = OmniRealtimeConversation(model=model, callback=callback, url=url)
# 3. Establish the connection and configure the session
conv.connect()
conv.update_session(output_modalities=[MultiModality.AUDIO, MultiModality.TEXT], voice=voice, instructions=instructions)
# 4. Initialize the audio input stream
mic = pya.open(format=pyaudio.paInt16, channels=1, rate=16000, input=True)
# 5. Main loop to process audio input
print("Conversation started. 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:
  # Clean up resources
  conv.close()
  mic.close()
  callback.out.close()
  pya.terminate()
  print("\nConversation ended")
Run vad_dash.py to have a real-time conversation with Qwen-Omni-Realtime through your microphone. The system detects the start and end of your speech and automatically sends it to the server without manual intervention.
Sample code on GitHubDownload complete sample code from GitHub, which includes:
  1. Audio conversation: Captures real-time audio from a microphone with VAD mode (enable_turn_detection = True) and supports voice interruption.
  2. Audio and video conversation: Captures real-time audio and video from a microphone and camera with VAD mode and supports voice interruption.
  3. Local call: Uses local audio and images as input with Manual mode (enable_turn_detection = False).
Use headphones for audio playback to prevent echoes from triggering voice interruption.

Interaction flow

  • VAD mode
  • Manual mode
VAD mode interaction flow
Set session.turn_detection.type in the session.update event to "server_vad" or "semantic_vad" to enable VAD mode, which is suitable for voice call scenarios.The interaction flow is as follows:
  1. The server detects the start of speech and sends the input_audio_buffer.speech_started event.
  2. The client can send input_audio_buffer.append and input_image_buffer.append events at any time to append audio and images to the buffer.
    Before sending an input_image_buffer.append event, send at least one input_audio_buffer.append event.
  3. The server detects the end of speech and sends the input_audio_buffer.speech_stopped event.
  4. The server sends the input_audio_buffer.committed event to commit the audio buffer.
  5. The server sends a conversation.item.created event. This event contains the user message item created from the buffer.
LifecycleClient eventsServer-side events
Session initializationsession.update - Session configurationsession.created - Session created. session.updated - Session configuration updated
User audio inputinput_audio_buffer.append - Add audio to the buffer. input_image_buffer.append - Add an image to the bufferinput_audio_buffer.speech_started - Speech start detected. input_audio_buffer.speech_stopped - Speech end detected. input_audio_buffer.committed - Server received the submitted audio
Server audio outputNoneresponse.created - Server starts generating a response. response.output_item.added - New output content during response. conversation.item.created - Conversation item created. response.content_part.added - New output content added to the assistant message. response.audio_transcript.delta - Incrementally generated transcribed text. response.audio.delta - Incrementally generated audio from the model. response.audio_transcript.done - Text transcription complete. response.audio.done - Audio generation complete. response.content_part.done - Streaming output of text or audio content for the assistant message is complete. response.output_item.done - Streaming of the entire output item for the assistant message is complete. response.done - Response complete
Web search lets the model reply using real-time retrieved data for scenarios that need up-to-date information, such as stock prices or weather forecasts. The model autonomously decides whether to search.
Only the Qwen3.5-Omni-Realtime model supports web search. It is disabled by default. Enable it using the session.update event.For billing details, see the agent policy in the Billing details.

How to enable

In the session.update event, add these parameters:
  • enable_search: Set to true to enable web search.
  • search_options.enable_source: Set to true to return a list of search result sources.
For full parameter details, see session.update.

Response format

After you enable web search, the response.done event includes a new plugins field in the usage object. This field records search usage metrics:
{
  "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"
      }
    }
  }
}

Code examples

The following examples show how to enable web search.
  • DashScope Python SDK
  • DashScope Java SDK
  • WebSocket (Python)
In the update_session call, pass the enable_search and search_options parameters:
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://dashscope-intl.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':
      # Streaming preview: text is the confirmed prefix, stash is the unconfirmed suffix.
      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 Xiao Yun, 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 (press 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")

API reference

Billing and rate limits

Billing rules

Qwen-Omni-Realtime is billed based on the number of tokens used for different input modalities, such as audio and images. For more information about billing, see Pricing.
In a multi-turn real-time conversation, each time the model generates a response, it processes all historical conversation content within the context window — including audio, images, and text from previous turns — together with the new input of the current turn as input tokens. As a result, input tokens accumulate with each turn rather than being counted only for the new input in the current turn.For example, if a 10-second audio input converts to 70 tokens (Qwen3.5-Omni-Realtime), and that audio is still within the context window at turn 3, it will still be counted toward the input tokens for turn 3. The actual billed input tokens = tokens from all historical turns within the context window + tokens from the new input in the current turn.
  • Audio
  • Image
  • Qwen3.5-Omni-Realtime: Input audio total tokens = Audio duration (in seconds) x 7; Output audio total tokens = Audio duration (in seconds) x 12.5
  • Qwen3-Omni-Flash-Realtime: Total tokens for both input and output audio = Audio duration (in seconds) x 12.5
  • Qwen-Omni-Turbo-Realtime: Total tokens for both input and output audio = Audio duration (in seconds) x 25
If the audio duration is less than 1 second, it is calculated as 1 second.

Rate limits

For more information about model rate limit rules, see Rate limits.

Error codes

If a call fails, see Error codes.

Voice list

For the voices each omni-modal model supports, including samples and voice parameter values, see Omni-modal voice list.