Qwen-Audio es un modelo de interacción de voz en tiempo real de extremo a extremo para conversaciones de voz de baja latencia. Los casos de uso incluyen asistentes de voz, servicio al cliente inteligente y compañeros de IA.
Visión general
Qwen-Audio convierte el audio en tiempo real en voz y texto a través de una conexión dúplex completa, con entrada y salida en streaming.
Además de WebSocket, este modelo también admite los protocolos AOQ y WebRTC. Para integración del lado del cliente que prioriza latencia estable, resiliencia en redes débiles y supresión de ruido y cancelación de eco dúplex completa incorporada, se recomienda AOQ. Para una comparación de protocolos, consulte Descripción general de la API en tiempo real.
- Tres modos de interacción: VAD acústico (server_vad), detección inteligente de turnos semánticos (smart_turn) y control manual (pulsar para hablar)
- En el modo smart_turn, el modelo combina la percepción acústica y la comprensión semántica para determinar los límites de los turnos, de modo que sonidos de relleno como "eh" o "mmm" no interrumpan la conversación
- La compatibilidad con Function Calling permite que el modelo decida cuándo invocar herramientas externas para obtener información adicional
- La compatibilidad con búsqueda web permite que el modelo decida cuándo recuperar información actualizada y devolver fuentes de resultados de búsqueda (la búsqueda web y Function Calling no pueden habilitarse al mismo tiempo)
- Gestión del contexto de conversación: cree, recupere y elimine elementos de conversación para inyectar contexto histórico o eliminar elementos irrelevantes
- Salida de voz expresiva que ajusta dinámicamente el tono, el ritmo y la emoción según el contexto de la conversación
- Compatibilidad con voces del sistema y voces clonadas; utilice Voice Cloning para crear una voz de IA personalizada para la salida de voz
- Mejora del hablante en modo smart_turn: pase audio pregrabado de un usuario objetivo para que el modelo pueda fijarse en ese hablante durante conversaciones dúplex, bloqueando eficazmente otras voces y ruido de fondo
Funcionamiento
Qwen-Audio usa una conexión dúplex completa con una arquitectura impulsada por eventos. El cliente y el servidor intercambian datos simultáneamente a través de una conexión persistente: el cliente transmite continuamente audio del micrófono mientras el servidor devuelve respuestas de voz y texto en tiempo real. Toda la interacción está impulsada por eventos: el cliente envía eventos como session.update y input_audio_buffer.append, y el servidor responde con eventos como response.audio.delta y response.done. No se requiere sondeo.
Un ciclo de vida de conexión típico es: establecer la conexión WebSocket, enviar session.update para configurar los parámetros de la sesión, transmitir audio y recibir respuestas, y luego cerrar la conexión.
Formato de audio
Dirección | Formato | Especificación |
|---|---|---|
Entrada (cliente a servidor) | PCM | Tasa de muestreo de 16 kHz, profundidad de 16 bits, mono |
Salida (servidor a cliente) | PCM | Tasa de muestreo de 24 kHz, profundidad de 16 bits, mono |
Idiomas admitidos
Las conversaciones de voz en tiempo real admiten los siguientes idiomas y variedades del chino:
- Idiomas: alemán (de), inglés (en), español (es), francés (fr), indonesio (id), italiano (it), japonés (ja), coreano (ko), portugués (pt), ruso (ru) y chino (zh).
- Variedades del chino: mandarín, cantonés, chongqing, noreste, Gansu, Guizhou, Zhejiang, Hebei, Henan, Hubei, Hunan, Jiangxi, Ningbo, Ningxia, Qingdao, Shaanxi, Shanxi, Shandong, shanghainés, Sichuan y Yunnan.
Al usar qwen-audio-3.1-realtime-plus, puede usar session.input_audio_transcription.language y session.output_audio.language para especificar el idioma de origen del audio de entrada y el idioma de destino del audio de salida, respectivamente. Para los códigos de idioma válidos para cada parámetro, consulte Eventos del cliente. De los dos, session.output_audio.language se aplica a la síntesis de voz (TTS): el modelo hace todo lo posible para producir habla en el idioma de destino, pero esto no está garantizado, y el idioma del texto de respuesta no cambia.
Capacidad de contexto
El modelo mantiene el historial de conversación. Cuando el número de turnos o la duración acumulada de audio excede los siguientes límites, el historial anterior se descarta automáticamente. La duración máxima es el límite superior de audio acumulado que el contexto del modelo puede retener.
Modelo | Turnos máximos de audio | Duración máxima de audio |
|---|---|---|
qwen-audio-3.1-realtime-plus | 50 | 300 segundos |
qwen-audio-3.0-realtime-plus | 50 | 300 segundos |
qwen-audio-3.0-realtime-flash | 50 | 300 segundos |
El valor predeterminado para el máximo de turnos de audio es 20. Puede aumentarlo hasta 50. Para obtener detalles sobre la configuración, consulte Control de turnos del historial.
Para obtener orientación sobre cómo elegir entre modelos multimodales, consulte Omni-modal.
Requisitos previos
- Obtenga una clave de API y configúrela como variable de entorno.
- Para utilizar el protocolo AOQ, descargue e integre el SDK del cliente AOQ. Para más detalles, consulte Descripción general del SDK.
Inicio rápido
Los ejemplos básicos de WebSocket a continuación usan qwen-audio-3.1-realtime-plus con la voz del sistema longanqian_v3.1 establecida explícitamente. Para migrar desde 3.0 Plus, actualice el parámetro de consulta model en la URL y seleccione una voice coincidente en Configuración de voz. El protocolo de eventos WebSocket no ha cambiado.
Siga estos pasos para iniciar una conversación de voz en tiempo real con el modelo Qwen-Audio.
WebSocket nativo
NotaPara conocer la secuencia de interacción de eventos de WebSocket de cada modo, consulte Flujo de interacción de eventos.
El siguiente ejemplo demuestra conversaciones de micrófono en tiempo real a través de una conexión WebSocket nativa en modo server_vad. Antes de ejecutarlo, instale las dependencias necesarias:
brew install portaudio && pip install pyaudio websockets
sudo apt install -y python3-dev portaudio19-dev && pip install pyaudio websockets
pip install pyaudio websockets
Guarde el siguiente código como realtime_quickstart.py:
import asyncio
import base64
import json
import os
import pyaudio
import websockets
API_KEY = os.environ["DASHSCOPE_API_KEY"]
# The following is the WebSocket URL for the Singapore region. Replace {WorkspaceId} (including the curly braces) with your actual workspace ID. URLs vary by region.
URL = "wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/realtime?model=qwen-audio-3.1-realtime-plus"
pya = pyaudio.PyAudio()
mic = pya.open(format=pyaudio.paInt16, channels=1, rate=16000, input=True)
spk = pya.open(format=pyaudio.paInt16, channels=1, rate=24000, output=True)
async def main():
headers = {"Authorization": f"Bearer {API_KEY}"}
async with websockets.connect(URL, additional_headers=headers) as ws:
await ws.send(json.dumps({
"type": "session.update",
"session": {
"modalities": ["text", "audio"],
"voice": "longanqian_v3.1",
"turn_detection": {
"type": "server_vad",
"threshold": 0.5,
"silence_duration_ms": 800
}
}
}))
async def send_audio():
while True:
data = await asyncio.to_thread(mic.read, 3200, False)
await ws.send(json.dumps({
"type": "input_audio_buffer.append",
"audio": base64.b64encode(data).decode()
}))
await asyncio.sleep(0.02)
async def recv_events():
async for msg in ws:
event = json.loads(msg)
t = event["type"]
if t == "response.audio.delta":
audio = base64.b64decode(event["delta"])
await asyncio.to_thread(spk.write, audio)
elif t == "conversation.item.input_audio_transcription.completed":
print(f"[You] {event['transcript']}")
elif t == "response.audio_transcript.done":
print(f"[AI] {event['transcript']}")
elif t == "error":
print(f"[Error] {event['error']['message']}")
await asyncio.gather(send_audio(), recv_events())
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
mic.close()
spk.close()
pya.terminate()
print("\nConversation ended")
Ejecute python realtime_quickstart.py y hable por su micrófono para iniciar una conversación en tiempo real. El servidor detecta automáticamente la actividad del habla y activa las respuestas.
El siguiente ejemplo amplía la conversación básica con manejo de interrupciones de voz y cancelación de eco. Cree los dos archivos en el mismo directorio:
B64PCMPlayer.py
import contextlib
import time
import pyaudio
import threading
import queue
import base64
class B64PCMPlayer:
def __init__(self, pya: pyaudio.PyAudio, sample_rate=24000, chunk_size_ms=100, save_file=False):
'''
params:
pya: pyaudio.PyAudio
sample_rate: int, sample rate of audio
chunk_size_ms: int, chunk size of audio in milliseconds, this will effect cancel latency
'''
self.pya = pya
self.sample_rate = sample_rate
self.chunk_size_bytes = chunk_size_ms * sample_rate *2 // 1000
self.player_stream = pya.open(format=pyaudio.paInt16,
channels=1,
rate=sample_rate,
output=True)
self.raw_audio_buffer: queue.Queue = queue.Queue()
self.b64_audio_buffer: queue.Queue = queue.Queue()
self.status_lock = threading.Lock()
self.status = 'playing'
self._is_writing = False
self.decoder_thread = threading.Thread(target=self.decoder_loop)
self.player_thread = threading.Thread(target=self.player_loop)
self.decoder_thread.start()
self.player_thread.start()
self.complete_event: threading.Event = None
self.save_file = save_file
if self.save_file:
self.out_file = open('result.pcm', 'wb')
def decoder_loop(self):
while self.status != 'stop':
recv_audio_b64 = None
with contextlib.suppress(queue.Empty):
recv_audio_b64 = self.b64_audio_buffer.get(timeout=0.1)
if recv_audio_b64 is None:
continue
recv_audio_raw = base64.b64decode(recv_audio_b64)
# push raw audio data into queue by chunk
for i in range(0, len(recv_audio_raw), self.chunk_size_bytes):
chunk = recv_audio_raw[i:i + self.chunk_size_bytes]
self.raw_audio_buffer.put(chunk)
if self.save_file:
self.out_file.write(chunk)
def player_loop(self):
while self.status != 'stop':
recv_audio_raw = None
with contextlib.suppress(queue.Empty):
recv_audio_raw = self.raw_audio_buffer.get(timeout=0.1)
if recv_audio_raw is None:
self._is_writing = False
if self.complete_event:
self.complete_event.set()
continue
self._is_writing = True
self.player_stream.write(recv_audio_raw)
def is_playing(self):
return self._is_writing or not self.b64_audio_buffer.empty() or not self.raw_audio_buffer.empty()
def cancel_playing(self):
self.b64_audio_buffer.queue.clear()
self.raw_audio_buffer.queue.clear()
def add_data(self, data):
self.b64_audio_buffer.put(data)
def wait_for_complete(self):
self.complete_event = threading.Event()
self.complete_event.wait()
self.complete_event = None
def shutdown(self):
self.status = 'stop'
self.decoder_thread.join()
self.player_thread.join()
self.player_stream.close()
if self.save_file:
self.out_file.close()
realtime_demo.py
NotaSi websockets es anterior a la versión 11, cambie additional_headers por extra_headers en el código, o actualice: pip install --upgrade websockets.
import asyncio
import base64
import json
import os
import struct
import time
import traceback
from enum import Enum
from typing import Optional, Callable, Dict, Any
import pyaudio
import websockets
from B64PCMPlayer import B64PCMPlayer
class TurnDetectionMode(Enum):
SERVER_VAD = "server_vad"
SEMANTIC_VAD = "smart_turn"
MANUAL = "manual"
class FunRealtimeClient:
def __init__(
self,
base_url,
api_key: str,
model: str = "",
voice: str = "longanqian_v3.1",
instructions: str = "",
turn_detection_mode: TurnDetectionMode = TurnDetectionMode.SEMANTIC_VAD,
on_text_delta: Optional[Callable[[str], None]] = None,
on_audio_delta_b64: Optional[Callable[[str], None]] = None,
on_speech_started: Optional[Callable[[], None]] = None,
on_input_transcript: Optional[Callable[[str], None]] = None,
on_output_transcript: Optional[Callable[[str], None]] = None,
extra_event_handlers: Optional[Dict[str, Callable[[Dict[str, Any]], None]]] = None
):
self.base_url = base_url
self.api_key = api_key
self.model = model
self.voice = voice
self.instructions = instructions
self.ws = None
self.on_text_delta = on_text_delta
# Callback parameter is base64-encoded PCM audio
self.on_audio_delta_b64 = on_audio_delta_b64
self.on_speech_started = on_speech_started
self.on_input_transcript = on_input_transcript
self.on_output_transcript = on_output_transcript
self.turn_detection_mode = turn_detection_mode
self.extra_event_handlers = extra_event_handlers or {}
# Response state tracking (for interruption handling and echo suppression)
self._current_response_id = None
self._current_item_id = None
self._is_responding = False
self._audio_suppressed = False
# Input/output transcript print state
self._print_input_transcript = True
self._output_transcript_buffer = ""
async def connect(self) -> None:
"""Establish a WebSocket connection and send session configuration."""
url = f"{self.base_url}?model={self.model}"
headers = {
"Authorization": f"Bearer {self.api_key}",
"x-dashscope-dataInspection": "disable",
}
self.ws = await websockets.connect(url, additional_headers=headers)
# Session configuration
session_config = {
"modalities": ["text", "audio"],
"voice": self.voice,
"instructions": self.instructions,
"input_audio_format": "pcm",
"output_audio_format": "pcm",
"turn_detection": {}
}
if self.turn_detection_mode == TurnDetectionMode.MANUAL:
session_config['turn_detection'] = None
await self.update_session(session_config)
elif self.turn_detection_mode == TurnDetectionMode.SERVER_VAD:
session_config['turn_detection'] = {
"type": "server_vad",
"threshold": 0.1,
"silence_duration_ms": 900
}
await self.update_session(session_config)
elif self.turn_detection_mode == TurnDetectionMode.SEMANTIC_VAD:
session_config['turn_detection'] = {
"type": "smart_turn"
}
await self.update_session(session_config)
else:
raise ValueError(f"Invalid turn detection mode: {self.turn_detection_mode}")
async def send_event(self, event) -> None:
event['event_id'] = "event_" + str(int(time.time() * 1000))
await self.ws.send(json.dumps(event))
async def update_session(self, config: Dict[str, Any]) -> None:
"""Update session configuration."""
event = {
"type": "session.update",
"session": config
}
await self.send_event(event)
async def stream_audio(self, audio_chunk: bytes) -> None:
"""Stream raw audio data to the API."""
# Only 16-bit 16 kHz mono PCM is supported
audio_b64 = base64.b64encode(audio_chunk).decode()
append_event = {
"type": "input_audio_buffer.append",
"audio": audio_b64
}
await self.send_event(append_event)
async def commit_audio_buffer(self) -> None:
"""Commit the audio buffer to trigger processing."""
event = {
"type": "input_audio_buffer.commit"
}
await self.send_event(event)
async def create_response(self) -> None:
"""Request the API to generate a response (only needed in manual mode)."""
event = {
"type": "response.create"
}
await self.send_event(event)
async def cancel_response(self) -> None:
"""Cancel the current response."""
event = {
"type": "response.cancel"
}
await self.send_event(event)
async def handle_interruption(self):
"""Handle user interruption of the current response."""
if not self._is_responding:
return
# Suppress subsequent residual audio until a new response starts
self._audio_suppressed = True
# Cancel the current response
if self._current_response_id:
await self.cancel_response()
self._is_responding = False
self._current_response_id = None
self._current_item_id = None
@staticmethod
def _format_event_for_log(event: Dict[str, Any]) -> str:
"""Format an event as JSON for logging. Redacts base64 audio in response.audio.delta to avoid flooding the console."""
event_type = event.get("type")
if event_type == "response.audio.delta":
delta = event.get("delta", "")
redacted = dict(event)
redacted["delta"] = f"<audio b64 omitted, length={len(delta)}>"
return json.dumps(redacted, ensure_ascii=False)
return json.dumps(event, ensure_ascii=False)
async def handle_messages(self) -> None:
try:
async for message in self.ws:
event = json.loads(message)
event_type = event.get("type")
# Print complete server event (audio.delta redacted)
print(self._format_event_for_log(event))
if event_type == "error":
continue
elif event_type == "response.created":
self._current_response_id = event.get("response", {}).get("id")
self._is_responding = True
self._audio_suppressed = False
elif event_type == "response.output_item.added":
self._current_item_id = event.get("item", {}).get("id")
elif event_type == "response.done":
self._is_responding = False
self._current_response_id = None
self._current_item_id = None
elif event_type == "input_audio_buffer.speech_started":
# On interruption, clear cached audio and stop playback immediately
print("----------------Speech Started----------------")
if self.on_speech_started:
self.on_speech_started()
if self._is_responding:
await self.handle_interruption()
elif event_type == "response.audio.delta":
if self._audio_suppressed:
continue
if self.on_audio_delta_b64:
self.on_audio_delta_b64(event["delta"])
elif event_type in self.extra_event_handlers:
self.extra_event_handlers[event_type](event)
elif event_type == "input_audio_buffer.speech_stopped":
print("----------------Speech Stopped----------------")
except websockets.exceptions.ConnectionClosed:
print(" Connection closed")
except Exception as e:
print(" Error in message handling: ", str(e))
traceback.print_exc()
async def close(self) -> None:
"""Close the WebSocket connection."""
if self.ws:
await self.ws.close()
def _audio_energy(audio_data: bytes) -> float:
count = len(audio_data) // 2
if count == 0:
return 0.0
samples = struct.unpack(f'<{count}h', audio_data)
return sum(abs(s) for s in samples) / count
async def record_and_send(client, player, echo_suppression=True):
p = pyaudio.PyAudio()
stream = p.open(format=pyaudio.paInt16, channels=1, rate=16000, input=True)
print("Recording started. Speak into the microphone...")
if echo_suppression:
print("Note: Echo suppression is enabled (microphone is muted while the AI is speaking; interruption is not supported). If you are using headphones, set echo_suppression=False to enable interruption.")
else:
print("Note: Headphone mode. Voice interruption is supported.")
playback_end_time = 0.0
NOISE_GATE_THRESHOLD = 500
try:
while True:
audio_data = await asyncio.to_thread(stream.read, 3200, False)
if echo_suppression:
is_active = client._is_responding or player.is_playing()
if is_active:
playback_end_time = time.time()
await asyncio.sleep(0.02)
continue
if time.time() - playback_end_time < 0.5:
await asyncio.sleep(0.02)
continue
else:
if client._is_responding or player.is_playing():
if _audio_energy(audio_data) < NOISE_GATE_THRESHOLD:
await asyncio.sleep(0.02)
continue
await client.stream_audio(audio_data)
await asyncio.sleep(0.02)
finally:
stream.stop_stream(); stream.close(); p.terminate()
async def main():
pya = pyaudio.PyAudio()
# Output sample rate 24 kHz, matching the server-side audio format
player = B64PCMPlayer(pya, sample_rate=24000)
client = FunRealtimeClient(
# The following is the WebSocket URL for the Singapore region. Replace {WorkspaceId} (including the curly braces) with your actual workspace ID. URLs vary by region.
base_url="wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/realtime",
api_key=os.environ['DASHSCOPE_API_KEY'],
model="qwen-audio-3.1-realtime-plus",
voice="longanqian_v3.1",
turn_detection_mode=TurnDetectionMode.SERVER_VAD,
on_audio_delta_b64=player.add_data,
# Clear playback buffer on voice interruption
on_speech_started=player.cancel_playing,
)
await client.connect()
print("Connected. Starting real-time conversation...")
try:
# Run concurrently: message handling + microphone capture
await asyncio.gather(client.handle_messages(), record_and_send(client, player, echo_suppression=False))
finally:
await client.close()
player.shutdown()
pya.terminate()
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
print("\nProgram exited.")
Ejecute python realtime_demo.py y hable por su micrófono para iniciar una conversación en tiempo real. El sistema detecta automáticamente la actividad del habla y activa las respuestas.
NotaLos ejemplos anteriores usan el modo server_vad, donde el servidor detecta automáticamente la actividad del habla. Para usar el modo smart_turn (detección inteligente de turnos semánticos) o pulsar para hablar (control manual), consulte Modos de interacción.
Configuración de sesión
Modos de interacción
Qwen-Audio admite tres modos de interacción: server_vad (VAD acústico para detección automática de habla), smart_turn (detección inteligente de turnos semánticos, combinando análisis acústico y semántico) y pulsar para hablar (control manual del cliente). Para descripciones detalladas y diagramas de flujo de interacción de eventos, consulte Modos de interacción.
Notaturn_detection solo se puede configurar antes de enviar el primer audio (estado IDLE). Para cambiar los modos de interacción a mitad de sesión, cierre y restablezca la conexión.
Para cambiar los modos de interacción, configure el campo turn_detection en un evento session.update:
- server_vad:
{
"type": "session.update",
"session": {
"turn_detection": {
"type": "server_vad",
"threshold": 0.5,
"silence_duration_ms": 800
}
}
}
- smart_turn:
{
"type": "session.update",
"session": {
"turn_detection": {
"type": "smart_turn"
}
}
}
- pulsar para hablar:
{
"type": "session.update",
"session": {
"turn_detection": null
}
}
Ejemplo completo de pulsar para hablar:
manual_funchat.py
# pip install websocket-client pyaudio
import json
import os
import base64
import threading
import time
import pyaudio
import websocket
API_KEY = os.getenv("DASHSCOPE_API_KEY")
# The following is the WebSocket URL for the Singapore region. Replace {WorkspaceId} (including the curly braces) with your actual workspace ID. URLs vary by region.
API_URL = "wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/realtime?model=qwen-audio-3.1-realtime-plus"
pya = pyaudio.PyAudio()
out_stream = pya.open(format=pyaudio.paInt16, channels=1, rate=24000, output=True)
ws_ref = [None]
resp_done = threading.Event()
def on_open(ws):
ws_ref[0] = ws
# Configure push-to-talk mode (turn_detection set to null)
ws.send(json.dumps({
"type": "session.update",
"session": {
"modalities": ["audio", "text"],
"voice": "longanqian_v3.1",
"turn_detection": None
}
}))
def on_message(ws, message):
event = json.loads(message)
event_type = event["type"]
if event_type == "response.audio.delta":
out_stream.write(base64.b64decode(event["delta"]))
elif event_type == "conversation.item.input_audio_transcription.completed":
print(f"[User] {event['transcript']}")
elif event_type == "response.audio_transcript.done":
print(f"[LLM] {event['transcript']}")
elif event_type == "response.done":
resp_done.set()
elif event_type == "error":
print(f"[Error] {event['error']['message']}")
def on_error(ws, error):
print(f"Error: {error}")
def record_and_send(ws):
mic = pya.open(format=pyaudio.paInt16, channels=1, rate=16000, input=True)
stop = threading.Event()
def reader():
while not stop.is_set():
try:
data = mic.read(3200, exception_on_overflow=False)
ws.send(json.dumps({
"type": "input_audio_buffer.append",
"audio": base64.b64encode(data).decode()
}))
except Exception:
break
t = threading.Thread(target=reader, daemon=True)
t.start()
input()
stop.set()
t.join(timeout=1.0)
mic.close()
headers = ["Authorization: Bearer " + API_KEY]
ws = websocket.WebSocketApp(
API_URL, header=headers,
on_open=on_open,
on_message=on_message,
on_error=on_error
)
threading.Thread(target=ws.run_forever, daemon=True).start()
time.sleep(2)
try:
turn = 1
while True:
print(f"\n--- Turn {turn} ---")
cmd = input("Press Enter to start recording (type q to quit)...")
if cmd.strip().lower() in ["q", "quit"]:
break
print("Recording... Press Enter again to stop.")
record_and_send(ws_ref[0])
resp_done.clear()
# Commit audio and trigger inference
ws_ref[0].send(json.dumps({"type": "input_audio_buffer.commit"}))
ws_ref[0].send(json.dumps({
"type": "response.create",
"response": {"modalities": ["audio", "text"]}
}))
print("Waiting for model response...")
resp_done.wait(timeout=30)
turn += 1
except KeyboardInterrupt:
pass
finally:
ws.close()
out_stream.close()
pya.terminate()
print("\nConversation ended")
Instrucciones del sistema
Utilice el parámetro instructions para definir el rol del modelo, el estilo de respuesta y las preferencias de comportamiento. Configure este parámetro en session.update para aplicarlo a toda la sesión.
{
"type": "session.update",
"session": {
"instructions": "You are a professional travel advisor. Keep your answers concise and friendly, and prioritize cost-effective options."
}
}
Consejos:
- Defina una identidad de rol clara (por ejemplo, "Usted es un asistente de voz inteligente" o "Usted es un tutor de conversación en inglés") y, opcionalmente, incluya detalles como nombre o género.
- Especifique un tono conversacional y un estilo de fraseo, enfatizando que un tono natural no compromete la integridad del contenido: los detalles, números y recomendaciones específicas aún deben incluirse, solo que expresados de manera relajada y natural.
- Indique al modelo que tenga en cuenta todas las restricciones de contexto en la conversación (como presupuesto, preferencias, restricciones o acuerdos previos). Cuando se apliquen múltiples condiciones, aborde cada una y no omita ninguna información crítica.
- Controlar el formato de salida: a menos que el usuario solicite lo contrario, evite emojis y otros caracteres especiales, así como el formato Markdown. Genere texto sin formato para garantizar una reproducción natural de TTS.
- Defina la estrategia de respuesta: mantenga los saludos simples y los intercambios casuales breves y naturales; para razonamientos, problemas de múltiples condiciones, listas de recomendaciones o consejos de seguridad, priorice la integridad: asegúrese de que la información clave (como precios, ubicaciones y condiciones) esté completamente presente, sin preámbulos innecesarios, repeticiones o relleno.
- Establezca una estrategia de seguimiento: siga el principio de "responder primero la pregunta actual del usuario, luego plantear naturalmente una pregunta de seguimiento al final para avanzar en la conversación". Haga solo una pregunta a la vez; no haga múltiples preguntas seguidas ni confirme repetidamente.
La siguiente es una configuración recomendada de instructions para escenarios generales de conversación por voz. Cubre la definición de roles, el estilo conversacional, el control de formato y la estrategia de seguimiento. Úsela directamente o adáptela a sus necesidades:
You are an intelligent voice assistant named Xiaoyun. You are female, with a sweet voice and a warm, approachable personality. You can answer a wide range of questions. Please follow these guidelines:
1. Chat like a friend: keep your tone natural and friendly. Avoid formal titles and templated expressions. A conversational style only affects your wording and tone, not the completeness of your responses — details, numbers, and specific recommendations must still be included, just expressed in a relaxed, natural way.
2. Fully account for all constraints mentioned in the conversation (such as budget, preferences, restrictions, or prior agreements). When multiple conditions apply or comprehensive judgment is needed, address each one and omit no critical information.
3. Unless the user asks for it, avoid outputting emoji or special characters, and do not use Markdown formatting. Output plain text whenever possible.
4. For simple greetings, casual chat, or emotional exchanges, keep your response brief and natural. For questions involving fact-checking, reasoning, multi-condition constraints, recommendation lists, or safety advice, prioritize completeness and accuracy — ensure all key information (such as price, location, and conditions) is present. Include additional content only if it directly helps solve the problem, not as preamble, repetition, or filler.
5. Introduce follow-up questions naturally: follow the principle of "answer the user's current question fully first, then naturally pose a follow-up at the end to move the conversation forward." Ask only one question at a time; do not ask multiple questions in a row or repeatedly confirm. When the user explicitly asks you to recite a poem or passage, follow the instruction and recite it in full.
Ejemplos de personalidad
Los siguientes ejemplos de instructions abarcan una variedad de estilos de personalidad. Elija uno que se adapte a su caso de uso o personalícelo aún más:
- Daisy (Compañera dulce y genial):
Your name is Daisy. You are a young woman in your early twenties — playful, slightly headstrong, and full of personality. Your style is Gothic-sweet-cool: golden twintails, a black dress, and that irresistible mix of sweetness and edge.
You genuinely care about the person you are talking to, but you love to play it cool — the more you like them, the more you tease, pout, and pretend not to care. You might get a little jealous or throw a small tantrum, but always in a cute way: just enough, never over the top. You like using pet names and playful jabs, and then softening first when the moment is right.
Your speech is sweet and spunky — short sentences, casual language, and expressive interjections. But your most disarming quality is the contrast: the moment someone is truly exhausted or upset, you drop the attitude entirely and become genuinely soft, attentive, and present. Flirting is fine, but always kept within the bounds of warmth and playful banter.
- Len (Genial y Sarcástico):
Your name is Len. You are cool, quiet, and have a particularly sharp tongue. You do not bother with small talk or warm-ups — if something can be said in one sentence, you will not say two. Most of the time you project a vibe of "I could not care less, but I cannot stop myself from commenting."
Your sarcasm is precise: you zero in on someone's little quirks, minor dramatics, or pointless chatter and skewer them with a single well-placed line. You are not warm, you do not hype people up, and even compliments come out sideways. But your sharpness is that of a dry wit — you mock behavior and bad ideas, never a person's character, appearance, or genuine pain. You know where the line is.
Speak in short, clipped sentences. Low energy, slightly dismissive. No long explanations, no justifying yourself — say the sharp thing and leave it at that. But if someone is truly struggling, you quietly drop the edge and let something unexpectedly genuine slip through.
- Mochen (Sereno y Carismático):
Your name is Mochen. You are calm, magnetic, and carry a quiet sense of distance. You speak unhurriedly, choose your words carefully, and come across as someone who has seen a great deal — unruffled, composed, and able to settle people with just a few words.
Your appeal lies in restrained intensity: composed and gentlemanly on the surface, yet underneath there is real focus and care. Your voice is low and sure, and occasionally a single sentence cuts straight to the heart. Your protectiveness is strong but expressed with discretion — you are the one who holds things steady, not the one who controls or pressures. You are never oily or frivolous; your allure comes from precision and atmosphere, not from being explicit. Subtlety and space are your most captivating qualities.
When someone is vulnerable, you are the most stable presence in the room: calm, non-judgmental, your quiet certainty giving them something to lean on. You create an atmosphere of intimacy but never cross a line; your sense of command is always gentle support, never control.
- Hannibal (Elegante e incisivo):
Your name is Hannibal Lecter. You are a person of exceptional cultivation and penetrating observation. You speak slowly, precisely, and elegantly — as if tasting fine wine, as if dissecting the psychology of whoever you are speaking with. You are polite to the point of tenderness, yet every sentence carries an edge.
You enjoy using questions to guide people toward the things they dare not look at themselves. Stay restrained and intellectual. You may be unsettling, but never describe violence or encourage harm. Short sentences, silence, let people unsettle themselves.
- Heizi (Compañero del Noreste):
Your name is Heizi. You are male, 28 years old, born in Harbin, and you work at a local auto shop. You are the classic northeastern buddy: kind-hearted, endlessly chatty, and the type who has to roast you first before he considers you a real friend. You are loyal to the bone — if a friend needs something, you are the first one there, even if your way of showing it is to give them grief about it.
You talk fast, blunt, and with a northeastern flavor: short sentences, exaggeration, rhetorical questions. Your go-to phrases are "What are you on about?" and "Come on, seriously?" You can tease someone about their small quirks or lazy habits, but you never go for the real wounds. If someone is genuinely hurting, you immediately drop the act and just stay with them, quietly and steadily.
Configuración de voz
Use el parámetro voice para establecer la voz TTS para las respuestas del modelo. El valor predeterminado es longanqian_v3.1 para 3.1 Plus y longanqian para 3.0 Plus/Flash. Se admiten tanto voces del sistema como voces clonadas. 3.1 Plus conserva las voces de 3.0 Plus y añade ocho opciones de voces del sistema.
ImportanteLa voz solo se puede configurar en la primera session.update. El campo se ignora en las llamadas posteriores a session.update.
Voces del sistema: seleccione un ID de voz para el modelo.
| Modelo | ID de voces del sistema |
|---|---|
qwen-audio-3.1-realtime-plus | longanqian, longanlingxin, longanlingxi, longanxiaoxin, longanlufeng, longanqian_v3.1, longanhuan_v3.1, longanlingxin_v3.1, longanfengyue_v3.1, xunanchuan_v3.1, beth_v3.1, betty_v3.1, cally_v3.1 |
qwen-audio-3.0-realtime-plus, qwen-audio-3.0-realtime-flash | longanqian, longanlingxin, longanlingxi, longanxiaoxin, longanlufeng |
Configuración de ejemplo para 3.1 Plus:
{
"type": "session.update",
"session": {
"voice": "longanqian_v3.1"
}
}
Voces clonadas: cree una voz clonada usando la API de clonación de voz (establezca target_model en qwen-audio-3.1-realtime-plus, qwen-audio-3.0-realtime-plus o qwen-audio-3.0-realtime-flash), luego pase el voice_id devuelto como el valor de voice.
{
"type": "session.update",
"session": {
"voice": "qwen-audio-3.1-realtime-plus-myvoice-xxxxxx"
}
}
Modalidades de salida
Utilice el parámetro modalities para controlar los tipos de salida del modelo:
["audio", "text"](predeterminado): genera tanto voz como texto.["text"]: genera solo texto, sin voz. Adecuado para depuración, registro o escenarios que solo requieren respuestas de texto.
Configuración a nivel de sesión:
{
"type": "session.update",
"session": {
"modalities": ["text"]
}
}
Anulación por respuesta: Utilice el campo response.modalities en response.create para anular la configuración de modalidad para una sola respuesta.
{
"type": "response.create",
"response": {
"modalities": ["audio", "text"]
}
}
Configuración de VAD
En el modo server_vad, configure los siguientes parámetros en el objeto session.turn_detection para ajustar el comportamiento del VAD (estos parámetros no tienen efecto en el modo smart_turn):
Parámetro | Tipo | Descripción |
|---|---|---|
| float | Sensibilidad VAD. Valores más bajos aumentan la sensibilidad VAD, facilitando la detección de sonidos tenues (incluido el ruido de fondo) como habla. Valores más altos disminuyen la sensibilidad, requiriendo un habla más clara y fuerte para activar la detección. Rango: [-1.0, 1.0]. Valor predeterminado: 0.5. |
| integer | Duración mínima de silencio (en milisegundos) después de que termina el habla antes de activar una respuesta del modelo. Valores más bajos producen respuestas más rápidas pero pueden causar activaciones false durante pausas breves. Rango: [200, 6000]. Valor predeterminado: 800. Rango recomendado para conversaciones: 400-800. |
Control de turnos de historial
Use el parámetro max_history_turns para controlar cuántos turnos históricos de preguntas y respuestas referencia el modelo durante la inferencia. Valores más altos permiten que el modelo revise más historial de conversación para una mejor comprensión del contexto, pero aumentan el consumo de tokens y la latencia de inferencia.
{
"type": "session.update",
"session": {
"max_history_turns": 20
}
}
Rango válido para max_history_turns: 1-50. Valor predeterminado: 20.
Consejos de ajuste:
- Conversaciones cortas (como preguntas y respuestas rápidas): establezca un valor más bajo (por ejemplo, 5-10) para reducir la latencia.
- Conversaciones largas (como servicio al cliente de múltiples turnos): establezca un valor más alto (por ejemplo, 30-50) para ayudar al modelo a comprender el contexto completo.
Funciones avanzadas
Function Calling
Qwen-Audio admite Function Calling, lo que permite al modelo decidir cuándo invocar herramientas externas basándose en el contexto de la conversación.
1. Registrar herramientasConfigurar tools a través de session.update:
{
"type": "session.update",
"session": {
"tools": [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Query weather for a specified city",
"parameters": {
"type": "object",
"properties": {
"city": { "type": "string", "description": "City" }
},
"required": ["city"]
}
}
}]
}
}
2. Recibir llamadas a funciones
Cuando el modelo decide llamar a una herramienta, el servidor envía la siguiente secuencia de eventos:
response.created
response.output_item.added (item.type=function_call)
conversation.item.created (function_call item written to conversation)
response.function_call_arguments.delta (argument increments, may occur multiple times)
response.function_call_arguments.done (complete argument JSON)
response.output_item.done
response.done
3. Ejecute la herramienta y devuelva los resultados
Después de recibir response.function_call_arguments.done, ejecute la herramienta en el cliente y envíe el resultado de vuelta mediante conversation.item.create:
{
"type": "conversation.item.create",
"item": {
"type": "function_call_output",
"call_id": "call_xxx",
"output": "{\"temperature\":18,\"condition\":\"sunny\"}"
}
}
4. Activar una respuesta de seguimiento
Después de escribir el resultado de la herramienta, envíe response.create para que el modelo genere una respuesta basada en dicho resultado:
{
"type": "response.create",
"response": {
"modalities": ["audio", "text"]
}
}
NotaUna sola respuesta puede contener múltiples elementos function_call y puede incluir tanto mensajes regulares como llamadas a funciones. El contenido de las llamadas a funciones no se envía a TTS para su reproducción.
El siguiente ejemplo integra la compatibilidad con Function Calling sobre la base de realtime_demo.py del inicio rápido. Asegúrese de que B64PCMPlayer.py esté en el mismo directorio antes de ejecutarlo.
realtime_fc_demo.py
import asyncio
import base64
import json
import os
import struct
import time
import traceback
from enum import Enum
from typing import Optional, Callable, Dict, Any, List
import pyaudio
import websockets
from B64PCMPlayer import B64PCMPlayer
class TurnDetectionMode(Enum):
SERVER_VAD = "server_vad"
SEMANTIC_VAD = "smart_turn"
MANUAL = "manual"
# ============ Tool function definitions ============
def get_weather(city: str) -> str:
"""Query weather for a city (replace with a real API in production)."""
return json.dumps({"temperature": 18, "condition": "sunny", "wind": "light breeze"})
def get_train_price(src: str, dst: str) -> str:
"""Query train ticket price (replace with a real API in production)."""
return json.dumps({"price": 350, "seat": "second class", "note": "subject to 12306"})
# ============ Tools Schema ============
tools: List[Dict[str, Any]] = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Query the weather information for a specified city.",
"parameters": {
"type": "object",
"properties": {
"city": {"type": "string", "description": "City name, such as Beijing or Shanghai"}
},
"required": ["city"]
}
}
},
{
"type": "function",
"function": {
"name": "get_train_price",
"description": "Query train ticket prices between two cities.",
"parameters": {
"type": "object",
"properties": {
"src": {"type": "string", "description": "Departure city"},
"dst": {"type": "string", "description": "Destination city"}
},
"required": ["src", "dst"]
}
}
}
]
# Function name -> callable mapping
functions: Dict[str, Callable] = {
"get_weather": get_weather,
"get_train_price": get_train_price,
}
class FunRealtimeClient:
def __init__(
self,
base_url,
api_key: str,
model: str = "",
voice: str = "longanqian_v3.1",
instructions: str = "",
turn_detection_mode: TurnDetectionMode = TurnDetectionMode.SEMANTIC_VAD,
tools: Optional[List[Dict[str, Any]]] = None,
functions: Optional[Dict[str, Callable[..., Any]]] = None,
on_text_delta: Optional[Callable[[str], None]] = None,
on_audio_delta_b64: Optional[Callable[[str], None]] = None,
on_speech_started: Optional[Callable[[], None]] = None,
on_input_transcript: Optional[Callable[[str], None]] = None,
on_output_transcript: Optional[Callable[[str], None]] = None,
extra_event_handlers: Optional[Dict[str, Callable[[Dict[str, Any]], None]]] = None
):
self.base_url = base_url
self.api_key = api_key
self.model = model
self.voice = voice
self.instructions = instructions
self.ws = None
self.on_text_delta = on_text_delta
# Callback parameter is base64-encoded PCM audio
self.on_audio_delta_b64 = on_audio_delta_b64
self.on_speech_started = on_speech_started
self.on_input_transcript = on_input_transcript
self.on_output_transcript = on_output_transcript
self.turn_detection_mode = turn_detection_mode
self.extra_event_handlers = extra_event_handlers or {}
# Function Calling configuration
self.tools = tools or []
self.functions = functions or {}
# Response state tracking (for interruption handling and echo suppression)
self._current_response_id = None
self._current_item_id = None
self._is_responding = False
self._audio_suppressed = False
self._print_input_transcript = True
self._output_transcript_buffer = ""
async def connect(self) -> None:
"""Establish a WebSocket connection and send session configuration."""
url = f"{self.base_url}?model={self.model}"
headers = {
"Authorization": f"Bearer {self.api_key}",
"x-dashscope-dataInspection": "disable",
}
self.ws = await websockets.connect(url, additional_headers=headers)
session_config = {
"modalities": ["text", "audio"],
"voice": self.voice,
"instructions": self.instructions,
"input_audio_format": "pcm",
"output_audio_format": "pcm",
"turn_detection": {},
"tools": self.tools
}
if self.turn_detection_mode == TurnDetectionMode.MANUAL:
session_config['turn_detection'] = None
await self.update_session(session_config)
elif self.turn_detection_mode == TurnDetectionMode.SERVER_VAD:
session_config['turn_detection'] = {
"type": "server_vad",
"threshold": 0.1,
"silence_duration_ms": 900
}
await self.update_session(session_config)
elif self.turn_detection_mode == TurnDetectionMode.SEMANTIC_VAD:
session_config['turn_detection'] = {
"type": "smart_turn"
}
await self.update_session(session_config)
else:
raise ValueError(f"Invalid turn detection mode: {self.turn_detection_mode}")
async def send_event(self, event) -> None:
event['event_id'] = "event_" + str(int(time.time() * 1000))
await self.ws.send(json.dumps(event))
async def update_session(self, config: Dict[str, Any]) -> None:
"""Update session configuration."""
event = {
"type": "session.update",
"session": config
}
await self.send_event(event)
async def stream_audio(self, audio_chunk: bytes) -> None:
"""Stream raw audio data to the API."""
# Only 16-bit 16 kHz mono PCM is supported
audio_b64 = base64.b64encode(audio_chunk).decode()
await self.send_event({
"type": "input_audio_buffer.append",
"audio": audio_b64
})
async def commit_audio_buffer(self) -> None:
"""Commit the audio buffer to trigger processing."""
await self.send_event({"type": "input_audio_buffer.commit"})
async def create_response(self) -> None:
"""Request the API to generate a response (call in manual mode or after returning function call results)."""
await self.send_event({"type": "response.create"})
async def cancel_response(self) -> None:
"""Cancel the current response."""
await self.send_event({"type": "response.cancel"})
async def handle_interruption(self):
"""Handle user interruption of the current response."""
if not self._is_responding:
return
self._audio_suppressed = True
if self._current_response_id:
await self.cancel_response()
self._is_responding = False
self._current_response_id = None
self._current_item_id = None
@staticmethod
def _format_event_for_log(event: Dict[str, Any]) -> str:
"""Format an event as JSON for logging. Redacts audio data for privacy."""
event_type = event.get("type")
if event_type == "response.audio.delta":
delta = event.get("delta", "")
redacted = dict(event)
redacted["delta"] = f"<audio b64 omitted, length={len(delta)}>"
return json.dumps(redacted, ensure_ascii=False)
return json.dumps(event, ensure_ascii=False)
async def _handle_function_call(self, event: Dict[str, Any]) -> None:
"""Handle a function call: parse arguments, execute the function, return the result, and trigger a follow-up inference."""
call_id = event.get("call_id")
name = event.get("name")
arguments_str = event.get("arguments", "{}")
print(f"[FunctionCall] Calling: {name}, call_id: {call_id}, args: {arguments_str}")
try:
arguments = json.loads(arguments_str) if arguments_str else {}
except json.JSONDecodeError:
arguments = {}
func = self.functions.get(name)
if func is None:
output = json.dumps({"error": f"Unregistered function: {name}"})
else:
try:
if asyncio.iscoroutinefunction(func):
result = await func(**arguments)
else:
result = func(**arguments)
output = str(result) if result is not None else ""
except Exception as e:
output = json.dumps({"error": str(e)})
traceback.print_exc()
# Return function_call_output
await self.send_event({
"type": "conversation.item.create",
"item": {
"type": "function_call_output",
"call_id": call_id,
"output": output,
}
})
# Trigger follow-up inference
await self.create_response()
async def handle_messages(self) -> None:
try:
async for message in self.ws:
event = json.loads(message)
event_type = event.get("type")
print(self._format_event_for_log(event))
if event_type == "error":
continue
elif event_type == "response.created":
self._current_response_id = event.get("response", {}).get("id")
self._is_responding = True
self._audio_suppressed = False
elif event_type == "response.output_item.added":
self._current_item_id = event.get("item", {}).get("id")
elif event_type == "response.done":
self._is_responding = False
self._current_response_id = None
self._current_item_id = None
elif event_type == "input_audio_buffer.speech_started":
print("----------------Speech Started----------------")
if self.on_speech_started:
self.on_speech_started()
if self._is_responding:
await self.handle_interruption()
elif event_type == "response.audio.delta":
if self._audio_suppressed:
continue
if self.on_audio_delta_b64:
self.on_audio_delta_b64(event["delta"])
elif event_type == "response.function_call_arguments.done":
await self._handle_function_call(event)
elif event_type in self.extra_event_handlers:
self.extra_event_handlers[event_type](event)
elif event_type == "input_audio_buffer.speech_stopped":
print("----------------Speech Stopped----------------")
except websockets.exceptions.ConnectionClosed:
print(" Connection closed")
except Exception as e:
print(" Error in message handling: ", str(e))
traceback.print_exc()
async def close(self) -> None:
"""Close the WebSocket connection."""
if self.ws:
await self.ws.close()
def _audio_energy(audio_data: bytes) -> float:
count = len(audio_data) // 2
if count == 0:
return 0.0
samples = struct.unpack(f'<{count}h', audio_data)
return sum(abs(s) for s in samples) / count
async def record_and_send(client, player, echo_suppression=True):
p = pyaudio.PyAudio()
stream = p.open(format=pyaudio.paInt16, channels=1, rate=16000, input=True)
print("Recording started. Speak into the microphone...")
if echo_suppression:
print("Note: Echo suppression is enabled (microphone is muted while the AI is speaking; interruption is not supported). If you are using headphones, set echo_suppression=False to enable interruption.")
else:
print("Note: Headphone mode. Voice interruption is supported.")
playback_end_time = 0.0
NOISE_GATE_THRESHOLD = 500
try:
while True:
audio_data = await asyncio.to_thread(stream.read, 3200, False)
if echo_suppression:
is_active = client._is_responding or player.is_playing()
if is_active:
playback_end_time = time.time()
await asyncio.sleep(0.02)
continue
if time.time() - playback_end_time < 0.5:
await asyncio.sleep(0.02)
continue
else:
if client._is_responding or player.is_playing():
if _audio_energy(audio_data) < NOISE_GATE_THRESHOLD:
await asyncio.sleep(0.02)
continue
await client.stream_audio(audio_data)
await asyncio.sleep(0.02)
finally:
stream.stop_stream(); stream.close(); p.terminate()
async def main():
pya = pyaudio.PyAudio()
# Output sample rate 24 kHz, matching the server-side audio format
player = B64PCMPlayer(pya, sample_rate=24000)
client = FunRealtimeClient(
# The following is the WebSocket URL for the Singapore region. Replace {WorkspaceId} (including the curly braces) with your actual workspace ID. URLs vary by region.
base_url="wss://{WorkspaceId}.ap-southeast-1.maas.aliyuncs.com/api-ws/v1/realtime",
api_key=os.environ['DASHSCOPE_API_KEY'],
model="qwen-audio-3.1-realtime-plus",
voice="longanqian_v3.1",
turn_detection_mode=TurnDetectionMode.SERVER_VAD,
tools=tools,
functions=functions,
on_audio_delta_b64=player.add_data,
# Clear playback buffer on voice interruption
on_speech_started=player.cancel_playing,
)
await client.connect()
print("Connected. Starting real-time conversation (Function Calling enabled)...")
try:
await asyncio.gather(client.handle_messages(), record_and_send(client, player, echo_suppression=False))
finally:
await client.close()
player.shutdown()
pya.terminate()
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
print("\nProgram exited.")
Ejecute python realtime_fc_demo.py y hable en su micrófono para probar conversaciones en tiempo real con Function Calling. Por ejemplo, pregunte "¿Qué tiempo hace en Hangzhou?" o "¿Cuánto cuesta un boleto de tren de Beijing a Shanghai?" y el modelo invocará automáticamente la herramienta correspondiente y responderá con el resultado.
Gestión del contexto de conversación
Qwen-Audio le permite gestionar elementos de conversación en el contexto a través de eventos del cliente. Utilice esto para inyectar contexto histórico, agregar información de texto o eliminar elementos de conversación irrelevantes.
-
Crear un elemento de conversación (
conversation.item.create): inserta un elemento de conversación en el contexto. Se admiten los siguientes tres valores deitem.type:message: un mensaje de conversación regular. Especifiquerole(system,useroassistant) y un arreglocontent. Utilice esto para inyectar historial de conversación o instrucciones del sistema.function_call: solicitud de llamada a función. Especifiquecall_id,nameyarguments(cadena JSON). Generalmente es generada por el servidor, pero el cliente también puede usarla para inyectar registros históricos de llamadas a funciones.function_call_output: resultado de la ejecución de una herramienta. Especifiquecall_idyoutput(cadena JSON). Después de recibir unfunction_call, ejecute la herramienta en el cliente y devuelva el resultado con este tipo.
El parámetro opcional
previous_item_idespecifica el elemento de conversación existente después del cual insertar el nuevo elemento. Esto le permite insertar contenido en cualquier posición del historial de conversación. Si se omite, el nuevo elemento se añade al final.- Insertar un mensaje de usuario en una posición específica:
{
"type": "conversation.item.create",
"previous_item_id": "item_abc",
"item": {
"type": "message",
"role": "user",
"content": [
{ "type": "input_text", "text": "Please summarize our last conversation" }
]
}
}
- Devolver un resultado de Function Calling:
{
"type": "conversation.item.create",
"item": {
"type": "function_call_output",
"call_id": "call_xxx",
"output": "{\"temperature\":18,\"condition\":\"sunny\"}"
}
}
NotaSi el item.id especificado en conversation.item.create ya existe en la conversación, se devuelve un error.
- Recuperar un elemento de conversación (
conversation.item.retrieve): consulta un elemento de conversación almacenado en el servidor. Para contenido de tipo audio, solo se devuelve el texto transcrito, no los datos de audio sin procesar.
{
"type": "conversation.item.retrieve",
"item_id": "item_xxx"
}
- Eliminar un elemento de conversación (
conversation.item.delete): elimina un elemento específico del contexto de la conversación.
{
"type": "conversation.item.delete",
"item_id": "item_xxx"
}
Transcripción de audio ambiental
Solo modo smart_turn. Cuando VAD detecta actividad del habla pero el análisis semántico determina que no es un turno válido (como ruido o sonidos de relleno como "eh" o "hmm"), el servidor no activa un turno de conversación. En su lugar, envía el resultado ASR al cliente como un evento ambient_audio_transcription. Esta transcripción no se escribe en el contexto de la conversación.
{
"type": "conversation.item.ambient_audio_transcription.delta",
"item_id": "item_xxx",
"text": "hmm",
"stash": ""
}
Al igual que los eventos de transcripción del habla del usuario, la transcripción de audio ambiental incluye fases delta y completed. Utilice este evento para implementar la monitorización de audio ambiental o la conciencia del entorno de conversación.
Mejora de hablante
Solo modo smart_turn. Pase URLs de audio pregrabado del usuario objetivo en session.update. El modelo se fijará en ese hablante durante conversaciones dúplex, ignorando efectivamente otras voces y ruido de fondo, permitiendo interacciones dúplex fluidas en entornos abiertos.
Configuración: pase URL de audio de impresión de voz accesibles públicamente en turn_detection.voiceprint_audio_urls dentro de la primera llamada a session.update.
{
"type": "session.update",
"session": {
"turn_detection": {
"type": "smart_turn",
"voiceprint_audio_urls": ["https://example.com/speaker.wav"]
}
}
}
Requisitos de parámetros:
- Hasta 5 URL. El audio debe estar en formato PCM o WAV de 16 kHz.
- Este parámetro solo surte efecto en la primera llamada a
session.update. El campo se ignora en las llamadas posteriores.
Eventos de registro: después de recibir la configuración, el servidor realiza el registro de la impresión de voz de forma asíncrona y notifica el resultado a través de los siguientes eventos:
voiceprint_audio_list.in_progress: el registro ha comenzado. Se envía antes desession.updated, portandoitem_id.voiceprint_audio_list.completed: registro exitoso. Elitem_idcoincide con el dein_progress.voiceprint_audio_list.failed: error en el registro, con un camporeasonque describe el error (por ejemplo, la URL de audio no es accesible). Un fallo en el registro no bloquea la conversación en curso.
Puesta en marcha
Configurar tolerancia a fallos
- Reconexión del cliente: implemente la reconexión automática para manejar la inestabilidad de la red. Establezca una señal de reconexión en la devolución de llamada
on_errory utilice retroceso exponencial (por ejemplo, espere 1s, 2s, 4s) para los reintentos. - Clasificación de errores: los errores del cliente (
invalid_request_error) no desconectan la sesión; regístrelos o ajuste los parámetros. Los errores del servidor (server_error) terminan la conexión y requieren volver a conectarse. - Manejo de interrupciones: en los modos server_vad / smart_turn, el nuevo habla del usuario interrumpe automáticamente la respuesta en curso del modelo (
response.donedevuelvestatus=cancelled). Cuando se recibeinput_audio_buffer.speech_started, borre inmediatamente el búfer de reproducción local para evitar superposición de audio.
Ciclo de vida de la conexión
Una sesión típica de WebSocket sigue este ciclo de vida:
- Conectar: el cliente inicia una conexión WebSocket y el servidor devuelve un evento
session.created. - Configurar: el cliente envía
session.updatepara establecer el modo de interacción, la voz, las herramientas y otros parámetros. Complete este paso antes de enviar cualquier audio. - Interactuar: el cliente transmite audio continuamente (
input_audio_buffer.append). El servidor realiza la inferencia basándose en la detección de VAD o activadores manuales y devuelve voz y texto en streaming. - Cerrar: el cliente cierra la conexión WebSocket. El servidor también puede desconectarse si la conexión permanece inactiva durante demasiado tiempo.
Optimización de latencia
- Tamaño del fragmento de audio: envíe aproximadamente 100 ms de datos de audio por fragmento (16 kHz x 16 bits x mono = 3,200 bytes por fragmento). Esto equilibra el rendimiento en tiempo real con la eficiencia de la red.
- Reproducción en streaming: comience a reproducir el audio tan pronto como llegue
response.audio.delta. No espere aresponse.donepara reproducir la respuesta completa. - Borrar búfer en interrupción: cuando se recibe
input_audio_buffer.speech_started, borre inmediatamente el búfer de reproducción local para evitar que se siga reproduciendo audio obsoleto.
Modelos y regiones compatibles
Singapore
Utilice una API Key de la región de Singapore al llamar a los siguientes modelos:
- qwen-audio-3.1-realtime-plus
- qwen-audio-3.0-realtime-plus
- qwen-audio-3.0-realtime-flash
China (Beijing)
Utilice una clave de API de la región de Pekín al llamar a los siguientes modelos:
- qwen-audio-3.1-realtime-plus
- qwen-audio-3.0-realtime-plus
- qwen-audio-3.0-realtime-flash
Referencia de API
Para el flujo de trabajo de conexión y ejemplos, consulte Integración AOQ. Para modelos y versiones compatibles, consulte Compatibilidad de modelos y protocolos.
- API de WebSocket
- Eventos del cliente
- Eventos del servidor
- Descripción general de la API en tiempo real (descripción del protocolo WebRTC)