Skip to main content
Omni-modal

Qwen-Omni-Realtime

O Qwen-Omni-Realtime processa entradas contínuas de áudio e imagem (incluindo frames de vídeo) e gera respostas em texto e áudio em tempo real.

Regiões suportadas: Singapore, China (Beijing). Cada região exige seu próprio API key.

Como usar

1. Estabelecer conexão

O Qwen-Omni-Realtime suporta WebSocket e WebRTC. O WebSocket é ideal para integração no lado do servidor com configuração rápida. Já o WebRTC foca em cenários de voz de baixa latência baseados em navegador, transmitindo áudio via UDP com cancelamento de eco e redução de ruído integrados.
  • WebSocket
  • WebRTC
  • Native WebSocket
  • DashScope Python SDK
  • DashScope Java SDK
Parâmetros de conexão:
ParâmetroDescrição
EndpointRegião China (Beijing): wss://{WorkspaceId}.cn-beijing.maas.aliyuncs.com/api-ws/v1/realtimeRegião Singapore: wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/realtimeSubstitua {WorkspaceId} pelo seu workspace ID real.
Parâmetro de consultaUse o parâmetro de consulta model para especificar o modelo. Exemplo: ?model=qwen3.5-omni-plus-realtime
Cabeçalho da requisiçãoUtilize um token Bearer para autenticação: Authorization: Bearer DASHSCOPE_API_KEY
DASHSCOPE_API_KEY é a API key obtida no Model Studio.
# pip install websocket-client
import json
import websocket
import os

API_KEY=os.getenv("DASHSCOPE_API_KEY")
# Singapore region. Replace {WorkspaceId} with your Bailian workspace ID. URLs vary by region.
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()

2. Configurar sessão

Envie o evento de cliente 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 series 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. Entrada de áudio e imagens

A entrada de áudio é obrigatória; a entrada de imagem é opcional. O método de entrada depende do protocolo utilizado.
  • WebSocket
  • WebRTC
Envie dados de áudio e imagem codificados em Base64 para o buffer do servidor usando os eventos input_audio_buffer.append e input_image_buffer.append.
As imagens podem vir de arquivos locais ou de capturas de fluxo de vídeo em tempo real.
Com o VAD no lado do servidor ativado, o servidor envia os dados automaticamente e aciona uma resposta ao final da fala. Com o VAD desativado (modo manual), chame o evento input_audio_buffer.commit para enviar os dados após a transmissão.

4. Receber respostas do modelo

O formato da resposta depende da modalidade de saída configurada.
  • WebSocket
  • WebRTC

Seleção de modelo

O Qwen3.5-Omni-Realtime apresenta melhorias em relação ao Qwen3-Omni-Flash-Realtime nas seguintes áreas:
  • Nível de inteligência Equivalente ao Qwen3.5-Plus.
  • Pesquisa na web Possui pesquisa na web integrada, permitindo que o modelo busque autonomamente respostas para perguntas em tempo real. Para mais detalhes, consulte Web search.
  • Chamada de ferramentas Suporta chamada de funções, habilitando o modelo a invocar ferramentas externas de forma autônoma. Para mais detalhes, consulte Qwen-Omni-Realtime series.
  • Interrupção semântica Identifica a intenção conversacional para evitar interrupções causadas por sons de confirmação ou ruídos de fundo.
  • Controle de voz Permite controlar volume, velocidade de fala e emoção via comandos de voz (por exemplo, "fale mais rápido", "mais alto", "em tom alegre").
  • Idiomas suportados Oferece reconhecimento de fala para 113 languages and dialects e geração de fala para 36 languages and dialects.
  • Vozes disponíveis Dispõe de 55 vozes, incluindo 47 vozes multilíngues e 8 vozes dialetais. Para a lista completa, consulte Voice list.
  • Clonagem de voz Possibilita o uso de uma voz clonada personalizada para conversas em tempo real (Qwen3.5-omni-plus-realtime e Qwen3.5-omni-flash-realtime). Para mais detalhes, consulte Voice cloning.
