チャッピー電話を作ろう!
はじめに
みなさん、こんにちは。KDDI ウェブコミュニケーションズで CPaaS のエバンジェリストをしている高橋です。
この記事では、Vonage と OpenAI の Realtime API を使った「チャッピー電話」について解説をします。
なお、この記事の内容をこちらから動画でご覧いただくことも可能です。
本記事の対象となる読者
- ボイスボットの開発を行いたいと考えている方
- Node.js + Fastify の仕組みがなんとなくわかる方
- Vonage の NCCO や WebSocket など、基本的な機能が理解できる方
- OpenAI の Realtime API を触ってみたい方
Vonage とは

Vonage は、米国ニュージャージー州に本社を置く、CPaaS(Communication Platform as a Service)企業です。
もともとは VoIP(Voice over IP)企業としてスタートしましたが、いくつかの企業買収を行うことで、コミュニケーションサービス全般をサポートすることができる企業に発展しました。現在はスウェーデンの大手通信機器会社エリクソンの傘下に入っています。
2024年2月14日より、株式会社KDDIウェブコミュニケーションズ(以後、KWC)が Vonage の再販事業を開始することとなりました。
KWC 経由でアカウントを開設する場合、Vonage で直接開設したアカウントとは一部仕様が異なります。(※提供する機能面において違いはありません)
なお、本記事ではどちらのアカウントを使っても問題ありません。
チャッピー電話とは
OpenAI 社が提供する ChatGPT はチャッピーという愛称で広く使われています。
今回は、OpenAI 社の Realtime API を使って、ボイスボットの開発を行います。
Realtime API とは
OpenAI の Realtime API は、音声・テキスト・マルチモーダル入力をリアルタイムに処理し、対話エージェントを即時応答させるための API です。
WebSocket 経由で高速かつ双方向通信が可能で、アプリやボットに自然な対話体験を組み込めます。
また、会話の間を短くすることができたり、会話の割り込みにも対応することができます。
シナリオ
- ユーザーは、Vonage の電話番号に電話をかけるか、/connect エンドポイントに自分の電話番号を POST することで電話がかかります。
- Vonage で転送する旨のガイダンスを流したあと、OpenAI に WebSocket 接続します。
- 最初にユーザー名をヒアリングし、以降は OpenAI と会話を行います。
- 会話の割り込みにも対応しています。
- Function Calling を使うことによって、各地の天気を外部サービスから調べることができます。
- 「オペレーターに代わって」のように転送を希望した場合は、通話を別の電話番号に転送します。
Vonage と OpenAI の相関図
ユーザーが発声した内容を OpenAI Realtime API が回答を返します。
準備
本記事の内容を実装するためには、以下の準備が必要です。
- Vonage アカウント(どちらか一方で作成してください)
- KWC 経由での作成 ←法人もしくは個人事業主のみ
- Vonage 本家での作成
- OpenAI の API キー
- ブラウザ(Chrome推奨)
- 通話が可能な携帯電話 or スマートフォン
- Vonage クーポンコード(トライアルアカウントで電話番号を購入する際に必要です)
- Vonage 電話番号(US番号でも可、日本の番号を購入する方法はこちら)
- OpenWeatherMap API アカウント(こちら から無料で作成できます)
- Node.js 22 以上、npm、Git 環境
- ngrok の準備(こちら からアカウントの作成ができます)
- 使い慣れたエディタ
Vonage アプリケーションの作成
- Vonage ダッシュボードにログインします。
- 左側のメニューからアプリケーションを選択します。
-
新しいアプリケーションを作成するボタンを押します。
- 名前欄に「websocket-openai」と入力します。
-
公開鍵と秘密鍵を生成ボタンを押します(ダイアログでも生成ボタンを押下)。秘密鍵(
private.key)がダウンロードされるので、なくさないように保存しておきます。 - 音声のスイッチをONにします。
- 地域のプルダウンから「Singapore」を選択します。
- 最後に新しいアプリケーションの生成を押します。

