> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cartesia.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# ターン検出

> ユーザーターンを自動的に検出し、別個の VAD を必要としません

[Realtime Speech-to-Text (Auto) API](/ja-jp/api-reference/stt/turns/websocket) がユーザーターンを検出するため、ボイスエージェントは独自の音声区間検出器（VAD）を必要としません。

従来の VAD ソリューションは、音声のエネルギーだけで発話中かどうかを判断します。Cartesia のモデルは意味的にターンを検出します。会話のコンテキストや、ユーザーの文が言語的に完結しているかどうかも考慮します。ユーザーが迷っているのか、文の途中で考えているのか、本当に話し終えたのかを判別できます。

## ターンのライフサイクル

```mermaid theme={null}
stateDiagram-v2
    direction LR
    [*] --> Idle
    Idle --> Speaking: turn.start
    Speaking --> EagerEnded: turn.eager_end
    Speaking --> Idle: turn.end
    EagerEnded --> Speaking: turn.resume
    EagerEnded --> Idle: turn.end
```

ユーザーターンの合間、セッションはアイドル状態です。ユーザーが話し始めると `turn.start` が発火し、トランスクリプトが構築されるに従って `turn.update` イベントが続きます。

API は会話の状態を表すために以下のイベントを発行します。

| イベント             | 発火条件                                            | トランスクリプトを含むか？ |
| ---------------- | ----------------------------------------------- | ------------- |
| `turn.start`     | ユーザーが話し始める。                                     | いいえ           |
| `turn.update`    | モデルがユーザーの発話を文字起こしする際に繰り返し。                      | はい            |
| `turn.eager_end` | モデルがユーザーが話し終えた可能性を予測する。                         | はい            |
| `turn.resume`    | ユーザーターンが継続中であり、直前の `turn.eager_end` イベントを無視すべき。 | いいえ           |
| `turn.end`       | ユーザーターンが確実に完了。                                  | はい            |

ライフサイクルにはいくつかの保証があります:

* すべてのターンの最初のイベントは `turn.start` です。
* `turn.eager_end` の後には必ず `turn.end` または `turn.resume` が続きます。
* `turn.resume` は直前の `turn.eager_end` のあとにのみ発火します。
* `turn.end` は常にターンを閉じ、次のターンは新しい `turn.start` で始まります。

## トランスクリプトの動作

`transcript` プロパティは**ターン内で累積的**です。デルタではなく、このユーザーターンでこれまでに文字起こしされた完全なテキストが含まれます。イベント間で部分結果を連結する必要はありません。

発行されるすべてのテキストは**確定 (final)** です。モデルは既に送信したテキストを修正しません。`turn.update` の部分トランスクリプトは、変更を心配することなく到着した瞬間に利用できます。

## 例: 1 ターン

ユーザーが「*Hi I need to cancel my subscription please.*」と発話します。

```json lines theme={null}
{ "type": "turn.start" }
{ "type": "turn.update",    "transcript": "Hi I" }
{ "type": "turn.update",    "transcript": "Hi I need to" }
{ "type": "turn.eager_end", "transcript": "Hi I need to cancel" }
{ "type": "turn.resume" }
{ "type": "turn.update",    "transcript": "Hi I need to cancel my subscription" }
{ "type": "turn.eager_end", "transcript": "Hi I need to cancel my subscription please." }
{ "type": "turn.end",       "transcript": "Hi I need to cancel my subscription please." }
```

最初の `turn.eager_end` は「cancel」のあとで早めに発火しますが、ユーザーが話し続けるため `turn.resume` が続きます。2 回目の `turn.eager_end` は正しく、`turn.end` がそれを確定します。

## ターン検出の設定

ターン検出は単一のシグナルによって駆動されます。各時点で、モデルは**ユーザーがアクティブなターンにある尤度**を推定します。これは `0` から `1` の間の値です。ステートマシンはこの尤度を 3 つのしきい値と比較して、`turn.start`、`turn.eager_end`、`turn.end` を発火するタイミングを決定します。

ターン検出には、ほとんどのボイスエージェントで十分に機能するデフォルト値が用意されています。レイテンシーと、各ターンの開始と終了をモデルがどれだけ正確に検出するかのバランスを取りながら、ユースケースに合わせてエージェントの会話フローを最適化するよう調整してください。

