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

WebRTC

Connectez vos applications vocales dans le navigateur avec WebRTC.

Choisissez l’API utilisée par votre application. Chaque API possède ses propres mécanismes d’authentification et de création de sessions, ainsi que son propre contrat d’événements.

Connectez un navigateur à GPT-Live

Utilisez WebRTC pour les applications vocales dans le navigateur. L’audio du microphone et la parole générée transitent par des pistes multimédias négociées. Un canal de données transporte les événements JSON liés aux transcriptions, aux mises à jour de session et aux tâches déléguées.

Votre navigateur crée une offre Session Description Protocol (SDP). Votre serveur d’application l’échange contre une réponse via POST /v1/live/sessions, en utilisant la clé API du projet. Conservez la clé et la configuration de session sur votre serveur de confiance.

Avant de commencer

Vous avez besoin des éléments suivants :

  • Une clé API de projet donnant accès à GPT-Live.
  • Un environnement d’exécution serveur adapté à l’exemple de SDK choisi. L’exemple Node.js nécessite Node.js 22.6 ou une version ultérieure.
  • Un navigateur autorisé à accéder au microphone, sur HTTPS ou localhost.

L’exemple utilise la délégation à Responses avec gpt-5.6-terra et la recherche web hébergée. Pour les instructions du backend et les outils de l’application, consultez Délégation et outils. Pour l’utilisation de la voix et du backend, consultez Optimisation des coûts.

Comprenez les étapes de connexion

  1. Demandez l’accès au microphone à la suite d’une action de l’utilisateur et ajoutez ses pistes à une connexion pair à pair.
  2. Créez le canal de données et enregistrez les écouteurs d’événements avant de créer l’offre SDP.
  3. Définissez la description locale, attendez la collecte des candidats ICE, puis envoyez l’offre à votre serveur.
  4. Faites envoyer par votre serveur une requête POST à OpenAI contenant du JSON avec session et transport: { type: "webrtc", sdp: ... }.
  5. Appliquez la réponse SDP reçue comme description distante. Attendez session.started sur le canal de données avant d’envoyer des commandes de l’application.

La requête HTTP démarre la session. N’envoyez pas session.start sur le canal de données. Dans l’exemple, la chaîne oai-events est le libellé du canal de données.

La création d’une session WebRTC avec POST /v1/live/sessions entraîne la facturation de 15 secondes de durée vocale lors de l’initialisation. Ce montant est déduit des frais liés à la durée une fois la session démarrée ; il ne s’agit pas de 15 secondes supplémentaires ajoutées à la session en cours. Consultez Frais d’initialisation WebRTC pour en savoir plus sur le calcul des coûts.

Créez le serveur d’application

Enregistrez l’exemple de serveur dans un nouveau répertoire et définissez OPENAI_API_KEY dans son environnement. Pour Node.js, utilisez server.mjs et installez openai et express avec npm install openai express. Pour Python, installez openai. Cet exemple écoute sur 127.0.0.1, accepte les requêtes de session provenant de http://localhost:3000 et sert index.html depuis le répertoire où vous l’exécutez.

Choisissez ci-dessous un langage pour le serveur ; chaque variante sert index.html et expose le même point de terminaison /api/session sur le port 3000. Utilisez une version du SDK prenant en charge Live. N’exécutez qu’une seule variante à la fois.

import express from "express";
import OpenAI from "openai";
import { readFile } from "node:fs/promises";
import { resolve } from "node:path";

const app = express();
const client = new OpenAI({ maxRetries: 0 });
const port = 3000;
const origin = `http://localhost:${port}`;
const indexPath = resolve("index.html");

app.use(express.json({ limit: "64kb" }));
app.get("/", async (_request, response) => {
  response.type("html").send(await readFile(indexPath, "utf8"));
});

