For the complete documentation index, see llms.txt. Markdown versions of documentation pages are available by appending .md to the page URL.
Navegação principal

WebRTC

Conecte aplicativos de voz no navegador com WebRTC.

Escolha a API que seu aplicativo usa. Cada API tem seus próprios mecanismos de autenticação e criação de sessões e seu próprio contrato de eventos.

Conecte um navegador ao GPT-Live

Use WebRTC para aplicativos de voz no navegador. A entrada do microfone e a fala gerada trafegam por faixas de mídia negociadas. Um canal de dados transporta eventos JSON de transcrições, atualizações de sessão e trabalho delegado.

Seu navegador cria uma oferta do Session Description Protocol (SDP). O servidor do seu aplicativo a troca por uma resposta usando POST /v1/live/sessions e a chave de API do projeto. Mantenha a chave e a configuração da sessão no seu servidor confiável.

Antes de começar

Você precisa de:

  • Uma chave de API do projeto com acesso ao GPT-Live.
  • Um ambiente de execução de servidor para o exemplo do SDK escolhido. O exemplo em Node.js exige Node.js 22.6 ou posterior.
  • Um navegador com permissão de acesso ao microfone, executando o aplicativo em HTTPS ou localhost.

O exemplo usa delegação para Responses com gpt-5.6-terra e pesquisa na Web hospedada. Para instruções de backend e ferramentas do aplicativo, consulte Delegação e ferramentas. Para informações sobre o uso de voz e backend, consulte Otimização de custos.

Entenda a sequência de conexão

  1. Solicite acesso ao microfone a partir de uma ação do usuário e adicione suas faixas a uma conexão entre pares.
  2. Crie o canal de dados e registre os ouvintes de eventos antes de criar a oferta SDP.
  3. Defina a descrição local, aguarde a coleta de candidatos ICE e envie a oferta ao seu servidor.
  4. Faça seu servidor enviar à OpenAI, via POST, um JSON contendo session e transport: { type: "webrtc", sdp: ... }.
  5. Aplique a resposta SDP retornada como descrição remota. Aguarde session.started no canal de dados antes de enviar comandos do aplicativo.

A requisição HTTP inicia a sessão. Não envie session.start pelo canal de dados. A string oai-events no exemplo é o rótulo do canal de dados.

A criação de uma sessão WebRTC com POST /v1/live/sessions gera uma cobrança de 15 segundos de duração de voz durante a inicialização. Esse valor é abatido das cobranças por duração quando a sessão começa a funcionar; não são 15 segundos extras adicionados à sessão em execução. Consulte Cobranças de inicialização do WebRTC para entender o cálculo dos custos.

Crie o servidor do aplicativo

Salve o exemplo de servidor em um novo diretório e defina OPENAI_API_KEY no ambiente dele. Para Node.js, use server.mjs e instale openai e express com npm install openai express. Para Python, instale openai. Este exemplo escuta em 127.0.0.1, aceita requisições de sessão de http://localhost:3000 e serve index.html a partir do diretório em que você o executa.

Escolha abaixo uma linguagem para o servidor; cada variante serve index.html e o mesmo endpoint /api/session na porta 3000. Use uma versão do SDK com suporte ao Live. Execute apenas uma variante por vez.

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}`));

Antes de disponibilizar o servidor para outros usuários, proteja /api/session com autenticação, autorização, limites de requisições e HTTPS do seu aplicativo. A verificação de origem neste exemplo local não autentica usuários.

Crie o cliente do navegador

Crie index.html no diretório em que você executa o servidor:

<!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>

Cole o código a seguir dentro do script de módulo. Ele adiciona controles para iniciar e encerrar, conecta o microfone e a saída de áudio e trata os eventos da sessão. /api/session é uma rota no servidor do seu aplicativo.

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);
});

Execute o servidor escolhido (node server.mjs ou python server.py), abra http://localhost:3000 e selecione Iniciar conversa. Depois que o status mudar para Conectado, faça uma pergunta que exija informações atuais para testar a pesquisa hospedada. Use os controles de áudio se o navegador bloquear a reprodução automática.

Leia a resposta da sessão

Uma requisição bem-sucedida retorna HTTP 201 com um JSON contendo o ID da sessão e a resposta SDP:

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

Leia result.session.id e passe result.transport.sdp para setRemoteDescription. Trate o ID da sessão como opaco e preserve-o sem alterações, incluindo seu prefixo.

Trate mídia e eventos

Envie o áudio do microfone e receba a fala gerada pelas faixas de mídia. O WebRTC negocia o formato de áudio por meio do SDP, portanto omita audio.format da configuração da sessão. Não envie session.input_audio.append nem espere receber session.output_audio.delta pelo canal de dados.

Use o canal de dados para deltas de transcrição, comandos de sessão e mensagens response.event aninhadas. Consulte Gerenciamento de sessões para saber como tratar transcrições e eventos do ciclo de vida, e Controles do lado do servidor se o seu servidor precisar de uma conexão própria para eventos.

Para encerrar a conversa, envie session.close e continue recebendo eventos até chegar session.closed, antes de fechar a conexão entre pares e encerrar as faixas do microfone. O exemplo registra o ouvinte do evento final antes de enviar o comando. Se a conexão falhar ou atingir o tempo limite antes disso, o uso final ficará sem confirmação. Consulte Uso e encerramento adequado para saber como tratar o uso final.