Una vez que te conectes a la Realtime API mediante WebRTC o WebSocket, puedes llamar a un modelo Realtime (como gpt-realtime-2.1) para mantener conversaciones de voz a voz. Para ello, debes enviar eventos del cliente para iniciar acciones y escuchar eventos del servidor para responder a las acciones de la Realtime API.
En esta guía verás qué secuencias de eventos necesitas para usar capacidades del modelo como la generación de audio y texto, la entrada de imágenes y la llamada a funciones. También verás cómo interpretar el estado de una sesión de Realtime.
Si no necesitas conversar con el modelo, es decir, si no esperas una respuesta, puedes usar la Realtime API en modo de transcripción.
Sesiones de voz a voz con Realtime
Una sesión de Realtime es una interacción entre el modelo y un cliente conectado en la que se conserva el estado. Los componentes principales de la sesión son los siguientes:
- El objeto Session controla los parámetros de la interacción, como el modelo que se usa, la voz con la que se genera el audio de salida y otras opciones de configuración.
- La conversación representa los elementos de entrada del usuario y los elementos de salida del modelo que se generan durante la sesión actual.
- Las respuestas son elementos de audio o texto que genera el modelo y que se agregan a la conversación.
Búfer de audio de entrada y WebSockets
Si usas WebRTC, sus API se encargan de gran parte del procesamiento multimedia necesario para enviar audio al modelo y recibir audio de él.
Si usas WebSockets para transmitir audio, debes gestionar manualmente el búfer de audio de entrada. Para ello, envía el audio al servidor codificado en Base64 dentro de eventos JSON.
En conjunto, estos componentes forman una sesión de Realtime. Usa eventos del cliente para actualizar el estado de la sesión y escucha los eventos del servidor para reaccionar a los cambios de estado.
Eventos del ciclo de vida de la sesión
Cuando inicias una sesión mediante WebRTC o WebSockets, el servidor envía un evento session.created que indica que la sesión está lista. Desde el cliente, puedes actualizar la configuración de la sesión actual con el evento session.update. Puedes actualizar la mayoría de las propiedades de la sesión en cualquier momento. La excepción es voice, la voz que usa el modelo para generar el audio de salida. Una vez que el modelo responde con audio por primera vez en la sesión, ya no puedes cambiarla. Una sesión de Realtime dura como máximo 60 minutos.
El siguiente ejemplo muestra cómo actualizar la sesión con un evento del cliente session.update. Consulta la guía de WebRTC o la de WebSocket para obtener más información sobre cómo enviar eventos del cliente por estos canales.
const event = {
type: "session.update",
session: {
type: "realtime",
model: "gpt-realtime-2.1",
// Lock the output to audio (set to ["text"] if you want text without audio)
output_modalities: ["audio"],
audio: {
input: {
format: {
type: "audio/pcm",
rate: 24000,
},
turn_detection: {
type: "semantic_vad",
},
},
output: {
format: {
type: "audio/pcm",
rate: 24000,
},
voice: "marin",
},
},
// Use a server-stored prompt by ID. Optionally pin a version and pass variables.
prompt: {
id: "pmpt_123", // your stored prompt ID
version: "89", // optional: pin a specific version
variables: {
city: "Paris", // example variable used by your prompt
},
},
// You can still set direct session fields; these override prompt fields if they overlap:
instructions:
"Speak clearly and briefly. Confirm understanding before taking actions.",
},
};
// WebRTC data channel and WebSocket both have .send()
dataChannel.send(JSON.stringify(event));Cuando se actualiza la sesión, el servidor emite un evento session.updated con el nuevo estado de la sesión.
| Eventos relacionados del cliente | Eventos relacionados del servidor |
|---|---|
Entradas y salidas de texto
Para generar texto con un modelo Realtime, puedes agregar entradas de texto a la conversación actual, pedirle al modelo que genere una respuesta y escuchar los eventos que envía el servidor para conocer el progreso de la respuesta. Para generar texto, la sesión debe estar configurada con la modalidad text, que está habilitada de forma predeterminada.
Crea un nuevo elemento de texto en la conversación con el evento del cliente conversation.item.create. Es similar a enviar un mensaje de usuario (prompt) en Chat Completions mediante la API REST.
const event = {
type: "conversation.item.create",
item: {
type: "message",
role: "user",
content: [
{
type: "input_text",
text: "What Prince album sold the most copies?",
},
],
},
};
// WebRTC data channel and WebSocket both have .send()
ws.send(JSON.stringify(event));Después de agregar el mensaje del usuario a la conversación, envía el evento response.create para que el modelo empiece a responder. Elige una sola modalidad de salida: audio genera audio con su transcripción y text genera solo texto. Si quieres generar solo texto, puedes especificarlo al enviar el evento del cliente response.create, como se muestra a continuación.
const event = {
type: "response.create",
response: {
output_modalities: ["text"],
},
};
// WebRTC data channel and WebSocket both have .send()
ws.send(JSON.stringify(event));Cuando el modelo termina de generar la respuesta, el servidor emite el evento response.done. Este evento contiene todo el texto que generó el modelo, como se muestra a continuación.
function handleEvent(message) {
const data = "data" in message ? message.data : message.toString();
const serverEvent = JSON.parse(data);
if (serverEvent.type === "response.done") {
console.log(serverEvent.response.output[0]);
}
}
// Listen for server messages (WebRTC)
// dataChannel.addEventListener("message", handleEvent);
// Listen for server messages (WebSocket)
ws.on("message", handleEvent);Mientras el modelo genera la respuesta, el servidor emite varios eventos del ciclo de vida. Puedes escuchar eventos como response.output_text.delta para mostrarles a los usuarios cómo avanza la respuesta en tiempo real. La columna Eventos relacionados del servidor de la siguiente tabla contiene la lista completa de eventos que emite el servidor. Los eventos aparecen en el orden aproximado en que se emiten, junto con los eventos del cliente que intervienen en la generación de texto.
| Eventos relacionados del cliente | Eventos relacionados del servidor |
|---|---|
Entradas y salidas de audio
Una de las capacidades más potentes de la Realtime API es la interacción de voz a voz con el modelo, sin un paso intermedio de conversión de texto a voz ni de voz a texto. Esto reduce la latencia de las interfaces de voz y le da al modelo más información sobre el tono y la inflexión de la voz que recibe.
Opciones de voz
Puedes configurar las sesiones de Realtime para que usen una de las voces integradas al generar audio de salida. Para controlar cómo suena el modelo, configura voice al crear la sesión (o en un evento response.create). Las voces disponibles actualmente son alloy, ash, ballad, coral, echo, sage, shimmer, verse, marin y cedar. Una vez que el modelo emite audio en una sesión, ya no puedes modificar voice en esa sesión. Para obtener la mejor calidad, recomendamos usar marin o cedar.
Gestionar el audio con WebRTC
Cuando te conectas a la Realtime API mediante WebRTC, la API se comunica con tu cliente a través de una conexión entre pares. El cliente recibe el audio de salida del modelo como un flujo multimedia remoto. El audio de entrada para el modelo se captura con dispositivos de audio mediante getUserMedia, y los flujos multimedia se agregan como pistas a la conexión entre pares.
El código de la guía de conexión con WebRTC muestra una configuración básica del audio local y remoto mediante las API del navegador:
// Create a peer connection
const pc = new RTCPeerConnection();
// Set up to play remote audio from the model
const audioEl = document.createElement("audio");
audioEl.autoplay = true;
pc.ontrack = (e) => (audioEl.srcObject = e.streams[0]);
// Add local audio track for microphone input in the browser
const ms = await navigator.mediaDevices.getUserMedia({
audio: true,
});
pc.addTrack(ms.getTracks()[0]);El fragmento anterior te permite interactuar con la Realtime API, pero puedes hacer mucho más. Para ver ejemplos de distintos tipos de interfaces de usuario, consulta el repositorio de ejemplos de WebRTC. También puedes probar las demostraciones en línea.
Con la captura y transmisión de contenido multimedia en el navegador, puedes activar y desactivar micrófonos, seleccionar el dispositivo de entrada y mucho más.
Eventos del cliente y del servidor para audio en WebRTC
De forma predeterminada, los clientes WebRTC pueden enviar audio de entrada a la Realtime API sin enviar antes ningún evento del cliente. En cuanto agregues una pista de audio local a la conexión entre pares, ¡los usuarios pueden empezar a hablar!
Aun así, mientras el cliente y el servidor intercambian audio a través de la conexión entre pares, los clientes WebRTC reciben varios eventos del ciclo de vida que envía el servidor. Por ejemplo:
- Cuando se envía audio de entrada por la pista multimedia local, recibes eventos
input_audio_buffer.speech_starteddel servidor. - Cuando se detiene la entrada de audio local, recibes el evento
input_audio_buffer.speech_stopped. - Recibes eventos delta con los avances de la transcripción del audio.
- Recibes un evento
response.donecuando el modelo ha transcrito la respuesta y ha terminado de enviarla.
Las API de WebRTC para flujos multimedia pueden darte todo el control que necesitas. Sin embargo, en ocasiones puede ser necesario usar interfaces de nivel más bajo para la entrada y la salida de audio. Consulta la siguiente sección sobre WebSockets para obtener más información y ver la lista de eventos necesarios para controlar la entrada de audio con mayor precisión.
Gestionar el audio con WebSockets
Con WebSocket, enviar audio desde el cliente y recibirlo del servidor requiere algunos pasos adicionales. La siguiente tabla describe la secuencia de eventos necesarios para enviar y recibir audio a través de una conexión WebSocket durante una sesión.
Los eventos aparecen en el orden del ciclo de vida, aunque algunos, como los eventos delta, pueden producirse al mismo tiempo.
| Etapa del ciclo de vida | Eventos del cliente | Eventos del servidor |
|---|---|---|
| Inicialización de la sesión | ||
| Audio de entrada del usuario | (envía el mensaje de audio completo) (transmite el audio en fragmentos) (se usa cuando la VAD está desactivada) (se usa cuando la VAD está desactivada) |
|
| Audio de salida del servidor | (se usa cuando la VAD está desactivada) |
|
Transmitir audio de entrada al servidor
Para transmitir audio de entrada al servidor, puedes usar el evento del cliente input_audio_buffer.append. Con este evento, debes enviar fragmentos de bytes de audio codificados en Base64 a la Realtime API a través del socket. Cada fragmento puede tener un tamaño máximo de 15 MB.
Configura session.audio.input.format en session.update. Para PCM, usa {"type": "audio/pcm", "rate": 24000} y envía audio mono de 24 kHz.
import fs from "fs";
import decodeAudio from "audio-decode";
// Converts Float32Array of audio data to PCM16 ArrayBuffer
function floatTo16BitPCM(float32Array) {
const buffer = new ArrayBuffer(float32Array.length * 2);
const view = new DataView(buffer);
let offset = 0;
for (let i = 0; i < float32Array.length; i++, offset += 2) {
const sample = Math.max(-1, Math.min(1, float32Array[i]));
view.setInt16(
offset,
Math.round(sample * (sample < 0 ? 0x8000 : 0x7fff)),
true
);
}
return buffer;
}
// Converts a Float32Array to base64-encoded PCM16 data
function base64EncodeAudio(float32Array) {
const arrayBuffer = floatTo16BitPCM(float32Array);
let binary = "";
let bytes = new Uint8Array(arrayBuffer);
const chunkSize = 0x8000; // 32KB chunk size
for (let i = 0; i < bytes.length; i += chunkSize) {
let chunk = bytes.subarray(i, i + chunkSize);
binary += String.fromCharCode(...chunk);
}
return btoa(binary);
}
// Fills the audio buffer with the contents of three files,
// then asks the model to generate a response.
const files = [
"fixtures/sample1.wav",
"fixtures/sample2.wav",
"fixtures/sample3.wav",
];
// Manual commit and response creation require automatic turn detection off.
ws.send(
JSON.stringify({
type: "session.update",
session: {
type: "realtime",
audio: { input: { turn_detection: null } },
},
})
);
for (const filename of files) {
const audioFile = fs.readFileSync(filename);
const audioBuffer = await decodeAudio(audioFile);
if (
audioBuffer.sampleRate !== 24000 ||
audioBuffer.channelData.length !== 1
) {
throw new Error("Expected 24 kHz mono audio: " + filename);
}
const channelData = audioBuffer.channelData[0];
const base64Chunk = base64EncodeAudio(channelData);
ws.send(
JSON.stringify({
type: "input_audio_buffer.append",
audio: base64Chunk,
})
);
}
ws.send(JSON.stringify({ type: "input_audio_buffer.commit" }));
ws.send(
JSON.stringify({
type: "response.create",
response: { output_modalities: ["audio"] },
})
);Enviar mensajes de audio completos
También puedes crear mensajes de conversación con grabaciones de audio completas. Usa el evento del cliente conversation.item.create para crear mensajes con contenido de tipo input_audio.
const fullAudio = "<a base64-encoded string of audio bytes>";
const event = {
type: "conversation.item.create",
item: {
type: "message",
role: "user",
content: [
{
type: "input_audio",
audio: fullAudio,
},
],
},
};
// WebRTC data channel and WebSocket both have .send()
dataChannel.send(JSON.stringify(event));Procesar la salida de audio de un WebSocket
Para reproducir el audio de salida en un cliente, como un navegador web, recomendamos usar WebRTC en lugar de WebSockets. WebRTC transmite contenido multimedia a los dispositivos cliente de forma más confiable cuando las condiciones de red son inestables.
Para procesar audio de salida en aplicaciones que se comunican de servidor a servidor mediante WebSocket, debes escuchar los eventos response.output_audio.delta. Estos contienen fragmentos de audio del modelo codificados en Base64. Puedes almacenar los fragmentos en un búfer y escribirlos en un archivo, o transmitirlos de inmediato a otro destino, como una llamada telefónica con Twilio.
Ten en cuenta que los eventos response.output_audio.done y response.done solo contienen transcripciones del audio. Para obtener los bytes de audio, debes escuchar los eventos response.output_audio.delta.
Puedes configurar el formato de los fragmentos de salida para toda la sesión o para cada respuesta.
- Para toda la sesión, configura
session.audio.output.formatensession.update. - Para una respuesta, configura
response.audio.output.formatenresponse.create.
function handleEvent(message) {
const serverEvent = JSON.parse(message.toString());
if (serverEvent.type === "response.output_audio.delta") {
const audio = Buffer.from(serverEvent.delta, "base64");
console.log(`Received ${audio.length} audio bytes`);
}
}
// Listen for server messages (WebSocket)
ws.on("message", handleEvent);Entradas de imagen
gpt-realtime-2 y gpt-realtime también admiten imágenes como entrada. Puedes adjuntar una imagen como parte del contenido de un mensaje de usuario, y el modelo puede tener en cuenta lo que aparece en ella al responder.
const base64Image = "<a base64-encoded string of image bytes>";
const event = {
type: "conversation.item.create",
item: {
type: "message",
role: "user",
content: [
{
type: "input_image",
image_url: `data:image/{format};base64,${base64Image}`,
},
],
},
};
// WebRTC data channel and WebSocket both have .send()
dataChannel.send(JSON.stringify(event));Detección de actividad de voz
De forma predeterminada, las sesiones de Realtime tienen activada la detección de actividad de voz (VAD). Esto permite que la API detecte cuándo el usuario empieza o deja de hablar y responda automáticamente.
Consulta cómo configurar la VAD en nuestra guía de detección de actividad de voz.
Desactivar la VAD
Para desactivar la VAD, establece turn_detection en null mediante el evento del cliente session.update. Esto puede ser útil para interfaces que requieren un control preciso de la entrada de audio, como las que permiten presionar para hablar.
Cuando la VAD está desactivada, el cliente debe emitir manualmente algunos eventos adicionales para que el modelo responda con audio:
- Envía manualmente
input_audio_buffer.commitpara crear un nuevo elemento de entrada del usuario en la conversación. - Envía manualmente
response.createpara que el modelo genere una respuesta de audio. - Envía
input_audio_buffer.clearantes de iniciar una nueva entrada del usuario.
Mantener la VAD y desactivar las respuestas automáticas
Si quieres mantener la VAD activada y decidir manualmente cuándo se genera una respuesta, puedes establecer turn_detection.interrupt_response y turn_detection.create_response en false mediante el evento del cliente session.update. Así conservas el comportamiento de la VAD y evitas que se creen respuestas automáticamente. Los clientes pueden solicitarlas manualmente con un evento response.create.
Esto puede ser útil para la moderación, la validación de entradas o los patrones de RAG cuando puedes aceptar un poco más de latencia en la interacción a cambio de un mayor control sobre las entradas.
Crear respuestas fuera de la conversación predeterminada
De forma predeterminada, todas las respuestas que se generan durante una sesión se agregan al estado de la conversación de esa sesión (la “conversación predeterminada”). Sin embargo, es posible que quieras generar respuestas del modelo fuera del contexto de esa conversación o generar varias respuestas a la vez. También puedes controlar con mayor precisión qué elementos de la conversación tiene en cuenta el modelo al generar una respuesta (por ejemplo, solo los últimos N turnos).
Para generar respuestas “fuera de banda” que no se agreguen al estado de la conversación predeterminada, establece el campo response.conversation en la cadena none al crear la respuesta con el evento del cliente response.create.
Al crear una respuesta fuera de banda, probablemente también necesites identificar qué eventos del servidor corresponden a esa respuesta. Puedes incluir metadata en la respuesta del modelo para identificar la respuesta que se está generando a partir del evento que envió el cliente.
const prompt = `
Analyze the conversation so far. If it is related to support, output
"support". If it is related to sales, output "sales".
`;
const event = {
type: "response.create",
response: {
// Setting to "none" indicates the response is out of band
// and will not be added to the default conversation
conversation: "none",
// Set metadata to help identify responses sent back from the model
metadata: { topic: "classification" },
// Set any other available response fields
output_modalities: ["text"],
instructions: prompt,
},
};
// WebRTC data channel and WebSocket both have .send()
ws.send(JSON.stringify(event));Ahora, al escuchar el evento del servidor response.done, puedes identificar el resultado de la respuesta fuera de banda.
function handleEvent(message) {
const data = "data" in message ? message.data : message.toString();
const serverEvent = JSON.parse(data);
if (
serverEvent.type === "response.done" &&
serverEvent.response.metadata?.topic === "classification"
) {
const { response } = serverEvent;
if (response.status !== "completed" || response.output.length === 0) {
throw new Error(
"Classification response ended with status " + response.status
);
}
console.log(response.output[0]);
}
}
// Listen for server messages (WebRTC)
// dataChannel.addEventListener("message", handleEvent);
// Listen for server messages (WebSocket)
ws.on("message", handleEvent);Crear un contexto personalizado para las respuestas
También puedes crear un contexto personalizado, independiente de la conversación predeterminada actual, que el modelo usará para generar una respuesta. Para ello, usa el arreglo input en un evento del cliente response.create. Puedes usar entradas nuevas o hacer referencia a elementos de entrada existentes en la conversación mediante su ID.
const existingItemId = "item_pizza";
ws.send(
JSON.stringify({
type: "conversation.item.create",
item: {
id: existingItemId,
type: "message",
role: "user",
content: [
{ type: "input_text", text: "We are ordering pizza for dinner." },
],
},
})
);
const event = {
type: "response.create",
response: {
conversation: "none",
metadata: { topic: "pizza" },
output_modalities: ["text"],
// Create a custom input array for this request with whatever context
// is appropriate
input: [
// potentially include existing conversation items:
{
type: "item_reference",
id: existingItemId,
},
{
type: "message",
role: "user",
content: [
{
type: "input_text",
text: "Is it okay to put pineapple on pizza?",
},
],
},
],
},
};
// WebRTC data channel and WebSocket both have .send()
ws.send(JSON.stringify(event));Crear respuestas sin contexto
También puedes insertar respuestas en la conversación predeterminada sin tener en cuenta las demás instrucciones ni el contexto. Para ello, establece input en un arreglo vacío.
const prompt = `
Say exactly the following:
I'm a little teapot, short and stout!
This is my handle, this is my spout!
`;
const event = {
type: "response.create",
response: {
// An empty input array removes existing context
input: [],
output_modalities: ["text"],
instructions: prompt,
},
};
// WebRTC data channel and WebSocket both have .send()
ws.send(JSON.stringify(event));Llamada a funciones
Los modelos Realtime también admiten la llamada a funciones, que te permite ejecutar código personalizado para ampliar las capacidades del modelo. El proceso funciona de la siguiente manera:
- Al actualizar la sesión o crear una respuesta, puedes especificar una lista de funciones que el modelo puede llamar.
- Si al procesar la entrada el modelo determina que debe llamar a una función, agrega a la conversación elementos que representan los argumentos de la llamada.
- Cuando el cliente detecta elementos de conversación que contienen los argumentos de una llamada a una función, ejecuta código personalizado con esos argumentos.
- Una vez ejecutado el código personalizado, el cliente crea nuevos elementos de conversación con el resultado de la llamada y le pide al modelo que responda.
Veamos cómo funciona en la práctica con una función que el modelo puede llamar para darles a los usuarios el horóscopo del día. Verás la estructura de los objetos de evento que debe enviar el cliente y los eventos que emite el servidor en respuesta.
Configurar las funciones que el modelo puede llamar
Primero, debes proporcionarle al modelo las funciones que puede llamar según la entrada del usuario. Puedes configurar las funciones disponibles para toda la sesión o para una respuesta específica.
- Para toda la sesión, usa la propiedad
session.toolsensession.update. - Para una respuesta, usa la propiedad
response.toolsenresponse.create.
El siguiente ejemplo muestra el contenido de un evento del cliente session.update que configura una función para generar horóscopos. La función recibe un solo argumento, el signo zodiacal para el que debe generar el horóscopo.
{
"type": "session.update",
"session": {
"tools": [
{
"type": "function",
"name": "generate_horoscope",
"description": "Give today's horoscope for an astrological sign.",
"parameters": {
"type": "object",
"properties": {
"sign": {
"type": "string",
"description": "The sign for the horoscope.",
"enum": [
"Aries",
"Taurus",
"Gemini",
"Cancer",
"Leo",
"Virgo",
"Libra",
"Scorpio",
"Sagittarius",
"Capricorn",
"Aquarius",
"Pisces"
]
}
},
"required": ["sign"]
}
}
],
"tool_choice": "auto"
}
}
Los campos description de la función y de los parámetros ayudan al modelo a decidir si debe llamar a la función y qué datos incluir en cada parámetro. Si la entrada indica que el usuario quiere conocer su horóscopo, el modelo llamará a esta función con el parámetro sign.
Detectar cuándo el modelo quiere llamar a una función
Según la entrada que reciba, el modelo puede decidir llamar a una función para generar la mejor respuesta. Supongamos que nuestra aplicación agrega el siguiente elemento a la conversación mediante un evento conversation.item.create y luego crea una respuesta:
{
"type": "conversation.item.create",
"item": {
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": "What is my horoscope? I am an aquarius."
}
]
}
}
A continuación, el cliente envía el evento response.create para generar la respuesta:
{
"type": "response.create"
}
En lugar de devolver de inmediato una respuesta de texto o audio, el modelo genera una respuesta con los argumentos que debes pasarle a una función de tu aplicación. Puedes escuchar el evento del servidor response.function_call_arguments.delta para recibir actualizaciones de los argumentos en tiempo real. El evento response.done también contiene todos los datos necesarios para llamar a la función.
{
"type": "response.done",
"event_id": "event_AeqLA8iR6FK20L4XZs2P6",
"response": {
"object": "realtime.response",
"id": "resp_AeqL8XwMUOri9OhcQJIu9",
"status": "completed",
"status_details": null,
"output": [
{
"object": "realtime.item",
"id": "item_AeqL8gmRWDn9bIsUM2T35",
"type": "function_call",
"status": "completed",
"name": "generate_horoscope",
"call_id": "call_sHlR7iaFwQ2YQOqm",
"arguments": "{\"sign\":\"Aquarius\"}"
}
]
}
}
En el JSON que emite el servidor, estas propiedades indican que el modelo quiere llamar a una función personalizada y proporcionan los datos para hacerlo:
| Propiedad | Uso en la llamada a funciones |
|---|---|
response.output[0].type |
Cuando su valor es function_call, indica que la respuesta contiene los argumentos para llamar a una función identificada por su nombre. |
response.output[0].name |
Nombre de la función configurada que se debe llamar, en este caso generate_horoscope. |
response.output[0].arguments |
Cadena JSON que contiene los argumentos de la función. En este caso, "{\"sign\":\"Aquarius\"}". |
response.output[0].call_id |
ID que genera el sistema para esta llamada. Necesitas este ID para devolverle al modelo el resultado de la llamada. |
Con esta información, puedes ejecutar código en tu aplicación para generar el horóscopo y luego pasarle el resultado al modelo para que genere una respuesta.
Entregarle al modelo los resultados de una llamada a una función
Cuando tu aplicación recibe una respuesta del modelo con los argumentos para llamar a una función, puede ejecutar el código que realiza la operación solicitada. Ese código puede hacer lo que necesites, como consultar API externas o acceder a bases de datos.
Cuando tengas listo el resultado de tu código personalizado, puedes entregárselo al modelo creando un elemento de conversación que lo contenga mediante el evento del cliente conversation.item.create.
{
"type": "conversation.item.create",
"item": {
"type": "function_call_output",
"call_id": "call_sHlR7iaFwQ2YQOqm",
"output": "{\"horoscope\": \"You will soon meet a new friend.\"}"
}
}
- El tipo del elemento de conversación es
function_call_output. item.call_ides el mismo ID que recibiste en el eventoresponse.doneanterior.item.outputes una cadena JSON que contiene los resultados de la llamada a la función.
Después de agregar el elemento de conversación que contiene los resultados de la llamada a la función, vuelve a emitir el evento response.create desde el cliente. Así, el modelo generará una respuesta con los datos de la llamada.
{
"type": "response.create"
}
Manejo de errores
El servidor emite el evento error cada vez que detecta un error durante la sesión. En ocasiones, puedes identificar el evento del cliente que emitió tu aplicación y que originó el error.
En HTTP, cada respuesta está asociada de forma implícita a una solicitud del cliente. Con los eventos del cliente, debes usar la propiedad event_id para identificar si uno de ellos provocó un error en el servidor. El siguiente código muestra cómo hacerlo cuando el cliente intenta emitir un evento de un tipo no admitido.
const event = {
event_id: "my_awesome_event",
type: "scooby.dooby.doo",
};
dataChannel.send(JSON.stringify(event));Cuando el cliente envía ese evento, el servidor emite un evento de error como el siguiente:
{
"type": "error",
"event_id": "event_error_123",
"error": {
"type": "invalid_request_error",
"code": "invalid_value",
"message": "Invalid value: 'scooby.dooby.doo'.",
"param": "type",
"event_id": "my_awesome_event"
}
}
Interrupción y truncamiento
En muchas aplicaciones de voz, el usuario puede interrumpir al modelo mientras habla. Cuando la VAD está activada, la Realtime API gestiona las interrupciones: detecta que el usuario está hablando, cancela la respuesta en curso e inicia una nueva. En ese caso, conviene que el modelo sepa en qué punto lo interrumpieron para poder continuar la conversación con naturalidad (por ejemplo, si el usuario pregunta “¿Qué fue lo último que dijiste?”). A esto lo llamamos truncar la última respuesta del modelo. Consiste en eliminar de la conversación la parte de esa respuesta que no llegó a reproducirse.
En las conexiones WebRTC y SIP, el servidor gestiona un búfer de audio de salida, por lo que sabe cuánto audio se ha reproducido en cada momento. Cuando el usuario interrumpe al modelo, el servidor trunca automáticamente el audio que aún no se ha reproducido.
En una conexión WebSocket, el cliente gestiona la reproducción de audio, por lo que debe detenerla y encargarse del truncamiento. Sigue estos pasos:
- Al recibir
input_audio_buffer.speech_started, detén de inmediato la reproducción del audio del modelo y descarta el audio de ese elemento que esté en cola. Registra cuántos milisegundos de audio de ese elemento llegó a escuchar el usuario. - Envía un evento
conversation.item.truncatecon el ID del elemento interrumpido, el índice del contenido de audio y la duración reproducida como valor deaudio_end_ms. Esto elimina de la conversación la parte que no se reprodujo. - Cuando la VAD interrumpe una respuesta en curso, el servidor la cancela y envía
response.doneconresponse.statusestablecido encancelled. Usa ese evento para saber que la respuesta ha finalizado. Detén la reproducción y envía el evento de truncamiento sin esperar a recibirlo.
Por ejemplo:
{
"type": "conversation.item.truncate",
"item_id": "item_1234",
"content_index": 0,
"audio_end_ms": 1500
}
Este ejemplo trunca el audio después de 1,5 segundos de reproducción.
¿También se puede truncar la transcripción? El modelo Realtime no cuenta con suficiente información para alinear con precisión la transcripción y el audio. Por eso, conversation.item.truncate corta el audio en el punto indicado y elimina la transcripción de texto de la parte que no se reprodujo. Así se elimina el audio que el usuario no escuchó, pero no se obtiene una transcripción truncada.
Presionar para hablar
De forma predeterminada, la Realtime API usa la detección de actividad de voz (VAD), por lo que la entrada de audio activa las respuestas del modelo. También puedes implementar la interacción de presionar para hablar. Para ello, desactiva la VAD y controla desde la aplicación cuándo se envía audio al modelo. Por ejemplo, puedes capturar audio mientras el usuario mantiene presionada la barra espaciadora y solicitar una respuesta cuando la suelta. En algunas aplicaciones, esto funciona sorprendentemente bien: los usuarios controlan la interacción, se evitan fallos de la VAD y la respuesta se siente ágil porque no hay que esperar a que venza el plazo de la VAD.
La implementación de presionar para hablar varía un poco entre WebSockets y WebRTC. En una conexión WebSocket con la Realtime API, todos los eventos se envían por el mismo canal y conservan su orden. En una conexión WebRTC, el audio y los eventos de control se envían por canales separados.
WebSockets
Para implementar la interacción de presionar para hablar con una conexión WebSocket, el cliente debe detener la reproducción de audio, gestionar las interrupciones e iniciar una nueva respuesta. Sigue estos pasos:
- Desactiva la VAD con
"turn_detection": nullen un eventosession.update. - Cuando el usuario presione el botón, empieza a grabar audio en el cliente.
- Si hay una respuesta del modelo en curso, cancélala enviando un evento
response.cancel. - Si se está reproduciendo audio del modelo, detén la reproducción de inmediato y envía un evento
conversation.item.truncatepara eliminar de la conversación el audio que no se reprodujo.
- Si hay una respuesta del modelo en curso, cancélala enviando un evento
- Cuando el usuario suelte el botón, envía un mensaje
input_audio_buffer.appendcon el audio para agregarlo al búfer de entrada. - Envía un evento
input_audio_buffer.commitpara confirmar el audio que se escribió en el búfer de entrada e iniciar la transcripción de la entrada si está activada. - Luego, solicita una respuesta con un evento
response.create.
WebRTC y SIP
La implementación de presionar para hablar con WebRTC es similar, pero debes vaciar explícitamente el búfer de audio de entrada. Sigue estos pasos:
- Desactiva la VAD con
"turn_detection": nullen un eventosession.update. - Cuando el usuario presione el botón, envía un evento
input_audio_buffer.clearpara borrar el audio de entrada anterior.- Si hay una respuesta del modelo en curso, cancélala enviando un evento
response.cancel. - Si se está reproduciendo audio del modelo, envía un evento
output_audio_buffer.clearpara eliminar el audio que aún no se ha reproducido. Esto también trunca la conversación.
- Si hay una respuesta del modelo en curso, cancélala enviando un evento
- Cuando el usuario suelte el botón, envía un evento
input_audio_buffer.commitpara confirmar el audio que se escribió en el búfer de entrada e iniciar la transcripción de la entrada si está activada. - Luego, solicita una respuesta con un evento
response.create.