qwen-audio-3.0-realtime-plus
A real-time voice conversation model that accepts speech and text and responds with voice or text. Designed for a natural conversational pace and interaction with tools.
About the model
This description covers the model’s capabilities. Available in Ailoot inputs, outputs and options are listed for the selected method below.
Qwen-Audio-3.0-Realtime-Plus is an Alibaba model for real-time voice conversation. It accepts speech and text, responds with audio and text, and supports a two-way conversation with natural turn-taking. Audio can be sent and received in chunks, allowing the interaction to develop during the conversation.
Knowing when a speaker has finished
Several ways to detect turn boundaries are provided: voice activity, semantic completion and explicit control. The manufacturer's mode is selected before the first audio is sent; changing it after a conversation has started requires a new connection. Support for this control depends on the selected method. The semantic mode considers pauses and fillers: a brief hesitation does not necessarily mean that the user has finished their thought. New user speech can interrupt a response being generated so that the conversation continues in light of the next turn. In semantic mode, recognition can be guided by a speech sample of the selected speaker to reduce distraction from other voices and background noise.
Instructions define the assistant's role and conversational style. Vocal delivery can reflect tone, pace and emotion; version 3.0 provides five system voices and voices based on a speech sample. The model supports 11 languages, including Russian, as well as the Chinese dialect variants specified by the manufacturer. In the manufacturer's API, the voice is specified in the first update of the session settings; subsequent attempts to supply a new voice in the same conversation are ignored. These settings require support from the selected access method.
Tools and conversation history
Function calling is supported. The model produces the requested action, the connected application executes it and returns the result to the conversation. This allows a conversation to be based on actual data and continued after an operation has been completed. A voice assistant can use tool results while retaining the context of the discussion. The manufacturer's capabilities also include built-in web search when provided by the selected method. Web search and function calls cannot both be enabled in the same session.
By default, the history contains 20 conversation turns, with a supported range of one to 50. Accumulated audio is limited to 300 seconds; older parts of the history are removed when the limits are exceeded. This differs from the length of an individual turn: the history determines which earlier material remains available as the dialogue continues.
Text context and audio
The formal context window is 40,960 tokens. The maximum input length is 16,384 tokens and the maximum output length is 8192. The same input and output limits are specified for reasoning mode, with a separate reasoning-chain limit of 1024 tokens.
Streaming audio uses mono, 16-bit PCM: 16 kHz for input and 24 kHz for output. Text and audio belong to the same conversation. Visual input and image output are not stated for this particular model; it specializes in speech, text and voice interaction.
Ailoot · Realtime voice GET /v1/realtime?model=qwen-audio-3.0-realtime-plus qwen-audio-3.0-realtime-plus
qwen-audio-3.0-realtime-plusOverview
A real-time voice conversation model that accepts speech and text and responds with voice or text. Designed for a natural conversational pace and interaction with tools.
- Accepted inputs
- Text · Audio
- Results
- Text · Audio
Available in Ailoot · inputs & methods
Ailoot · Realtime voice GET /v1/realtime?model=qwen-audio-3.0-realtime-plus
- Input types
text,audio- Output types
text,audio- Output format
- session events
| Field | Type | Requirement | Values & limits | Default |
|---|---|---|---|---|
Fieldquery.model | Typestring | RequirementRequired | Values & limits qwen-audio-3.0-realtime-plus | Default— |
Fieldsession.input_audio_format | Typestring | RequirementOptional | Values & limits pcm16, pcm | Default— |
Fieldsession.output_audio_format | Typestring | RequirementOptional | Values & limits pcm16, pcm | Default— |
Fieldsession.instructions | Typestring | RequirementOptional | Values & limits Limit applies to: selected session policy | Default— |
Fieldsession.max_history_turns | Typeinteger | RequirementOptional | Values & limits Minimum: 1 Limit applies to: selected session policy | Default— |
Fieldinput_audio_buffer.append.audio | Typebase64_string | RequirementRequired | Values & limits Input content: PCM16 mono; 16 kHz input, 24 kHz output; nonempty even byte length | Default— |
Input material combinations
realtime_text_audio
Method parameters and lifecycle
| Parameter | Contract |
|---|---|
Parametermaterial_types | Contracttext, audio |
Parameterrequired | Contractnull |
Parameteroptional_controls | Contractnull |
Parametercontent_type | ContractWebSocket |
Lifecycle: exact retained published method
Method shapes and IDs are exact; supplier capability is separate from admission.
Pricing
Ailoot method rates
GET /v1/realtime?model=qwen-audio-3.0-realtime-plus- Input
- $0.0008 / 1K
- Output
- $0.0064 / 1K
- Input
- $0.0048 per minute of audio
- Output
- $0.018 per minute of output audio
Ailoot reserves an estimated amount before a request. After the result, it settles the final charge from measured usage. Streaming settlement uses the final usage record. How reservation and settlement work →
Example requests
Ailoot · Realtime voice
# Install the documented client once: python3 -m pip install websockets
import base64, json, os, time, wave
from pathlib import Path
from websockets.sync.client import connect
key = os.environ.get("AILOOT_API_KEY", "")
if not key.strip():
raise SystemExit("Set your AILOOT_API_KEY before connecting")
file = Path(os.environ.get("AILOOT_TEXT_FILE", ""))
if not file.is_file(): raise SystemExit("Set AILOOT_TEXT_FILE to your actual UTF-8 material")
material = file.read_text(encoding="utf-8")
if not material.strip(): raise SystemExit("Text material must not be empty")
out = Path("realtime-results")
out.mkdir(exist_ok=True)
audio, texts, complete = {}, {}, []
current_response = None
pending = False
deadline = time.monotonic() + 570 # Local client bound; lower server/session limits still apply.
def receive(ws, log):
global current_response, pending
left = deadline - time.monotonic()
if left <= 0:
raise TimeoutError("Client session limit; preserve IDs and partial results, do not replay")
event = json.loads(ws.recv(timeout=min(60, left)))
log.write(json.dumps(event, ensure_ascii=False) + "\n")
log.flush()
kind = event.get("type")
if kind == "error":
raise RuntimeError(json.dumps(event, ensure_ascii=False))
if kind == "response.created":
current_response = event["response"]["id"]
if kind in {"response.audio.delta", "response.output_audio.delta", "response.text.delta", "response.output_text.delta", "response.audio_transcript.delta", "response.output_audio_transcript.delta", "response.text.done", "response.audio_transcript.done"}:
identity = (event["response_id"], event["item_id"], event["output_index"], event["content_index"])
if kind in {"response.audio.delta", "response.output_audio.delta"}:
raw = base64.b64decode(event["delta"], validate=True)
if len(raw) % 2: raise ValueError("Output PCM16 chunk must have an even byte length")
audio.setdefault(identity, bytearray()).extend(raw)
elif kind.endswith(".done"):
texts[identity] = event.get("text", event.get("transcript", ""))
else:
texts[identity] = texts.get(identity, "") + event["delta"]
print(event["delta"], end="", flush=True)
if kind == "response.done":
response = event["response"]
complete.append(response)
if current_response is None: current_response = response["id"]
if response["id"] == current_response: pending = False
return event
def wait_for(ws, log, kind):
while True:
event = receive(ws, log)
if event.get("type") == kind: return event
def send(ws, event):
ws.send(json.dumps(event, ensure_ascii=False))
try:
# One exact model query; no reconnect/retry loop and no provider credentials.
with connect("wss://api.ailoot.ai/v1/realtime?model=qwen-audio-3.0-realtime-plus", additional_headers={"Authorization": "Bearer " + key}) as ws:
with (out / "events.jsonl").open("w", encoding="utf-8") as log:
wait_for(ws, log, "session.created")
send(ws, {"type":"session.update", "session":{"modalities":["audio","text"], "input_audio_format":"pcm", "output_audio_format":"pcm", "turn_detection":None}})
wait_for(ws, log, "session.updated")
send(ws, {"type":"conversation.item.create", "item":{"type":"message", "role":"user", "content":[{"type":"input_text", "text":material}]}})
send(ws, {"type":"response.create"})
pending = True
try:
while pending: receive(ws, log)
except KeyboardInterrupt:
# Cancel the actual current inference, then preserve its returned status.
send(ws, {"type":"response.cancel"})
while pending: receive(ws, log)
if not complete or complete[-1].get("status") != "completed":
raise RuntimeError("Response did not complete normally; inspect saved events and status")
finally:
manifest = {"responses":complete, "parts":[]}
for number, identity in enumerate(dict.fromkeys(list(audio) + list(texts))):
record = {"response_id":identity[0], "item_id":identity[1], "output_index":identity[2], "content_index":identity[3]}
if identity in audio:
raw = bytes(audio[identity])
pcm_file = out / ("audio-" + str(number) + ".pcm")
wav_file = out / ("audio-" + str(number) + ".wav")
pcm_file.write_bytes(raw)
with wave.open(str(wav_file), "wb") as wav:
wav.setnchannels(1); wav.setsampwidth(2); wav.setframerate(24000); wav.writeframes(raw)
record.update({"pcm":str(pcm_file), "wav":str(wav_file), "sample_rate_hz":24000})
if identity in texts:
text_file = out / ("text-" + str(number) + ".txt")
text_file.write_text(texts[identity], encoding="utf-8")
record["text"] = str(text_file)
manifest["parts"].append(record)
(out / "results.json").write_text(json.dumps(manifest, ensure_ascii=False, indent=2), encoding="utf-8")
print("\nSaved every received event and result in", out)
# Install the documented client once: python3 -m pip install websockets
import base64, json, os, time, wave
from pathlib import Path
from websockets.sync.client import connect
key = os.environ.get("AILOOT_API_KEY", "")
if not key.strip():
raise SystemExit("Set your AILOOT_API_KEY before connecting")
file = Path(os.environ.get("AILOOT_AUDIO_FILE", ""))
if not file.is_file(): raise SystemExit("Set AILOOT_AUDIO_FILE to your raw PCM16 mono 16-kHz recording")
material = file.read_bytes()
if not material or len(material) % 2 or material.startswith(b"RIFF"):
raise SystemExit("Use nonempty signed little-endian PCM16 mono 16-kHz bytes, not a WAV header")
out = Path("realtime-results")
out.mkdir(exist_ok=True)
audio, texts, complete = {}, {}, []
current_response = None
pending = False
deadline = time.monotonic() + 570 # Local client bound; lower server/session limits still apply.
def receive(ws, log):
global current_response, pending
left = deadline - time.monotonic()
if left <= 0:
raise TimeoutError("Client session limit; preserve IDs and partial results, do not replay")
event = json.loads(ws.recv(timeout=min(60, left)))
log.write(json.dumps(event, ensure_ascii=False) + "\n")
log.flush()
kind = event.get("type")
if kind == "error":
raise RuntimeError(json.dumps(event, ensure_ascii=False))
if kind == "response.created":
current_response = event["response"]["id"]
if kind in {"response.audio.delta", "response.output_audio.delta", "response.text.delta", "response.output_text.delta", "response.audio_transcript.delta", "response.output_audio_transcript.delta", "response.text.done", "response.audio_transcript.done"}:
identity = (event["response_id"], event["item_id"], event["output_index"], event["content_index"])
if kind in {"response.audio.delta", "response.output_audio.delta"}:
raw = base64.b64decode(event["delta"], validate=True)
if len(raw) % 2: raise ValueError("Output PCM16 chunk must have an even byte length")
audio.setdefault(identity, bytearray()).extend(raw)
elif kind.endswith(".done"):
texts[identity] = event.get("text", event.get("transcript", ""))
else:
texts[identity] = texts.get(identity, "") + event["delta"]
print(event["delta"], end="", flush=True)
if kind == "response.done":
response = event["response"]
complete.append(response)
if current_response is None: current_response = response["id"]
if response["id"] == current_response: pending = False
return event
def wait_for(ws, log, kind):
while True:
event = receive(ws, log)
if event.get("type") == kind: return event
def send(ws, event):
ws.send(json.dumps(event, ensure_ascii=False))
try:
# One exact model query; no reconnect/retry loop and no provider credentials.
with connect("wss://api.ailoot.ai/v1/realtime?model=qwen-audio-3.0-realtime-plus", additional_headers={"Authorization": "Bearer " + key}) as ws:
with (out / "events.jsonl").open("w", encoding="utf-8") as log:
wait_for(ws, log, "session.created")
send(ws, {"type":"session.update", "session":{"modalities":["audio","text"], "input_audio_format":"pcm", "output_audio_format":"pcm", "turn_detection":None}})
wait_for(ws, log, "session.updated")
for offset in range(0, len(material), 640):
send(ws, {"type":"input_audio_buffer.append", "audio":base64.b64encode(material[offset:offset+640]).decode("ascii")})
time.sleep(0.02)
send(ws, {"type":"input_audio_buffer.commit"})
wait_for(ws, log, "input_audio_buffer.committed")
send(ws, {"type":"response.create"})
pending = True
try:
while pending: receive(ws, log)
except KeyboardInterrupt:
# Cancel the actual current inference, then preserve its returned status.
send(ws, {"type":"response.cancel"})
while pending: receive(ws, log)
if not complete or complete[-1].get("status") != "completed":
raise RuntimeError("Response did not complete normally; inspect saved events and status")
finally:
manifest = {"responses":complete, "parts":[]}
for number, identity in enumerate(dict.fromkeys(list(audio) + list(texts))):
record = {"response_id":identity[0], "item_id":identity[1], "output_index":identity[2], "content_index":identity[3]}
if identity in audio:
raw = bytes(audio[identity])
pcm_file = out / ("audio-" + str(number) + ".pcm")
wav_file = out / ("audio-" + str(number) + ".wav")
pcm_file.write_bytes(raw)
with wave.open(str(wav_file), "wb") as wav:
wav.setnchannels(1); wav.setsampwidth(2); wav.setframerate(24000); wav.writeframes(raw)
record.update({"pcm":str(pcm_file), "wav":str(wav_file), "sample_rate_hz":24000})
if identity in texts:
text_file = out / ("text-" + str(number) + ".txt")
text_file.write_text(texts[identity], encoding="utf-8")
record["text"] = str(text_file)
manifest["parts"].append(record)
(out / "results.json").write_text(json.dumps(manifest, ensure_ascii=False, indent=2), encoding="utf-8")
print("\nSaved every received event and result in", out)
Receive and save results
Ailoot · Realtime voice
session events
Keep all completed results
Keep every complete returned response, file and content part.
Assemble every returned audio delta and text/event result for each actual response ID.
If the original response needs checking
Keepactualsession/response/eventIDs andreceipts.
Errors and next steps
| Response | Condition | Next step |
|---|---|---|
Response426 · invalid_request_error | ConditionNo WebSocket upgrade | Next stepUse a WebSocket client with Upgrade: websocket. |
Aliases
Model aliases
Ailoot qwen-audio-3.0-realtime-plus
Method path aliases
Use the method paths shown above.
Related caller IDs
FAQ · qwen-audio-3.0-realtime-plus
- What inputs and results are available?
GET /v1/realtime?model=qwen-audio-3.0-realtime-plus·qwen-audio-3.0-realtime-plus:text,audio→text,audio- What limits apply?
GET /v1/realtime?model=qwen-audio-3.0-realtime-plus·qwen-audio-3.0-realtime-plus:session.max_history_turns.minimum:1