For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to the page URL.
Navigation principale

Conversations Realtime

Découvrez comment gérer les conversations Realtime de parole à parole.

Une fois connecté à la Realtime API via WebRTC ou WebSocket, vous pouvez appeler un modèle Realtime (comme gpt-realtime-2.1) pour mener des conversations de parole à parole. Pour cela, vous devez envoyer des événements client pour déclencher des actions et écouter les événements serveur pour réagir aux actions effectuées par la Realtime API.

Ce guide présente les séquences d’événements nécessaires pour utiliser les capacités du modèle, comme la génération d’audio et de texte, les images en entrée et l’appel de fonction. Il explique également comment appréhender l’état d’une session Realtime.

Si vous n’avez pas besoin de converser avec le modèle, c’est-à-dire si vous n’attendez aucune réponse, vous pouvez utiliser la Realtime API en mode transcription.

Sessions Realtime de parole à parole

Une session Realtime est une interaction avec état entre le modèle et un client connecté. Ses principaux composants sont les suivants :

  • L’objet Session , qui contrôle les paramètres de l’interaction, comme le modèle utilisé, la voix utilisée pour générer les sorties et d’autres paramètres de configuration.
  • Une Conversation, qui représente les éléments d’entrée de l’utilisateur et les éléments de sortie du modèle générés au cours de la session actuelle.
  • Les réponses, qui sont des éléments audio ou textuels générés par le modèle et ajoutés à la conversation.

Tampon audio d’entrée et WebSockets

Si vous utilisez WebRTC, les API WebRTC facilitent une grande partie de la gestion des médias nécessaire pour envoyer de l’audio au modèle et en recevoir.


Si vous utilisez WebSockets pour l’audio, vous devez gérer vous-même les interactions avec le tampon audio d’entrée en envoyant au serveur des événements JSON contenant de l’audio encodé en base64.

L’ensemble de ces composants constitue une session Realtime. Vous utiliserez les événements client pour mettre à jour l’état de la session et écouterez les événements serveur pour réagir aux changements d’état au sein de la session.

Schéma de l’état d’une session Realtime

Événements du cycle de vie d’une session

Après le démarrage d’une session via WebRTC ou WebSockets, le serveur envoie un événement session.created indiquant que la session est prête. Côté client, vous pouvez mettre à jour la configuration de la session en cours avec l’événement session.update. La plupart des propriétés de la session peuvent être modifiées à tout moment, à l’exception du paramètre voice utilisé par le modèle pour la sortie audio, qui ne peut plus être modifié dès que le modèle a répondu une première fois en audio pendant la session. La durée maximale d’une session Realtime est de 60 minutes.

L’exemple suivant montre comment mettre à jour la session avec un événement client session.update. Consultez le guide WebRTC ou WebSocket pour en savoir plus sur l’envoi d’événements client via ces canaux.

Mettez à jour les instructions système utilisées par le modèle dans cette session
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));

Une fois la session mise à jour, le serveur émet un événement session.updated contenant le nouvel état de la session.

Événements client associés Événements serveur associés

session.update

session.created

session.updated

Entrées et sorties textuelles

Pour générer du texte avec un modèle Realtime, vous pouvez ajouter des entrées textuelles à la conversation actuelle, demander au modèle de générer une réponse et écouter les événements envoyés par le serveur qui indiquent la progression de cette réponse. Pour générer du texte, la session doit être configurée avec la modalité text (c’est le cas par défaut).

Créez un nouvel élément textuel dans la conversation à l’aide de l’événement client conversation.item.create. Cela revient à envoyer un message utilisateur (prompt) dans Chat Completions via l’API REST.

Créez un élément de conversation contenant une entrée utilisateur
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));

Après avoir ajouté le message utilisateur à la conversation, envoyez l’événement response.create pour déclencher une réponse du modèle. Si l’audio et le texte sont tous deux activés pour la session actuelle, le modèle répondra avec du contenu audio et textuel. Si vous souhaitez générer uniquement du texte, vous pouvez le préciser lors de l’envoi de l’événement client response.create, comme illustré ci-dessous.

Générez une réponse uniquement textuelle
const event = {
  type: "response.create",
  response: {
    output_modalities: ["text"],
  },
};

// WebRTC data channel and WebSocket both have .send()
dataChannel.send(JSON.stringify(event));

