openai SDK, and print each thing you say as a line of text. Then change the instructions and the same program becomes a support agent that hears the caller and answers in text. The SDK needs a new base URL and key, nothing else.
Full code on GitHub
A terminal transcriber in Python, and a voice-in, text-out support agent in Python and Node.
What you’ll build
- A transcriber. Speak, and each finished sentence prints with a timestamp, optionally appended to a file.
- A support agent. Speak, and Sam from a fictional outdoor store answers in text about three orders held in the prompt.
- The same support agent in Node, on the npm
openaipackage.
How transcription works on Dialog-RSN-1
Dialog-RSN-1 has no separate speech-to-text model. It transcribes the caller as part of writing its reply, so every transcript belongs to a turn. That shapes the whole program:- You need a reply to get a transcript. Set
create_response: trueso each turn starts a reply on its own. Withcreate_response: false, no transcript arrives until you sendresponse.create. - The transcriber asks for a one-word reply. Its instructions say to answer “ok”, which keeps each turn short.
- The transcript arrives when the turn ends. You get no partial text while the caller is still talking. The whole line lands in a few events just after
input_audio_buffer.speech_stopped. - Only a completed reply sends
.completed. If the caller cuts in while the reply is still generating, or the reply fails, the turn gets at most some.deltaevents and no.completed. The transcript is the last thing the model writes, so a turn interrupted early may get no transcript at all. - The reply starts before the transcript. The first
response.output_text.deltacomes within about 100 ms ofspeech_stopped, and the transcript follows. The support agent holds the reply back so the terminal reads in order. audio.input.transcriptionchanges nothing. The service accepts the field and echoes it insession.updated, but transcripts arrive the same with or without it.
Prerequisites
- A Dialog-RSN-1 API key. Sign up or log in at studio.poly.ai and create one in the API Keys tab. The quickstart walks through it.
- For Python: Python 3.12 and uv.
- For Node: Node 22.
npm installcompiles thenaudiodon2audio package, so you need a C++ toolchain. - A microphone, or a 24 kHz, 16-bit mono WAV file to stream instead.
Run it
--reply to see the model’s “ok” under each line, and --out notes.txt to append each line to a file:
say command and stream it at real time:
Step 1: Connect with the SDK
Pass the Dialog-RSN-1 base URL and key to the client.url is the full realtime URL, so drop /realtime and switch the scheme to get the base. The SDK builds wss://api.studio.poly.ai/v1/realtime?model=dialog-rsn-1, sends the key as Authorization: Bearer, and parses every event into its typed models.
url defaults to wss://api.studio.poly.ai/v1/realtime. Set DIALOG_REALTIME_URL in .env, or pass --url, to point somewhere else.
Step 2: Configure the session
output_modalities: ["text"]. This program prints text, so it asks for text. A new session defaults to["audio"], where the server speaks the reply and sends its words asresponse.output_audio_transcript.*. Text only keeps the reply onresponse.output_text.*, which the program reads.rate: 24000. The program opens the microphone at 24 kHz, so nothing resamples on the client. The service resamples to the model’s own rate.create_response: true. Dialog-RSN-1 decides when you’ve finished and starts the turn itself. That turn carries the transcript.
Step 3: Stream the microphone
sounddevice calls back with 100 ms of PCM16 at a time. The program base64-encodes each chunk and appends it to the input buffer:
Step 4: Print the transcript
Two events carry the transcript.conversation.item.input_audio_transcription.delta streams it in pieces, and conversation.item.input_audio_transcription.completed carries the whole line:
line() stamps the text with the time, prints it and appends it to the --out file. The response.done branch keeps what arrived when a reply ends without a .completed, so a line isn’t left half printed.
Stop on invalid_request_error. It usually means the session.update was refused, which leaves the session on the server’s defaults:
Turn it into a support agent
support_agent.py keeps the connection, the session shape and the audio path. It changes three things.
The instructions. prompt.py holds everything Sam knows: three orders in different states, the return and cancellation policy, and a rule to confirm the order before giving details. There are no tools. The data sits in the prompt.
A seeded greeting. The program prints the greeting itself and adds it to the conversation as an assistant message, so the model’s history starts with it:
--file call.wav to stream a recording instead of the microphone, and --list-devices and --device to pick a microphone.
What the timing looks like
We streamed recorded clips to us-1 production over 14 turns, 20 ms at a time as a microphone would, from London (about 90 ms round trip):speech_stopped reports audio_end_ms, the point in your audio where Dialog-RSN-1 places the end of speech. That mark sits about 400 to 570 ms after the last word, so time your own measurements from the audio, not from audio_end_ms.
Troubleshooting
What this isn’t
The transcriber shows that a stock OpenAI Realtime client works unchanged against Dialog-RSN-1. It isn’t a full speech-to-text service. It has no speaker labels, no punctuation settings, and no partial results while the caller speaks.Where to go next
- The OpenAI Realtime migration guide lists every field Dialog-RSN-1 accepts and rejects.
- The streaming text-to-speech guide gives the support agent a voice.
- The API reference lists every client and server event.

