Transcripts and turn detection

Update partial text and choose when an utterance is complete.
View as Markdown

An utterance is the audio segment finalized by a manual commit, server VAD, or the automatic duration boundary. Each utterance has an item_id and can produce partial results followed by a completed transcript.

Update the transcript

Each partial is the entire current hypothesis. Replace the previous text for that item_id; do not append it.

For example, earlier words may be revised as the utterance becomes clearer:

Same item_id (illustrative):
partial "I scream"
partial "Ice cream"
completed "Ice cream is delicious."

Store the completed result as authoritative, even if it differs from the last partial. This handler keeps each utterance separate:

1partials = {}
2finals = {}
3
4
5def handle_transcript(event):
6 kind = event["type"]
7 if kind == "transcript.partial":
8 item_id = event["item_id"]
9 if item_id not in finals:
10 partials[item_id] = event["transcript"]
11 elif kind == "transcript.completed":
12 item_id = event["item_id"]
13 partials.pop(item_id, None)
14 finals[item_id] = event["transcript"]

Pass this handler as on_event=handle_transcript to the Python streaming example. A completed result may arrive without preceding partials and may contain an empty transcript if no speech was recognized.

Manual commit

Use manual commit when your application knows the boundary—for example, when a user releases a push-to-talk button. This is the default when turn_detection is omitted or null.

After sending the utterance’s audio, send a commit. An optional event_id lets you associate the acknowledgement with your request:

1{
2 "type": "input_audio_buffer.commit",
3 "event_id": "commit_1"
4}
1{
2 "type": "input_audio_buffer.committed",
3 "event_id": "event_2",
4 "item_id": "utterance_1",
5 "previous_item_id": null,
6 "client_event_id": "commit_1"
7}

committed acknowledges the audio boundary; completed delivers the transcript. Wait for the completed result with the acknowledged item_id before marking that utterance finished.

If no audio is pending, the server returns input_audio_buffer.commit_empty. Do not wait for a new completed result for that commit. Previously committed utterances can still have final results pending.

Transcribe multiple utterances

Save this alongside stt_stream.py. Each recording contains one utterance in raw 16 kHz PCM16 mono. The source yields None after each file to send a manual commit while the receiver continues handling results.

1import asyncio
2from stt_stream import pcm_file, transcribe
3
4
5async def utterances():
6 for path in ("first.pcm", "second.pcm"):
7 async for chunk in pcm_file(path):
8 yield chunk
9 yield None
10
11
12def show_result(event):
13 if event["type"] == "input_audio_buffer.committed":
14 print("Accepted:", event["item_id"])
15 elif event["type"] == "transcript.completed":
16 print("Final:", event["item_id"], event["transcript"])
17
18
19asyncio.run(transcribe(utterances(), on_event=show_result))

The receiver tracks each acknowledged item_id until its completed result arrives. An empty final commit does not end the receiver while earlier results are pending.

A completed result leaves the connection open for another utterance. Track pending results by item_id when sending more audio, and close only after receiving all final results you need.

Automatic turn detection

Use server voice activity detection (VAD) when you want silence to end an utterance. Set turn_detection in the initial session configuration:

1{
2 "type": "session.configure",
3 "session": {
4 "model": "qwen3-asr:free",
5 "turn_detection": {
6 "type": "server_vad",
7 "silence_duration_ms": 500
8 }
9 }
10}

Continue sending audio during pauses. VAD commits the utterance after receiving the configured duration of silence:

input_audio_buffer.speech_started
input_audio_buffer.speech_stopped
input_audio_buffer.committed
transcript.completed

Partial transcripts may arrive between these events.

VAD argumentDefaultEffect
typeRequired: "server_vad"Enables automatic speech boundaries
silence_duration_ms500Silence needed to end an utterance. Increase to allow longer pauses; decrease to finalize sooner.
threshold0.5Speech probability threshold. Higher values require stronger evidence of speech.
prefix_padding_ms300Audio retained before detected speech begins, to preserve the start of words

Stream with VAD

Use the same module with turn_detection enabled. conversation.pcm should include the pauses between utterances. The receiver processes every completed result instead of stopping after the first.

1import asyncio
2from stt_stream import pcm_file, transcribe
3
4
5def show_result(event):
6 if event["type"] == "transcript.completed":
7 print("Final:", event["item_id"], event["transcript"])
8
9
10asyncio.run(transcribe(
11 pcm_file("conversation.pcm"),
12 turn_detection={"type": "server_vad", "silence_duration_ms": 500},
13 on_event=show_result,
14))

During the recording, VAD commits on silence. When the file ends, the example sends one manual commit to flush any remaining speech, then waits for pending final results before closing. A live source can keep yielding audio for subsequent turns.

VAD events report audio_start_ms and audio_end_ms on the connection’s cumulative audio timeline; they do not reset after commit.

Automatic duration boundary

An utterance is also finalized when its input audio reaches the 36-second duration limit, including when VAD is enabled. The result has commit_reason: "max_duration". The connection stays open; continue receiving results for subsequent utterances.