Une fois la réponse entièrement terminée, le serveur émet l’événement response.done. Cet événement contient l’intégralité du texte généré par le modèle, comme illustré ci-dessous.

Écoutez l’événement response.done pour obtenir les résultats finaux
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);

Pendant la génération de la réponse du modèle, le serveur émet plusieurs événements du cycle de vie. Vous pouvez écouter ces événements, comme response.output_text.delta, pour informer les utilisateurs en temps réel à mesure que la réponse est générée. La liste complète des événements émis par le serveur figure ci-dessous, dans la colonne Événements serveur associés. Ils sont présentés dans leur ordre approximatif d’émission, avec les événements client pertinents pour la génération de texte.

Événements client associés Événements serveur associés

conversation.item.create

response.create

conversation.item.added

conversation.item.done

response.created

response.output_item.added

response.content_part.added

response.output_text.delta

response.output_text.done

response.content_part.done

response.output_item.done

response.done

rate_limits.updated

Entrées et sorties audio

L’une des fonctionnalités les plus puissantes de la Realtime API est l’interaction vocale directe avec le modèle, sans étape intermédiaire de synthèse vocale ou de transcription. Elle permet de réduire la latence des interfaces vocales et donne au modèle davantage d’informations sur le ton et les intonations de la voix reçue en entrée.

Choix de la voix

Les sessions Realtime peuvent être configurées pour utiliser l’une des voix intégrées lors de la production de sorties audio. Vous pouvez définir voice à la création de la session (ou dans un événement response.create) pour choisir la voix du modèle. Les voix actuellement disponibles sont alloy, ash, ballad, coral, echo, sage, shimmer, verse, marin et cedar. Une fois que le modèle a émis de l’audio dans une session, voice ne peut plus être modifié pour cette session. Pour une qualité optimale, nous vous recommandons d’utiliser marin ou cedar.

Gestion de l’audio avec WebRTC

Si vous vous connectez à la Realtime API via WebRTC, la Realtime API établit une connexion pair à pair avec votre client. Les sorties audio du modèle sont transmises à votre client sous forme de flux multimédia distant. Les entrées audio du modèle sont captées à l’aide de périphériques audio (getUserMedia), et les flux multimédias sont ajoutés en tant que pistes à la connexion pair à pair.

Le code du guide de connexion WebRTC montre un exemple simple de configuration de l’audio local et distant à l’aide des API du navigateur :

// 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]);

L’extrait de code ci-dessus permet d’interagir avec la Realtime API, mais les possibilités vont bien au-delà. Pour découvrir d’autres exemples de types d’interfaces utilisateur, consultez le dépôt d’exemples WebRTC. Des démonstrations interactives de ces exemples sont également disponibles ici.

L’utilisation de la capture et des flux multimédias dans le navigateur permet notamment de couper et de réactiver les microphones, ou de sélectionner le périphérique utilisé pour capter les entrées.

Événements client et serveur pour l’audio avec WebRTC

Par défaut, les clients WebRTC n’ont pas besoin d’envoyer d’événements client à la Realtime API avant d’envoyer des entrées audio. Une fois qu’une piste audio locale est ajoutée à la connexion pair à pair, vos utilisateurs peuvent tout simplement commencer à parler !

Toutefois, les clients WebRTC reçoivent plusieurs événements du cycle de vie émis par le serveur pendant les échanges audio entre le client et le serveur via la connexion pair à pair. Par exemple :

Les API WebRTC de gestion des flux multimédias peuvent vous offrir tout le contrôle dont vous avez besoin. Il peut toutefois être nécessaire, dans certains cas, d’utiliser des interfaces de plus bas niveau pour les entrées et sorties audio. Consultez la section WebSockets ci-dessous pour en savoir plus et obtenir la liste des événements nécessaires à une gestion fine des entrées audio.

Gestion de l’audio avec WebSockets

L’envoi et la réception d’audio via une connexion WebSocket demandent un peu plus de travail pour envoyer les médias depuis le client et recevoir ceux du serveur. Le tableau ci-dessous décrit la séquence d’événements nécessaires à l’envoi et à la réception d’audio au cours d’une session WebSocket.

Les événements ci-dessous sont présentés dans l’ordre du cycle de vie, bien que certains (comme les événements delta) puissent se produire simultanément.

Étape du cycle de vie Événements client Événements serveur
Initialisation de la session