// Local-only demo. Add your application's authentication and authorization
// before exposing session creation to other users.
app.post("/api/session", async (request, response) => {
  if (request.headers.origin !== origin) {
    response.status(403).json({ error: "Unexpected request origin" });
    return;
  }
  if (typeof request.body?.sdp !== "string" || !request.body.sdp.trim()) {
    response.status(400).json({ error: "An SDP offer is required" });
    return;
  }
  if (!process.env.OPENAI_API_KEY) {
    response.status(503).json({ error: "Set OPENAI_API_KEY on the server" });
    return;
  }

  try {
    const result = await client.live.create({
      session: {
        model: "gpt-live-1",
        instructions:
          "Be concise. Delegate requests needing current information to the backend, which can search the web.",
        delegation: {
          type: "responses",
          responses: {
            model: "gpt-5.6-terra",
            instructions:
              "Use web search when current facts are needed. Return concise, grounded results for a spoken conversation.",
            tools: [{ type: "web_search" }],
            tool_choice: "auto",
          },
        },
      },
      transport: {
        type: "webrtc",
        sdp: request.body.sdp,
      },
    });
    // Preserve the SDK's typed session ID and SDP answer.
    response.status(201).json(result);
  } catch (error) {
    if (!(error instanceof OpenAI.APIError)) throw error;
    console.error("Live session creation failed", error.status);
    response
      .status(error.status ?? 502)
      .json({ error: "Live session creation failed" });
  }
});

app.listen(port, "127.0.0.1", () => console.log(`Open ${origin}`));

Avant de rendre le serveur accessible à d’autres utilisateurs, protégez /api/session avec les mécanismes d’authentification, d’autorisation et de limitation des requêtes de votre application, ainsi qu’avec HTTPS. La vérification de l’origine dans cet exemple local n’authentifie pas les utilisateurs.

Créez le client navigateur

Créez index.html dans le répertoire où vous exécutez le serveur :

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>GPT-Live connection</title>
  </head>
  <body>
    <script type="module">
      // Paste the browser code below here.
    </script>
  </body>
</html>

Collez le code suivant dans le script de type module. Il ajoute les commandes de démarrage et de fin, connecte le microphone et la sortie audio, et gère les événements de session. /api/session est une route de votre serveur d’application.

const start = document.createElement("button");
start.textContent = "Start conversation";
const stop = document.createElement("button");
stop.textContent = "End conversation";
stop.disabled = true;
const status = document.createElement("p");
const audio = new Audio();
audio.autoplay = true;
audio.controls = true;
document.body.append(start, stop, status, audio);

let peer;

let events;

let microphone;

let closeTimeout;
let ready = false;
let finalized = false;

function cleanup() {
  clearTimeout(closeTimeout);
  microphone?.getTracks().forEach((track) => track.stop());
  events?.close();
  peer?.close();
  audio.srcObject = null;
  ready = false;
  start.disabled = false;
  stop.disabled = true;
}

