「コールセンターで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経由で低遅延な応答制御・割り込み |
構成イメージ
デモ構成とコード
ご紹介するコードは下記になります。
セットアップ後、電話をかけると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を選択して保存。
使い方
- サーバー起動:
node server.js
- Ngrokで公開:
ngrok http 9999 --subdomain=xxxx
- TwiML Binの「url="wss://.jp.ngrok.io"」をサブドメインを修正
- Twilioの番号に発信
- 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対応
ぜひ、自分のプロジェクトでも試してみてください。