26 26
27For Node.js, install `openai` and `ws` with `npm install openai ws` and save the JavaScript example as `client.mjs`. For Python on macOS or Linux, install `openai[realtime]` and save the Python example as `client.py`. Set `OPENAI_API_KEY` in the server environment. These examples require an SDK version with Live support. The example reads raw, mono PCM16 audio at 24 kHz from standard input and writes returned audio in the same format to standard output. Connect these streams to your application's audio capture and playback. Logs and transcript events go to standard error so they do not corrupt the audio stream.27For Node.js, install `openai` and `ws` with `npm install openai ws` and save the JavaScript example as `client.mjs`. For Python on macOS or Linux, install `openai[realtime]` and save the Python example as `client.py`. Set `OPENAI_API_KEY` in the server environment. These examples require an SDK version with Live support. The example reads raw, mono PCM16 audio at 24 kHz from standard input and writes returned audio in the same format to standard output. Connect these streams to your application's audio capture and playback. Logs and transcript events go to standard error so they do not corrupt the audio stream.
28 28
29For Go, use Go 1.26 or later, save the example as `main.go`, initialize a module with `go mod init live-demo`, and install `github.com/openai/openai-go/v3@v3.85.1` with `go get`. For Java, use Java 17 or later, add the Maven dependency `com.openai:openai-java:4.90.0`, and use the `LiveConnectionWebsocketExample` class. For Ruby 3.3 or later, install the SDK with `gem install openai -v 0.110.0`, install its WebSocket dependency with `gem install async-websocket`, and save the example as `client.rb`.
30
29```javascript31```javascript
30import OpenAI from "openai";32import OpenAI from "openai";
31import { LiveWS } from "openai/resources/live/ws";33import { LiveWS } from "openai/resources/live/ws";
237 asyncio.run(main())239 asyncio.run(main())
238```240```
239 241
242```go
243package main
244
245import (
246 "context"
247 "encoding/base64"
248 "fmt"
249 "github.com/openai/openai-go/v3"
250 "github.com/openai/openai-go/v3/live"
251 "io"
252 "os"
253 "os/signal"
254 "time"
255)
256
257func run() error {
258 ctx, cancel := context.WithCancel(context.Background())
259 defer cancel()
260 client := openai.NewClient()
261 connection, err := client.Live.Connect(ctx, live.LiveConnectionOptions{})
262 if err != nil {
263 return err
264 }
265 defer connection.Abort()
266 session := live.SessionConfigParam{
267 Model: "gpt-live-1",
268 Instructions: openai.String("Be concise. Delegate requests needing current information to the backend, which can search the web."),
269 Audio: live.SessionConfigAudioParam{Format: live.AudioFormatParamOfAudioPCM(24000), Output: live.SessionConfigAudioOutputParam{Voice: live.SessionConfigAudioOutputVoiceUnionParam{OfString: openai.String("marin")}}},
270 Delegation: live.SessionConfigDelegationUnionParam{OfResponses: &live.SessionConfigDelegationResponsesParam{Responses: live.ResponsesDelegationConfigParam{
271 Model: "gpt-5.6-luna", Tools: []live.ResponsesDelegationConfigToolUnionParam{{OfWebSearch: &live.ResponsesDelegationConfigToolWebSearchParam{}}},
272 ToolChoice: live.ResponsesDelegationConfigToolChoiceUnionParam{OfLiveToolChoiceEnum: openai.String("auto")},
273 }}},
274 }
275 if err := connection.Send(ctx, live.ClientEventUnionParam{OfSessionStart: &live.SessionStartEventParam{Session: session, EventID: openai.String("event_start")}}); err != nil {
276 return err
277 }
278 events := make(chan live.ServerEventUnion)
279 failures := make(chan error, 2)
280 go func() {
281 for {
282 event, err := connection.Recv(ctx)
283 if err != nil {
284 select {
285 case failures <- err:
286 case <-ctx.Done():
287 }
288 return
289 }
290 select {
291 case events <- event:
292 case <-ctx.Done():
293 return
294 }
295 }
296 }()
297 audio := make(chan []byte)
298 interrupts := make(chan os.Signal, 1)
299 signal.Notify(interrupts, os.Interrupt)
300 defer signal.Stop(interrupts)
301 timer := time.NewTimer(30 * time.Second)
302 defer timer.Stop()
303 started, closing := false, false
304 for {
305 select {
306 case <-timer.C:
307 return fmt.Errorf("session startup or finalization timed out")
308 case err := <-failures:
309 return err
310 case <-interrupts:
311 if !started {
312 return fmt.Errorf("interrupted before session.started")
313 }
314 if closing {
315 continue
316 }
317 closing = true
318 timer.Reset(15 * time.Second)
319 if err := connection.Send(ctx, live.ClientEventUnionParam{OfSessionClose: &live.SessionCloseEventParam{}}); err != nil {
320 return err
321 }
322 case chunk := <-audio:
323 if closing {
324 continue
325 }
326 if err := connection.Send(ctx, live.ClientEventUnionParam{OfSessionInputAudioAppend: &live.InputAudioAppendEventParam{Audio: base64.StdEncoding.EncodeToString(chunk)}}); err != nil {
327 return err
328 }
329 case event := <-events:
330 switch event.Type {
331 case "error":
332 return fmt.Errorf("Live error: %s", event.RawJSON())
333 case "session.started":
334 if started {
335 continue
336 }
337 started = true
338 timer.Stop()
339 fmt.Fprintln(os.Stderr, "Session ready", event.AsSessionStartedEvent().Session.ID)
340 // stdin/stdout carry raw mono PCM16 at 24 kHz, not WAV files.
341 go func() {
342 pending := []byte{}
343 for {
344 buffer := make([]byte, 4800)
345 n, err := os.Stdin.Read(buffer)
346 pending = append(pending, buffer[:n]...)
347 complete := len(pending) - len(pending)%2
348 if complete > 0 {
349 chunk := append([]byte(nil), pending[:complete]...)
350 pending = pending[complete:]
351 select {
352 case audio <- chunk:
353 case <-ctx.Done():
354 return
355 }
356 }
357 if err != nil {
358 if err != io.EOF {
359 select {
360 case failures <- err:
361 case <-ctx.Done():
362 }
363 }
364 // EOF does not close the session. Press Ctrl+C to finalize it.
365 return
366 }
367 }
368 }()
369 case "session.output_audio.delta":
370 chunk, err := base64.StdEncoding.DecodeString(event.AsOutputAudioDeltaEvent().Delta)
371 if err != nil {
372 return err
373 }
374 if _, err = os.Stdout.Write(chunk); err != nil {
375 return err
376 }
377 case "session.closed":
378 if !started {
379 return fmt.Errorf("session closed before startup")
380 }
381 fmt.Fprintln(os.Stderr, "Final session usage", event.AsSessionClosedEvent().Usage)
382 return nil
383 default:
384 fmt.Fprintln(os.Stderr, event.RawJSON())
385 }
386 }
387 }
388}
389func main() {
390 if err := run(); err != nil {
391 fmt.Fprintln(os.Stderr, err)
392 os.Exit(1)
393 }
394}
395```
396
397```java
398import com.openai.client.okhttp.OkHttpClient;
399import com.openai.core.ClientOptions;
400import com.openai.helpers.LiveConnection;
401import com.openai.models.live.*;
402import java.util.*;
403import java.util.concurrent.*;
404import java.util.concurrent.atomic.AtomicBoolean;
405
406public final class LiveConnectionWebsocketExample {
407 public static void main(String[] args) throws Exception {
408 var session =
409 SessionConfig.builder()
410 .model("gpt-live-1")
411 .instructions(
412 "Be concise. Delegate requests needing current information to the backend, which can search the web.")
413 .audio(
414 SessionConfig.Audio.builder()
415 .audioPcmFormat(AudioFormat.AudioPcm.Rate.of(24000L))
416 .output(SessionConfig.Audio.Output.builder().voice("marin").build())
417 .build())
418 .responsesDelegation(
419 ResponsesDelegationConfig.builder()
420 .model("gpt-5.6-luna")
421 .addToolWebSearch()
422 .toolChoice(ResponsesDelegationConfig.ToolChoice.LiveToolChoiceEnum.AUTO)
423 .build())
424 .build();
425 var closing = new AtomicBoolean();
426 var finished = new CountDownLatch(1);
427 var audio = new ArrayBlockingQueue<byte[]>(8);
428 var failures = new ArrayBlockingQueue<Exception>(1);
429 try (var http = OkHttpClient.builder().build();
430 var connection =
431 LiveConnection.connect(ClientOptions.builder().fromEnv().httpClient(http).build())) {
432 Thread shutdown =
433 new Thread(
434 () -> {
435 closing.set(true);
436 try {
437 if (!finished.await(15, TimeUnit.SECONDS)) {
438 System.err.println("Incomplete finalization: no session.closed event");
439 connection.close();
440 }
441 } catch (InterruptedException interrupted) {
442 Thread.currentThread().interrupt();
443 }
444 });
445 Runtime.getRuntime().addShutdownHook(shutdown);
446 try {
447 connection.send(
448 ClientEvent.ofSessionStart(
449 SessionStartEvent.builder().session(session).eventId("event_start").build()));
450 boolean started = false, closeSent = false;
451 long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(30);
452 var incoming = connection.receiveAsync();
453 while (true) {
454 if (System.nanoTime() > deadline)
455 throw new IllegalStateException("Session startup or finalization timed out");
456 if (!failures.isEmpty()) throw failures.take();
457 if (closing.get() && started && !closeSent) {
458 closeSent = true;
459 deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(15);
460 connection.send(ClientEvent.ofSessionClose(SessionCloseEvent.builder().build()));
461 }
462 var chunk = audio.poll();
463 if (chunk != null && !closing.get())
464 connection.send(
465 ClientEvent.ofSessionInputAudioAppend(
466 InputAudioAppendEvent.builder()
467 .audio(Base64.getEncoder().encodeToString(chunk))
468 .build()));
469 ServerEvent event;
470 try {
471 event = incoming.get(20, TimeUnit.MILLISECONDS);
472 } catch (TimeoutException waiting) {
473 continue;
474 }
475 if (event.isErrorEvent()) throw new IllegalStateException(event.toString());
476 if (event.isSessionStarted() && !started) {
477 started = true;
478 deadline = Long.MAX_VALUE;
479 System.err.println("Session ready " + event.asSessionStarted().session().id());
480 // stdin/stdout carry raw mono PCM16 at 24 kHz, not WAV files.
481 Thread reader =
482 new Thread(
483 () -> {
484 try {
485 int pending = -1;
486 while (!closing.get()) {
487 byte[] buffer = new byte[4800];
488 int offset = pending < 0 ? 0 : 1;
489 if (offset == 1) buffer[0] = (byte) pending;
490 int count = System.in.read(buffer, offset, buffer.length - offset);
491 // EOF does not close the session. Press Ctrl+C to finalize it.
492 if (count < 0) return;
493 count += offset;
494 pending = count % 2 == 0 ? -1 : Byte.toUnsignedInt(buffer[count - 1]);
495 int complete = count - count % 2;
496 if (complete > 0) audio.put(Arrays.copyOf(buffer, complete));
497 }
498 } catch (Exception error) {
499 failures.offer(error);
500 }
501 });
502 reader.setDaemon(true);
503 reader.start();
504 } else if (event.isSessionOutputAudioDelta()) {
505 System.out.write(Base64.getDecoder().decode(event.asSessionOutputAudioDelta().delta()));
506 System.out.flush();
507 } else if (event.isSessionClosed()) {
508 if (!started) throw new IllegalStateException("Session closed before startup");
509 System.err.println("Final session usage " + event.asSessionClosed().usage());
510 return;
511 } else {
512 System.err.println(event);
513 }
514 incoming = connection.receiveAsync();
515 }
516 } finally {
517 closing.set(true);
518 finished.countDown();
519 try {
520 Runtime.getRuntime().removeShutdownHook(shutdown);
521 } catch (IllegalStateException shuttingDown) {
522 /* The hook is already draining. */
523 }
524 }
525 }
526 }
527}
528```
529
530```ruby
531require "openai"
532require "base64"
533$stdin.binmode
534$stdout.binmode
535session = {
536 model: "gpt-live-1",
537 instructions: "Be concise. Delegate requests needing current information to the backend, which can search the web.",
538 audio: {
539 format: {
540 type: :"audio/pcm",
541 rate: 24_000
542 },
543 output: { voice: :marin }
544 },
545 delegation: {
546 type: :responses,
547 responses: {
548 model: "gpt-5.6-luna",
549 tools: [{ type: :web_search }],
550 tool_choice: :auto
551 }
552 }
553}
554client = OpenAI::Client.new
555client.live.connect do |connection|
556 state = {
557 closing: false,
558 finalized: false,
559 started: false
560 }
561 threads = {}
562 mutex = Mutex.new
563 previous = trap("INT") do
564 threads[:closer] ||= Thread.new do
565 mutex.synchronize do
566 state[:closing] = true
567 if state[:started]
568 connection.send_event(type: :"session.close")
569 else
570 connection.close
571 end
572 end
573 sleep 15
574 unless state[:finalized]
575 warn "Incomplete finalization: no session.closed event"
576 connection.close
577 end
578 end
579 end
580 begin
581 threads[:startup] = Thread.new do
582 sleep 30
583 unless state[:started]
584 warn "Session startup timed out"
585 connection.close
586 end
587 end
588 connection.send_event(type: :"session.start", session: session, event_id: "event_start")
589 connection.each do |event|
590 case event
591 when OpenAI::Models::Live::ErrorEvent
592 raise "Live error: #{event.to_json}"
593 when OpenAI::Models::Live::SessionStartedEvent
594 next if state[:started]
595
596 state[:started] = true
597 warn "Session ready #{event.session.id}"
598 # stdin/stdout carry raw mono PCM16 at 24 kHz, not WAV files.
599 threads[:sender] = Thread.new do
600 pending = +"".b
601 loop do
602 pending << $stdin.readpartial(4800)
603 complete = pending.bytesize - pending.bytesize % 2
604 chunk = pending.slice!(0, complete).to_s
605 mutex.synchronize do
606 break if state[:closing]
607
608 connection.send_event(type: :"session.input_audio.append", audio: Base64.strict_encode64(chunk)) unless chunk.empty?
609 end
610 end
611 rescue EOFError
612 # EOF does not close the session. Press Ctrl+C to finalize it.
613 nil
614 rescue => error
615 warn "Audio input failed: #{error.class}"
616 connection.close
617 end
618 when OpenAI::Models::Live::OutputAudioDeltaEvent
619 $stdout.write(Base64.strict_decode64(event.delta))
620 $stdout.flush
621 when OpenAI::Models::Live::SessionClosedEvent
622 raise "Session closed before startup" unless state[:started]
623
624 warn "Final session usage #{event.usage}"
625 state[:finalized] = true
626 break
627 else
628 warn event.to_json
629 end
630 end
631 raise "Connection closed without final session usage" unless state[:finalized]
632 ensure
633 trap("INT", previous)
634 threads[:startup]&.kill
635 threads[:startup]&.join
636 threads[:sender]&.kill
637 threads[:sender]&.join
638 threads[:closer]&.kill
639 threads[:closer]&.join
640 end
641end
642```
240 643
241Run `node client.mjs` or `python client.py` with your audio source and player attached. After `Session ready` appears, supply a continuous microphone stream paced at its recorded sample rate. Piping an entire file at once does not simulate a live microphone. EOF on the audio source does not end the conversation. Send `SIGINT` to the process to request a graceful close.644
645Run `node client.mjs`, `python client.py`, `go run .`, `ruby client.rb`, or your Java application with your audio source and player attached. After `Session ready` appears, supply a continuous microphone stream paced at its recorded sample rate. Piping an entire file at once does not simulate a live microphone. EOF on the audio source does not end the conversation. Send `SIGINT` to the process to request a graceful close.
242 646
243The example connects the audio streams; your application handles capture, buffering, playback, and resampling when needed. Test these parts with your devices and network before evaluating model behavior.647The example connects the audio streams; your application handles capture, buffering, playback, and resampling when needed. Test these parts with your devices and network before evaluating model behavior.
244 648
328 },732 },
329});733});
330 734
331let sessionCreated = false;735let connected = false;
332
333ws.on("message", function incoming(message) {736ws.on("message", function incoming(message) {
334 const event = JSON.parse(message.toString());737 const event = JSON.parse(message.toString());
738 if (event.type === "error") throw new Error(event.error.message);
335 if (event.type === "session.created") {739 if (event.type === "session.created") {
336 sessionCreated = true;740 connected = true;
337 console.log("Connected to the Realtime API.", event);741 console.log(`Connected to the Realtime API: ${event.session.id}`);
338 ws.close();742 ws.close(1000);
339 } else if (event.type === "error") {
340 console.error(event);
341 ws.close();
342 }743 }
343});744});
344ws.on("error", function handleError(error) {745ws.on("close", () => {
345 console.error(error);746 if (!connected)
346 ws.close();747 throw new Error("Connection closed before the session was created");
347});
348ws.on("close", function closed() {
349 if (!sessionCreated) {
350 console.error("Connection closed before the Realtime session was created.");
351 process.exitCode = 1;
352 }
353});748});
354```749```
355 750
428)823)
429 824
430client.realtime.connect(model: "gpt-realtime-2.1") do |connection|825client.realtime.connect(model: "gpt-realtime-2.1") do |connection|
431 puts("Connected to the Realtime API: #{connection.url.host}")826 created = connection.find do |event|
432 connection.each { |event| puts("Received event: #{event.type}") }827 raise event.error.message if event.is_a?(OpenAI::Realtime::RealtimeErrorEvent)
828
829 next unless event.is_a?(OpenAI::Realtime::SessionCreatedEvent)
830
831 puts("Connected to the Realtime API: #{event.session[:id]}")
832 true
833 end
834 raise "Connection closed before the session was created" unless created
433end835end
434```836```
435 837