Comment créer un agent vocal d'IA avec l'API Voice de Vonage et Deepgram
Introduction
Ce guide décrit le processus de création d'un agent vocal IA en temps réel à l'aide de l'API Voice de Vonage et de la plateforme Voice Agent de Deepgram. Vous allez créer un assistant vocal intelligent qui répond aux appels téléphoniques, écoute les utilisateurs via la reconnaissance automatique de la parole (ASR), traite les demandes à l'aide d'un modèle de langage étendu (LLM) et répond par une synthèse vocale naturelle, le tout en temps réel. En outre, l'installation prend en charge l'interruption de la conversation, également connue sous le nom de "barge-in".
Conditions préalables
Avant de commencer, assurez-vous d'avoir
- Un compte API Vonage. S'inscrire gratuitement.
- Node.js version 18 ou supérieure installée sur votre machine.
- A Compte Deepgram avec une clé API.
- 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:
Note: Keep this terminal open and copy your ngrok URL. You'll need it in the next steps.
Approvisionnement de vos ressources Vonage
Se connecter à l'application Tableau de bord Vonage pour commencer.
Créer une application Vonage
Générez vos informations d'identification via le tableau de bord et enregistrez-les dans le dossier que vous venez de créer.
- Aller à Applications > Créer une nouvelle application.
- Donnez un nom à votre application.
- Authentification : Cliquez sur Générer des clés publiques et privées.
- Un fichier nommé
private.keysera téléchargé. - Déplacer ceci
private.keyde votre dossier Téléchargements dans votrevonage-deepgram-voice-agentdossier.
- Un fichier nommé
- Sous Capacités, activer Voix.
- Dans les paramètres vocaux, définissez les webhooks suivants :
- URL de la réponse :
https://{ngrok-url}/answer(Méthode :GET) - URL de l'événement :
https://{ngrok-url}/event(Méthode :POST)
- URL de la réponse :
- Cliquez sur Générer une nouvelle application en bas.
Lier un numéro
- Aller à Numbers de téléphone > Acheter des Numbers et acheter un numéro à commande vocale.
- Aller à 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.
Configuration des variables d'environnement
Créer un .env dans le répertoire de votre projet avec les variables suivantes :
Important: Stockez vos clés d'API dans des variables d'environnement plutôt que de les coder en dur dans votre code source pour des raisons de sécurité.
Construire le connecteur d'agent vocal
Créer un fichier nommé server.js et ajoutez le code suivant. Cette application agit comme un connecteur entre Vonage Voice API et Deepgram Voice Agent.
'use strict'
require('dotenv').config();
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
require('express-ws')(app);
const webSocket = require('ws');
app.use(bodyParser.json());
//---- CORS policy ----
app.use(function (req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
res.header("Access-Control-Allow-Methods", "OPTIONS,GET,POST,PUT,DELETE");
next();
});
//---- Configuration ----
const servicePhoneNumber = process.env.SERVICE_PHONE_NUMBER;
//---- Vonage API Setup ----
const { Auth } = require('@vonage/auth');
const credentials = new Auth({
apiKey: process.env.API_KEY,
apiSecret: process.env.API_SECRET,
applicationId: process.env.APP_ID,
privateKey: './private.key'
});
const apiBaseUrl = "https://api.nexmo.com";
const options = { apiHost: apiBaseUrl };
const { Vonage } = require('@vonage/server-sdk');
const vonage = new Vonage(credentials, options);
//---- Deepgram Voice Agent Configuration ----
const dgApiKey = process.env.DEEPGRAM_API_KEY;
const dgVoiceAgentEndpoint = process.env.DEEPGRAM_VOICE_AGENT_ENDPOINT;
const dgVoiceAgentSettings = {
"type": "Settings",
"audio": {
"input": { "encoding": "linear16", "sample_rate": 8000 },
"output": { "encoding": "linear16", "sample_rate": 8000, "container": "none" }
},
"agent": {
"listen": { "provider": { "type": "deepgram", "model": "nova-3" } },
"think": {
"provider": { "type": "anthropic", "model": "claude-sonnet-4-20250514" },
"prompt": "You are a helpful AI assistant on a live phone call. Keep responses concise and natural for spoken conversation."
},
"speak": {
"provider": {
"type": "deepgram",
"model": process.env.DEEPGRAM_AGENT_SPEAK
}
}
}
};
//---- Handle incoming PSTN calls ----
app.get('/answer', async (req, res) => {
const hostName = req.hostname;
const uuid = req.query.uuid;
// For local development with ngrok, use your ngrok URL directly
// const publicUrl = 'https://your-ngrok-url.ngrok.io';
const wsUri = `wss://${hostName}/socket?original_uuid=${uuid}`;
const nccoResponse = [
{
"action": "talk",
"text": "Hello, please wait while we're connecting your call!",
"language": "en-US",
"style": 11
},
{
"action": "connect",
"eventType": "synchronous",
"eventUrl": [`https://${hostName}/ws_event`],
"from": req.query.from,
"endpoint": [
{
"type": "websocket",
"uri": wsUri,
"content-type": "audio/l16;rate=8000",
"headers": {}
}
]
}
];
res.status(200).json(nccoResponse);
});
//---- Event webhook for call status ----
app.post('/event', async (req, res) => {
res.status(200).send('Ok');
});
//---- WebSocket event handler ----
app.post('/ws_event', async (req, res) => {
res.status(200).send('Ok');
// Trigger a greeting when WebSocket is connected
setTimeout(() => {
if (req.body.status === 'answered') {
vonage.voice.playTTS(req.body.uuid, {
text: "Hello",
language: 'en-US',
style: 11
})
.then(res => console.log("Initial greeting sent"))
.catch(err => console.error("Failed to play TTS:", err));
}
}, 1500);
});
//---- Start server ----
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log(`Voice Agent application listening on port ${port}`);
console.log(`Make sure ngrok is forwarding to this port!`);
});
Note: Lors d'une exécution locale avec ngrok, la fonction req.hostname peut ne pas correspondre à l'URL de votre tunnel public. Si les webhooks échouent, définissez votre URL de base ngrok en tant que variable d'environnement et utilisez-la pour construire l'URL de base de ngrok. eventUrl et wsUri au lieu de cela.
Ajouter la logique du connecteur WebSocket
Ajoutez maintenant la logique du connecteur de base qui relie l'API Voice de Vonage à l'agent vocal de Deepgram. Ajoutez ceci à votre server.js:
//---- WebSocket Connector ----
app.ws('/socket', async (ws, req) => {
let wsDgOpen = false; // Deepgram WebSocket ready?
const originalUuid = req.query.original_uuid;
console.log('WebSocket connected for call UUID:', originalUuid);
//---- Connect to Deepgram Voice Agent ----
console.log('Opening connection to Deepgram Voice Agent');
const wsDg = new webSocket(`wss://${dgVoiceAgentEndpoint}`, {
headers: { authorization: `token ${dgApiKey}` }
});
wsDg.on('error', async (event) => {
console.log('WebSocket to Deepgram error:', event);
});
wsDg.on('open', () => {
console.log('WebSocket to Deepgram opened');
// Send configuration to Deepgram Voice Agent
wsDg.send(JSON.stringify(dgVoiceAgentSettings));
wsDgOpen = true;
});
//---- Handle messages from Deepgram ----
wsDg.on('message', async (msg, isBinary) => {
if (isBinary) {
// Audio data from agent - send directly to Vonage
ws.send(msg);
} else {
// Text messages (transcripts, events, etc.)
const message = JSON.parse(msg.toString('utf8'));
console.log(`Message from Deepgram:`, message);
// Handle barge-in: clear Vonage's audio buffer when user starts speaking
if (message.type === "UserStartedSpeaking") {
ws.send(JSON.stringify({ action: "clear" }));
console.log('Sent CLEAR command to Vonage');
}
}
});
wsDg.on('close', async () => {
wsDgOpen = false;
console.log("Deepgram WebSocket closed");
});
//---- Handle messages from Vonage (user audio) ----
ws.on('message', async (msg) => {
if (typeof msg === "string") {
const event = JSON.parse(msg);
console.log("Vonage event:", event.event);
// The first message from Vonage is always websocket:connected
if (event.event === "websocket:connected") {
console.log('Vonage WebSocket established:', event['content-type']);
}
// Handle Vonage control message confirmations
if (event.event === "websocket:cleared") {
console.log('Vonage audio buffer cleared');
}
} else {
// Binary audio data from caller - forward to Deepgram
if (wsDgOpen) {
wsDg.send(msg);
}
}
});
//---- Clean up on disconnect ----
ws.on('close', async () => {
wsDgOpen = false;
wsDg.close();
console.log("Vonage WebSocket closed");
});
});
Comment ça marche
Streaming audio simplifié: L'audio de Deepgram est envoyé directement à Vonage sous forme de messages binaires. Aucune mise en mémoire tampon manuelle ou synchronisation n'est nécessaire - Vonage gère automatiquement la mise en mémoire tampon interne.
Message de contrôle de la mémoire tampon: Lorsque Deepgram détecte que l'utilisateur a commencé à parler (UserStartedSpeaking ), l'application envoie un message de contrôle CLEAR à Vonage : {"action": "clear"}. Cela demande à l'API Voice de Vonage de se débarrasser immédiatement de toutes les trames audio mises en mémoire tampon, créant ainsi une fonctionnalité d'intrusion instantanée sans gestion manuelle de la mémoire tampon.
Confirmation de l'événement: Vonage répond par un websocket:cleared pour confirmer que la mémoire tampon a été vidée avec succès. Cela vous permet de savoir quand les interruptions se produisent.
Communication bidirectionnelle: L'audio de l'utilisateur circule de Vonage → Deepgram sous forme de messages WebSocket binaires, tandis que l'audio et les transcriptions de l'agent circulent de Deepgram → Vonage en temps réel.
Transcriptions en temps réel: Deepgram envoie des messages JSON contenant des transcriptions du discours de l'utilisateur et des réponses de l'agent, que vous pouvez enregistrer ou traiter à des fins d'analyse et d'assurance qualité.
Tester l'application
- Assurez-vous que votre
private.keyse trouve dans le répertoire du projet. - Démarrer ngrok dans un terminal :
- Exécutez votre serveur dans un autre terminal :
- Appelez votre numéro de téléphone Vonage à partir de votre téléphone mobile.
- L'agent vocal vous accueillera et répondra à vos questions au moyen d'une conversation alimentée par l'IA.
Ajouter une capacité d'appel sortant
Pour permettre à votre application d'effectuer des appels sortants, ajoutez ce point de terminaison à votre server.js:
//---- Trigger outbound PSTN calls ----
app.get('/call', async (req, res) => {
if (req.query.callee == null) {
res.status(400).send('"callee" number missing as query parameter');
} else {
res.status(200).send('Ok');
const hostName = req.hostname;
vonage.voice.createOutboundCall({
to: [{
type: 'phone',
number: req.query.callee
}],
from: {
type: 'phone',
number: servicePhoneNumber
},
limit: process.env.MAX_CALL_DURATION,
answer_url: [`https://${hostName}/answer`],
answer_method: 'GET',
event_url: [`https://${hostName}/event`],
event_method: 'POST'
})
.then(res => console.log("Outgoing PSTN call status:", res))
.catch(err => console.error("Outgoing PSTN call error:", err));
}
});
Pour déclencher un appel sortant, ouvrez votre navigateur et rendez-vous sur :
https://your-ngrok-url.ngrok.io/call?callee=15551234567
Remplacer 15551234567 avec le numéro de téléphone que vous souhaitez appeler (au format E.164 sans les caractères + ).
Personnalisez votre agent vocal
Vous pouvez personnaliser divers aspects de l'agent vocal en modifiant les paramètres suivants dgVoiceAgentSettings objet :
Changer le modèle d'IA
"think": {
"provider": { "type": "open_ai", "model": "gpt-4o-mini" },
"prompt": "You are a helpful AI assistant on a live phone call. Keep responses concise and natural for spoken conversation."
}
Changer la voix
Mettre à jour le DEEPGRAM_AGENT_SPEAK dans votre .env dossier. Voir aussi Documentation sur les modèles TTS de Deepgram pour connaître les options vocales disponibles.
Personnaliser l'invite du système
Modifier le prompt dans le champ think section pour changer la personnalité et le comportement de votre agent :
"prompt": "You are a friendly customer service representative for Acme Corp. Help users with their inquiries about our products and services. Be professional but warm."
Prochaines étapes
- Explorer Documentation WebSocket pour des schémas de diffusion audio avancés.
- Ajouter enregistrement et transcription des appels à des fins d'audit et de contrôle de la qualité.