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:
- Una cuenta API de Vonage. Regístrate gratis.
- Node.js versión 18 o superior instalada en su máquina.
- A Account Deepgram con una clave API.
- ngrok instalado en su máquina.
Configure su entorno local
Cree un nuevo directorio para su proyecto e instale las dependencias necesarias:
Exponga su servidor 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.
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.
- Ir a Applications > Crear una nueva aplicación.
- Dale un nombre a tu aplicación.
- Autentificación: Haga clic en Generar claves públicas y privadas.
- Un archivo denominado
private.keyse descargará. - Mueve esto
private.keyde la carpeta Descargas en suvonage-deepgram-voice-agentcarpeta.
- Un archivo denominado
- En Capacidadesactivar Voz.
- 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)
- URL de respuesta:
- Haga clic en Generar nueva aplicación en la parte inferior.
Vincular un número
- Ir a Numbers de téléphone > Comprar Numbers y adquiera un número vocal.
- Ir a Applicationsseleccione su aplicación bot y haga clic en Editar.
- 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
- Asegúrese de que su
private.keyse encuentra en el directorio del proyecto. - Inicie ngrok en un terminal:
- Ejecute su servidor en otro terminal:
- Llama a tu número de teléfono de Vonage desde tu teléfono móvil.
- 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
- Explore Documentación sobre WebSocket para patrones avanzados de transmisión de audio.
- Añadir grabación y transcripción de llamadas con fines de auditoría y control de calidad.