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
Theme

WebRTC

Conecte aplicativos de voz com WebRTC.

Escolha a API para ver como se conectar e quais eventos a sessão gera.

Conecte um navegador ao GPT-Live

Recomendamos o uso de WebRTC para conexões de áudio com o GPT-Live em aplicativos que rodam no navegador, em dispositivos móveis ou no servidor. Este guia mostra como fazer a conexão em um navegador. As trilhas de mídia negociadas transportam o áudio do microfone e a fala gerada. Um canal de dados transmite eventos JSON de transcrição, atualização da sessão e delegação de tarefas.

O navegador cria uma oferta usando o protocolo de descrição de sessão (SDP). O servidor do aplicativo envia a oferta com POST /v1/live/sessions, usa a chave de API do projeto para autenticar a requisição e recebe uma resposta SDP. Mantenha a chave e a configuração da sessão no seu servidor confiável.

Antes de começar

Você precisa dos seguintes recursos:

  • Uma chave de API do projeto com acesso ao GPT-Live.
  • Um ambiente de execução no servidor compatível com o exemplo de SDK escolhido. O exemplo em Node.js requer Node.js 22.6 ou posterior.
  • Um navegador com permissão para usar o microfone e com a página aberta via HTTPS ou em localhost.

O exemplo usa a delegação pela API Responses com gpt-5.6-terra e a ferramenta hospedada de pesquisa na Web. Para saber como configurar instruções de backend e ferramentas do aplicativo, consulte Delegação e ferramentas. Para detalhes sobre o uso de voz e do backend, consulte Otimização de custos.

Entenda as etapas da conexão

  1. Em resposta a uma ação do usuário, solicite acesso ao microfone e adicione as trilhas de áudio do microfone 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 o fim da coleta dos candidatos ICE e envie a oferta ao servidor.
  4. No servidor, envie à OpenAI uma requisição POST com um corpo JSON que contenha session e transport: { type: "webrtc", sdp: ... }.
  5. Use a resposta SDP recebida como descrição remota. Aguarde o evento 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. No exemplo, a string oai-events é o rótulo do canal de dados.

Ao criar uma sessão WebRTC com POST /v1/live/sessions, você paga por 15 segundos de voz durante a inicialização. Quando a sessão começa, o valor já pago é descontado da cobrança pela duração da sessão. Assim, você não paga por 15 segundos extras. Para entender o cálculo, consulte Cobrança pela inicialização do WebRTC.

Crie o servidor do aplicativo

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

Para Go, salve o exemplo como main.go, inicialize um módulo com go mod init live-demo e instale github.com/openai/openai-go/v3@v3.85.1 com go get. Execute go run . com Go 1.26 ou posterior. Para Java, use Java 17 ou posterior, adicione a dependência Maven com.openai:openai-java:4.95.0 e execute a classe LiveConnectionWebrtcExample. Para Ruby 3.3 ou posterior, instale a versão 0.110.0 de openai e o pacote webrick com gem install openai -v 0.110.0 e gem install webrick. Salve o exemplo como server.rb e execute ruby server.rb.

Escolha abaixo a linguagem do servidor. Todas as versões disponibilizam index.html e o mesmo endpoint /api/session na porta 3000. Use uma versão do SDK compatível com Live. Execute apenas uma das versões do exemplo 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 os mecanismos de autenticação e autorização do aplicativo, limites de requisições e HTTPS. A verificação de origem deste exemplo local não autentica os usuários.

Crie o cliente para o 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 a conversa, conecta o microfone e a saída de áudio e processa os eventos da sessão. /api/session é uma rota no servidor do 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());
  const closingEvents = events;
  events = undefined;
  closingEvents?.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 Start conversation. Quando o status mudar para Connected, faça uma pergunta que exija informações atualizadas para testar a ferramenta de pesquisa hospedada. Use os controles de áudio se o navegador bloquear a reprodução automática.

Leia a resposta da sessão

Se a requisição for bem-sucedida, o servidor retornará o status HTTP 201 e um corpo JSON com 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 um valor opaco e preserve-o exatamente como foi recebido, inclusive o prefixo.

Para reduzir a latência ao iniciar a conexão, consulte WebRTC com WARP.

Processe mídia e eventos

Use as trilhas de mídia para enviar o áudio do microfone e receber a fala gerada. Como o WebRTC negocia o formato de áudio via SDP, 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 receber trechos incrementais de transcrição, enviar comandos de sessão e processar mensagens response.event aninhadas. Consulte Gerenciamento de sessões para saber como processar transcrições e eventos do ciclo de vida da sessão. Consulte Controles no servidor se o servidor precisar de uma conexão própria para eventos.

Para encerrar a conversa, envie session.close e continue recebendo eventos até receber session.closed. Só então feche a conexão entre pares e encerre as trilhas do microfone. O exemplo registra o ouvinte do evento final antes de enviar o comando. Se a conexão falhar ou o tempo de espera se esgotar antes desse evento, o uso final ficará sem confirmação. Consulte Uso e encerramento ordenado para saber como processar os dados finais de uso.