session.update

session.created

session.updated

Entrée audio de l’utilisateur

conversation.item.create


  (envoi du message audio complet)

input_audio_buffer.append


  (envoi de l’audio en streaming par fragments)

input_audio_buffer.commit


  (utilisé lorsque la VAD est désactivée)

response.create


  (utilisé lorsque la VAD est désactivée)

input_audio_buffer.speech_started

input_audio_buffer.speech_stopped

input_audio_buffer.committed

Sortie audio du serveur

input_audio_buffer.clear


  (utilisé lorsque la VAD est désactivée)

conversation.item.added

conversation.item.done

response.created

response.output_item.added

response.content_part.added

response.output_audio.delta

response.output_audio.done

response.output_audio_transcript.delta

response.output_audio_transcript.done

response.output_text.delta

response.output_text.done

response.content_part.done

response.output_item.done

response.done

rate_limits.updated

Envoyez l’entrée audio au serveur en streaming

Pour envoyer l’entrée audio au serveur en streaming, vous pouvez utiliser l’événement client input_audio_buffer.append. Cet événement nécessite l’envoi de fragments d’ octets audio encodés en Base64 à la Realtime API via le socket. Chaque fragment ne doit pas dépasser 15 Mo.

Le format des fragments d’entrée peut être configuré pour l’ensemble de la session ou pour chaque réponse.

Ajoutez des octets audio en entrée à la conversation
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" }));

Envoyez des messages audio complets

Vous pouvez également créer des messages de conversation constitués d’enregistrements audio complets. Utilisez l’événement client conversation.item.create pour créer des messages avec un contenu de type input_audio.

Créez des éléments de conversation contenant des enregistrements audio complets en entrée
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));

Traitez la sortie audio d’un WebSocket

Pour lire l’audio de sortie côté client, par exemple dans un navigateur web, nous vous recommandons d’utiliser WebRTC plutôt que WebSockets. WebRTC offre une transmission des médias plus robuste vers les appareils clients lorsque les conditions réseau sont instables.

En revanche, pour traiter la sortie audio dans des applications serveur à serveur utilisant un WebSocket, vous devez écouter les événements response.output_audio.delta, qui contiennent les fragments de données audio du modèle encodés en Base64. Vous devrez soit mettre ces fragments en mémoire tampon et les écrire dans un fichier, soit éventuellement les transmettre immédiatement en streaming, par exemple vers un appel téléphonique avec Twilio.

Les événements response.output_audio.done et response.done ne contiennent pas de données audio, mais uniquement des transcriptions du contenu audio. Pour récupérer les octets audio eux-mêmes, vous devez écouter les événements response.output_audio.delta.

Le format des fragments de sortie peut être configuré pour l’ensemble de la session ou pour chaque réponse.

Écoutez les événements response.output_audio.delta
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);

Images en entrée

gpt-realtime-2 et gpt-realtime prennent également en charge les images en entrée. Vous pouvez joindre une image comme élément de contenu dans un message utilisateur, et le modèle peut tenir compte de ce qu’elle contient dans sa réponse.

Ajoutez une image à la conversation
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));

Détection de l’activité vocale

Par défaut, la détection de l’activité vocale (VAD) est activée dans les sessions Realtime. L’API détermine ainsi quand l’utilisateur commence ou cesse de parler et répond automatiquement.

Pour en savoir plus sur la configuration de la VAD, consultez notre guide sur la détection de l’activité vocale.

Désactivez la VAD

Vous pouvez désactiver la VAD en définissant turn_detection sur null avec l’événement client session.update. Cela peut être utile pour les interfaces qui nécessitent un contrôle précis de l’entrée audio, comme celles où l’on appuie pour parler.

Lorsque la VAD est désactivée, le client doit émettre manuellement certains événements clients supplémentaires pour déclencher les réponses audio :

Conservez la VAD, mais désactivez les réponses automatiques

Si vous souhaitez conserver la VAD tout en décidant manuellement du moment où une réponse est générée, vous pouvez définir turn_detection.interrupt_response et turn_detection.create_response sur false avec l’événement client session.update. Le comportement de la VAD sera conservé, sans création automatique de nouvelles réponses. Les clients peuvent les déclencher manuellement avec un événement response.create.