start.addEventListener("click", async () => {
  start.disabled = true;
  finalized = false;
  status.textContent = "Connecting…";
  try {
    const connection = new RTCPeerConnection();
    peer = connection;
    connection.addEventListener("track", (event) => {
      audio.srcObject = new MediaStream([event.track]);
      audio.play().catch(() => {
        status.textContent =
          "Select play on the audio controls to hear the assistant.";
      });
    });
    microphone = await navigator.mediaDevices.getUserMedia({ audio: true });
    for (const track of microphone.getAudioTracks()) {
      connection.addTrack(track, microphone);
    }

    // Create the event channel before creating the SDP offer.
    events = connection.createDataChannel("oai-events");
    events.addEventListener("message", ({ data }) => {
      const event = JSON.parse(data);
      if (event.type === "session.started") {
        ready = true;
        stop.disabled = false;
        status.textContent = "Connected: " + event.session.id;
      } else if (event.type === "session.closed") {
        finalized = true;
        console.log("Final session usage", event.usage);
        status.textContent = "Conversation ended.";
        cleanup();
      } else {
        // Save transcript and nested Responses events as needed by your app.
        console.log(event);
      }
    });
    events.addEventListener("close", (event) => {
      if (event.target !== events) return;
      if (!finalized) {
        status.textContent = "Disconnected without final session usage.";
        cleanup();
      }
    });

    const offer = await connection.createOffer();
    await connection.setLocalDescription(offer);
    if (connection.iceGatheringState !== "complete") {
      await new Promise((resolve, reject) => {
        const timeout = setTimeout(() => {
          connection.removeEventListener("icegatheringstatechange", onState);
          reject(new Error("Timed out while gathering ICE candidates"));
        }, 10_000);
        function onState() {
          if (connection.iceGatheringState !== "complete") return;
          clearTimeout(timeout);
          connection.removeEventListener("icegatheringstatechange", onState);
          resolve(undefined);
        }
        connection.addEventListener("icegatheringstatechange", onState);
        onState();
      });
    }

    const sdp = connection.localDescription?.sdp;
    if (!sdp) throw new Error("Missing local SDP offer");
    const response = await fetch("/api/session", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ sdp }),
    });
    if (!response.ok) throw new Error(await response.text());

    const result = await response.json();
    console.log("Created session", result.session.id);
    await connection.setRemoteDescription({
      type: "answer",
      sdp: result.transport.sdp,
    });
    // The HTTP request started this session. Do not send session.start here.
  } catch (error) {
    status.textContent =
      error instanceof Error ? error.message : String(error);
    cleanup();
  }
});

stop.addEventListener("click", () => {
  if (!ready || !events || events.readyState !== "open") return;
  stop.disabled = true;
  status.textContent = "Finishing the conversation…";
  // The session.closed handler is already registered. Keep media and events
  // alive while pending work drains; only clean up after the final event.
  events.send(JSON.stringify({ type: "session.close" }));
  closeTimeout = setTimeout(() => {
    status.textContent = "Incomplete finalization: no session.closed event.";
    cleanup();
  }, 15_000);
});

Exécutez le serveur choisi (node server.mjs ou python server.py), ouvrez http://localhost:3000, puis sélectionnez Démarrer la conversation. Lorsque l’état passe à Connecté, posez une question nécessitant des informations récentes pour tester la recherche hébergée. Utilisez les commandes audio si votre navigateur bloque la lecture automatique.

Lisez la réponse de session

Une requête réussie renvoie un statut HTTP 201 et du JSON contenant l’identifiant de session et la réponse SDP :

{
  "session": { "id": "live_123" },
  "transport": { "type": "webrtc", "sdp": "<SDP answer>" }
}

Lisez result.session.id et transmettez result.transport.sdp à setRemoteDescription. Traitez l’identifiant de session comme une valeur opaque et conservez-le tel quel, y compris son préfixe.

Gérez les médias et les événements

Envoyez l’audio du microphone et recevez la parole générée via les pistes multimédias. WebRTC négocie le format audio via SDP : omettez donc audio.format de la configuration de session. N’envoyez pas session.input_audio.append et ne vous attendez pas à recevoir session.output_audio.delta sur le canal de données.

Utilisez le canal de données pour les mises à jour incrémentales des transcriptions, les commandes de session et les messages response.event imbriqués. Consultez Gestion des sessions pour le traitement des transcriptions et les événements du cycle de vie, et Contrôles côté serveur si votre serveur a besoin de sa propre connexion aux événements.

Pour terminer la conversation, envoyez session.close et continuez à recevoir les événements jusqu’à session.closed avant de fermer la connexion pair à pair et d’arrêter les pistes du microphone. L’exemple enregistre l’écouteur de l’événement final avant d’envoyer la commande. Si la connexion échoue ou expire avant cela, les données d’utilisation finales ne sont pas confirmées. Consultez Utilisation et fermeture propre pour savoir comment gérer ces données.