🀄️

チャッピー電話を作ろう!

に公開
2026/08/28

はじめに

みなさん、こんにちは。KDDI ウェブコミュニケーションズで CPaaS のエバンジェリストをしている高橋です。
この記事では、Vonage と OpenAI の Realtime API を使った「チャッピー電話」について解説をします。

なお、この記事の内容をこちらから動画でご覧いただくことも可能です。

本記事の対象となる読者

  • ボイスボットの開発を行いたいと考えている方
  • Node.js + Fastify の仕組みがなんとなくわかる方
  • Vonage の NCCO や WebSocket など、基本的な機能が理解できる方
  • OpenAI の Realtime API を触ってみたい方

Vonage とは

Vonage_logo

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 経由で高速かつ双方向通信が可能で、アプリやボットに自然な対話体験を組み込めます。
また、会話の間を短くすることができたり、会話の割り込みにも対応することができます。

シナリオ

  1. ユーザーは、Vonage の電話番号に電話をかけるか、/connect エンドポイントに自分の電話番号を POST することで電話がかかります。
  2. Vonage で転送する旨のガイダンスを流したあと、OpenAI に WebSocket 接続します。
  3. 最初にユーザー名をヒアリングし、以降は OpenAI と会話を行います。
  4. 会話の割り込みにも対応しています。
  5. Function Calling を使うことによって、各地の天気を外部サービスから調べることができます。
  6. 「オペレーターに代わって」のように転送を希望した場合は、通話を別の電話番号に転送します。

Vonage と OpenAI の相関図

ユーザーが発声した内容を OpenAI Realtime API が回答を返します。

準備

本記事の内容を実装するためには、以下の準備が必要です。

  • Vonage アカウント(どちらか一方で作成してください)
  • OpenAI の API キー
  • ブラウザ(Chrome推奨)
  • 通話が可能な携帯電話 or スマートフォン
  • Vonage クーポンコード(トライアルアカウントで電話番号を購入する際に必要です)
  • Vonage 電話番号(US番号でも可、日本の番号を購入する方法はこちら)
  • OpenWeatherMap API アカウント(こちら から無料で作成できます)
  • Node.js 22 以上、npm、Git 環境
  • ngrok の準備(こちら からアカウントの作成ができます)
  • 使い慣れたエディタ

Vonage アプリケーションの作成

  • Vonage ダッシュボードにログインします。
    Create Application
  • 左側のメニューからアプリケーションを選択します。
  • 新しいアプリケーションを作成するボタンを押します。
    Application Configuration
  • 名前欄に「websocket-openai」と入力します。
  • 公開鍵と秘密鍵を生成ボタンを押します(ダイアログでも生成ボタンを押下)。秘密鍵(private.key)がダウンロードされるので、なくさないように保存しておきます。
  • 音声のスイッチをONにします。
  • 地域のプルダウンから「Singapore」を選択します。
  • 最後に新しいアプリケーションの生成を押します。

Link a number

  • アプリケーション 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の料金については、こちらで確認してください。

KWCPLUS

Discussion