Cela peut être utile pour la modération, la validation des entrées ou les architectures RAG, lorsque vous acceptez une latence légèrement plus élevée dans l’interaction pour mieux contrôler les entrées.

Créez des réponses en dehors de la conversation par défaut

Par défaut, toutes les réponses générées pendant une session sont ajoutées à l’état de la conversation de cette session (la « conversation par défaut »). Vous pouvez toutefois souhaiter générer des réponses du modèle en dehors du contexte de la conversation par défaut de la session, ou générer plusieurs réponses en parallèle. Vous pouvez également vouloir contrôler plus précisément les éléments de conversation pris en compte par le modèle lors de la génération d’une réponse, par exemple uniquement les N derniers tours de parole.

Pour générer des réponses « hors bande », qui ne sont pas ajoutées à l’état de la conversation par défaut, définissez le champ response.conversation sur la chaîne none lors de la création d’une réponse avec l’événement client response.create.

Lorsque vous créez une réponse hors bande, vous souhaiterez probablement aussi identifier les événements envoyés par le serveur qui s’y rapportent. Vous pouvez renseigner metadata pour la réponse du modèle afin d’identifier la réponse générée pour cet événement envoyé par le client.

Créez une réponse hors bande du modèle
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));

Vous pouvez maintenant identifier le résultat de votre réponse hors bande en écoutant l’événement serveur response.done.

Créez une réponse hors bande du modèle
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);

Créez un contexte personnalisé pour les réponses

Vous pouvez également construire un contexte personnalisé que le modèle utilisera pour générer une réponse en dehors de la conversation par défaut ou en cours. Pour cela, utilisez le tableau input dans un événement client response.create. Vous pouvez fournir de nouvelles entrées ou référencer des éléments d’entrée existants de la conversation par leur ID.

Écoutez la réponse hors bande du modèle avec un contexte personnalisé
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));

Créez des réponses sans contexte

Vous pouvez également insérer des réponses dans la conversation par défaut en ignorant toutes les autres instructions et le contexte. Pour cela, définissez input sur un tableau vide.

Insérez des réponses du modèle sans contexte dans la conversation par défaut
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));

Appel de fonction

Les modèles Realtime prennent également en charge l’ appel de fonction, qui vous permet d’exécuter du code personnalisé pour étendre les capacités du modèle. Voici les grandes étapes de son fonctionnement :

  1. Lors de la mise à jour de la session ou de la création d’une réponse, vous pouvez spécifier une liste de fonctions que le modèle peut appeler.
  2. Si, lors du traitement des données d’entrée, le modèle détermine qu’il doit appeler une fonction, il ajoute à la conversation des éléments représentant les arguments de cet appel.
  3. Lorsque le client détecte des éléments de conversation contenant des arguments d’appel de fonction, il exécute du code personnalisé avec ces arguments
  4. Une fois le code personnalisé exécuté, le client crée de nouveaux éléments de conversation contenant le résultat de l’appel de fonction et demande au modèle de répondre.

Voyons comment cela fonctionne en pratique en ajoutant une fonction que le modèle pourra appeler pour fournir l’horoscope du jour à ses utilisateurs. Nous présenterons la structure des objets d’événement client à envoyer, ainsi que les événements émis en retour par le serveur.

Configurez les fonctions que le modèle peut appeler

Commencez par fournir au modèle un ensemble de fonctions qu’il peut appeler en fonction des données saisies par l’utilisateur. Vous pouvez configurer les fonctions disponibles pour toute la session ou pour une réponse particulière.

Voici un exemple de données envoyées avec un événement client session.update pour configurer une fonction de génération d’horoscope. Cette fonction accepte un seul argument : le signe astrologique pour lequel générer l’horoscope.

session.update

{
  "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"
  }
}

Les champs description de la fonction et de ses paramètres aident le modèle à déterminer s’il doit appeler la fonction et quelles données inclure dans chaque paramètre. Si le modèle reçoit des données indiquant que l’utilisateur souhaite connaître son horoscope, il appelle cette fonction avec un paramètre sign.

Détectez quand le modèle souhaite appeler une fonction

En fonction des données qu’il reçoit, le modèle peut décider d’appeler une fonction pour générer la meilleure réponse possible. Supposons que notre application ajoute l’élément de conversation suivant avec un événement conversation.item.create, puis crée une réponse :

