4
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?

AIボットと電話を繋ぐ - Twilio Voice - ConversationRelay

Posted at

「コールセンターでAIを利用したい」「代表電話の応対をAIに任せたい」など電話応対をAIに行わせたいという要望を多く聞きます。今回のブログでは、Twilioの新機能「Conversation Relay」を使って、AIと低遅延で通話できる仕組みをご紹介します。

今回は、私がGitHubリポジトリにアップしているConversationRelayDemo を中心に、セットアップや実際のコードを詳しく解説していきます。

Conversation Relayとは?

「Conversation Relay」は、Twilio Voiceの通話をTwilioの中継サーバーを通すことで、音声をリアルタイムに処理したり、AIと連携できる機能です。

たとえばこんなことができます:

  • 音声通話をリアルタイムで文字起こし&翻訳
  • AIを使って話し相手の感情を分析
  • 会話内容を録音&オン・オフ制御
  • 自前のLLM(大規模言語モデル)を組み込んで応答を生成
  • 1秒未満のレイテンシで自然なやりとりを実現!

技術構成

Conversation Relayは主に以下の3つで構成されます:

機能 説明
Speech to Text(STT) 話者の音声をテキストに変換
Text to Speech(TTS) AIの返答テキストを自然な音声に変換
Orchestration WebSocket経由で低遅延な応答制御・割り込み

構成イメージ

ConversationRelayイメージ図 (1).png

デモ構成とコード

ご紹介するコードは下記になります。
セットアップ後、電話をかけるとOpenAIと接続して自由な会話が楽しめます。
https://github.com/MitsuharuNakamura/conversationRelayDemo

必要なもの

  • Node.js v14以上
  • OpenAIのAPIキー
  • Ngrok(有料プランでサブドメイン固定できると便利)
  • Twilioアカウント(電話番号付き)

セットアップ手順

git clone https://github.com/MitsuharuNakamura/conversationRelayDemo.git
cd conversationRelayDemo
npm install

.env ファイルを作って環境変数をセット:

PORT=9999
OPENAI_API_KEY=sk-xxxx
SYSTEM_PROMPT=あなたは、コールセンターのオペレーターです。

TwilioのTwiML設定

TwiML Binに以下を設定:

<Response>
  <Connect>
    <ConversationRelay 
      url="wss://<your_subdomain>.jp.ngrok.io"
      language="ja-JP"
      welcomeGreeting="もしもし。こちらは旅行アシスタントです。ご要件をお話ください。"
      ttsProvider="google"
      voice="ja-JP-Standard-B" />
  </Connect>
</Response>

電話番号の「A call comes in」でこのTwiML Binを選択して保存。

使い方

  1. サーバー起動:node server.js
  2. Ngrokで公開:ngrok http 9999 --subdomain=xxxx
  3. TwiML Binの「url="wss://.jp.ngrok.io"」をサブドメインを修正
  4. Twilioの番号に発信
  5. AIが日本語で応答してくれれば成功

※通話終了時は自分の端末側で切るのをお忘れなく

Websocketサーバのコード解説

必要なモジュールのインポートと初期設定

const WebSocket = require('ws');
const dotenv = require('dotenv');
const { Configuration, OpenAIApi } = require('openai');

dotenv.config();

const configuration = new Configuration({
  apiKey: process.env.OPENAI_API_KEY,
});
const openai = new OpenAIApi(configuration);

WebSocketサーバー、環境変数、OpenAIのAPIクライアントを初期化しています。

WebSocketサーバーの作成

const wss = new WebSocket.Server({ port: process.env.PORT || 9999 });

指定したポートでサーバーを起動します。

WebSocketサーバが受信するメッセージ

Twilioからのメッセージを受け取り、適切に振り分けて処理します。

ws.on('message', async (message) => {
    console.log(`Received: ${message}`);

    try {
        const parsedMessage = JSON.parse(message);

        if(parsedMessage.type === 'prompt'){
            const question = parsedMessage.voicePrompt;
            // AIに問い合わせ処理...
        } else {
            console.log("voicePromptプロパティは存在しません。");
        }
    } catch (error) {
        console.error('メッセージのパースに失敗しました:', error);
    }
});
  • prompt 以外(例:setup, interrupt)はログ出力のみにしています。
  • voicePrompt があればそれをAIに送信します。

WebSocketサーバから送信するメッセージ

OpenAI APIから得られたストリーミング応答を、適切に一文ごとに区切ってクライアントに返します。

for await (const chunk of stream) {
    const message = chunk.choices[0]?.delta?.content;
    if (message) {
        buffer += message;
        let sentences = buffer.split(sentenceDelimiterRegex);
        buffer = sentences.pop();
        for (const sentence of sentences) {
            console.log("DEBUG: " + sentence);
            ws.send(JSON.stringify({ type: 'text', token: sentence, last: true }));
        }
    }
}

if (buffer) {
    console.log("DEBUG: " + buffer);
    ws.send(JSON.stringify({ type: 'text', token: buffer, last: true }));
}
  • OpenAIの応答を逐次受け取り、文単位で分割して type: text として送信
  • last: true を付けることでTTS側の制御を簡素化
  • 文の判定には日本語用の正規表現を使用

補足:日本語文章の分割に使っている正規表現について

const sentenceDelimiterRegex = /(?<=[。、?])/; // 日本語の文末記号で分割
// const sentenceDelimiterRegex = /(?<=[.!?])\s+/; // 英語用(参考)

(?<=...) は肯定の後読みで、句点や読点、疑問符の直後で文を分割しています。

"こんにちは。今日は天気がいいですね?明日も晴れるといいな。".split(sentenceDelimiterRegex)
// => ["こんにちは。", "今日は天気がいいですね?", "明日も晴れるといいな。"]

文章を分割することで、AIの応答がより自然になります。

ConversationRelayとのインターフェース

ConversationRelay → server.jsに送られるメッセージ

種別 (type) 説明 主なフィールド 送信タイミング
setup セッション初期情報の通知 sessionId, callSid, from, to, customParameters WebSocket接続直後
prompt 発話検出・STT結果の通知 voicePrompt, lang, last 音声認識された直後
dtmf キー入力検出(DTMF) digit DTMF検出時(DTMF有効時)
interrupt 話者がTTSを割り込んだ通知 utteranceUntilInterrupt, durationUntilInterruptMs 話し始めによる割り込み
error エラー通知 description セッション中にエラー発生時

server.js → ConversationRelay に送るメッセージ

種別 (type) 説明 主なフィールド 使用タイミング
text テキストをTTSで再生 token, last AI応答や案内を再生したいとき
play メディアファイル再生指示 source, loop, preemptible 音声ファイルの再生
sendDigits DTMF信号送出 digits IVRやDTMF操作時
language 言語設定の変更 ttsLanguage, transcriptionLanguage 言語を切り替えたいとき
end セッション終了の指示 handoffData(任意) 通話の制御をTwilioへ戻す

まとめ

Conversation Relayを使えば、Twilioで以下が実現できます:

  • AIによる自然な会話(1秒未満のレイテンシ)
  • 音声のリアルタイム処理・記録・監視
  • 自前のAIモデルを統合するBYO-LLM対応

ぜひ、自分のプロジェクトでも試してみてください。

4
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
4
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?