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

Configuration de l'environnement local

Créez un nouveau répertoire pour votre projet et installez les dépendances nécessaires :

mkdir vonage-deepgram-voice-agent cd vonage-deepgram-voice-agent npm init -y npm install @vonage/server-sdk express express-ws body-parser dotenv ws

Exposez votre serveur local

Vonage needs to send webhooks to your local machine. Use ngrok to expose your server:

ngrok http 3000

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.

  1. Aller à Applications > Créer une nouvelle application.
  2. Donnez un nom à votre application.
  3. Authentification : Cliquez sur Générer des clés publiques et privées.
    • Un fichier nommé private.key sera téléchargé.
    • Déplacer ceci private.key de votre dossier Téléchargements dans votre vonage-deepgram-voice-agent dossier.
  4. Sous Capacités, activer Voix.
  5. 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
      )
  6. Cliquez sur Générer une nouvelle application en bas.

Lier un numéro

  1. Aller à Numbers de téléphone > Acheter des Numbers et acheter un numéro à commande vocale.
  2. Aller à Applicationssélectionnez votre application bot et cliquez sur Editer.
  3. 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

  1. Assurez-vous que votre private.key se trouve dans le répertoire du projet.
  2. Démarrer ngrok dans un terminal :
ngrok http 3000
  1. Exécutez votre serveur dans un autre terminal :
node server.js
  1. Appelez votre numéro de téléphone Vonage à partir de votre téléphone mobile.
  2. 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