Realtime API OpenAI memungkinkan browser mengirim audio mikrofon dan memutar jawaban lisan melalui WebRTC. Panduan ini membuat satu percakapan lokal dengan gpt-realtime-2.1. Kunci API tetap di server Node pada komputer Anda. Dibutuhkan proyek OpenAI API dengan akses model dan penagihan; langganan ChatGPT tidak membayar penggunaan API. BIG CHANGE memeriksa dokumentasi terkini dan kode contoh, tetapi belum membuka sesi berbayar atau mendengar jawaban.
Perubahan besar
- Apa yang berubah: OpenAI mendokumentasikan jalur WebRTC browser untuk percakapan Realtime langsung, dengan server lokal menyimpan kunci proyek dan menukar tawaran koneksi browser dengan jawaban.
- Mengapa penting: Pengembang dapat memeriksa seluruh jalur mikrofon hingga jawaban lisan pada satu halaman lokal sebelum merancang produk suara lebih besar. Pemilik proyek mengendalikan kunci dan membayar penggunaan API.
- Yang perlu diperhatikan: Pertukaran lokal yang berhasil tidak membuktikan kesiapan produksi. Versi bersama memerlukan autentikasi pemanggil dan kontrol belanja; permintaan batal atau kedaluwarsa mungkin sudah mencapai OpenAI.
Sebelum memulai
Pasang Node.js 22.6 atau lebih baru dan gunakan browser terkini dengan mikrofon serta pengeras suara yang berfungsi. Akses mikrofon membutuhkan konteks aman; http://localhost memenuhi syarat pada contoh lokal ini. Siapkan kunci proyek OpenAI API dengan akses gpt-realtime-2.1 dan penagihan aktif. Periksa batas laju dan belanja dahulu. Halaman model menyatakan tingkat Free tidak didukung. Simpan kunci dalam lingkungan proses server melalui pengelola rahasia atau pengaturan shell lokal biasa. Jangan letakkan di index.html, commit ke berkas .env, atau tempel ke transkrip. Panduan WebRTC OpenAI mendokumentasikan pertukaran server tepercaya; halaman model mencantumkan akses dan harga terkini.
Ini adalah rute terpadu /v1/realtime/calls pada Realtime API. Panduan WebRTC juga menjelaskan GPT-Live melalui /v1/live/sessions, dengan protokol sesi berbeda. Gunakan rute dan muatan berikut bersama. Contoh dibatasi ke komputer sendiri; pemeriksaan Host dan Origin lokal tidak mengautentikasi pengguna layanan bersama.
- Buat direktori kosong untuk demo dan pasang Express dari sana:
npm init -y
npm install expressInstalasi membuat package.json dan menambah dependensi server web. Simpan dua berkas berikut di direktori yang sama. Kode memakai bawaan Node fetch, FormData, dan AbortSignal.timeout; pengaturan yang ditinjau memerlukan Node.js 22.6 atau lebih baru.
- Simpan berikut sebagai
server.mjs. Server menerima tawaran SDP browser (deskripsi koneksi WebRTC yang diusulkan) dan mengirimkannya bersama pengaturan sesi Realtime ke OpenAI. Kunci API standar hanya dibaca di server ini.
import express from "express";
const app = express();
// This demo accepts only the local page, including protection against DNS rebinding.
app.use((req, res, next) => {
const hosts = ["localhost:3000", "127.0.0.1:3000"];
if (!hosts.includes(req.get("host"))) return res.status(403).send("Local host required");
if (req.method === "POST" && req.get("origin") !== `http://${req.get("host")}`) {
return res.status(403).send("Same-origin local page required");
}
next();
});
app.use(express.text({ type: "application/sdp", limit: "64kb" }));
app.get("/", (_req, res) => res.sendFile("index.html", { root: process.cwd() }));
app.post("/session", async (req, res) => {
if (!process.env.OPENAI_API_KEY) return res.status(500).send("Server API key missing");
if (typeof req.body !== "string" || !req.body.startsWith("v=")) {
return res.status(400).send("Expected an SDP offer");
}
const form = new FormData();
form.set("sdp", req.body);
form.set("session", JSON.stringify({
type: "realtime",
model: "gpt-realtime-2.1",
output_modalities: ["audio"],
audio: { output: { voice: "marin" } },
instructions: "Be a concise, helpful voice assistant. Do not claim access to external tools."
}));
try {
const upstream = await fetch("https://api.openai.com/v1/realtime/calls", {
method: "POST",
headers: { Authorization: `Bearer ${process.env.OPENAI_API_KEY}` },
body: form,
signal: AbortSignal.timeout(25000)
});
const answer = await upstream.text();
if (!upstream.ok) {
console.error("Realtime session rejected", upstream.status);
return res.status(502).send(`OpenAI session request failed (${upstream.status})`);
}
res.type("application/sdp").send(answer);
} catch (error) {
console.error("Realtime session network failure", error.name);
res.status(502).send("Could not reach the Realtime API");
}
});
app.listen(3000, "127.0.0.1", () => console.log("Open http://localhost:3000"));Server terikat pada 127.0.0.1:3000, hanya menerima host dan POST origin halaman lokal, lalu mengembalikan jawaban SDP. Batas permintaan hulu 25 detik adalah pilihan contoh. Waktu habis atau browser terputus tidak membuktikan OpenAI menolak permintaan; tinjau penggunaan sebelum mengulangi awal yang tidak pasti.
- Simpan berikut sebagai
index.html. Halaman meminta izin mikrofon setelah Start voice, mengirim tawaran ke server lokal, dan memutar audio masuk. Stop menutup koneksi dan menghentikan trek mikrofon meski pengaturan masih berjalan. Pengatur waktu 30 detik juga pilihan contoh, bukan jaminan API.
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<title>Local Realtime voice check</title>
<button id="start">Start voice</button>
<button id="stop" disabled>Stop</button>
<p id="status" role="status">Idle</p>
<audio id="reply" autoplay controls></audio>
<script type="module">
const start = document.querySelector("#start");
const stop = document.querySelector("#stop");
const status = document.querySelector("#status");
const reply = document.querySelector("#reply");
let current;
function closeSession(session = current, message = "Stopped") {
if (!session) return;
session.closed = true;
clearTimeout(session.timer);
session.abort.abort();
session.mic?.getTracks().forEach(track => track.stop());
if (session.pc) session.pc.onconnectionstatechange = null;
session.dc?.close();
session.pc?.close();
if (current === session) {
current = null;
reply.srcObject = null;
start.disabled = false;
stop.disabled = true;
status.textContent = message;
}
}
start.onclick = async () => {
const session = { abort: new AbortController(), closed: false };
current = session;
start.disabled = true;
stop.disabled = false;
status.textContent = "Requesting microphone";
session.timer = setTimeout(() => closeSession(session, "Setup timed out; microphone stopped"), 30000);
try {
const mic = await navigator.mediaDevices.getUserMedia({ audio: true });
if (session.closed) { mic.getTracks().forEach(track => track.stop()); return; }
session.mic = mic;
const pc = session.pc = new RTCPeerConnection();
pc.ontrack = event => { if (!session.closed) reply.srcObject = event.streams[0]; };
pc.onconnectionstatechange = () => {
if (session.closed) return;
status.textContent = `Connection: ${pc.connectionState}`;
if (pc.connectionState === "connected") clearTimeout(session.timer);
if (["failed", "closed", "disconnected"].includes(pc.connectionState)) {
closeSession(session, "Connection ended; microphone stopped. Start a new session.");
}
};
pc.addTrack(mic.getAudioTracks()[0], mic);
const dc = session.dc = pc.createDataChannel("oai-events");
dc.onmessage = message => {
if (session.closed) return;
try {
const event = JSON.parse(message.data);
if (event.type === "session.created") status.textContent = "Session ready: speak when connected";
if (event.type === "response.done") {
status.textContent = `Response status: ${event.response?.status || "unknown"}; verify audible playback`;
}
if (event.type === "error") closeSession(session, `Realtime error: ${event.error?.message || "unknown"}; stopped`);
} catch { closeSession(session, "Invalid event; stopped"); }
};
dc.onclose = () => { if (!session.closed) closeSession(session, "Event channel closed; stopped"); };
dc.onerror = () => closeSession(session, "Event channel failed; stopped");
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
if (session.closed) return;
// The official WebRTC example submits the generated offer directly.
const response = await fetch("/session", {
method: "POST",
headers: { "Content-Type": "application/sdp" },
body: offer.sdp,
signal: session.abort.signal
});
if (!response.ok) throw new Error(await response.text());
const answer = await response.text();
if (session.closed) return;
await pc.setRemoteDescription({ type: "answer", sdp: answer });
if (!session.closed && pc.connectionState !== "connected") status.textContent = "Signaling complete; waiting for connection";
} catch (error) {
if (!session.closed) closeSession(session, `Start failed: ${error.message}`);
}
};
stop.onclick = () => closeSession();
window.addEventListener("pagehide", () => closeSession());
</script>
</html>Browser menutup sesi ketika peer atau kanal peristiwa gagal dan saat meninggalkan halaman. Izin mikrofon dapat datang setelah berhenti atau waktu habis; trek terlambat dihentikan. Baris status menampilkan response.done bersama status yang dilaporkan. Peristiwa itu sendiri tidak membuktikan jawaban terdengar atau respons batal/gagal berhasil.
- Mulai server dari direktori kedua berkas, dengan
OPENAI_API_KEYsudah ditetapkan di lingkungan proses server:
node server.mjsBuka http://localhost:3000. Anda seharusnya melihat Start voice, Stop, status Idle, dan pemutar audio. Klik Start voice, izinkan mikrofon, dan ucapkan pertanyaan singkat tanpa data sensitif. Menurut panduan percakapan Realtime, deteksi aktivitas suara bawaan dapat mengubah jeda setelah bicara menjadi respons. Periksa keberhasilan Anda: mikrofon aktif, koneksi mencapai connected, peristiwa session.created muncul, suara terdengar, dan response.done melaporkan respons selesai. Klik Stop dan pastikan indikator mikrofon mati. Pemeriksaan ini untuk Anda lakukan; BIG CHANGE belum menjalankan sesi browser berbayar ini.
Jika pemeriksaan gagal
Jika izin ditolak atau perangkat tidak muncul, periksa izin browser dan perangkat, lalu gunakan URL lokal localhost atau HTTPS. Jika halaman melaporkan kegagalan /session, periksa status HTTP server, keberadaan kunci, akses model proyek, penagihan, batas laju, dan endpoint. Server sengaja tidak mencetak kunci atau isi respons OpenAI. 502 juga bisa berarti kegagalan jaringan atau tenggat 25 detik; tidak menjelaskan apakah permintaan hulu yang tak pasti diterima.
Jika pensinyalan berhasil tetapi koneksi gagal, periksa status halaman, pembatasan jaringan, dan jawaban SDP. Jika terhubung tetapi senyap, periksa masukan mikrofon dan keluaran pilihan. Putar otomatis mungkin diblokir: klik Play pada kontrol audio, lalu hentikan dan mulai sesi baru jika perlu. Pada setiap kegagalan gunakan Stop dan pastikan indikator mikrofon mati. Periksa penggunaan proyek sebelum mengulangi awal dengan hasil permintaan tidak pasti.
Biaya, privasi, dan batas
OpenAI mencantumkan gpt-realtime-2.1 masukan/keluaran teks tanpa cache pada $4/$24 per juta tokens dan masukan/keluaran audio pada $32/$64 per juta tokens, diperiksa 19 Oktober 2026. Biaya bergantung pada tokens yang dipakai, termasuk giliran selanjutnya yang membawa konteks percakapan sebelumnya. Periksa harga dan batas model serta penggunaan proyek sebelum mencoba; waktu berlalu saja tidak bisa memperkirakan tagihan. Panduan percakapan Realtime menyatakan sesi maksimal 60 menit.
Audio mikrofon dikirim ke OpenAI selama sesi. Gunakan ucapan yang boleh Anda kirim dan hindari audio pribadi atau pihak lain. Halaman lokal tidak menawarkan masuk akun atau anggaran per pengguna. Layanan bersama memerlukan akses /session yang diautentikasi dan dibatasi laju serta pengenal keselamatan dari server; pemeriksaan Host dan Origin lokal tidak cukup. Contoh tidak memanggil alat luar atau menelepon. Tidak ada hasil latensi, keandalan, kualitas suara, penghematan, atau penerapan produksi yang diukur.
Sumber dan bacaan lanjutan
- Panduan WebRTC OpenAI: pertukaran server Realtime terpadu, audio browser, dan protokol GPT-Live terpisah.
- Panduan percakapan OpenAI Realtime: peristiwa sesi, giliran, status respons, dan batas durasi.
- OpenAI
gpt-realtime-2.1halaman model: akses model, harga token, dan batas laju. Periksa kembali harga dan akses saat menjalankan contoh.