Verifique no console do Model Studio os nomes dos modelos, contexto, preços e versões de snapshot. Para limites de taxa de concorrência, consulte Rate limiting .

Limitações

  • A pesquisa na web e a chamada de ferramentas são mutuamente exclusivas.
  • Uma única sessão WebSocket pode durar até 120 minutos. A conexão é encerrada automaticamente ao atingir esse limite.
  • O modelo retém o histórico de conversas até os seguintes limites de turnos e duração. Quando esses limites são excedidos, o histórico mais antigo é descartado. A duração máxima refere-se ao tempo acumulado de áudio ou vídeo (frames de imagem) retido no contexto.
    O vídeo é inserido como frames extraídos (recomendado: 1 fps). A duração máxima de vídeo corresponde ao tempo acumulado de frames retidos — por exemplo, 240 s significa que apenas os frames dos últimos 240 segundos são mantidos.
    O modelo qwen3-omni-flash-realtime tem um limite de 8 turnos de diálogo (geralmente atingido primeiro). Seu limite de duração depende do comprimento de contexto do modelo e não está listado separadamente.

    Modelo

    Máx. turnos de áudio

    Máx. turnos de vídeo

    Duração máx. de áudio

    Duração máx. de vídeo

    qwen3.5-omni-plus-realtime

    100 turnos

    50 turnos

    600 segundos

    240 segundos

    qwen3.5-omni-flash-realtime

    80 turnos

    50 turnos

    480 segundos

    120 segundos

    qwen3-omni-flash-realtime

    8 turnos

    8 turnos

Primeiros passos

Get an API key e set it as an environment variable. Selecione uma linguagem de programação e siga as etapas para iniciar um chat em tempo real.
  • WebRTC
  • Python
  • JavaScript
  • Ambiente de execução É necessário ter o Python 3.10 ou superior. Instale as seguintes dependências:
