AI-translated from English; not yet reviewed by a fluent editor.
# صرف پڑھنے والے ٹول کالز کے ساتھ مقامی Realtime صوتی ایجنٹ بنائیں
> OpenAI Realtime اور دو فرضی، صرف پڑھنے والے ٹولز سے براؤزر میں براہ راست صوتی گفتگو بنائیں۔ سرور، براؤزر اور کنٹرولر کا مکمل کوڈ کال ID ملانے، محفوظ تسلسل، مداخلت اور مائیکروفون آزاد کرنے کا طریقہ دکھاتا ہے۔
By BIG CHANGE Editorial
Published: 2026-10-19T23:39:53.236Z
Updated: 2026-10-19T23:39:53.236Z
Canonical: https://bigchange.ai/blog/openai-realtime-voice-read-only-tool-calls-guide

AI-generated conceptual illustration by BIG CHANGE; no actual OpenAI interface, API session, device test or heard answer is depicted.
جب زبانی جواب کے لیے کئی تلاشیں درکار ہوں تو براؤزر کی صوتی گفتگو پیچیدہ ہوتی ہے۔ ماڈل ایک مکمل جواب میں کئی فنکشن کالز واپس کر سکتا ہے۔ ہر نتیجہ اپنے اصل `call_id` کے ساتھ بھیجنا چاہیے، اور تمام نتائج بھیجنے کے بعد ہی نیا جواب شروع ہو۔ یہ رہنما ڈویلپر کو مکمل localhost مثال دیتا ہے: براہ راست مائیکروفون ان پٹ، WebRTC مسلسل پلے بیک، دو فرضی صرف پڑھنے والے ٹولز، بار بار گفتگو اور مداخلت پر صفائی۔ یہ ہمارے [بنیادی براؤزر صوتی رہنما](https://bigchange.ai/blog/openai-realtime-browser-voice-webrtc-guide) کو آگے بڑھاتا ہے، جو بیرونی ٹول کے بغیر بولا ہوا جواب بناتا ہے۔
## بڑی تبدیلی
- **کیا بدلا:** OpenAI کی [Realtime API](https://developers.openai.com/api/docs/guides/realtime-conversations) براہ راست صوتی سیشن میں فنکشن کالز واپس کر سکتی ہے۔ مثال `call_id` سے مکمل کالز کو مقامی نتائج سے ملاتی، پھر ماڈل کو جاری رکھنے کو کہتی ہے۔
- **یہ کیوں اہم ہے:** ڈویلپر کئی صرف پڑھنے والی تلاشوں پر منحصر زبانی جواب آزما سکتا ہے۔ مائیکروفون کی آواز لینا اور بجانا براؤزر میں جبکہ عام API کلید مقامی سرور پر رہتی ہے۔
- **کس بات پر نظر رکھیں:** مقامی جانچیں فرضی واقعات سے ترتیب اور صفائی آزماتی ہیں۔ اپنے اکاؤنٹ، براؤزر، آلات اور نیٹ ورک سے قابل سماعت درست جواب اور متوقع بل ملتے ہیں یا نہیں، یہ اپنے بامعاوضہ سیشن میں ثابت کرنا ہوگا۔
مثال OpenAI کا عام Realtime WebRTC اینڈپوائنٹ `/v1/realtime/calls` اور `gpt-realtime-2.1` استعمال کرتی ہے۔ یہ الگ GPT-Live سیشن پروٹوکول نہیں استعمال کرتی۔ نیچے کی دو «لائبریری» قدریں فرضی سافٹ ویئر ٹیسٹ ڈیٹا ہیں، کسی حقیقی جگہ کی معلومات نہیں۔ BIG CHANGE نے 20 اکتوبر 2026 کو موجودہ [WebRTC](https://developers.openai.com/api/docs/guides/voice-webrtc) ، [گفتگو](https://developers.openai.com/api/docs/guides/realtime-conversations) ، [ٹول](https://developers.openai.com/api/docs/guides/realtime-mcp) اور [واقعات](https://developers.openai.com/api/reference/resources/realtime/client-events) کی دستاویزات جانچیں۔ ہم نے مقامی فرضی کنٹرولر اور براؤزر جانچیں چلائیں، مگر بامعاوضہ API سیشن نہیں کھولا اور نہ حقیقی مائیکروفون یا آواز آزمائی۔
## 1. رسائی جانچیں اور پانچ فائلیں بنائیں
Node.js 22.6 یا جدید تر، مائیکروفون اور اسپیکر والا موجودہ براؤزر، اور `gpt-realtime-2.1` تک رسائی والا OpenAI API پروجیکٹ چاہیے۔ اس کا [ماڈل صفحہ](https://developers.openai.com/api/docs/models/gpt-realtime-2.1) کہتا ہے کہ Free درجہ معاون نہیں۔ پہلے API بلنگ اور پروجیکٹ کے درخواست رفتار اور خرچ کی حدیں جانچیں؛ ChatGPT سبسکرپشن API استعمال کی ادائیگی نہیں کرتی۔ معمول کے مقامی راز رکھنے کے طریقے سے `OPENAI_API_KEY` کو **سرور عمل کے ماحول** میں رکھیں۔ اسے ان فائلوں، براؤزر کوڈ یا گفتگو کی تحریر میں نہ لکھیں۔ صرف وہ آواز استعمال کریں جسے OpenAI کو بھیجنے کی اجازت ہو۔
نئی ڈائریکٹری بنائیں اور نیچے کے پانچ مکمل بلاک ان کے اوپر دیے گئے فائل نام سے محفوظ کریں۔ `.mjs` ایکسٹینشن برقرار رکھیں اور تمام فائلیں ایک ڈائریکٹری میں رکھیں۔ سرور `127.0.0.1:3000` پر سنتا ہے۔ مقامی صفحے کا SDP offer لے کر سرور کی کلید سے OpenAI کو بھیجتا اور SDP answer واپس کرتا ہے۔ براؤزر کو عام API کلید نہیں ملتی۔ مقامی Host اور Origin جانچیں نمونے کو localhost تک محدود رکھنے میں مدد کرتی ہیں؛ مشترک سروس کے صارفین کی شناخت ثابت نہیں کرتیں۔
**server.mjs**
```javascript
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.get("/controller.mjs", (_req, res) => res.sendFile("controller.mjs", { 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: "Answer questions about the fictional demo library only using its read-only tools. State clearly that the data is synthetic software test data, not real venue information. For a lookup that may take noticeable time, give one short spoken progress preamble when useful, then call the tools. A preamble is never a result. Do not invent missing information. If a tool result is an error, say that lookup failed. Respond concisely in speech.",
tools: [
{ type: "function", name: "get_demo_hours", description: "Read synthetic demo-library opening hours; no external account or network.", parameters: { type: "object", properties: {}, additionalProperties: false } },
{ type: "function", name: "get_demo_address", description: "Read synthetic demo-library address; no external account or network.", parameters: { type: "object", properties: {}, additionalProperties: false } }
],
tool_choice: "auto"
}));
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"));
```
**index.html**
```html
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Local Realtime voice and read-only tool check</title>
<h1>Local voice and tool check</h1>
<p>The library data in this demo is synthetic software test data.</p>
<button id="start">Start voice</button>
<button id="stop" disabled>Stop and release microphone</button>
<p id="status" role="status">Idle</p>
<audio id="reply" autoplay controls></audio>
<pre id="events" aria-label="Event status"></pre>
<script type="module">
import { ToolController } from "/controller.mjs";
const start = document.querySelector("#start");
const stop = document.querySelector("#stop");
const status = document.querySelector("#status");
const events = document.querySelector("#events");
const reply = document.querySelector("#reply");
let current;
function note(line) {
events.textContent = `${line}\n${events.textContent}`.slice(0, 3000);
}
// These values are synthetic test fixtures, never real business records.
async function runDemoTool(call, signal) {
if (signal.aborted) throw Error("interrupted");
let args;
try { args = JSON.parse(call.arguments); } catch { return { ok: false, error: "Invalid arguments" }; }
if (!args || Array.isArray(args) || typeof args !== "object" || Object.keys(args).length) {
return { ok: false, error: "Expected an empty object" };
}
if (call.name === "get_demo_hours") return { ok: true, value: "Synthetic demo library hours: Monday-Friday 09:00-17:00." };
if (call.name === "get_demo_address") return { ok: true, value: "Synthetic demo library address: 1 Example Lane. This is not a real venue." };
return { ok: false, error: "Tool is not allowed" };
}
function closeSession(session = current, message = "Stopped") {
if (!session || session.closed) return;
session.closed = true;
clearTimeout(session.setupTimer);
clearTimeout(session.sessionTimer);
session.controller?.close();
session.abort.abort();
session.mic?.getTracks().forEach(track => track.stop());
if (session.pc) session.pc.onconnectionstatechange = null;
session.dc?.close();
session.pc?.close();
reply.pause();
if (current === session) {
current = null;
reply.srcObject = null;
start.disabled = false;
stop.disabled = true;
status.textContent = message;
note(message);
}
}
start.onclick = async () => {
const session = { abort: new AbortController(), closed: false };
current = session;
start.disabled = true;
stop.disabled = false;
status.textContent = "Requesting microphone";
events.textContent = "";
session.setupTimer = 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) return;
reply.srcObject = event.streams[0];
reply.play().catch(() => note("Playback blocked; press Play on the audio control"));
};
pc.onconnectionstatechange = () => {
if (session.closed) return;
note(`Peer: ${pc.connectionState}`);
if (pc.connectionState === "connected") {
clearTimeout(session.setupTimer);
clearTimeout(session.sessionTimer);
session.sessionTimer = setTimeout(() => closeSession(session, "Local session limit reached; microphone stopped"), 55 * 60000);
status.textContent = "Connected; ask about the synthetic demo library";
}
if (["failed", "closed", "disconnected"].includes(pc.connectionState)) closeSession(session, "Connection ended; microphone stopped");
};
pc.addTrack(mic.getAudioTracks()[0], mic);
const dc = session.dc = pc.createDataChannel("oai-events");
const send = event => {
if (session.closed || dc.readyState !== "open") throw Error("Event channel is not open");
dc.send(JSON.stringify(event));
};
session.controller = new ToolController({
send, runTool: runDemoTool,
status: note,
fatal: message => closeSession(session, `${message}; microphone stopped`)
});
dc.onmessage = message => {
if (session.closed) return;
let event;
try { event = JSON.parse(message.data); } catch { closeSession(session, "Invalid event; microphone stopped"); return; }
if (event.type === "session.created") note("Realtime session created");
if (event.type === "output_audio_buffer.cleared") note("Server cleared unplayed speech after interruption");
session.controller.handle(event).catch(() => closeSession(session, "Controller failed; microphone stopped"));
};
dc.onclose = () => { if (!session.closed) closeSession(session, "Event channel closed; microphone stopped"); };
dc.onerror = () => closeSession(session, "Event channel failed; microphone stopped");
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
if (session.closed) return;
const response = await fetch("/session", {
method: "POST", headers: { "Content-Type": "application/sdp" },
body: offer.sdp, signal: session.abort.signal
});
if (!response.ok) throw Error(`Session request failed (${response.status})`);
const answer = await response.text();
if (session.closed) return;
await pc.setRemoteDescription({ type: "answer", sdp: answer });
if (!session.closed) 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>
```
**controller.mjs**
```javascript
// The same controller runs in the browser and in the synthetic local checks.
export class ToolController {
constructor({ send, runTool, status, fatal, setTimer = setTimeout, clearTimer = clearTimeout }) {
Object.assign(this, { send, runTool, status, fatal, setTimer, clearTimer });
this.epoch = 0;
this.rounds = 0;
this.counter = 0;
this.responses = new Map();
this.pending = null;
this.closed = false;
}
abortPending() {
if (!this.pending) return;
this.pending.abort.abort();
if (this.pending.timer) this.clearTimer(this.pending.timer);
this.pending = null;
}
close() {
this.closed = true;
this.epoch++;
this.abortPending();
this.responses.clear();
}
fail(message) {
if (this.closed) return;
this.status(message);
this.close();
this.fatal(message);
}
interrupt() {
if (this.closed) return;
this.epoch++;
this.rounds = 0;
this.abortPending();
this.status("Speech detected; interrupted result discarded");
}
async handle(event) {
if (this.closed) return;
if (event.type === "error") {
this.fail(`Realtime error: ${event.error?.code || event.error?.message || "unknown"}`);
return;
}
if (event.type === "input_audio_buffer.speech_started") { this.interrupt(); return; }
if (event.type === "response.created") {
if (event.response?.id) this.responses.set(event.response.id, this.epoch);
return;
}
if (event.type === "conversation.item.done") {
const p = this.pending;
const id = event.item?.id;
if (!p || p.epoch !== this.epoch || !p.acks.has(id)) return;
p.acks.delete(id);
if (p.acks.size === 0) {
if (p.timer) this.clearTimer(p.timer);
this.pending = null;
if (!p.abort.signal.aborted && !this.closed && p.epoch === this.epoch) {
this.status("All tool outputs acknowledged; resuming response");
this.send({ type: "response.create" });
}
}
return;
}
if (event.type !== "response.done") return;
const response = event.response;
const id = response?.id;
const responseEpoch = this.responses.get(id);
this.responses.delete(id);
if (responseEpoch === undefined || responseEpoch !== this.epoch) return;
this.status(`Response ${id}: ${response.status || "unknown"}`);
if (response.status !== "completed") return; // Never execute partial/cancelled calls.
const calls = (response.output || []).filter(item => item.type === "function_call");
if (!calls.length) return;
if (this.pending) { this.fail("Overlapping tool batches in one turn"); return; }
if (calls.length > 8 || ++this.rounds > 3) { this.fail("Tool call or round bound reached"); return; }
const ids = new Set();
for (const call of calls) {
if (call.status !== "completed" || !call.call_id || ids.has(call.call_id)) {
this.fail("Incomplete or repeated function call ID"); return;
}
ids.add(call.call_id);
}
const p = { epoch: this.epoch, abort: new AbortController(), acks: new Set(), timer: null };
this.pending = p;
this.status(`Executing ${calls.length} read-only local call(s) from ${id}`);
const outputs = await Promise.all(calls.map(async call => {
try { return { call, result: await this.runTool(call, p.abort.signal) }; }
catch { return { call, result: { ok: false, error: "Local lookup failed" } }; }
}));
if (this.closed || this.pending !== p || p.epoch !== this.epoch || p.abort.signal.aborted) return;
try {
for (const { call, result } of outputs) {
const itemId = `tool_output_${++this.counter}`;
p.acks.add(itemId);
this.send({
type: "conversation.item.create",
event_id: `tool_event_${this.counter}`,
item: { id: itemId, type: "function_call_output", call_id: call.call_id, output: JSON.stringify(result) }
});
this.status(`Sent result for ${call.call_id}`);
}
p.timer = this.setTimer(() => this.fail("Tool output acknowledgement timed out"), 8000);
} catch { this.fail("Could not send tool outputs"); }
}
}
```
**protocol-check.mjs**
```javascript
import assert from "node:assert/strict";
import { ToolController } from "./controller.mjs";
const deferred = () => { let resolve, reject; const promise = new Promise((a, b) => { resolve = a; reject = b; }); return { promise, resolve, reject }; };
const tick = () => new Promise(resolve => setImmediate(resolve));
const call = (id, name = "get_demo_hours") => ({ type: "function_call", status: "completed", call_id: id, name, arguments: "{}" });
const created = id => ({ type: "response.created", response: { id } });
const done = (id, status, output = []) => ({ type: "response.done", response: { id, status, output } });
function harness(runTool = async x => ({ ok: true, value: x.name })) {
const sent = [], statuses = [], failures = [];
const timers = new Map(); let nextTimer = 0;
const controller = new ToolController({
send: e => sent.push(e), runTool,
status: s => statuses.push(s), fatal: e => failures.push(e),
setTimer: callback => { const id = ++nextTimer; timers.set(id, callback); return id; },
clearTimer: id => timers.delete(id)
});
return { controller, sent, statuses, failures, timers };
}
// Mixed audio/message output, two asynchronous calls resolved in reverse order.
{
const a = deferred(), b = deferred();
const h = harness(x => x.call_id === "call_A" ? a.promise : b.promise);
await h.controller.handle(created("resp_1"));
const work = h.controller.handle(done("resp_1", "completed", [
{ type: "message", status: "completed", content: [{ type: "output_audio", transcript: "Checking." }] },
call("call_A"), call("call_B", "get_demo_address")
]));
b.resolve({ ok: true, value: "address" }); await tick();
assert.equal(h.sent.length, 0, "first finished lookup must not send or resume early");
a.resolve({ ok: true, value: "hours" }); await work;
assert.deepEqual(h.sent.map(x => x.item?.call_id), ["call_A", "call_B"]);
assert.equal(h.sent.filter(x => x.type === "response.create").length, 0);
await h.controller.handle({ type: "conversation.item.done", item: { id: h.sent[1].item.id } });
assert.equal(h.sent.filter(x => x.type === "response.create").length, 0);
await h.controller.handle({ type: "conversation.item.done", item: { id: h.sent[0].item.id } });
assert.equal(h.sent.filter(x => x.type === "response.create").length, 1, "one resume after both item acknowledgements");
}
// One local lookup error does not erase the other result.
{
const h = harness(async x => { if (x.call_id === "bad") throw Error("synthetic failure"); return { ok: true, value: "fixture" }; });
await h.controller.handle(created("resp_2"));
await h.controller.handle(done("resp_2", "completed", [call("bad"), call("good")]));
assert.equal(JSON.parse(h.sent[0].item.output).ok, false);
assert.equal(JSON.parse(h.sent[1].item.output).ok, true);
for (const e of h.sent.slice()) await h.controller.handle({ type: "conversation.item.done", item: { id: e.item.id } });
assert.equal(h.sent.filter(x => x.type === "response.create").length, 1);
}
// Partial/cancelled responses never dispatch a call.
for (const status of ["failed", "incomplete", "cancelled"]) {
const h = harness();
await h.controller.handle(created(`resp_${status}`));
await h.controller.handle(done(`resp_${status}`, status, [call("ignored")]));
assert.equal(h.sent.length, 0);
}
// Distinct rounds are bounded, and missing/duplicate IDs are rejected.
{
const h = harness();
for (let i = 1; i <= 3; i++) {
await h.controller.handle(created(`round_${i}`));
await h.controller.handle(done(`round_${i}`, "completed", [call(`call_${i}`)]));
const last = h.sent.at(-1);
await h.controller.handle({ type: "conversation.item.done", item: { id: last.item.id } });
}
await h.controller.handle(created("round_4"));
await h.controller.handle(done("round_4", "completed", [call("too_many")]));
assert.match(h.failures[0], /bound/);
assert.equal(h.sent.filter(x => x.type === "response.create").length, 3);
}
{
const h = harness();
await h.controller.handle(created("dup"));
await h.controller.handle(done("dup", "completed", [call("same"), call("same")]));
assert.match(h.failures[0], /repeated/);
}
// User speech or Stop invalidates in-flight lookups and ignores their late results.
for (const mode of ["speech", "stop"]) {
const d = deferred(), h = harness(() => d.promise);
await h.controller.handle(created(`resp_${mode}`));
const work = h.controller.handle(done(`resp_${mode}`, "completed", [call("late")]));
if (mode === "speech") await h.controller.handle({ type: "input_audio_buffer.speech_started" });
else h.controller.close();
d.resolve({ ok: true, value: "late fixture" }); await work;
assert.equal(h.sent.length, 0);
}
// A late response completion or acknowledgement from the interrupted turn cannot resume it.
{
const h = harness();
await h.controller.handle(created("old"));
await h.controller.handle({ type: "input_audio_buffer.speech_started" });
await h.controller.handle(done("old", "completed", [call("late_call")]));
assert.equal(h.sent.length, 0);
await h.controller.handle(created("new"));
await h.controller.handle(done("new", "completed", [call("fresh_call")]));
const output = h.sent[0];
await h.controller.handle({ type: "input_audio_buffer.speech_started" });
await h.controller.handle({ type: "conversation.item.done", item: { id: output.item.id } });
assert.equal(h.sent.filter(x => x.type === "response.create").length, 0);
}
// Missing server acknowledgement closes instead of speculatively resuming.
{
const h = harness();
await h.controller.handle(created("no_ack"));
await h.controller.handle(done("no_ack", "completed", [call("one")]));
assert.equal(h.timers.size, 1);
[...h.timers.values()][0]();
assert.match(h.failures[0], /timed out/);
assert.equal(h.sent.filter(x => x.type === "response.create").length, 0);
}
console.log("Synthetic controller checks passed: ordering, single resume, error isolation, bounds and interruption.");
```
**browser-check.mjs**
```javascript
import assert from "node:assert/strict";
import fs from "node:fs";
import vm from "node:vm";
import { ToolController } from "./controller.mjs";
const html = fs.readFileSync(new URL("./index.html", import.meta.url), "utf8");
const script = html.match(/<script type="module">([\s\S]*?)<\/script>/)?.[1]
.replace('import { ToolController } from "/controller.mjs";', "");
assert.ok(script, "inline browser module found");
function fakePage(mediaPromise) {
const nodes = Object.fromEntries(["#start", "#stop", "#status", "#events", "#reply"].map(k => [k, {
disabled: k === "#stop", textContent: "", srcObject: null,
pause() { this.paused = true; }, play() { return Promise.resolve(); }
}]));
let fetches = 0, peer;
class FakePeer {
constructor() { peer = this; this.connectionState = "new"; }
addTrack() {}
createDataChannel() { return this.dc = { readyState: "open", close() { this.closed = true; }, send() {} }; }
async createOffer() { return { sdp: "v=0" }; }
async setLocalDescription() {}
async setRemoteDescription() {}
close() { this.closed = true; }
}
vm.runInNewContext(script, {
ToolController, document: { querySelector: k => nodes[k] },
navigator: { mediaDevices: { getUserMedia: () => mediaPromise } },
RTCPeerConnection: FakePeer,
fetch: async () => { fetches++; return { ok: true, text: async () => "answer" }; },
window: { addEventListener() {} },
AbortController, setTimeout, clearTimeout, console
});
return { nodes, peer: () => peer, fetches: () => fetches };
}
// Stop while permission is pending; any late track is released without a session request.
{
let allowMic;
const page = fakePage(new Promise(resolve => { allowMic = resolve; }));
const starting = page.nodes["#start"].onclick();
page.nodes["#stop"].onclick();
const track = { stopped: false, stop() { this.stopped = true; } };
allowMic({ getTracks: () => [track], getAudioTracks: () => [track] });
await starting;
assert.equal(track.stopped, true);
assert.equal(page.fetches(), 0);
assert.equal(page.nodes["#start"].disabled, false);
}
// Stop after signaling: microphone, data channel, peer and playback all close.
{
const track = { stopped: false, stop() { this.stopped = true; } };
const page = fakePage(Promise.resolve({ getTracks: () => [track], getAudioTracks: () => [track] }));
await page.nodes["#start"].onclick();
page.nodes["#stop"].onclick();
assert.equal(page.fetches(), 1);
assert.equal(track.stopped, true);
assert.equal(page.peer().closed, true);
assert.equal(page.peer().dc.closed, true);
assert.equal(page.nodes["#reply"].paused, true);
assert.equal(page.nodes["#reply"].srcObject, null);
}
console.log("Synthetic browser lifecycle checks passed: late microphone and full Stop cleanup.");
```
اسی ڈائریکٹری میں سرور کا واحد انحصار انسٹال کریں اور سرور چلائیں:
```bash
npm init -y
npm install express
node server.mjs
```
کھولیں `http://localhost:3000` ۔ صفحے پر **Start voice** ، **Stop and release microphone** ، آڈیو پلیئر اور واقعات کی حالت دکھنی چاہیے۔ صرف براؤزر صفحہ اور کنٹرولر ماڈیول فراہم ہوتے ہیں؛ دو ٹیسٹ فنکشن براؤزر میں چلتے اور حقیقی جگہ، کیلنڈر، CRM یا کاروباری اکاؤنٹ تک نہیں پہنچتے۔
## 2. شروع کریں، سنیں اور نتیجہ دیکھیں
کلک کریں **Start voice** ، مائیکروفون کی اجازت دیں اور **Connected** کا انتظار کریں۔ فرضی نمائشی لائبریری کے اوقات اور پتے کا سوال کریں۔ براؤزر WebRTC میڈیا ٹریک سے مائیکروفون آواز بھیجتا اور دور کی صوتی ٹریک آتے ہی آڈیو کنٹرول سے جوڑتا ہے۔ خودکار پلے بیک رکے تو نظر آنے والے کنٹرول پر **Play** دبائیں۔ سرور کی ہدایت مفید ہونے پر پہلے مختصر زبانی پیش رفت بتانے کو کہتی ہے۔ ماڈل اسے چھوڑ سکتا ہے؛ اسے سننا بھی تلاش کی کامیابی ثابت نہیں کرتا۔
واقعات کا پینل کلید، خام آڈیو یا تحریری گفتگو چھاپے بغیر جواب کی حالت، کال IDs اور آؤٹ پٹ کی تصدیقات دکھاتا ہے۔ ماڈل کوئی ٹول نہ چنے، ایک یا کئی چنے۔ جب `response.done` میں `status: completed` ہو تو کنٹرولر **تمام** آؤٹ پٹ آئٹمز میں مکمل `function_call` آئٹمز تلاش کرتا اور جانچتا ہے کہ ہر `call_id` موجود اور منفرد ہو۔ یہ مجاز تلاشیں، کئی بیک وقت بھی، چلاتا ہے، پھر ہر اصل ID کے لیے ایک `function_call_output` بھیجتا ہے۔ ناکام تلاش اسی کال کا خرابی نتیجہ بنتی ہے، دوسرے نتائج بچتے ہیں۔ کنٹرولر ہر مقرر کردہ آؤٹ پٹ آئٹم ID کے لیے `conversation.item.done` کا انتظار کرتا، پھر صرف ایک `response.create` بھیجتا ہے۔ تمام تصدیقات کا انتظار اس مثال کا محتاط مقامی ترتیب کا انتخاب ہے، API کی شرط نہیں۔ ایک جواب میں زیادہ سے زیادہ آٹھ کالز اور بغیر مداخلت ایک باری میں تین ٹول راؤنڈ ہیں؛ یہ مقامی حدیں ہیں۔
دیکھیں **Realtime session created** ، منسلک پیر، کالز ہوں تو ان کی IDs، آؤٹ پٹ تصدیقات اور مکمل بعد والا جواب۔ جواب سنیں اور بتائے وقت اور پتے کو `index.html` کی فرضی سٹرنگز سے ملائیں۔ جواب مکمل ہونے کا واقعہ بذات خود قابل سماعت پلے بیک یا درست جواب کا ثبوت نہیں۔ یہ مشاہدات اپنے بل والے براؤزر رن میں کرنے ہوں گے؛ BIG CHANGE نے ایسا رن نہیں کیا۔
## 3. باری دہرائیں، مداخلت کریں اور مائیکروفون آزاد کریں
دوبارہ شروع کیے بغیر دوسرا سوال کریں۔ مداخلت آزمانے کے لیے ماڈل بول رہا ہو یا تلاش باقی ہو تو بولیں۔ WebRTC اور صوتی سرگرمی شناخت کے لیے OpenAI منسوخی اور ابھی نہ بجنے والی آواز کو سرور سے کاٹنا دستاویز کرتا ہے۔ `input_audio_buffer.speech_started` پر مقامی کنٹرولر باقی تلاشیں بھی نامعتبر کرتا ہے تاکہ دیر کا نتیجہ روکا جواب دوبارہ نہ چلائے۔ یہ کوڈ WebSocket آڈیو پلیئر کا الگ پلے بیک کرسر اور آواز کاٹنے کے واقعات نافذ نہیں کرتا۔
کلک کریں **Stop and release microphone** اور تصدیق کریں کہ براؤزر مائیکروفون اشارہ بند ہو گیا۔ اجازت کی درخواست زیر التوا ہو تب بھی روکنا کام کرتا ہے: اجازت دیر سے آئے تو حاصل شدہ ٹریک فوراً رک جاتا ہے۔ یہی صفائی ٹریک روکتی، جاری سیشن درخواست منسوخ کرتی، ڈیٹا چینل اور پیر بند کرتی، پلے بیک روک کر الگ کرتی، ٹول کا کام منسوخ کرتی اور صفحہ چھوڑنے، تیاری یا مقامی سیشن کا وقت ختم ہونے، یا کنکشن ناکام ہونے پر ٹائمر ہٹاتی ہے۔ مثال کی تیاری 30 سیکنڈ اور مقامی سیشن 55 منٹ ہے؛ OpenAI Realtime کا زیادہ سے زیادہ 60 منٹ بتاتا ہے۔
## 4. مقامی جانچیں چلائیں اور ناکامیاں درست کریں
اسی ڈائریکٹری میں چلائیں:
```bash
node --check server.mjs
node --check controller.mjs
node protocol-check.mjs
node browser-check.mjs
```
دونوں اسکرپٹ فرضی واقعات اور براؤزر نقل استعمال کرتی ہیں۔ یہ ملا جلا پیغام/فنکشن جواب، الٹے ترتیب سے پورے دو کالز، ایک تسلسل سے پہلے سارے نتائج اور تصدیقات، ہر کال کی خرابی الگ رکھنا، صرف مکمل کال چلانا، راؤنڈ حد، بول کر مداخلت، دیر کی اجازت چھوڑنا اور Stop صفائی جانچتی ہیں۔ API درخواست نہیں کرتیں۔ کامیابی نیٹ ورک وقت، حقیقی آلات، آواز معیار یا عملی اعتبار نہیں ناپتی۔
اگر **Start** ناکام ہو تو نظر آنے والی حالت اور مقامی سرور کو ملا بیرونی HTTP اسٹیٹس پڑھیں۔ اجازت، `localhost:3000` ، سرور ماحول کی کلید، ماڈل رسائی، بلنگ، حدیں اور نیٹ ورک جانچیں۔ 502 بیرونی انکار، نیٹ ورک ناکامی یا مثال کی 25 سیکنڈ درخواست حد ہو سکتی ہے؛ اس سے ثابت نہیں ہوتا کہ OpenAI کو درخواست ملی ہی نہیں۔ کنکشن ہو مگر آواز نہ ہو تو ان پٹ و آؤٹ پٹ آلات دیکھیں اور **Play** دبائیں۔ ناکام، نامکمل یا منسوخ جواب جزوی کال نہیں چلا سکتا۔ آؤٹ پٹ تصدیق کی مدت گزرے تو کنٹرولر غیر یقینی دہرے تسلسل کے بجائے سیشن بند کرتا ہے۔ شروع یا نیٹ ورک کا نتیجہ نامعلوم ہو تو سیشن روکیں، مائیکروفون آزاد ہونا یقینی کریں اور دوبارہ کوشش سے پہلے پروجیکٹ استعمال دیکھیں؛ منسوخ درخواست سے خرچ پہلے ہی ہو سکتا ہے۔
## خرچ اور حدیں
OpenAI کا [ماڈل صفحہ](https://developers.openai.com/api/docs/models/gpt-realtime-2.1) اور [قیمت جدول](https://developers.openai.com/api/docs/pricing) ، 20 اکتوبر 2026 کو جانچے گئے، بغیر کیش متن ان پٹ/آؤٹ پٹ کے لیے فی دس لاکھ ٹوکن 4/24 امریکی ڈالر اور آڈیو ان پٹ/آؤٹ پٹ کے لیے 32/64 ڈالر بتاتے ہیں۔ اصل خرچ ٹوکن، آڈیو اور بار بار گفتگو کے سیاق پر منحصر ہے۔ ہم نے بل، تاخیر، براہ راست ٹول رویہ یا ابتدائی اطلاع نہیں ناپی۔ مائیکروفون کی آواز OpenAI کو جاتی ہے۔ مقرر فرضی فنکشن حقیقی کاروباری انضمام یا لکھنے کے عمل کی اجازت نہیں دیتے۔ مشترک تعیناتی کو اپنی صارف شناخت، حدیں، ٹول اجازت، نگرانی اور جانچ چاہیے۔
### ماخذ اور مزید مطالعہ
- [OpenAI WebRTC رہنما](https://developers.openai.com/api/docs/guides/voice-webrtc): براؤزر میڈیا ٹریک، دور کا پلے بیک، ڈیٹا چینل اور قابل اعتماد سرور سے SDP تبادلہ۔ یہ خاص براؤزر یا اکاؤنٹ نہیں آزماتا۔
- [Realtime گفتگو](https://developers.openai.com/api/docs/guides/realtime-conversations): مکمل جواب کا پورا آؤٹ پٹ، فنکشن نتائج کی ترتیب، مداخلت اور 60 منٹ سیشن حد۔ کسی خاص ماڈل کے ٹول چلانے کا وعدہ نہیں۔
- [Realtime ٹولز](https://developers.openai.com/api/docs/guides/realtime-mcp): ایپ کے زیر اختیار فنکشن کالز اور `function_call_output` ۔ یہ مقامی ٹیسٹ ڈیٹا کی کامیابی ثابت نہیں کرتا۔
- [کلائنٹ](https://developers.openai.com/api/reference/resources/realtime/client-events) اور [سرور واقعات](https://developers.openai.com/api/reference/resources/realtime/server-events) کے حوالے: آئٹم IDs، تصدیقات اور جواب کے واقعات کی ساخت۔ سب تصدیقات کا انتظار مثال کا مقامی انتخاب ہے۔
- [Realtime ہدایات لکھنا](https://developers.openai.com/api/docs/guides/voice-prompting): مشروط مختصر صوتی تمہید کی رہنمائی؛ قابل سماعت نتیجے کی ضمانت نہیں۔
- [ماڈل رسائی](https://developers.openai.com/api/docs/models/gpt-realtime-2.1) اور [API قیمتیں](https://developers.openai.com/api/docs/pricing): موجودہ رسائی درجے اور ٹوکن نرخ۔ اصل بل اپنے پروجیکٹ استعمال سے دیکھیں۔
## Sources
- [OpenAI WebRTC رہنما](https://developers.openai.com/api/docs/guides/voice-webrtc) — براؤزر مائیکروفون اور دور کے آڈیو ٹریک، oai-events ڈیٹا چینل اور /v1/realtime/calls سے عام API کلید کے ساتھ قابل اعتماد سرور SDP تبادلہ بیان کرتا ہے۔ مثال کے حقیقی آلات یا نیٹ ورک رویے کا ثبوت نہیں۔
- [OpenAI Realtime گفتگو](https://developers.openai.com/api/docs/guides/realtime-conversations) — response.done کا پورا آؤٹ پٹ، فنکشن نتائج بعد تسلسل، نہ بجی آواز سرور سے کاٹنے سمیت WebRTC VAD مداخلت اور 60 منٹ حد بیان کرتا ہے۔ قاری کے رن میں ٹول استعمال یا درست بولنے کی ضمانت نہیں۔
- [ٹولز کے ساتھ OpenAI Realtime](https://developers.openai.com/api/docs/guides/realtime-mcp) — ایپ فنکشن ٹولز، اصل call_id والے function_call_output آئٹمز اور response.create تسلسل بیان کرتا ہے۔ ہر آؤٹ پٹ تصدیق کا انتظار اضافی مقامی ترتیب ہے۔
- [OpenAI Realtime کلائنٹ واقعات](https://developers.openai.com/api/reference/resources/realtime/client-events) — مثال کی conversation.item.create، آئٹم IDs، function_call_output اور response.create ساختیں بتاتا ہے؛ حوالہ انضمام ٹیسٹ نہیں۔
- [OpenAI Realtime سرور واقعات](https://developers.openai.com/api/reference/resources/realtime/server-events) — مثال کے مکمل جواب، آخری گفتگو آئٹم تصدیقات اور خرابی واقعات کی ساخت بتاتا ہے۔ تمام تصدیقات کا انتظار مقامی انتخاب ہے، API شرط یا انضمام ٹیسٹ نہیں۔
- [OpenAI صوتی سرگرمی شناخت](https://developers.openai.com/api/docs/guides/realtime-vad) — مقامی کام نامعتبر کرنے کے speech_started اور speech_stopped سگنل بیان کرتا ہے؛ یہاں حقیقی مداخلت نہیں آزمائی۔
- [OpenAI Realtime ہدایات لکھنا](https://developers.openai.com/api/docs/guides/voice-prompting) — مشروط چھوٹی پیش رفت اطلاع مانگنے کی تائید۔ بامعاوضہ سیشن میں کوئی اطلاع دیکھی نہیں گئی۔
- [OpenAI gpt-realtime-2.1 ماڈل](https://developers.openai.com/api/docs/models/gpt-realtime-2.1) — ماڈل ID، غیر معاون Free درجہ اور بغیر کیش فی دس لاکھ ٹوکن نرخ بتاتا ہے: متن ان پٹ/آؤٹ پٹ 4/24 امریکی ڈالر، آڈیو 32/64۔ قاری کو اکاؤنٹ کی رسائی اور بلنگ جانچنی ہوگی۔
- [OpenAI API قیمتیں](https://developers.openai.com/api/docs/pricing) — gpt-realtime-2.1 کے شائع شدہ متن و آڈیو ٹوکن نرخ کی تائید؛ اس رہنما کے سیشن کی مقرر قیمت نہیں دیتا۔
- [OpenAI API اور ChatGPT بلنگ](https://help.openai.com/en/articles/9039756-managing-billing-for-chatgpt-and-the-api-platform) — OpenAI کہتا ہے کہ API استعمال ChatGPT سبسکرپشن سے الگ بل ہوتا ہے۔ اس سے قاری کا موجودہ بیلنس یا پروجیکٹ حد طے نہیں ہوتی۔
BIG CHANGE نیوز لیٹر
بڑی تصویر۔ اپنی رفتار سے۔
AI اور روبوٹکس کی تازہ کہانیاں، قابلِ توجہ تبدیلیاں اور عملی خیالات۔ روزانہ بریفنگ، ہفتہ وار خلاصہ یا ماہانہ نقطۂ نظر منتخب کریں۔
Next scheduled send (UTC): . Your first edition arrives at the next scheduled send after you confirm.
آپ کی رازداری، آپ کا انتخاب۔
ضروری اسٹوریج سائٹ کی حفاظت میں مدد دیتا ہے اور آپ کی ترجیحات یاد رکھتا ہے۔ آپ کی اجازت تک اختیاری Google Analytics بند رہتا ہے۔ آپ ہر کہانی صرف ضروری اسٹوریج کے ساتھ پڑھ سکتے ہیں۔ رازداری کی تفصیل