0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

タイムアウト/キャンセル/失敗時の作法(無言通話を避けるためのポリシー草案)

Last updated at Posted at 2025-12-21

12/21 は app層(dialog)の状態機械(Listening/Thinking/Speaking/Ending)を草案としてまとめました。
今日はそれを支える「運用の作法」―― タイムアウト / キャンセル / 失敗時の扱い を、SIP/RTP/AI それぞれの責務に沿って整理します。

音声ボットで一番つらいのは「無言」です。
なので本記事のゴールは、エラーや遅延が起きても 何かしらの反応を返し、必要なら切断まで持っていく ための方針を固定することです。


0. まず大原則:検知と判断を分ける

このプロジェクトではレイヤ分離を前提にしているので、失敗時も次の原則を守ります。

  • 検知するレイヤ(sip/rtp/ai):事実を検知してイベントで上げる
  • 判断するレイヤ(session/app):継続するか、謝罪するか、切るか決める

例:

  • RTPが来ない → rtpが RtpTimeout を出す(検知)
    → session/app が「待つ/警告/切断」を決める(判断)

1. タイムアウトの種類(何が起きるか)

音声ボットで起きるタイムアウトは大きく3種類です。

  1. SIP側(呼制御)

    • トランザクションの再送や応答待ち
    • セッションタイマ(将来)
  2. RTP側(メディア)

    • 音が来ない(無音ではなく“パケット無着信”)
    • ジッタ過大(遅延が大きすぎる)
  3. AI側(ASR/LLM/TTS)

    • API遅延
    • エラー(ネットワーク/上限/内部エラー)
    • ストリーム中断

2. SIPの作法(MVP→次の段階でも壊れないために)

2.1 検知(sipの責務)

  • パース不能 → ログ+(必要なら)400
  • トランザクション期限切れ → TransactionTimeout を上位へ通知
  • 再送 → 最後の応答キャッシュで返す(MVP)

2.2 判断(sessionの責務)

  • early(200前)でタイムアウト → セッション破棄(相手は再INVITEする前提でOK)
  • confirmed(通話中)でタイムアウト → 原則BYEで終了に寄せる

MVP段階では「厳密なRFC」より「変な状態で固まらない」を優先します。


3. RTPの作法(メディア無着信は“段階対応”)

3.1 検知(rtpの責務)

  • 一定時間RTPが来ない → RtpTimeout(call_id, elapsed) を上げる
  • 変なパケット(SSRC違い等) → ログ+(必要なら)ストリームエラーイベント

3.2 判断(session/appの責務)

RTP無着信は「一発で切る」より段階対応が安全です。

  • 1回目:警告ログ+継続(回線揺れの可能性)
  • 連続 or 一定回数超え:ボットが音声で「聞こえません」等を返す(可能なら)
  • さらに継続:BYEで切断(無言通話を避ける)

MVPの現実解としては「連続N回で切断」でも十分です。


4. AIの作法(ASR/LLM/TTS:無言にならない最重要ポイント)

AI失敗は頻度が高いので、明確な方針が必要です。

4.1 検知(aiモジュールの責務)

  • タイムアウト / HTTPエラー / 解析失敗などを AsrError / LlmError / TtsError としてappへ返す
  • リトライは ai 側で吸収して良い(回数と間隔を決める)
  • “最終的に失敗した” だけを app に伝える(appを複雑にしない)

4.2 判断(appの責務)

基本方針(例):

  • 初回失敗:謝罪定型を喋って継続

    • ASR失敗:「すみません、もう一度お願いします」
    • LLM失敗:「ただいま混み合っています。もう一度お願いします」
    • TTS失敗:可能ならテキストでログ、次は短い別文でリトライ
  • 同一フェーズで連続失敗(例:2回):切断判断へ

    • Ending に遷移 → 最後に謝罪 → SessionAction::Bye

「失敗回数カウント」は app の状態に持たせるのが管理しやすいです。


5. キャンセルの作法(barge-inや“遅いLLM”対策)

音声ボットはリアルタイムなので、キャンセルを設計しないと破綻します。

5.1 何をキャンセルするか

  • LLM問い合わせ中にユーザが話し始めた(barge-in)
  • TTSで喋っている途中にユーザが被せた
  • 古い要求(前の発話)に対する結果が今さら返ってきた

5.2 最小実装の考え方

  • app側で turn_id(発話ターン番号)を持つ
  • ASR final を受けたら turn_id += 1
  • LLM/TTSの要求にも turn_id を付ける
  • 返ってきた結果の turn_id が古ければ捨てる(“遅延結果”対策)

さらに進めるなら:

  • tokio task を abort して「本当にキャンセル」する(ただしまずは論理キャンセルでも十分)

6. “無言回避”の定型文(MVPのおすすめ)

失敗時に喋る定型は、長いと逆に邪魔なので短いのが良いです。

  • ASR失敗:「すみません、もう一度お願いします。」
  • LLM失敗:「ただいま混み合っています。もう一度お願いします。」
  • RTP途絶:「音声が聞こえません。もう一度お願いします。」
  • 連続失敗で終了:「申し訳ありません。通話を終了します。」

※ ずんだもん口調は後で統一(LLM側プロンプトでも可)


7. どこにポリシーを書くか(置き場所)

設計上はこの形が分かりやすいです。

  • ai:リトライ方針(回数/間隔/タイムアウト)
  • rtp/sip:検知イベントの定義(Timeout/Error)
  • app:ユーザ向け振る舞い(謝罪/継続/終了)、turn_id、連続失敗の閾値
  • session:通話維持/終了の最終操作(BYE、RTP停止など)

まとめ(12/22の固定点)

  • 「検知(sip/rtp/ai)」と「判断(session/app)」を分ける
  • RTP無着信は段階対応(警告→謝罪→切断)
  • AI失敗は“無言回避”が最優先(初回は謝罪で継続、連続失敗で終了)
  • キャンセルは turn_id による論理キャンセルから始めるのが現実的

次回(12/23)は、あとから困らないための 録音・メタ情報・可観測性(ログ相関、レイテンシ計測) をまとめる予定です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?