高度なIVR/音声ボットの構築方法
このガイドでは、Vonage Voice APIとOpenAIを使って音声ベースのAIエージェントを構築する方法を説明します。このガイドでは 音声ボット インバウンドコールに応答し、ユーザーの質問に耳を傾ける。 自動音声認識 (ASR)そして、LLMによって生成されたインテリジェントな回答で応答する。
前提条件
始める前に、あなたが持っていることを確認してください:
- A Vonage APIアカウント.
- Node.js あなたのマシンにインストールされています。
- アン OpenAI APIキー.
- ングロク あなたのマシンにインストールされています。
ローカル環境のセットアップ
プロジェクト用に新しいディレクトリを作成し、必要な依存関係をインストールします:
ローカルサーバーの公開
Vonage needs to send webhooks to your local machine. Use ngrok to expose your server:
ngrok will forward your local port 3000 to a public URL like https://{random-id}.ngrok.app.
Important: Keep this terminal window open while developing and testing. If you close ngrok, you’ll need to update your webhook URLs with the new address.
Copy this URL. You’ll need it when configuring your Vonage application in the next step.
Note: The free version of ngrok generates a new random URL each time you restart it. For a consistent URL during development, consider using ngrok reserved domains or upgrading to a paid plan.
Vonageリソースのプロビジョニング
にログインする。 Vonageダッシュボード を開始する。
音声アプリケーションの作成
- に移動する。 Applications > 新規アプリケーションの作成.
- 名前をつける(例:Voice AI Bot)。
- アンダー 能力有効にする 声.
- の中で 回答URL フィールドに ngrok Base URL を入力し、その後に
/webhooks/answer(例https://{random-id}.ngrok.app/webhooks/answer).メソッドをGETに設定します。 - の中で イベントURL フィールドに ngrok Base URL を入力し、その後に
/webhooks/events(例https://{random-id}.ngrok.app/webhooks/events).メソッドをPOSTに設定します。 - クリック 公開鍵と秘密鍵の生成.を保存する。
private.keyこの基本的なASRフローでは使用しませんが、アプリの作成には必要です)。 - クリック 変更を保存する.
Numbersをリンクする
- こちらへ Numbers > ナンバーズを買う をクリックし、音声対応番号を購入する。
- こちらへ アプリケーションをクリックし、ボットアプリケーションを選択します。 編集.
- の下にある。 Numbers タブをクリックします。 リンク 新しく購入したNumbersの横。
音声ボットを作る
という名前のファイルを作成する。 index.js そして以下のコードを追加する。置き換える YOUR_OPENAI_API_KEY を実際のキーで入力します。
注:ngrokでローカルに実行する場合、 req.protocol/req.get('host') は、あなたの公開トンネルURLと一致しないかもしれません。ウェブフックに失敗した場合は、configでトンネルのベースURLを設定し(例えばenv var)、ビルドしてください。 eventUrl その代わりだ。
const express = require('express');
const { OpenAI } = require('openai');
const app = express();
app.use(express.json());
const openai = new OpenAI({ apiKey: 'YOUR_OPENAI_API_KEY' });
// 1. Handle the initial call
app.get('/webhooks/answer', (req, res) => {
const ncco = [
{
action: 'talk',
text: 'Hi, I am your AI assistant. How can I help you today?'
},
{
action: 'input',
eventUrl: [`${req.protocol}://${req.get('host')}/webhooks/asr`],
type: ['speech'],
speech: {
language: 'en-us',
endOnSilence: 1
}
}
];
res.json(ncco);
});
// 2. Process the Speech-to-Text result and query OpenAI
app.post('/webhooks/asr', async (req, res) => {
const speechResults = req.body.speech?.results;
if (!speechResults || speechResults.length === 0) {
return res.json([{ action: 'talk', text: 'I am sorry, I didn\'t catch that. Goodbye.' }]);
}
const userText = speechResults[0].text;
console.log(`User said: ${userText}`);
try {
// Request a completion from OpenAI
const completion = await openai.chat.completions.create({
model: "gpt-4o",
messages: [
{ role: "system", content: "You are a helpful assistant on a phone call. Keep answers concise." },
{ role: "user", content: userText }
],
});
const aiResponse = completion.choices[0].message.content;
// Respond back to the user
res.json([{ action: 'talk', text: aiResponse }]);
} catch (error) {
console.error("OpenAI Error:", error);
res.json([{ action: 'talk', text: 'I encountered an error processing your request.' }]);
}
});
// 3. Log call events
app.post('/webhooks/events', (req, res) => {
console.log('Event:', req.body.status);
res.sendStatus(200);
});
app.listen(3000, () => console.log('Server running on port 3000'));
アプリケーションのテスト
サーバーを起動してください:
node index.jsお使いの電話からVonage番号にダイヤルしてください。
プロンプトが表示されたら、質問をする(例. なぜ空は青いのか? または ジョークを言ってくれ).
ボットはあなたの発話をキャプチャし、それをOpenAIに送信し、以下の方法で応答を読み返します。 音声合成.
文脈に沿った会話を可能にする
会話を自然なものにするためには、アプリが以前のやりとりを記憶し、ユーザーに再度入力を促すように修正しなければならない。
注:ngrokでローカルに実行する場合、 req.get('host') はあなたのパブリック・トンネル・ホストと一致しないかもしれない。ウェブフックに失敗した場合は eventUrl リクエストホストの代わりに、パブリックトンネルのベースURL(例えばconfig/env)を使用します。
更新 index.js このステートフルなロジックで:
// 1. Add a Map to store conversation history by Call UUID
const sessions = new Map();
// Helper to generate a NCCO that "loops" back to ASR
const getConversationalNCCO = (text, host) => [
{ action: 'talk', text: text },
{
action: 'input',
eventUrl: [`https://${host}/webhooks/asr`],
type: ['speech'],
speech: { language: 'en-us', endOnSilence: 1 }
}
];
app.get('/webhooks/answer', (req, res) => {
const uuid = req.query.uuid;
// Initialize history for this specific caller
sessions.set(uuid, [{ role: "system", content: "You are a helpful, concise assistant." }]);
res.json(getConversationalNCCO('Hello! What is on your mind?', req.get('host')));
});
app.post('/webhooks/asr', async (req, res) => {
const { uuid, speech } = req.body;
const userText = speech?.results?.[0]?.text;
if (!userText) {
sessions.delete(uuid);
return res.json([{ action: 'talk', text: 'Goodbye!' }]);
}
// Retrieve history and append the new question
let history = sessions.get(uuid) || [];
history.push({ role: "user", content: userText });
const completion = await openai.chat.completions.create({
model: "gpt-4o",
messages: history,
});
const aiResponse = completion.choices[0].message.content;
history.push({ role: "assistant", content: aiResponse });
sessions.set(uuid, history);
// Return the AI response AND listen for the next question
res.json(getConversationalNCCO(aiResponse, req.get('host')));
});
// Clean up memory when the call ends
app.post('/webhooks/events', (req, res) => {
if (req.body.status === 'completed') sessions.delete(req.body.uuid);
res.sendStatus(200);
});
何が変わったのか
- セッション・マップ私たちは
uuid異なる発信者の履歴を別々に管理するため。 - 再帰的NCCO:単純な
talkアクションを返します。talk続いてinputアクション。これで回線は開いたままだ。 - メモリー:メモリ全体を渡すことで
historyOpenAIの配列では、ボットは次のようなフォローアップの質問を理解するようになりました。 それについて詳しく教えてください。
サーバーを再起動し、アプリケーションにリンクされているVonage番号にダイヤルして、更新されたアプリケーションをお試しください。 アプリケーションのテスト ステップ
人とつながる」ツールを追加
このステップでは、ツール定義を更新し、ASRロジックにVonageの connect アクションだ。
更新 index.js
新しいツール定義を追加し asr ウェブフックで転送を処理する:
// Define the transfer tool
const tools = [
{
type: "function",
function: {
name: "connect_to_human",
description: "Call this when the user wants to speak to a real person or a human agent.",
parameters: { type: "object", properties: {} } // No arguments needed
}
}
];
const HUMAN_AGENT_NUMBER = '15551234567'; // Replace with your phone number
app.post('/webhooks/asr', async (req, res) => {
const { uuid, speech } = req.body;
const userText = speech?.results?.[0]?.text;
if (!userText) return res.json([{ action: 'talk', text: 'Goodbye.' }]);
let history = sessions.get(uuid) || [];
history.push({ role: "user", content: userText });
const response = await openai.chat.completions.create({
model: "gpt-4o",
messages: history,
tools: tools // Provide the tool to the LLM
});
const message = response.choices[0].message;
// Check if the AI wants to transfer the call
if (message.tool_calls && message.tool_calls[0].function.name === 'connect_to_human') {
console.log(`Transferring call ${uuid} to human agent...`);
// Clean up session since the AI is leaving the call
sessions.delete(uuid);
// Return the "connect" NCCO
return res.json([
{
action: 'talk',
text: 'Please hold while I connect you to a human representative.'
},
{
action: 'connect',
from: 'YOUR_VONAGE_NUMBER', // Your linked Vonage number
endpoint: [{ type: 'phone', number: HUMAN_AGENT_NUMBER }]
}
]);
}
// Regular conversational flow
const aiResponse = message.content;
history.push({ role: "assistant", content: aiResponse });
sessions.set(uuid, history);
res.json(getConversationalNCCO(aiResponse, req.get('host')));
});
仕組み
- 意図:ユーザーが マネージャーと話したい または 助けてください。 LLMは、その意図を認識し、それを発動する。
connect_to_human関数である。 - ハンドオフ:サーバーはASRのループを停止し
connectアクションをVonageへ。 - 接続:Vonageは新しいアウトバウンドレッグを作成します。
HUMAN_AGENT_NUMBERそして2つの通話をブリッジする。接続が行われると、AIはもはや "リッスン "していない。
サーバーを再起動し、アプリケーションのVonage番号に電話をかけます。ボットに人間と話すように頼むと、次のように言うはずです。 担当者におつなぎしますので、少々お待ちください。に設定した電話番号に接続します。 HUMAN_AGENT_NUMBER.
次のステップ
- カスタムボイス: 音声名の変更 での
talkアクションで、よりブランディングされた体験を提供する。 - WebSocketストリーミング:低レイテンシーを実現するには ウェブソケット オーディオをリアルタイムでストリーミングする。
- エンドポイントPBXまたはコンタクトセンターへの接続 SIP経由 を使用して、人間のエージェントのための独自のウェブインターフェースを構築することもできます。 Client SDK.
- .NETバージョン:.NETで実装された同じIVR/ボイスボットのシナリオをご覧ください。 ブログ記事.