高度な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:
Note: Keep this terminal open and copy your ngrok URL. You'll need it in the next steps.
Vonageリソースのプロビジョニング
にログインする。 Vonageダッシュボード を開始する。
音声アプリケーションの作成
- に移動する。 Applications > 新規アプリケーションの作成.
- 名前をつける(例:Voice AI Bot)。
- クリック 公開鍵と秘密鍵の生成.を保存する。
private.keyこの基本的なASRフローでは使用しませんが、アプリの作成には必要です)。 - アンダー 能力有効にする 声.
- の中で 回答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に設定します。 - クリック 新規アプリケーションの作成 一番下にある。
Numbersをリンクする
- こちらへ 電話番号 > ナンバーズを買う をクリックし、音声対応番号を購入する。
- こちらへ Applicationsをクリックし、ボットアプリケーションを選択します。 編集.
- の下にある。 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/ボイスボットのシナリオをご覧ください。 ブログ記事.