Una vez que te hayas conectado 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, deberás enviar eventos de cliente para iniciar acciones y escuchar eventos del servidor para responder a las acciones que realice la Realtime API.
Esta guía explica los flujos de eventos necesarios para usar capacidades del modelo como la generación de audio y texto, la entrada de imágenes y la llamada a funciones, así como la forma de entender el estado de una sesión en tiempo real.
Si no necesitas mantener una conversación con el modelo, es decir, si no esperas ninguna respuesta, puedes usar la Realtime API en modo de transcripción.
Sesiones de voz a voz en tiempo real
Una sesión en tiempo real es una interacción que mantiene un estado entre el modelo y un cliente conectado. Los componentes principales de la sesión son:
- El objeto Sesión , que controla los parámetros de la interacción, como el modelo que se usa, la voz con la que se genera la salida y otras opciones de configuración.
- Una Conversación, que representa los elementos de entrada del usuario y los elementos de salida del modelo generados durante la sesión actual.
- Las Respuestas, que son elementos de audio o texto generados por el modelo que se agregan a la conversación.
Búfer de audio de entrada y WebSockets
Si usas WebRTC, sus API facilitan gran parte del manejo de contenido multimedia necesario para enviar audio al modelo y recibirlo.
Si usas WebSockets para el audio, deberás interactuar manualmente con el búfer de audio de entrada , enviando audio al servidor mediante eventos JSON que contengan audio codificado en base64.
Todos estos componentes conforman una sesión en tiempo real. Usarás eventos del cliente para actualizar el estado de la sesión y escucharás eventos del servidor para reaccionar a los cambios de estado dentro de ella.
Eventos del ciclo de vida de la sesión
Después de iniciar una sesión mediante WebRTC o WebSockets, el servidor enviará un evento session.created para indicar que la sesión está lista. En el cliente, puedes actualizar la configuración de la sesión actual con el evento session.update. La mayoría de las propiedades de la sesión se pueden actualizar en cualquier momento, excepto voice, que define la voz que usa el modelo para el audio de salida y no se puede cambiar una vez que el modelo haya respondido con audio durante la sesión. La duración máxima de una sesión Realtime es de 60 minutos.
El siguiente ejemplo muestra cómo actualizar la sesión con un evento de cliente session.update. Consulta la guía de WebRTC o WebSocket para obtener más información sobre cómo enviar eventos de cliente a través de 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",
},
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 haya actualizado la sesión, el servidor emitirá un evento session.updated con el nuevo estado de la sesión.
| Eventos del cliente relacionados | Eventos del servidor relacionados |
|---|---|
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 enviados por el servidor que indican el progreso de la respuesta del modelo. Para generar texto, la sesión debe estar configurada con la modalidad text (esta es la configuración predeterminada).
Crea un nuevo elemento de texto en la conversación con el evento del cliente conversation.item.create. Esto es similar a enviar un mensaje de usuario (prompt) en Chat Completions en 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()
dataChannel.send(JSON.stringify(event));Después de agregar el mensaje del usuario a la conversación, envía el evento response.create para iniciar una respuesta del modelo. Si tanto el audio como el texto están habilitados para la sesión actual, el modelo responderá con contenido de audio y 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()
dataChannel.send(JSON.stringify(event));Cuando la respuesta esté completamente terminada, el servidor emitirá el evento response.done. Este evento contendrá todo el texto generado por 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 se genera la respuesta del modelo, el servidor emitirá varios eventos del ciclo de vida. Puedes escuchar estos eventos, como response.output_text.delta, para mostrar a los usuarios el progreso en tiempo real a medida que se genera la respuesta. Más abajo, en eventos del servidor relacionados, encontrarás la lista completa de los eventos que emite el servidor. Se presentan en el orden aproximado en que se emiten, junto con los eventos del cliente pertinentes para la generación de texto.
| Eventos del cliente relacionados | Eventos del servidor relacionados |
|---|---|
Entradas y salidas de audio
Una de las funciones 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 o de voz a texto. Esto permite reducir la latencia de las interfaces de voz y le proporciona al modelo más datos sobre el tono y la entonación de la voz de entrada.
Opciones de voz
Las sesiones en tiempo real se pueden configurar para usar una de las distintas voces integradas al generar audio de salida. Puedes establecer voice al crear la sesión (o en un evento response.create) para controlar cómo suena el modelo. Las opciones de voz actuales son alloy, ash, ballad, coral, echo, sage, shimmer, verse, marin y cedar. Una vez que el modelo haya emitido audio en una sesión, no se podrá modificar voice para esa sesión. Para obtener la mejor calidad, recomendamos usar marin o cedar.
Manejo de audio con WebRTC
Si te conectas a la Realtime API mediante WebRTC, la Realtime API actúa como un par en una conexión entre pares con tu cliente. El audio de salida del modelo se entrega a tu cliente como un flujo multimedia remoto. El audio de entrada al modelo se captura mediante dispositivos de audio (getUserMedia) y los flujos multimedia se agregan como pistas a la conexión entre pares.
El código de ejemplo de la guía de conexión mediante WebRTC muestra una forma básica de configurar tanto el audio local como el remoto con 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 de código anterior permite interactuar con la Realtime API, pero se puede hacer mucho más. Para ver más ejemplos de distintos tipos de interfaces de usuario, consulta el repositorio de ejemplos de WebRTC. También puedes encontrar aquí demostraciones en vivo de estos ejemplos.
El uso de capturas y flujos multimedia en el navegador te permite realizar acciones como silenciar y reactivar micrófonos, seleccionar el dispositivo del que se obtiene la entrada y mucho más.
Eventos del cliente y del servidor para audio en WebRTC
De forma predeterminada, los clientes WebRTC no necesitan enviar ningún evento del cliente a la Realtime API antes de enviar entradas de audio. Una vez que se agrega una pista de audio local a la conexión entre pares, ¡tus usuarios pueden empezar a hablar!
Sin embargo, los clientes WebRTC siguen recibiendo varios eventos del ciclo de vida enviados por el servidor mientras el audio se transmite en ambas direcciones entre el cliente y el servidor a través de la conexión entre pares. Por ejemplo:
- Cuando se envíe una entrada a través de la pista multimedia local, recibirás eventos
input_audio_buffer.speech_starteddel servidor. - Cuando se detenga la entrada de audio local, recibirás el evento
input_audio_buffer.speech_stopped. - Recibirás eventos delta de la transcripción de audio en curso.
- Recibirás un evento
response.donecuando el modelo haya transcrito una respuesta y terminado de enviarla.
El manejo de las API de WebRTC para flujos multimedia puede darte todo el control que necesitas. Sin embargo, en ocasiones puede ser necesario usar interfaces de nivel más bajo para la entrada y salida de audio. Consulta la sección sobre WebSockets a continuación para obtener más información y una lista de los eventos necesarios para controlar la entrada de audio con mayor detalle.
Manejo de audio con WebSockets
Al enviar y recibir audio a través de un WebSocket, tendrás que realizar un poco más de trabajo para enviar contenido multimedia desde el cliente y recibirlo desde el servidor. A continuación, encontrarás una tabla que describe el flujo de eventos necesarios durante una sesión WebSocket para enviar y recibir audio a través de ella.
Los eventos siguientes se presentan en el orden del ciclo de vida, aunque algunos (como los eventos delta) pueden ocurrir de forma simultánea.
| Etapa del ciclo de vida | Eventos del cliente | Eventos del servidor |
|---|---|---|
| Inicialización de la sesión | ||
| Audio de entrada del usuario | (enviar el mensaje de audio completo) (transmitir audio en fragmentos) (se usa cuando VAD está desactivada) (se usa cuando VAD está desactivada) |
|
| Audio de salida del servidor | (se usa cuando VAD está desactivada) |
|
Transmitir audio de entrada al servidor
Para transmitir audio de entrada al servidor, puedes usar el evento de cliente input_audio_buffer.append. Este evento requiere que envíes fragmentos de bytes de audio codificados en Base64 a la Realtime API a través del socket. Cada fragmento no puede superar los 15 MB.
El formato de los fragmentos de entrada se puede configurar para toda la sesión o para cada respuesta.
- Sesión:
session.input_audio_formatensession.update - Respuesta:
response.input_audio_formatenresponse.create
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) {
let s = Math.max(-1, Math.min(1, float32Array[i]));
view.setInt16(offset, s < 0 ? s * 0x8000 : s * 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",
];
for (const filename of files) {
const audioFile = fs.readFileSync(filename);
const audioBuffer = await decodeAudio(audioFile);
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" }));Enviar mensajes de audio completos
También es posible crear mensajes de conversación que sean grabaciones de audio completas. Usa el evento de cliente conversation.item.create para crear mensajes con contenido 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));Trabajar con audio de salida de un WebSocket
Para reproducir el audio de salida en un dispositivo cliente, como un navegador web, recomendamos usar WebRTC en lugar de WebSockets. WebRTC ofrece mayor robustez al enviar contenido multimedia a dispositivos cliente en condiciones de red variables.
Pero para trabajar con audio de salida en aplicaciones de servidor a servidor mediante un WebSocket, tendrás que escuchar los eventos response.output_audio.delta que contienen los fragmentos de datos de audio del modelo codificados en Base64. Tendrás que almacenar estos fragmentos en un búfer y escribirlos en un archivo, o quizá 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 no contienen datos de audio, sino solo transcripciones del contenido de audio. Para obtener los bytes de audio, tendrás que escuchar los eventos response.output_audio.delta.
El formato de los fragmentos de salida se puede configurar para toda la sesión o para cada respuesta.
- Sesión:
session.audio.output.formatensession.update - Respuesta:
response.audio.output.formatenresponse.create
function handleEvent(message) {
const serverEvent = JSON.parse(message.toString());
if (serverEvent.type === "response.output_audio.delta") {
// Access Base64-encoded audio chunks
// console.log(serverEvent.delta);
}
}
// Listen for server messages (WebSocket)
ws.on("message", handleEvent);Imágenes de entrada
gpt-realtime-2 y gpt-realtime también admiten imágenes de entrada. Puedes adjuntar una imagen como parte del contenido de un mensaje de usuario, y el modelo puede incorporar el contenido de la imagen 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 en tiempo real tienen activada la detección de actividad de voz (VAD) , lo que significa que la API determinará cuándo el usuario empieza o deja de hablar y responderá automáticamente.
Obtén más información sobre cómo configurar VAD en nuestra guía de detección de actividad de voz.
Desactivar VAD
Puedes desactivar VAD estableciendo turn_detection en null con el evento de cliente session.update. Esto puede ser útil para interfaces en las que quieras tener un control detallado del audio de entrada, como las interfaces de presionar para hablar.
Cuando VAD está desactivada, el cliente tendrá que emitir manualmente algunos eventos de cliente adicionales para iniciar respuestas de audio:
- Envía manualmente
input_audio_buffer.commit, lo que creará un nuevo elemento de entrada del usuario para la conversación. - Envía manualmente
response.createpara iniciar una respuesta de audio del modelo. - Envía
input_audio_buffer.clearantes de comenzar una nueva entrada del usuario.
Mantener VAD, pero desactivar las respuestas automáticas
Si quieres mantener el modo VAD activado, pero conservar la posibilidad de decidir manualmente cuándo se genera una respuesta, puedes establecer turn_detection.interrupt_response y turn_detection.create_response en false con el evento de cliente session.update. Esto conservará todo el comportamiento de VAD, pero no creará nuevas respuestas automáticamente. Los clientes pueden iniciarlas manualmente con un evento response.create.
Esto puede ser útil para la moderación, la validación de entradas o los patrones RAG, cuando estás dispuesto a aceptar un poco más de latencia en la interacción a cambio de tener control sobre las entradas.
Crear respuestas fuera de la conversación predeterminada
De forma predeterminada, todas las respuestas generadas 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 la conversación predeterminada de la sesión o generar varias respuestas al mismo tiempo. También podrías querer un control más preciso sobre qué elementos de la conversación se toman en cuenta al generar una respuesta (por ejemplo, solo los últimos N turnos).
Puedes generar respuestas “fuera de banda” que no se agreguen al estado de la conversación predeterminada estableciendo el campo response.conversation en la cadena none al crear una respuesta con el evento de cliente response.create.
Al crear una respuesta fuera de banda, probablemente también quieras una forma de identificar qué eventos enviados por el servidor corresponden a esa respuesta. Puedes proporcionar metadata para la respuesta del modelo que te ayuden a identificar qué respuesta se está generando para este evento enviado por 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()
dataChannel.send(JSON.stringify(event));Ahora, al escuchar el evento de servidor response.done, puedes identificar el resultado de tu 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"
) {
// this server event pertained to our OOB model response
console.log(serverEvent.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 construir un contexto personalizado que el modelo usará para generar una respuesta fuera de la conversación predeterminada o actual. Para hacerlo, usa el arreglo input en un evento de cliente response.create. Puedes usar entradas nuevas o hacer referencia por ID a elementos de entrada existentes en la conversación.
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: "some_conversation_item_id",
},
{
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()
dataChannel.send(JSON.stringify(event));Crear respuestas sin contexto
También puedes insertar respuestas en la conversación predeterminada e ignorar todas las demás instrucciones y el contexto. Para hacerlo, 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: [],
instructions: prompt,
},
};
// WebRTC data channel and WebSocket both have .send()
dataChannel.send(JSON.stringify(event));Llamada a funciones
Los modelos Realtime también admiten llamada a funciones, lo que te permite ejecutar código personalizado para ampliar las capacidades del modelo. Así funciona, a grandes rasgos:
- Al actualizar la sesión o crear una respuesta, puedes especificar una lista de funciones disponibles que el modelo puede llamar.
- Si, al procesar la entrada, el modelo determina que debe realizar una llamada a una función, agregará elementos a la conversación que representen los argumentos de esa llamada.
- Cuando el cliente detecte elementos de la conversación que contengan argumentos de una llamada a una función, ejecutará código personalizado con esos argumentos
- Una vez que se haya ejecutado el código personalizado, el cliente creará nuevos elementos de la conversación que contengan la salida de la llamada a la función y le pedirá al modelo que responda.
Veamos cómo funcionaría esto en la práctica al agregar una función que el modelo pueda llamar para proporcionar el horóscopo de hoy a sus usuarios. Mostraremos la estructura de los objetos de eventos del cliente que se deben enviar y lo que emitirá el servidor a su vez.
Configurar funciones que el modelo puede llamar
Primero, debemos proporcionar al modelo un conjunto de funciones que pueda llamar según la entrada del usuario. Las funciones disponibles se pueden configurar para toda la sesión o para cada respuesta individual.
- Sesión: propiedad
session.toolsensession.update - Respuesta: propiedad
response.toolsenresponse.create
Este es un ejemplo del 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 astrológico para el que se 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 el modelo recibe una entrada que indica que el usuario quiere conocer su horóscopo, llamará a esta función con un parámetro sign.
Detectar cuándo el modelo quiere llamar a una función
Según las entradas 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 con 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, envía un evento del cliente response.create para generar una respuesta:
{
"type": "response.create"
}
En lugar de devolver de inmediato una respuesta de texto o audio, el modelo generará una respuesta que contenga los argumentos que se deben pasar a una función en la aplicación del desarrollador. Puedes escuchar las actualizaciones en tiempo real de los argumentos de la llamada a la función mediante el evento del servidor response.function_call_arguments.delta, pero response.done también contendrá todos los datos que necesitamos para llamar a nuestra 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 emitido por el servidor, podemos detectar que el modelo quiere llamar a una función personalizada:
| Propiedad | Propósito en la llamada a funciones |
|---|---|
response.output[0].type | Cuando se establece en function_call, indica que esta respuesta contiene argumentos para una llamada a una función identificada por su nombre. |
response.output[0].name | El nombre de la función configurada que se va a llamar; en este caso, generate_horoscope |
response.output[0].arguments | Una cadena JSON que contiene los argumentos de la función. En nuestro caso, "{\"sign\":\"Aquarius\"}". |
response.output[0].call_id | Un ID generado por el sistema para esta llamada a la función. Necesitarás este ID para devolver al modelo el resultado de una llamada a una función. |
Con esta información, podemos ejecutar código en nuestra aplicación para generar el horóscopo y luego proporcionar esa información al modelo para que genere una respuesta.
Proporcionar al modelo los resultados de una llamada a una función
Al recibir una respuesta del modelo con argumentos para una llamada a una función, tu aplicación puede ejecutar código que lleve a cabo esa llamada. Puede hacer lo que quieras, como comunicarse con API externas o acceder a bases de datos.
Cuando tengas todo listo para proporcionar al modelo los resultados de tu código personalizado, puedes crear un nuevo elemento de la conversación que contenga el resultado 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 la conversación es
function_call_output item.call_ides el mismo ID que recibimos en el eventoresponse.doneanterioritem.outputes una cadena JSON que contiene los resultados de nuestra llamada a la función
Una vez que hayamos agregado el elemento de la conversación que contiene los resultados de nuestra llamada a la función, volvemos a emitir el evento response.create desde el cliente. Esto iniciará una respuesta del modelo con los datos de la llamada a la función.
{
"type": "response.create"
}
Manejo de errores
El servidor emite el evento error cada vez que encuentra un error durante la sesión. En ocasiones, estos errores se pueden atribuir a un evento del cliente emitido por tu aplicación.
A diferencia de las solicitudes y respuestas HTTP, en las que una respuesta está vinculada implícitamente a una solicitud del cliente, necesitamos usar una propiedad event_id en los eventos del cliente para saber cuándo uno de ellos ha provocado un error en el servidor. El siguiente código muestra esta técnica: el cliente intenta emitir un tipo de evento no admitido.
const event = {
event_id: "my_awesome_event",
type: "scooby.dooby.doo",
};
dataChannel.send(JSON.stringify(event));Este evento fallido enviado desde el cliente provocará la emisión de un evento de error como el siguiente:
{
"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. La Realtime API gestiona las interrupciones cuando VAD está habilitado: detecta la voz del usuario, cancela la respuesta en curso e inicia una nueva. Sin embargo, en este caso conviene que el modelo sepa en qué punto se lo interrumpió para poder continuar la conversación de forma natural (por ejemplo, si el usuario dice “¿qué fue lo último que dijiste?”). A esto lo llamamos truncar la última respuesta del modelo, es decir, eliminar de la conversación la parte de esa respuesta que no se reprodujo.
En las conexiones WebRTC y SIP, el servidor administra un búfer de audio de salida, por lo que sabe cuánto audio se ha reproducido en un momento dado. El servidor truncará automáticamente el audio no reproducido cuando el usuario interrumpa.
Con una conexión WebSocket, el cliente administra la reproducción de audio, por lo que debe detenerla y encargarse del truncamiento. El procedimiento funciona así:
- El cliente está atento a nuevos eventos
input_audio_buffer.speech_starteddel servidor, que indican que el usuario ha comenzado a hablar. El servidor cancelará automáticamente cualquier respuesta del modelo que esté en curso y se emitirá un eventoresponse.cancelled. - Cuando el cliente detecte este evento, debe detener de inmediato cualquier audio del modelo que se esté reproduciendo. Debe registrar cuánto de la última respuesta de audio se reprodujo antes de la interrupción.
- El cliente debe enviar un evento
conversation.item.truncatepara eliminar de la conversación la parte no reproducida de la última respuesta del modelo.
Este es un ejemplo:
{
"type": "conversation.item.truncate",
"item_id": "item_1234", # this is the item ID of the model's last response
"content_index": 0,
"audio_end_ms": 1500 # truncate audio after 1.5 seconds
}
¿Y qué pasa si también se quiere truncar la transcripción? El modelo Realtime no tiene suficiente información para alinear con precisión la transcripción y el audio, por lo que conversation.item.truncate cortará el audio en un punto determinado y eliminará la transcripción de texto de la parte no reproducida. Esto resuelve el problema de eliminar el audio no reproducido, pero no proporciona una transcripción truncada.
Presionar para hablar
Realtime API usa la detección de actividad de voz (VAD) de forma predeterminada, lo que significa que la entrada de audio activa las respuestas del modelo. También puedes implementar una interacción de presionar para hablar si deshabilitas la VAD y usas un control en la aplicación para decidir cuándo se envía la entrada de audio al modelo; por ejemplo, mantener presionada la barra espaciadora para capturar audio y activar una respuesta al soltarla. En algunas aplicaciones, esto funciona sorprendentemente bien: les da a los usuarios el control de las interacciones, evita fallas de la VAD y se siente ágil porque no hay que esperar a que se agote el tiempo de espera de la VAD.
La implementación de presionar para hablar difiere un poco entre WebSockets y WebRTC. En una conexión WebSocket de Realtime API, todos los eventos se envían por el mismo canal y en el mismo orden, mientras que una conexión WebRTC tiene canales separados para el audio y los eventos de control.
WebSockets
Para implementar presionar para hablar con una conexión WebSocket, el cliente debe detener la reproducción de audio, manejar las interrupciones e iniciar una nueva respuesta. Este es el procedimiento con más detalle:
- Desactiva la VAD estableciendo
"turn_detection": nullen un eventosession.update. - Al presionar, inicia la grabación de audio en el cliente.
- Si hay una respuesta del modelo en curso, cancélala enviando un evento
response.cancel. - Si se está reproduciendo audio de salida 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 haya reproducido.
- Si hay una respuesta del modelo en curso, cancélala enviando un evento
- Al soltar, envía un mensaje
input_audio_buffer.appendcon el audio para agregar el nuevo audio al búfer de entrada. - Envía un evento
input_audio_buffer.commit. Esto confirmará el audio escrito en el búfer de entrada e iniciará la transcripción de la entrada, si está habilitada. - Luego, activa una respuesta con un evento
response.create.
WebRTC y SIP
La implementación de presionar para hablar con WebRTC es similar, pero se debe vaciar explícitamente el búfer de audio de entrada. Sigue estos pasos:
- Desactiva VAD configurando
"turn_detection": nullen un eventosession.update. - Al presionar el botón, envía un evento
input_audio_buffer.clearpara eliminar cualquier entrada de audio anterior.- Si hay una respuesta del modelo en curso, cancélala enviando un evento
response.cancel. - Si se está reproduciendo audio de salida del modelo, envía un evento
output_audio_buffer.clearpara eliminar el audio que no se haya reproducido. Esto también trunca la conversación.
- Si hay una respuesta del modelo en curso, cancélala enviando un evento
- Al soltar el botón, envía un evento
input_audio_buffer.commit. Esto confirmará el audio escrito en el búfer de entrada e iniciará la transcripción de la entrada (si está habilitada). - Luego, inicia una respuesta con un evento
response.create.