{
  "type": "conversation.item.create",
  "item": {
    "type": "message",
    "role": "user",
    "content": [
      {
        "type": "input_text",
        "text": "What is my horoscope? I am an aquarius."
      }
    ]
  }
}

Elle envoie ensuite un événement client response.create pour générer une réponse :

{
  "type": "response.create"
}

Au lieu de renvoyer immédiatement une réponse textuelle ou audio, le modèle génère une réponse contenant les arguments à transmettre à une fonction dans l’application du développeur. Vous pouvez écouter l’événement serveur response.function_call_arguments.delta pour suivre les mises à jour des arguments de l’appel de fonction en temps réel, mais response.done contient également toutes les données nécessaires pour appeler notre fonction.

response.done

{
    "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\"}"
            }
        ],
        ...
    }
}

Le JSON émis par le serveur permet de détecter que le modèle souhaite appeler une fonction personnalisée :

PropriétéRôle dans l’appel de fonction
response.output[0].typeLorsqu’elle vaut function_call, indique que cette réponse contient des arguments pour appeler une fonction nommée.
response.output[0].nameLe nom de la fonction configurée à appeler, ici generate_horoscope
response.output[0].argumentsUne chaîne JSON contenant les arguments de la fonction. Dans notre cas, "{\"sign\":\"Aquarius\"}".
response.output[0].call_idUn identifiant généré par le système pour cet appel de fonction. Vous aurez besoin de cet identifiant pour transmettre au modèle le résultat de l’appel de fonction.

Avec ces informations, nous pouvons exécuter du code dans notre application pour générer l’horoscope, puis transmettre le résultat au modèle afin qu’il puisse générer une réponse.

Transmettez les résultats d’un appel de fonction au modèle

Lorsque votre application reçoit une réponse du modèle contenant des arguments d’appel de fonction, elle peut exécuter le code correspondant à cet appel. Ce code peut effectuer les opérations de votre choix, comme interroger des API externes ou accéder à des bases de données.

Lorsque vous êtes prêt à transmettre au modèle les résultats de votre code personnalisé, vous pouvez créer un nouvel élément de conversation contenant le résultat à l’aide de l’événement client 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.\"}"
  }
}
  • L’élément de conversation est de type function_call_output
  • item.call_id correspond à l’identifiant reçu dans l’événement response.done ci-dessus
  • item.output est une chaîne JSON contenant les résultats de notre appel de fonction

Une fois l’élément de conversation contenant les résultats de notre appel de fonction ajouté, nous émettons à nouveau l’événement response.create depuis le client. Cela déclenche une réponse du modèle utilisant les données de l’appel de fonction.

{
  "type": "response.create"
}

Gestion des erreurs

Le serveur émet l’événement error chaque fois qu’une erreur survient côté serveur pendant la session. Ces erreurs peuvent parfois provenir d’un événement client émis par votre application.

Contrairement aux échanges HTTP, où une réponse est implicitement liée à une requête du client, il faut ici utiliser une propriété event_id sur les événements client pour savoir lequel a déclenché une erreur côté serveur. Le code ci-dessous illustre cette technique : le client tente d’émettre un type d’événement non pris en charge.

const event = {
  event_id: "my_awesome_event",
  type: "scooby.dooby.doo",
};

dataChannel.send(JSON.stringify(event));

L’échec de cet événement envoyé par le client entraîne l’émission d’un événement d’erreur semblable à celui-ci :

{
  "type": "invalid_request_error",
  "code": "invalid_value",
  "message": "Invalid value: 'scooby.dooby.doo' ...",
  "param": "type",
  "event_id": "my_awesome_event"
}

Interruption et troncature

Dans de nombreuses applications vocales, l’utilisateur peut interrompre le modèle pendant qu’il parle. Lorsque la VAD est activée, la Realtime API gère les interruptions : elle détecte la parole de l’utilisateur, annule la réponse en cours et en commence une nouvelle. Dans ce cas, le modèle doit toutefois savoir à quel moment il a été interrompu pour poursuivre naturellement la conversation, par exemple si l’utilisateur demande « qu’est-ce que vous venez de dire ? ». Nous appelons cette opération la troncature de la dernière réponse du modèle : elle consiste à supprimer de la conversation la partie non lue de cette réponse.

Avec les connexions WebRTC et SIP, le serveur gère un tampon audio de sortie et sait donc quelle quantité d’audio a été lue à un instant donné. Il supprime automatiquement la partie audio non lue lorsque l’utilisateur interrompt le modèle.

