Comment créer un SVI avancé / un robot vocal
Ce guide montre comment construire un agent d'intelligence artificielle basé sur la voix en utilisant l'API Voice de Vonage et OpenAI. Vous allez créer un Bot de la voix qui répond aux appels entrants, écoute la question d'un utilisateur à l'aide d'un logiciel de gestion des appels. Reconnaissance automatique de la parole (ASR)et répond par une réponse intelligente générée par un LLM.
Conditions préalables
Avant de commencer, assurez-vous d'avoir
- A Compte API Vonage.
- Node.js installé sur votre machine.
- Un Clé API OpenAI.
- ngrok installé sur votre machine.
Configuration de l'environnement local
Créez un nouveau répertoire pour votre projet et installez les dépendances nécessaires :
Exposez votre serveur local
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.
Approvisionnement de vos ressources Vonage
Se connecter à l'application Tableau de bord Vonage pour commencer.
Créer une application vocale
- Naviguez jusqu'à Applications > Créer une nouvelle application.
- Donnez-lui un nom (par exemple, Voice AI Bot).
- Sous Capacités, activer Voix.
- Dans le cadre de la Réponse URL Dans le champ, entrez l'URL de votre base ngrok suivie de
/webhooks/answer(par exemple,https://{random-id}.ngrok.app/webhooks/answer). Réglez la méthode surGET. - Dans le cadre de la URL de l'événement Dans le champ, entrez l'URL de votre base ngrok suivie de
/webhooks/events(par exemple,https://{random-id}.ngrok.app/webhooks/events). Réglez la méthode surPOST. - Cliquez sur Générer une clé publique et une clé privée. Sauvegarder le
private.keydans votre dossier de projet (bien que nous ne l'utilisions pas pour ce flux ASR de base, il est nécessaire pour la création de l'application). - Cliquez sur Enregistrer les modifications.
Lier un numéro
- Aller à Numbers > Acheter des Numbers et acheter un numéro à commande vocale.
- Aller à Vos applicationssélectionnez votre application bot et cliquez sur Editer.
- En vertu de la Numbers cliquer sur l'onglet Lien à côté du numéro que vous venez d'acheter.
Construire le robot vocal
Créer un fichier nommé index.js et ajoutez le code suivant. Remplacer YOUR_OPENAI_API_KEY avec votre clé actuelle.
Note : Lors d'une exécution locale avec ngrok, req.protocol/req.get('host') peut ne pas correspondre à l'URL publique de votre tunnel. Si les webhooks échouent, définissez l'URL de base de votre tunnel dans la configuration (par exemple une variable d'environnement) et construisez eventUrl de cela à la place.
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'));
Tester l'application
Exécutez votre serveur :
node index.jsComposez votre numéro Vonage à partir de votre téléphone.
Lorsqu'on vous le demande, posez une question (par ex, Pourquoi le ciel est-il bleu ? ou Racontez-moi une blague).
Le robot enregistrera votre discours, l'enverra à OpenAI et vous lira la réponse à l'aide de la fonction Synthèse vocale.
Permettre une conversation contextuelle
Pour que la conversation soit naturelle, nous devons modifier l'application afin qu'elle se souvienne des échanges précédents et qu'elle invite à nouveau l'utilisateur à fournir des informations.
Note : Lors d'une exécution locale avec ngrok, req.get('host') peut ne pas correspondre à l'hôte de votre tunnel public. Si les webhooks échouent, construire eventUrl en utilisant l'URL de base de votre tunnel public (par exemple dans config/env) au lieu de l'hôte de la requête.
Mettez à jour votre index.js avec cette logique d'état :
// 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);
});
Ce qui a changé
- Le plan de session : Nous utilisons la carte de session
uuidpour séparer l'historique des différents appelants. - BCN récursif : au lieu d'un simple
talknous renvoyons maintenant untalksuivi d'uninputaction. Cela permet de maintenir la ligne ouverte. - La mémoire : En transmettant l'intégralité de la
historySelon OpenAI, le robot comprend désormais des questions complémentaires telles que Dites-m'en plus à ce sujet.
Essayez l'application mise à jour en redémarrant votre serveur et en composant le numéro de Vonage lié à votre application, comme dans l'image ci-dessous. Tester l'application pas.
Ajouter l'outil "Connect to Human
Cette étape implique la mise à jour de vos définitions d'outils et l'ajout d'une branche à votre logique ASR qui renvoie l'information Vonage connect action.
Mise à jour index.js
Ajoutez la nouvelle définition d'outil et modifiez l'élément asr pour gérer le transfert :
// 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')));
});
Comment ça marche
- L'intention : Lorsque l'utilisateur dit Je souhaite parler à un responsable ou Aidez-moi, c'est trop dur le LLM reconnaît l'intention et déclenche la
connect_to_humanfonction. - Le transfert : votre serveur arrête la boucle ASR et envoie le message
connectà Vonage. - La connexion : Vonage crée un nouveau tronçon sortant vers le
HUMAN_AGENT_NUMBERet établit un pont entre les deux appels. L'IA n'est plus "à l'écoute" une fois la connexion établie.
Redémarrez votre serveur et appelez le numéro Vonage de votre application. Lorsque vous demandez au robot de parler à un humain, il doit prononcer la phrase suivante Veuillez patienter pendant que je vous mets en relation avec un représentant humainet vous connecte ensuite au numéro de téléphone que vous avez défini en tant que HUMAN_AGENT_NUMBER.
Prochaines étapes
- Voix personnalisées : Modifier le nom de la voix dans le
talkpour une expérience plus personnalisée. - WebSocket Streaming : Pour une latence plus faible, utilisez WebSockets pour diffuser de l'audio en temps réel.
- Points d'extrémité : Connexion à votre PBX ou Contact Center via SIP ou créez votre propre interface web pour un agent humain avec Client SDK.
- Version .NET : Voir le même scénario IVR/voice-bot mis en œuvre en .NET dans cette version. article de blog.