Cómo crear un agente de voz de IA con Vonage Voice API y Deepgram

Introducción

Esta guía describe el proceso de creación de un agente de voz de IA en tiempo real mediante la API de voz de Vonage y la plataforma de agente de voz de Deepgram. Crearás un asistente de voz inteligente que responda llamadas telefónicas, escuche a los usuarios a través del reconocimiento automático de voz (ASR), procese solicitudes con un modelo de lenguaje amplio (LLM) y responda con texto a voz de sonido natural, todo en tiempo real. Además, la configuración admite la interrupción de la conversación, también conocida como barge-in.

Requisitos previos

Antes de empezar, asegúrate de que tienes:

Configure su entorno local

Cree un nuevo directorio para su proyecto e instale las dependencias necesarias:

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

Exponga su servidor 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.

Aprovisiona tus recursos de Vonage

Inicie sesión en Panel de Vonage para empezar.

Crear una aplicación de Vonage

Genera tus credenciales a través del Panel de control y guárdalas en la carpeta que acabas de crear.

  1. Ir a Applications > Crear una nueva aplicación.
  2. Dale un nombre a tu aplicación.
  3. Autentificación: Haga clic en Generar claves públicas y privadas.
    • Un archivo denominado private.key se descargará.
    • Mueve esto private.key de la carpeta Descargas en su vonage-deepgram-voice-agent carpeta.
  4. En Capacidadesactivar Voz.
  5. En los ajustes de Voz, configure los siguientes webhooks:
    • URL de respuesta: https://{ngrok-url}/answer (Método:
      GET
      )
    • URL del evento: https://{ngrok-url}/event (Método:
      POST
      )
  6. Haga clic en Generar nueva aplicación en la parte inferior.

Vincular un número

  1. Ir a Numbers de téléphone > Comprar Numbers y adquiera un número vocal.
  2. Ir a Applicationsseleccione su aplicación bot y haga clic en Editar.
  3. En virtud de la Numbers haga clic en Enlace junto al número que acaba de adquirir.

Configurar variables de entorno

Crear un .env en el directorio de su proyecto con las siguientes variables:

#==== Vonage Voice API ====
API_KEY=your_vonage_api_key
API_SECRET=your_vonage_api_secret
APP_ID=your_application_id
SERVICE_PHONE_NUMBER=your_vonage_number

#==== Deepgram Voice Agent API ====
DEEPGRAM_API_KEY=your_deepgram_api_key
DEEPGRAM_VOICE_AGENT_ENDPOINT=agent.deepgram.com/v1/agent/converse
DEEPGRAM_AGENT_SPEAK=aura-orion-en

#==== Other custom parameters ====
MAX_CALL_DURATION=300

Importante: Almacena tus claves API en variables de entorno en lugar de codificarlas en tu código fuente por seguridad.

Construir el conector de agentes de voz

Cree un archivo llamado server.js y añade el siguiente código. Esta aplicación actúa como conector entre Vonage Voice API y 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!`);
});

Nota: Cuando se ejecuta localmente con ngrok, el req.hostname puede no coincidir con la URL de tu túnel público. Si los webhooks fallan, establezca la URL base de ngrok como variable de entorno y utilícela para crear el archivo eventUrl y wsUri en su lugar.

Añadir la lógica del conector WebSocket

Ahora agrega la lógica del conector central que conecta Vonage Voice API con Deepgram Voice Agent. Añade esto a tu 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");
  });
});

Cómo funciona

Streaming de audio simplificado: El audio de Deepgram se envía directamente a Vonage como mensajes binarios. No es necesario el almacenamiento en búfer manual ni la sincronización: Vonage gestiona el almacenamiento en búfer interno automáticamente.

Mensaje de control de borrado del búfer: Cuando Deepgram detecta que el usuario ha empezado a hablar (UserStartedSpeaking ), la aplicación envía un mensaje de control CLEAR a Vonage: {"action": "clear"}. Esto le indica a Voice API de Vonage que descarte de inmediato cualquier cuadro de audio almacenado en el búfer, lo que crea una funcionalidad de irrupción instantánea sin administración manual del búfer.

Confirmación del evento: Vonage responde con un websocket:cleared para confirmar que el búfer se ha borrado correctamente. Esto permite realizar un seguimiento de cuándo se producen interrupciones.

Comunicación bidireccional: El audio del usuario fluye desde Vonage → Deepgram como mensajes binarios WebSocket, mientras que el audio y las transcripciones del agente fluyen desde Deepgram → Vonage en tiempo real.

Transcripciones en tiempo real: Deepgram envía mensajes JSON que contienen transcripciones tanto de la voz del usuario como de las respuestas del agente, que puedes registrar o procesar para análisis y control de calidad.

Probar la aplicación

  1. Asegúrese de que su private.key se encuentra en el directorio del proyecto.
  2. Inicie ngrok en un terminal:
ngrok http 3000
  1. Ejecute su servidor en otro terminal:
node server.js
  1. Llama a tu número de teléfono de Vonage desde tu teléfono móvil.
  2. El agente de voz le dará la bienvenida y responderá a sus preguntas mediante una conversación basada en inteligencia artificial.

Añadir capacidad de llamadas salientes

Para que su aplicación pueda realizar llamadas salientes, añada este endpoint a su 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));
  }
});

Para activar una llamada saliente, abra su navegador y navegue hasta:

https://your-ngrok-url.ngrok.io/call?callee=15551234567

Sustituir 15551234567 con el número de teléfono al que desea llamar (en formato E.164 sin el carácter + signo).

Personalice su agente de voz

Puede personalizar varios aspectos del agente de voz modificando los botones dgVoiceAgentSettings objeto:

Cambiar el modelo de 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."
}

Cambiar la voz

Actualizar el DEEPGRAM_AGENT_SPEAK en su .env archivo. Véase Documentación sobre los modelos TTS de Deepgram para ver las opciones de voz disponibles.

Personalizar el indicador del sistema

Modificar el prompt en el campo think para cambiar la personalidad y el comportamiento de su agente:

"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."

Próximos pasos