Avec une connexion WebSocket, le client gère la lecture audio et doit donc l’arrêter et gérer la troncature. Voici comment se déroule cette procédure :

  1. Le client surveille les nouveaux événements input_audio_buffer.speech_started du serveur, qui indiquent que l’utilisateur a commencé à parler. Le serveur annule automatiquement toute réponse du modèle en cours et un événement response.cancelled est émis.
  2. Lorsque le client détecte cet événement, il doit immédiatement arrêter toute lecture audio du modèle en cours. Il doit noter quelle portion de la dernière réponse audio a été lue avant l’interruption.
  3. Le client doit envoyer un événement conversation.item.truncate pour retirer de la conversation la partie non lue de la dernière réponse du modèle.

Voici un exemple :

{
    "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
}

Qu’en est-il de la troncature de la transcription ? Le modèle Realtime ne dispose pas de suffisamment d’informations pour aligner précisément la transcription et l’audio. conversation.item.truncate coupe donc l’audio à un endroit donné et supprime la transcription textuelle de la partie non lue. Cela permet de supprimer l’audio non lu, mais ne fournit pas de transcription tronquée.

Appuyer pour parler

La Realtime API utilise par défaut la détection de l’activité vocale (VAD), ce qui signifie que les réponses du modèle sont déclenchées par l’entrée audio. Vous pouvez également proposer un mode « appuyer pour parler » en désactivant la VAD et en contrôlant, au niveau de l’application, le moment où l’entrée audio est envoyée au modèle. Par exemple, l’utilisateur peut maintenir la barre d’espace enfoncée pour enregistrer l’audio, puis la relâcher pour déclencher une réponse. Dans certaines applications, cette approche fonctionne étonnamment bien : elle donne aux utilisateurs le contrôle des interactions, évite les erreurs de la VAD et offre une sensation de réactivité, puisqu’il n’est plus nécessaire d’attendre l’expiration du délai de la VAD.

La mise en œuvre du mode « appuyer pour parler » diffère légèrement entre WebSockets et WebRTC. Dans une connexion WebSocket à la Realtime API, tous les événements sont envoyés sur le même canal et dans le même ordre, tandis qu’une connexion WebRTC utilise des canaux distincts pour l’audio et les événements de contrôle.

WebSockets

Pour mettre en œuvre le mode « appuyer pour parler » avec une connexion WebSocket, le client doit arrêter la lecture audio, gérer les interruptions et déclencher une nouvelle réponse. Voici la procédure détaillée :

  1. Désactivez la VAD en définissant "turn_detection": null dans un événement session.update.
  2. À l’appui, démarrez l’enregistrement audio côté client.
    1. Si une réponse du modèle est en cours, annulez-la en envoyant un événement response.cancel.
    2. Si la lecture de la sortie audio du modèle est en cours, arrêtez-la immédiatement et envoyez un événement conversation.item.truncate pour supprimer de la conversation tout l’audio non lu.
  3. Au relâchement, envoyez un message input_audio_buffer.append contenant l’audio pour l’ajouter au tampon d’entrée.
  4. Envoyez un événement input_audio_buffer.commit pour valider l’audio écrit dans le tampon d’entrée et lancer la transcription de l’entrée audio, si elle est activée.
  5. Déclenchez ensuite une réponse avec un événement response.create.

WebRTC et SIP

La mise en œuvre de la fonction « appuyer pour parler » avec WebRTC est similaire, mais le tampon audio d’entrée doit être vidé explicitement. Voici la procédure :

  1. Désactivez la VAD en définissant "turn_detection": null dans un événement session.update.
  2. Lorsque vous appuyez sur le bouton, envoyez un événement input_audio_buffer.clear pour effacer toute entrée audio précédente.
    1. Si une réponse du modèle est en cours, annulez-la en envoyant un événement response.cancel.
    2. Si la lecture de la sortie audio du modèle est en cours, envoyez un événement output_audio_buffer.clear pour effacer l’audio non lu. Cela tronque également la conversation.
  3. Lorsque vous relâchez le bouton, envoyez un événement input_audio_buffer.commit. Cela valide l’audio écrit dans le tampon d’entrée et lance sa transcription, si elle est activée.
  4. Déclenchez ensuite une réponse avec un événement response.create.