- アプリケーション IDをメモ帳などに控えておきます。
- 画面下部に電話番号のリストが表示されるので、リンクボタンを押してアプリケーションに紐づけます。紐づけされるとボタンがリンク解除になります。
もし、この時点で電話番号を購入していない場合は、Buy more numbersから購入が可能です。 - リンクした電話番号も後ほど利用するので、メモ帳などに控えておいてください。
プロジェクトの準備
まずは、ターミナル(コマンドプロンプト)を起動し、作業ディレクトリに移動してください。
- 以下のコマンドを使って、プロジェクトを GitHub からクローンします。
git clone https://github.com/mobilebiz/websocket-openai.git
- 以下のコマンドを使ってクローンしたディレクトリに移動します。
- さらに、環境変数のサンプルをコピーします。
cd websocket-openai
cp .env.example .env
- エディタを使って、ディレクトリを開きます。
-
.envの内容を以下のように更新します。
| Key | Value |
|---|---|
| SERVER_URL | 後ほど ngrok を実行したときに払い出されるホスト名に書き換えます。 |
| OPENAI_API_KEY | sk から始まる OpenAI の API キーを記述します。 |
| OPENAI_MODEL | gpt-realtime |
| VONAGE_PRIVATE_KEY_PATH | ./private.key |
| VONAGE_OUTBOUND_FROM | Vonage で購入した電話番号を E.164 形式で記載します。 |
| VONAGE_APPLICATION_ID | 先ほど作成した Vonage アプリケーションの ID を記載します。 |
| VONAGE_API_KEY | Vonage ダッシュボードで確認できます(8桁の文字列)。 |
| VONAGE_API_SECRET | Vonage アカウント作成時に表示されたものを記載します。 |
| VONAGE_TRANSPORT_NUMBER | 転送する場合の転送先電話番号を E.164 形式で記載します。 |
| OPEN_WEATHER_API_KEY | OpenWeatherMap の API キーを記載します。 |
- 次に、先ほど作成した Vonage アプリケーションの秘密鍵(
private.key)を現在のディレクトリに移動してください。
アプリケーションの実装
- 以下のコマンドを使って必要なライブラリのインストールを行います。
npm install
ngrok の実行
ngrok は、インターネット上からローカルのプログラムを参照させるための仕組みを提供します。
これにより、皆さんがローカルで実行したプログラムを、外部のサーバー(今回はVonage)から呼び出すことができます。
- 別のターミナル(コマンドプロンプト)を開き、以下のコマンドを使って http のポート 3000 で待ち受けしているプログラムを外部から参照できるようにします。
ngrok http 3000
- 以下のように ngrok が起動し、ホストの払い出しが行われます。
ngrok by @inconshreveable (Ctrl+C to quit)
Session Status online
Account Katsumi Takahashi (Plan: Basic)
Update update available (version 2.3.41, Ctrl-U to update)
Version 2.3.38
Region United States (us)
Web Interface http://127.0.0.1:4040
Forwarding http://XXXXXXXXXXXX.ngrok.app -> http://localhost:3000
Forwarding https://XXXXXXXXXXXX.ngrok.app -> http://localhost:3000
Connections ttl opn rt1 rt5 p50 p90
0 0 0.00 0.00 0.00 0.00
Forwarding と書かれた行(https の方)が外から見たときの皆さんの実行環境となります。
この XXXXXXXXXXXX.ngrok.app の部分(https:// は不要です)を、先ほどの .env の SERVER_URL に記載してください。
.env の保存を忘れないようにしてください。
ngrok はこのまま実行させておいてください。もし終了したい場合は、Ctrl+Cを押します。
プログラムの実行
ngrok が実行しているターミナル以外で、以下のコマンドを実行することでプログラムが起動します。
npm run debug
以下のように表示されれば、プログラムは無事に起動しています。
> websocket-openai@0.1.0 debug
> node scripts/change-url.js dev && node index.js
dev環境用の.envファイルを使用します。
Vonageアプリケーション(ID: 63596563-77ec-40c2-a280-XXXXXXXXXXXXX)のWebhook URLを更新します...
- Answer URL: https://XXXXXXXXXXXX.ngrok.app/answer (メソッド: POST)
- Event URL: https://XXXXXXXXXXXX.ngrok.app/event (メソッド: POST)
Webhook URLの更新が完了しました。
アプリケーション情報: {
"id": "63596563-77ec-40c2-a280-XXXXXXXXXXXX",
"name": "websocket-openai",
"keys": {
"public_key": "-----BEGIN PUBLIC KEY-----\n\n-----END PUBLIC KEY-----\n"
},
"capabilities": {
"voice": {
"webhooks": {
"event_url": {
"address": "https://XXXXXXXXXXXX.ngrok.app/event",
"http_method": "POST"
},
"answer_url": {
"address": "https://XXXXXXXXXXXX.ngrok.app/answer",
"http_method": "POST"
}
},
"signed_callbacks": false,
"conversations_ttl": 744,
"leg_persistence_time": 7
}
},
"_links": {
"self": {
"href": "/v2/applications/63596563-77ec-40c2-a280-XXXXXXXXXXXX"
}
}
}
[09:12:34] INFO: Server listening at http://0.0.0.0:3000
テスト
ではいよいよテストをしましょう。
以下のコマンドを実行するとご自分の電話に電話がかかってきます。
- MacやLinuxをご利用の方はこちら
curl -X POST https://<ngrok で払い出されたホスト名>/connect \
-H "Content-Type: application/json" \
-H "X-API-Key: <Vonage のアプリケーション ID>" \
-d '{
"to": "<ご自分の携帯電話の番号(E.164 形式)>"
}'
- Windowsをご利用の方はこちら
curl -X POST https://<ngrok で払い出されたホスト名>/connect ^
-H "Content-Type: application/json" ^
-H "X-API-Key: <Vonage のアプリケーション ID>" ^
-d "{\"to\": \"<ご自分の携帯電話の番号(E.164 形式)>\"}"
どうでしょうか。チャッピーが電話で応対してくれれば、テストは成功です。
会話の状況は逐次ログに出力されるので、どのような会話が行われたかはログを確認してください。
Function Calling について
このプログラムには、ChatGPT から外部のサービスを呼び出す Function Calling という仕組みを実装しています。
Function Calling を利用することで、ChatGPT には保有していない情報を検索したり、外部サービスを起動したりできます。
今回は、天気を調べるツール(OpenWeatherMap API を利用)、ユーザーの名前を取得したときに動作するツール、
そして通話を別の電話番号に転送するツールの 3 つを定義しています。
どのようなシーンでこれらのツールを利用するかは、OpenAI 側が判断をしますが、確実にツールを起動させたい場合は、プロンプトを工夫することでツールの呼び出しを確実にします。
今回はプロンプトとして以下のように設定をしています(プロンプトは、system-message.txt に記載します)。
あなたの名前はチャッピーです。
明るくフレンドリーなAIアシスタントです。
ユーザーが興味を持っている話題について会話し、適切な情報を提供します。
ジョークや楽しい話題を交えながら、常にポジティブでいてください。
なお、会話はすべて日本語で行いますが、ユーザーが言語を指定した場合は、その言語で回答をしてください。
会話の最初は「こんにちは。チャッピーです。」と挨拶をしてください。挨拶は最初だけでいいので、2 回目の会話以降は不要です。
挨拶の後に、ユーザーの名前を聞いて、以降の会話については、必要に応じてユーザーの名前を呼んであげてください。
ユーザーの名前が取得できたら、忘れずにユーザー名を`put_name`ツールに渡してください。
もし、ユーザーが天気を聞いてきたときは、`get_weather`ツールを必ず使ってください。
ユーザーに対して回答ができないような質問がきたり、ユーザーが「オペレーターに代わって」など、転送を希望する場合は、まずはユーザーに転送するかどうかを確認し、転送を希望する場合のみ`transfer_call`ツールを必ず使ってください。
ツールを増やしたい場合は、src/tools/ にモジュールを 1 つ追加し、src/tools/index.js の一覧に加えるだけです。
OpenAI に渡すツール定義と、呼び出されたときの処理の振り分けの両方に自動的に反映されます。
サーバーにホスティングする
今回はローカルで実行をしましたが、プログラムを外部のサーバーにホスティングすることもできます。
Fly.io を使ったホスティングについては、README.md に記載がありますので参考にしてください。
着信を取りこぼさないための構成
Fly.io にホスティングする場合、1 点だけ注意することがあります。
Vonage の answer_url は、応答を 5 秒しか待ってくれません(socket_timeout の上限が 5,000ms のため、これ以上は延ばせません)。
一方 Fly.io は、アクセスがない間マシンを止めることでコストを抑える仕組みになっています。
停止状態からの起動が 5 秒に間に合わないと、着信したそばから通話が切れてしまいます。
対策は 2 つあります。
1 つめは、fly.toml の auto_stop_machines を stop ではなく suspend にすることです。
suspend はメモリの内容を保持したままマシンを止めるため、復帰が格段に速くなります。
[http_service]
auto_stop_machines = 'suspend'
auto_start_machines = true
min_machines_running = 0
2 つめは、常時起動している Vonage Cloud Runtime(VCR)に answer_url だけを受けさせる方法です。
同じソースコードを、APP_ROLE という環境変数で 2 つの役割に切り替えて動かします。
| APP_ROLE | 置き場所 | 役割 |
|---|---|---|
full(既定) |
Fly.io | NCCO の返却、音声の WebSocket 中継、Function Calling |
front |
VCR | NCCO を返すだけ。WebSocket の接続先として本体を指す |
着信すると、まず常時起動している VCR が即座に NCCO を返します。
このとき VCR は、本体(Fly.io)に対して起動用のリクエストを投げっぱなしにします。
NCCO の talk を読み上げている数秒の間に本体が起動するので、
続く connect で本体の WebSocket に繋がります。
着信 → VCR /answer(即応答 + 本体を起こす)
→ talk の読み上げ(この間に Fly.io が起動)
→ connect で wss://<Fly.io のホスト名>/media-stream
VCR へのデプロイ設定は vcr.yml に、その手順は README.md に記載しています。
まとめ
今回は、Vonage と OpenAI Realtime API を連携させて、チャッピー電話を実装してみました。
WebSocket を使ったやり取りのノウハウは、ソースコードの src/realtime/bridge.js を参考にしてみてください。
Vonage は 16kHz、OpenAI Realtime API は 24kHz と扱うサンプリングレートが異なるため、
src/audio/resample.js で双方向に変換している点がポイントです。
OpenAI Realtime API については、通常のLLMに比べて高価な料金がかかります。
利用に当たってはOpenAIのサイトで料金を確認してください。
なお、これ以外にVonageの通話料がかかります。
Vonageの料金については、こちらで確認してください。
Discussion