pip install aiortc aiohttp sounddevice numpy certifi av
  • Executar a demonstração Crie um arquivo Python chamado webrtc_demo.py e cole o código a seguir:
    # Dependencies: pip install aiortc aiohttp sounddevice numpy certifi av
    import asyncio
    import json
    import os
    import queue
    import ssl
    import threading
    
    import aiohttp
    import certifi
    import numpy as np
    import sounddevice as sd
    from aiortc import RTCPeerConnection, RTCConfiguration, RTCSessionDescription
    from aiortc.contrib.media import MediaPlayer
    from av import AudioFrame
    
    # Replace with your API key, or set the DASHSCOPE_API_KEY environment variable
    API_KEY = os.getenv("DASHSCOPE_API_KEY", "your-api-key")
    MODEL = "qwen3.5-omni-plus-realtime"
    # Singapore region. Replace {WorkspaceId} with your Bailian workspace ID. URLs vary by region.
    SIGNALING_URL = "https://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api/v1/webrtc/realtime?model=" + MODEL
    
    # --------------- Audio frame parsing ---------------
    
    def _nb_channels(frame: AudioFrame) -> int:
        """Get the number of channels in an audio frame, compatible with different PyAV versions"""
        if hasattr(frame.layout, "nb_channels"):
            return int(frame.layout.nb_channels)
        ch = getattr(frame.layout, "channels", 1)
        if isinstance(ch, (tuple, list)):
            return len(ch)
        return int(ch)
    
    def audioframe_to_s16_samples(frame: AudioFrame) -> np.ndarray:
        """
        Server audio frames are stereo interleaved. Direct reshape causes channel misalignment.
        Rearrange to (samples, channels) based on actual channel count.
        Different aiortc decoder versions return different array shapes for the same audio,
        so unified handling is needed here.
        """
        arr = np.asarray(frame.to_ndarray())
        ch = _nb_channels(frame)
        samples = int(frame.samples)
    
        if arr.ndim == 2 and arr.shape[0] == ch and arr.shape[1] == samples:
            return arr.T.copy()
        if arr.ndim == 2 and arr.shape[0] == 1 and arr.shape[1] == samples * ch:
            return arr.reshape(-1).reshape(samples, ch).copy()
        if arr.ndim == 1 and arr.shape[0] == samples * ch:
            return arr.reshape(samples, ch).copy()
    
        flat = arr.reshape(-1)
        if ch > 0 and flat.size % ch == 0:
            return flat.reshape(flat.size // ch, ch).copy()
        raise ValueError(f"unexpected shape={arr.shape}, ch={ch}, samples={samples}")
    
    # --------------- Low-latency audio player ---------------
    
    class RemoteAudioPlayer:
        """
        Low-latency audio player that plays 5ms audio blocks to minimize delay.
        Supports voice interruption: clears the buffer when the user starts speaking,
        stopping playback of old model responses.
        Merges stereo server audio to mono (average of left and right channels) for playback.
        """
        def __init__(self, samplerate=48000, out_channels=1, blocksize=240, max_seconds=0.2):
            self.samplerate = samplerate
            self.out_channels = out_channels
            self.blocksize = blocksize
            self._q = queue.Queue(maxsize=max(5, int(max_seconds * samplerate / blocksize) + 5))
            self._lock = threading.Lock()
            self._rb_size = max(1, int(max_seconds * samplerate))
            self._rb = np.zeros((self._rb_size, out_channels), dtype=np.int16)
            self._rb_w = 0
            self._rb_r = 0
            self._rb_len = 0
            self._stream = None
            self._closed = False
    
        def start(self):
            if self._stream:
                return
    
            def callback(outdata, frames, _time, status):
                if self._closed:
                    outdata[:] = np.zeros((frames, self.out_channels), dtype=np.int16)
                    return
                while True:
                    try:
                        chunk = self._q.get_nowait()
                    except queue.Empty:
                        break
                    with self._lock:
                        self._write_rb(chunk)
                with self._lock:
                    out = self._read_rb(frames)
                outdata[:] = out
    
            self._stream = sd.OutputStream(
                samplerate=self.samplerate,
                channels=self.out_channels,
                dtype="int16",
                blocksize=self.blocksize,
                callback=callback,
            )
            self._stream.start()
    
        def clear(self):
            """Clear playback buffer for voice interruption"""
            try:
                while True:
                    self._q.get_nowait()
            except queue.Empty:
                pass
            with self._lock:
                self._rb_w = 0
                self._rb_r = 0
                self._rb_len = 0
                self._rb[:] = 0
    
        def _write_rb(self, chunk: np.ndarray):
            n = int(chunk.shape[0])
            if n <= 0:
                return
            overflow = max(0, self._rb_len + n - self._rb_size)
            if overflow > 0:
                self._rb_r = (self._rb_r + overflow) % self._rb_size
                self._rb_len -= overflow
            end = self._rb_size - self._rb_w
            if n <= end:
                self._rb[self._rb_w:self._rb_w + n] = chunk
            else:
                self._rb[self._rb_w:] = chunk[:end]
                self._rb[:n - end] = chunk[end:]
            self._rb_w = (self._rb_w + n) % self._rb_size
            self._rb_len += n
    
        def _read_rb(self, frames: int) -> np.ndarray:
            if self._rb_len <= 0:
                return np.zeros((frames, self.out_channels), dtype=np.int16)
            n = min(frames, self._rb_len)
            out = np.zeros((frames, self.out_channels), dtype=np.int16)
            end = self._rb_size - self._rb_r
            if n <= end:
                out[:n] = self._rb[self._rb_r:self._rb_r + n]
            else:
                out[:end] = self._rb[self._rb_r:]
                out[end:n] = self._rb[:n - end]
            self._rb_r = (self._rb_r + n) % self._rb_size
            self._rb_len -= n
            return out
    
        async def push_frame(self, frame: AudioFrame):
            """Receive audio frames, auto-merge channels and enqueue"""
            if self._closed:
                return
            pcm = audioframe_to_s16_samples(frame)
            in_ch = pcm.shape[1]
            if self.out_channels == 1:
                if in_ch == 1:
                    out = pcm
                else:
                    out = np.mean(pcm.astype(np.int32), axis=1).astype(np.int16).reshape(-1, 1)
            else:
                if in_ch == self.out_channels:
                    out = pcm
                elif in_ch == 1 and self.out_channels == 2:
                    out = np.repeat(pcm, 2, axis=1)
                else:
                    out = pcm[:, :self.out_channels]
            try:
                self._q.put_nowait(out)
            except queue.Full:
                try:
                    self._q.get_nowait()
                except queue.Empty:
                    pass
                try:
                    self._q.put_nowait(out)
                except queue.Full:
                    pass
    
        async def close(self):
            self._closed = True
            if self._stream:
                self._stream.stop()
                self._stream.close()
                self._stream = None
    
    # --------------- main ---------------
    
    async def main():
        pc = RTCPeerConnection(RTCConfiguration(iceServers=[]))
    
        # Initialize audio player (mono output, 5ms blocksize for low latency)
        speaker = RemoteAudioPlayer(samplerate=48000, out_channels=1, blocksize=240, max_seconds=0.2)
        speaker.start()
    
        # Initialize microphone (macOS avfoundation; for Linux use pulse or alsa)
        mic = MediaPlayer("none:0", format="avfoundation",
                          options={"sample_rate": "48000", "channels": "1"})
        if not mic.audio:
            raise RuntimeError("No microphone detected. Check the avfoundation audio device index.")
        pc.addTrack(mic.audio)
    
        # Client creates DataChannel (name is customizable); server pushes events through a channel named "txt"
        pc.createDataChannel("oai-events")
    
        remote_dc = None
        got_first_txt_msg = False
    
        def make_session_update() -> dict:
            """Build session.update config: voice, audio format, VAD strategy, inference parameters"""
            return {
                "type": "session.update",
                "session": {
                    "modalities": ["text", "audio"],
                    "voice": "Tina",
                    "input_audio_format": "pcm",
                    "output_audio_format": "pcm",
                    "instructions": "You are a friendly AI assistant.",
                    "turn_detection": {"type": "server_vad", "threshold": 0.5, "silence_duration_ms": 800},
                    "max_tokens": 16384,
                    "temperature": 0.9,
                },
            }
    
        # Handle server-pushed DataChannel events
        @pc.on("datachannel")
        def on_datachannel(ch):
            nonlocal remote_dc, got_first_txt_msg
            print(f"[DC] Received server DataChannel: {ch.label}")
            if ch.label == "txt":
                remote_dc = ch
    
            @ch.on("message")
            def on_msg(msg):
                nonlocal got_first_txt_msg
                try:
                    evt = json.loads(msg)
                except Exception:
                    return
                print(f"[{ch.label}] {evt.get('type')}")
    
                # Clear playback buffer when user starts speaking (voice interruption)
                if isinstance(evt, dict) and evt.get("type") == "input_audio_buffer.speech_started":
                    speaker.clear()
                    print("[Playback] User speech detected, clearing buffer (interruption)")
    
                # Send session.update after receiving first message on txt channel
                if ch.label == "txt" and not got_first_txt_msg:
                    got_first_txt_msg = True
                    if remote_dc and remote_dc.readyState == "open":
                        remote_dc.send(json.dumps(make_session_update(), ensure_ascii=False))
                        print("[DC] session.update sent")
    
        # Receive server audio and play with low latency
        @pc.on("track")
        async def on_track(track):
            if track.kind == "audio":
                async def _play():
                    try:
                        while True:
                            frame = await track.recv()
                            await speaker.push_frame(frame)
                    except Exception:
                        pass
                asyncio.create_task(_play())
    
        @pc.on("iceconnectionstatechange")
        def on_ice():
            print(f"[ICE] {pc.iceConnectionState}")
    
        @pc.on("connectionstatechange")
        async def on_conn():
            print(f"[Connection] {pc.connectionState}")
            if pc.connectionState in ("failed", "closed", "disconnected"):
                await pc.close()
    
        # SDP exchange: create Offer and POST to signaling server, get Answer
        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}",
                },
                timeout=aiohttp.ClientTimeout(total=10),
            ) as resp:
                if not resp.ok:
                    raise Exception(f"SDP exchange failed: {resp.status} {await resp.text()}")
                answer_sdp = await resp.text()
    
        await pc.setRemoteDescription(RTCSessionDescription(sdp=answer_sdp, type="answer"))
        print("SDP exchange complete, waiting for connection...")
    
        try:
            await asyncio.Event().wait()
        except (KeyboardInterrupt, asyncio.CancelledError):
            pass
        finally:
            print(f"\nExiting. Final state: connection={pc.connectionState}, ICE={pc.iceConnectionState}")
            await speaker.close()
            try:
                if mic and mic.audio:
                    mic.audio.stop()
            except Exception:
                pass
            await pc.close()
    
    asyncio.run(main())
    
    Execute webrtc_demo.py para iniciar uma conversa em tempo real com o modelo Qwen-Omni-Realtime por meio do seu microfone. O sistema detecta automaticamente o início da sua fala e envia o áudio para o servidor.