| パラメーター                | 説明                                                                            |  デフォルト |            範囲 |
| --------------------- | ----------------------------------------------------------------------------- | -----: | ------------: |
| `start_threshold`     | これを上回るとモデルが `turn.start` を発行する尤度。                                             |  `0.8` |   `0.5`–`0.9` |
| `eager_end_threshold` | これを下回るとモデルが `turn.eager_end`（ユーザーが話し終えた可能性を示す早期シグナル）を発行する尤度。                  |  `0.4` |   `0.3`–`0.6` |
| `end_threshold`       | これを下回るとモデルが `turn.end`（ユーザーターンの完了を示す）を発行する尤度。                                 |  `0.2` |  `0.05`–`0.5` |
| `end_timeout_ms`      | 尤度が `end_threshold` を下回らない場合でも、ユーザーが話し終えてから `turn.end` を発行するまでに待機する最大時間（ミリ秒）。 | `5600` | `640`–`11200` |

各パラメーターはレイテンシーと精度のトレードオフです。以下の表は、どちらの方向に動かすと何が起こるかを示しています。

| パラメーター                | 上げると                                                        | 下げると                                                      |
| --------------------- | ----------------------------------------------------------- | --------------------------------------------------------- |
| `start_threshold`     | ユーザーが話し始めたというより強い根拠を要求し、背景ノイズによる誤検出を減らすが、実際に話し始めたときの反応が遅くなる | 誤検出のリスクと引き換えに、より速く反応性の高い割り込み処理を実現する                       |
| `eager_end_threshold` | 早期終了をより早くトリガーし、体感レイテンシーを改善するが、ユーザーが話し続ける可能性が高まる             | 早期終了をより保守的にし、ユーザーが考えの途中でポーズしたときの誤った早期終了とキャンセルを減らす         |
| `end_threshold`       | ターンをより速く終了し、レイテンシーを改善するが、早すぎる終了のリスクが高まる                     | ユーザーが話し終えたというより強い根拠を待ち、迷っている、あるいはゆっくり話す人の考えの途中でのターン終了を減らす |
| `end_timeout_ms`      | エージェントが応答する前に、ユーザーがポーズ、思考、継続する時間をより多く与える                    | モデルが不確実なままの場合に、より厳格なレイテンシー上限を強制する                         |

3 つのしきい値は厳密に順序付けられています: **`start_threshold` > `eager_end_threshold` > `end_threshold`**。上記の範囲に加えて、各値はこの順序を保つために隣接する値によって制約されます。そのため、eager end のしきい値を start のしきい値より高く設定したり、end のしきい値を eager end のしきい値より高く設定したりすることはできません。

これらは[クエリパラメーター](/ja-jp/api-reference/stt/turns/websocket)（`turn_start_threshold`、`turn_eager_end_threshold`、`turn_end_threshold`、`turn_end_timeout_ms`）で接続ごとに設定するか、[config コマンド](/ja-jp/api-reference/stt/turns/websocket)を送信してセッション中に変更できます。

### 一般的な構成

以下は、有用な出発点となる構成の例です。ユースケースに最も近いものを選び、そこから個々のしきい値を調整してください。

| プロファイル           | `start_threshold` | `eager_end_threshold` | `end_threshold` | `end_timeout_ms` |
| ---------------- | ----------------: | --------------------: | --------------: | ---------------: |
| **バランス型**（デフォルト） |             `0.8` |                 `0.4` |           `0.2` |           `5600` |
| **応答重視型**        |             `0.7` |                 `0.5` |           `0.4` |           `4500` |
| **待機重視型**        |             `0.8` |                 `0.3` |           `0.1` |           `8000` |

* **バランス型**は出発点として適しており、多くのボイスエージェントの会話で十分に機能します。
* **応答重視型**は、速いテンポの会話のやり取りなど、レイテンシーが最優先の場合に最適です。
* **待機重視型**は、ユーザーが考えたり情報を調べたりするためにポーズする場合や、ターンの途中で遮ることが大きな損失になる場合など、精度が最優先の場合に最適です。

## サンプルコード

