> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.narilabs.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.narilabs.com/_mcp/server.

# Transcripts and turn detection

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:

```text
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:

```python
partials = {}
finals = {}


def handle_transcript(event):
    kind = event["type"]
    if kind == "transcript.partial":
        item_id = event["item_id"]
        if item_id not in finals:
            partials[item_id] = event["transcript"]
    elif kind == "transcript.completed":
        item_id = event["item_id"]
        partials.pop(item_id, None)
        finals[item_id] = event["transcript"]
```

Pass this handler as `on_event=handle_transcript` to the [Python streaming example](/transcribe-audio#send-and-receive-in-python). 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:

```json
{
  "type": "input_audio_buffer.commit",
  "event_id": "commit_1"
}
```

```json
{
  "type": "input_audio_buffer.committed",
  "event_id": "event_2",
  "item_id": "utterance_1",
  "previous_item_id": null,
  "client_event_id": "commit_1"
}
```

**`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.

```python
import asyncio
from stt_stream import pcm_file, transcribe


async def utterances():
    for path in ("first.pcm", "second.pcm"):
        async for chunk in pcm_file(path):
            yield chunk
        yield None


def show_result(event):
    if event["type"] == "input_audio_buffer.committed":
        print("Accepted:", event["item_id"])
    elif event["type"] == "transcript.completed":
        print("Final:", event["item_id"], event["transcript"])


asyncio.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:

```json
{
  "type": "session.configure",
  "session": {
    "model": "qwen3-asr:free",
    "turn_detection": {
      "type": "server_vad",
      "silence_duration_ms": 500
    }
  }
}
```

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

```text
input_audio_buffer.speech_started
  ↓
input_audio_buffer.speech_stopped
  ↓
input_audio_buffer.committed
  ↓
transcript.completed
```

Partial transcripts may arrive between these events.

| VAD argument          | Default                  | Effect                                                                                            |
| --------------------- | ------------------------ | ------------------------------------------------------------------------------------------------- |
| `type`                | Required: `"server_vad"` | Enables automatic speech boundaries                                                               |
| `silence_duration_ms` | `500`                    | Silence needed to end an utterance. Increase to allow longer pauses; decrease to finalize sooner. |
| `threshold`           | `0.5`                    | Speech probability threshold. Higher values require stronger evidence of speech.                  |
| `prefix_padding_ms`   | `300`                    | Audio 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.

```python
import asyncio
from stt_stream import pcm_file, transcribe


def show_result(event):
    if event["type"] == "transcript.completed":
        print("Final:", event["item_id"], event["transcript"])


asyncio.run(transcribe(
    pcm_file("conversation.pcm"),
    turn_detection={"type": "server_vad", "silence_duration_ms": 500},
    on_event=show_result,
))
```

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.