Fluxo de interação

  • Modo VAD
  • Modo Manual
Defina session.turn_detection.type em session.update como "server_vad" ou "semantic_vad" para enable VAD mode. Esta configuração é adequada para cenários de chamada de voz. Tanto WebSocket quanto WebRTC suportam o modo VAD com os mesmos eventos de servidor; a diferença reside apenas na forma de transmissão de áudio e imagens.
O WebRTC suporta exclusivamente o modo VAD e não oferece suporte ao modo Manual. Com WebRTC, o áudio é transmitido diretamente via RTP sem o envio de eventos input_audio_buffer.append ; as imagens são transmitidas por faixas de vídeo, sem suporte a eventos input_image_buffer.append . Os comandos de controle e eventos do servidor trafegam via DataChannel, utilizando os mesmos tipos de evento do WebSocket.
O fluxo de interação ocorre da seguinte forma:
  1. Envie dados de áudio pelo cliente. O WebSocket realiza o envio por meio de eventos input_audio_buffer.append; já o WebRTC transmite automaticamente pela faixa de áudio (RTP), dispensando o envio manual de eventos.
  2. Ao detectar o início da fala, o servidor envia o evento input_audio_buffer.speech_started via DataChannel (WebRTC) ou WebSocket.
  3. Quando a fala termina, o servidor detecta o fim e dispara o evento input_audio_buffer.speech_stopped.
  4. Confirme o buffer de áudio no servidor, que então envia o evento input_audio_buffer.committed.
  5. Inicie a geração de resposta no servidor, enviando conversation.item.created e outros eventos relacionados. As respostas de áudio retornam incrementalmente pelo evento response.audio.delta do WebSocket ou são transmitidas diretamente pela faixa de áudio (RTP) do WebRTC.
  6. Durante a resposta, o servidor retorna a transcrição de texto incremental via eventos response.audio_transcript.delta e envia o evento response.done ao concluir a resposta.
