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?

RustでSIP音声ボット(ずんだもん)を作る:実装の「方向性」メモ(アーキテクチャ方針)

Posted at

この記事は筆者オンリーのAdvent Calendar 202514日目の記事です。

Rustで、SIPのUASとして通話を受けて、相手の音声をASR→LLM→TTS(VOICEVOX)で返す「ずんだもん音声ボット」を作っています。

この記事は「このプロジェクト、どういう方向性でRustコードを書いていくか?」という 方針(ガイド) を、ざっくりまとめたメモです。
(SIPの細かいRFCや、Zoiperの設定、RTPの地獄などは別記事に回します)


目標:いったん “通話が成立して会話できる” を最短で作る

最初にやりたいことはこれだけです。

  • SIP UASとしてINVITEを受ける
  • RTPで相手の音声を受ける
  • ASRでテキスト化する
  • LLMで返答文を作る
  • VOICEVOXで音声を作る
  • RTPで相手に返す

「全部完璧」より、まず 通話ができて会話が回る ことを優先します。


大方針:イベント駆動 + レイヤ分離で“迷子”を防ぐ

SIP/RTP(プロトコル)と、会話ロジックと、AI連携が混ざると、一気に破綻します。
なので最初から、役割を分けて“混ぜない”ことを徹底します。

ざっくり分けるとこう:

  • transport:UDP/TCPの生パケットの送受信だけ
  • sip:SIPメッセージ/トランザクション/タイマ(プロトコル)
  • rtp:RTPのパケット処理・ジッタ吸収・PCM化(プロトコル)
  • session:1通話のライフサイクル管理(SIP+RTPをまとめる)
  • app:会話の状態機械(ASR→LLM→TTSのオーケストレーション)
  • ai:ASR/LLM/TTSのクライアント(差し替え前提)
  • http/media:通話ログや録音の参照・配信(プロトコルと混ぜない)

重要なのは「依存方向」です。

  • app → session → sip/rtp → transport
  • app → ai

逆向きに呼び出さない。逆向きはイベントで通知する、というルールにします。


“何かしたくなったらイベント” に寄せる

モジュール間のやり取りは、基本 enumイベント + 非同期チャネルでつなぐ想定です。

例(イメージ):

  • transport → sip : RawPacket(bytes + addr)
  • sip → session : IncomingInvite / IncomingBye
  • rtp → app : PcmInputChunk
  • app → ai::llm : LlmRequest
  • ai::tts → rtp : PcmOutputChunk
  • app → session : 「200 OK返して」「BYE送って」みたいな高レベル指示

こうすると「どこで判断してるか」が分かりやすくなって、後から改修しやすいです。


並行処理の方針:1通話 = 1タスク(Actorっぽく)

Tokioで実装するときの基本方針は:

  • transportは受信ループ(SIP用/RTP用)
  • sipはプロトコル処理タスク
  • rtpは受信/送信処理タスク(必要ならキュー)
  • sessionは 1通話ごとに1タスク
  • app(会話)は session からイベントを受けて進める

共有Mutexで全体を守るのではなく、セッションタスク内に状態を閉じ込める方向で考えます。


AI部分の方針:差し替え前提で “Port/Adapter” にする

ASR/LLM/TTSは、将来的にローカル/クラウドを切り替える可能性が高いので、最初から差し替え前提にします。

  • appが見るのは「trait(Port)」だけ
  • 実装は ai モジュールの「Adapter」に閉じる

この方針にすると、例えば

  • ASR:ローカル→クラウド
  • LLM:クラウド→ローカル
  • TTS:VOICEVOX前提(ずんだもん使うのでここは固定寄り)

みたいな変更がしやすくなります。


音声ボットなので“タイムアウトと失敗”を前提にする

音声ボットは「無言」が一番つらいです。

  • ASRが詰まる
  • LLMが遅い
  • TTSが落ちる
  • RTPが途切れる

こういうのは普通に起こるので、最初から

  • 各処理にタイムアウトを置く
  • 失敗したら定型文(「ごめんなさい、もう一度お願いします」)を返して継続
  • 連続失敗なら切断(BYE)

みたいな雑なポリシーを app 側に持たせる想定です。


ざっくり実装の進め方(おすすめ順)

自分はこの順で作ると迷子になりにくいと思っています。

  1. SIPで着信できる(INVITE→200→ACK、BYEで切れる)
  2. RTPで音声の受信ができる(まずは録音して確認でもOK)
  3. RTP→PCM化して、appまでイベントとして届く
  4. ASRをつなぐ(ローカル/クラウドどちらでも)
  5. LLMをつなぐ(まずは固定返答でもOK)
  6. VOICEVOXで音声を作る(ずんだもん)
  7. PCM→RTPで相手に返して“喋る”
  8. 最後に録音・HTTP参照・可視化を足す

おわりに

この記事は「SIP音声ボットをRustで作る時に、どういう方向でコードを書くか」というメモでした。

次は、どれを書くかで悩んでいます:

  • Zoiper/SIP側(UASとして最低限どこまでやるか)
  • RTP周り(8kHz/G.711/ジッタ吸収)
  • VOICEVOXの音声を“通話に返す”ところ(リアルタイム性)

自分の理解が進んだら、順番に記事にしていきます。

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?