`turn.start` と `turn.end` を処理すれば、動作するエージェントが得られます。ユーザーが話し始めたら割り込み、話し終えたら応答を生成します。レイテンシーを削減するには、`turn.eager_end` も処理します。発火した瞬間に応答生成を開始し、`turn.resume` が到着したらその処理をキャンセルし、`turn.end` がユーザーの完了を確定した瞬間に再生します。

<CodeGroup>
  ```python 開始と終了 theme={null}
  async for message in websocket:
      event = json.loads(message)
      if event["type"] == "turn.start":
          tts.interrupt()
      elif event["type"] == "turn.end":
          reply = llm.generate(event["transcript"])
          tts.speak(reply)
  ```

  ```python 早期終了あり theme={null}
  pending_reply = None

  async for message in websocket:
      event = json.loads(message)
      match event["type"]:
          case "turn.start":
              tts.interrupt()

          case "turn.eager_end":
              pending_reply = llm.generate_async(event["transcript"])

          case "turn.resume":
              pending_reply.cancel()
              pending_reply = None

          case "turn.end":
              if pending_reply:
                  reply = pending_reply
                  pending_reply = None
                  tts.speak(reply)
              else:
                  tts.speak(llm.generate(event["transcript"]))
  ```
</CodeGroup>

## エッジケース

### 音声なし vs 無音

Cartesia の API は連続した音声ストリームを想定しています。
音声の送信を停止すると、サーバーはユーザーが無音であると見なすのではなく、追加の音声チャンクが到着するのを待ちます。

これは通常、ネットワークの遅延に対応するために望ましい挙動ですが、クライアントが音声入力をミュートしている間、無音 (すべてゼロ) を送信し続ける必要があることも意味します。

### イベントのドレイン

セッションの音声をすべて送信し終えたら、`{"type": "close"}` を送信して、バッファリングされている音声をフラッシュし、残りのイベントを発行するようモデルに伝えます。モデルが完了すると、サーバーがソケットをクローズします。

サーバーは文字起こしの精度を高めるために音声を一定量バッファします。close コマンドを送信しなかったり、メッセージの読み取りを早期に停止したりすると、バッファされた音声は処理されません。最後の 1 秒の音声を気にしないのであれば、これは問題ありません。

```python theme={null}
await websocket.send(json.dumps({"type": "close"}))
async for message in websocket:
    event = json.loads(message)
    if event["type"] == "turn.end":
        turns.append(event["transcript"])
        # do not stop reading from the websocket!
print("server closed the connection")
```

### トランスクリプトの結合

`transcript` フィールドは **ターン内で累積的** です。各 `turn.update`、`turn.eager_end`、`turn.end` イベントには、これまでのターンの全文がすでに含まれています。

最終的なトランスクリプトだけが必要な場合は、完了したターンごとに 1 つずつ、各 `turn.end` から `transcript` プロパティを取得してください。**`transcript` はそのまま連結してください。`strip()` で空白を取り除いたり、正規化したり、独自の区切り文字を追加してはいけません。**

```python theme={null}
import json

full_audio_transcript = ""
turns: list[str] = []
async for message in websocket:
    event = json.loads(message)
    if event["type"] == "turn.end":
        # transcripts across turns should be
        # concatenated without formatting!
        full_audio_transcript += event["transcript"]

        # per-turn transcript
        turns.append(event["transcript"])
```

`turn.update` や `turn.eager_end` イベントのトランスクリプトを連結するのは、テキストの重複の典型的な原因です。各更新は累積的なので、それらを連結するとトランスクリプトの一部が繰り返されます。
`turn.update` や `turn.eager_end` は、トランスクリプトのチャンクではなく、ターン状態の更新として扱ってください。

最終トランスクリプトは `turn.end` のみから読み取ってください。

## 次に進む

<CardGroup cols={3}>
  <Card title="オンラインで試す" icon="arrow-pointer" href="https://www.cartesia.ai/ink">
    サインアップやコード不要でターン検出を動作確認
  </Card>

  <Card title="API を使う" icon="code" href="/ja-jp/api-reference/stt/turns/websocket">
    Realtime STT API で構築を開始
  </Card>

  <Card title="SDK を使う" icon="brackets-curly" href="/ja-jp/examples/stt-auto-finalize-websocket">
    実際のコードを見てみる
  </Card>
</CardGroup>