Ciclo de vidaEventos do clienteEventos do servidor
Inicialização da sessãosession.update
Configuração da sessão
session.created
Sessão criada.
session.updated
Configuração da sessão atualizada.
Entrada de áudio do usuárioinput_audio_buffer.append
WebSocket: Adiciona áudio ao buffer por meio deste evento.
input_image_buffer.append
WebSocket: Adiciona uma imagem ao buffer por meio deste evento.
WebRTC: O áudio é transmitido automaticamente pela faixa de áudio RTP e as imagens pela faixa de vídeo. Estes eventos não são necessários.
input_audio_buffer.speech_started
Início de fala detectado.
input_audio_buffer.speech_stopped
Fim de fala detectado.
input_audio_buffer.committed
Buffer de áudio confirmado.
Saída de áudio do servidorNenhumresponse.created
Geração de resposta iniciada.
response.output_item.added
Novo item de saída adicionado à resposta.
conversation.item.created
Item de conversa criado.
response.content_part.added
Nova parte de conteúdo adicionada à mensagem do assistente.
response.audio_transcript.delta
Texto transcrito gerado incrementalmente.
response.audio.delta
WebSocket: O áudio gerado incrementalmente pelo modelo retorna por meio deste evento. WebRTC: O áudio é transmitido diretamente pela faixa de áudio RTP; este evento não é retornado.
response.audio_transcript.done
Transcrição de texto concluída.
response.audio.done
Geração de áudio concluída.
response.content_part.done
Streaming do conteúdo de texto ou áudio do assistente concluído.
response.output_item.done
Streaming completo do item de saída inteiro do assistente.
response.done
Resposta concluída.
conversation.item.input_audio_transcription.completed
Transcrição da entrada de áudio do usuário concluída (requer ativação de input_audio_transcription em session.update).

