nur-v1.
There are two ways to connect, and both speak the OpenAI Realtime protocol:
Events, session settings and replies are the same on both.
WebRTC in the browser
The browser captures the microphone and plays Nur through a normal audio element. Your server only relays two requests, so the key stays private.server.js
browser.js
client.realtime.calls.create(sdp=..., session=...) posts the same
request.
Hold to talk
A product with a talk button can keep what the person said before the call connected and end their turn the moment they let go. Addheld_audio=1 to the
/realtime/calls URL. Once the session is up, send what you held as
input_audio_buffer.append events on the data channel (16 kHz, 16-bit mono
PCM, base64, up to 20 seconds): the first second at once and the rest at no
more than three times real time, since Nur keeps two seconds of input. Then
send {"type": "eesi.input.track"} and add the microphone’s track; its audio
waits behind the marker. When the button comes up, send
{"type": "eesi.input.release"} once the last of the speech has gone out, and
{"type": "eesi.input.hold"} if it goes down again within 700 ms. A call that
takes them says hold_to_talk: true in session.created’s
capabilities.events.
WebSocket from a server
Configure the session
Sendsession.update at any time. Later updates merge with earlier ones, so a
frame that changes only the voice keeps your instructions.
A field it cannot take, such as a
replace phrase with punctuation in it,
refuses the whole update with an invalid_session_config error and changes
nothing.
Put today’s date and anything else the assistant should know in
instructions.
Audio on a WebSocket
WebRTC handles audio formats for you. On a WebSocket, audio is 16-bit mono PCM, base64 encoded:- Input defaults to 16 kHz. Send about 20 ms per message (640 bytes) at the pace it was recorded. Turn detection listens to the timing, so sending a file all at once makes for poor turn-taking, not a faster reply.
- Output defaults to 16 kHz. For 24 kHz, set
"output": {"format": {"type": "audio/pcm", "rate": 24000}}. - For telephony,
{"type": "audio/pcmu"}and{"type": "audio/pcma"}select G.711 at 8 kHz.
format out. Don’t write "rate": 16000, which the
schema rejects.
response.create:
input_audio_buffer.clear drops audio that turn detection has not picked up
yet, answered with input_audio_buffer.cleared; speech it already heard
finishes as a turn. conversation.item.delete with an item_id takes an item
out of what Nur reads next: one you added, one of Nur’s replies by its
item_id, or a spoken turn by the item_id its transcript came with. It is
answered with conversation.item.deleted, or an item_not_found error.
Binary audio
Setaudio.input.transport or audio.output.transport to "binary" to send
or receive audio as binary WebSocket frames that hold the audio’s bytes and
nothing else, in place of input_audio_buffer.append and
response.output_audio.delta. Every other event stays JSON, and a reply’s
audio frames belong to the response.created before them. Input is accepted
either way. A change to the output transport applies from the next reply on, so
a reply never arrives half in one form and half in the other.
{"type": "audio/opus"}, each binary frame (or each base64 audio and
delta) is one raw Opus packet, 24 kHz mono. Nur’s packets are 20 ms each.
Over JSON, each packet is its own response.output_audio.delta.
Scripted lines
To have Nur say something word for word, such as a disclosure or a greeting your product wrote, send aforce_message:
response.created, its transcript and
audio, response.done) and joins the conversation as something Nur said, under
the item id you give or one the server picks. Don’t follow it with
response.create: the line is the turn. A line sent while Nur is speaking
waits for the reply to end, and lines are said in the order you send them.
interruptible defaults to true, so people can talk over the line like any
reply. With false, what they say is not heard until the line has played, so
nothing cuts it off. response.cancel still stops it. Lines are up to 4,096
characters. Add "language": "es" (any language code) when you know what
language the line is in, so the voice says it as a speaker of that language
would; without it, the server tells from the line itself.
Interruptions
People talk over each other, and Nur is built for it. When someone starts speaking over a reply, the server stops generating and tells you withinput_audio_buffer.speech_started. On WebRTC the gateway also stops the
audio it already sent. On a WebSocket, stop your own playback when that event
arrives, or Nur will seem to keep talking.
Events you will use
The server also sends a few
eesi.* events. eesi.session carries the
session_id and resume_token for reconnecting, eesi.recording_disclosure
has wording to show people when sessions are recorded, and
eesi.synthetic_audio declares the audio machine-generated. You can ignore
them if you don’t need them.
Reconnecting
If a connection drops, reconnect within 15 minutes with?resume=<session_id>&resume_token=<resume_token> from the eesi.session
event, and the conversation continues where it left off. The new
eesi.session event then includes resumed; if it doesn’t, you got a fresh
session.
Languages
Nur understands and speaks more than a hundred languages and follows the person when they switch. To keep it in one language, say so ininstructions. For
interpreting rather than conversing, use Live translation.
When the person speaks first, as in a push-to-talk app, name the languages they
speak, most used first and three at most, so that a word or two at the start is
heard in one of them: "eesi_languages": ["ar", "en"] in session.update.
Once they have said more, Nur follows whatever language they speak.
Limits
- A session can last up to three hours. A WebSocket with no client traffic for
five minutes closes; send
{"type": "eesi.keepalive"}while a person is reading or thinking. - Usage is billed per minute of session time.
- If your organization runs out of credits during a call, the session ends with a message saying why before it closes.