Pesquisa na web

A pesquisa na web permite que o modelo utilize dados em tempo real para responder a perguntas sobre informações atuais, como cotações de ações e previsão do tempo. O próprio modelo determina automaticamente se uma pesquisa é necessária.
Apenas o modelo qwen3.5-omni-plus-realtime oferece suporte à pesquisa na web. Este recurso vem desativado por padrão; para ativá-lo, use session.update .
Para informações sobre faturamento, consulte a política agent em billing rules .

Ativar pesquisa na web

Adicione os seguintes parâmetros ao evento session.update:
  • enable_search: Defina como true para ativar o recurso de pesquisa na web.
  • search_options.enable_source: Configure como true para incluir as fontes dos resultados da pesquisa na resposta.
Para mais parâmetros, consulte session.update.

Formato da resposta

Com a pesquisa na web ativada, o objeto usage em response.done passa a incluir um campo plugins contendo informações de medição da pesquisa:
{
    "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"
            }
        }
    }
}

Exemplo de código

Ative a pesquisa na web em uma conversa em tempo real:
  • DashScope Python SDK
  • DashScope Java SDK
  • WebSocket (Python)
Passe os parâmetros enable_search e search_options na chamada update_session:
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')
# Singapore region. Replace {WorkspaceId} with your Bailian workspace ID. URLs vary by region.
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.")

Referência da API

Faturamento e limites de taxa

Faturamento

O faturamento é baseado em tokens e medido por modalidade (áudio, imagem, texto). Consulte o console do Model Studio para verificar os preços.
Em uma conversa em tempo real com múltiplas turnos, sempre que o modelo gera uma resposta, ele processa todo o conteúdo histórico da conversa dentro da janela de contexto — incluindo áudio, imagens e texto dos turnos anteriores — juntamente com a nova entrada do turno atual como tokens de entrada. Como resultado, os tokens de entrada se acumulam a cada turno, em vez de serem contados apenas para a nova entrada do turno atual.Por exemplo, se uma entrada de áudio de 10 segundos for convertida em 70 tokens (Qwen3.5-Omni-Realtime) e esse áudio ainda estiver dentro da janela de contexto no turno 3, ele ainda será contabilizado nos tokens de entrada desse turno. Os tokens de entrada faturados correspondem à soma dos tokens de todos os turnos históricos dentro da janela de contexto mais os tokens da nova entrada do turno atual.
  • Áudio
  • Imagem
  • Qwen3.5-Omni-Realtime:
    • Áudio de entrada: total tokens = Audio duration (seconds) * 7
    • Áudio de saída: total tokens = Audio duration (seconds) * 12.5
  • Qwen3-Omni-Flash-Realtime: O áudio de entrada e saída utiliza a mesma fórmula: total tokens = Audio duration (seconds) * 12.5
  • Qwen-Omni-Turbo-Realtime: O áudio de entrada e saída utiliza a mesma fórmula: total tokens = Audio duration (seconds) * 25 Durações de áudio inferiores a 1 segundo são faturadas como 1 segundo.

Limites de taxa

Para consultar os limites de taxa dos modelos, veja Rate limiting.

Códigos de erro

Se a chamada ao modelo falhar e retornar uma mensagem de erro, consulte Error codes para obter a solução.

Lista de vozes

Para visualizar a lista de vozes disponíveis para o modelo Qwen-Omni-Realtime, consulte Voices.
Plano de Tokens
Playground de Modelos
Inferência do Modelo
Avaliação
Compressão de Modelos
Estatísticas e Monitoramento
Suporte
Qwen-Omni-Realtime - Alibaba Cloud Model Studio