Como criar um agente de voz com IA usando a Voice API da Vonage e o Deepgram

Introdução

Este guia descreve o processo de criação de um agente de voz com IA em tempo real usando a Voice API da Vonage e a plataforma Voice Agent da Deepgram. Você criará um assistente de voz inteligente que atende chamadas telefônicas, escuta os usuários por meio do Reconhecimento Automático de Fala (ASR), processa solicitações com um Modelo de Linguagem de Grande Escala (LLM) e responde com uma síntese de voz que soa natural, tudo em tempo real. Além disso, a configuração suporta a interrupção da conversa, também conhecida como “barge-in”.

Para uma visão geral dos conceitos de automação de voz e uma comparação entre as três abordagens de implementação, consulte Entendendo a automação por voz.

Pré-requisitos

Antes de começar, certifique-se de que você tenha:

Configure seu ambiente local

Crie um novo diretório para o seu projeto e instale as dependências necessárias:

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

Exponha seu 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.

Configure seus recursos da Vonage

Faça login no Painel do Vonage para começar.

Criar uma aplicação da Vonage

Gere suas credenciais pelo Painel e salve-as na pasta que você acabou de criar.

  1. Acesse Applications > Criar um novo aplicativo.
  2. Dê um nome ao seu aplicativo.
  3. Autenticação: Clique Gerar chaves públicas e privadas.
    • Um arquivo chamado private.key vai baixar.
    • Mova isso private.key arquivo da pasta “Downloads” para a sua vonage-deepgram-voice-agent pasta.
  4. Sob Recursos, ativar Voz.
  5. Nas configurações do Voice, defina os seguintes webhooks:
    • URL da resposta: https://{ngrok-url}/answer (Método: GET)
    • URL do evento: https://{ngrok-url}/event (Método: POST)
  6. Clique Criar um novo pedido na parte de baixo.

Vincular um número

  1. Acesse Números de telefone > Números para compra e adquirir um número com serviço de voz.
  2. Acesse Applications, selecione seu aplicativo de bot e clique em Editar.
  3. De acordo com o Numbers aba, clique em Link ao lado do número que você acabou de adquirir.

Configurar variáveis de ambiente

Criar um .env arquivo no diretório do seu projeto com as seguintes variáveis:

#==== 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: Por motivos de segurança, armazene suas chaves de API em variáveis de ambiente, em vez de inseri-las diretamente no código-fonte.

Criar o conector do agente de voz

Crie um arquivo chamado server.js e adicione o código a seguir. Este aplicativo atua como um conector entre a Voice API da Vonage e o 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: Ao executar localmente com o ngrok, o req.hostname pode não corresponder à URL do seu túnel público. Se os webhooks falharem, defina a URL base do ngrok como uma variável de ambiente e use-a para criar o eventUrl e wsUri em vez disso.

Adicionar a lógica do conector WebSocket

Agora adicione a lógica central do conector que faz a ponte entre a Voice API da Vonage e o Deepgram Voice Agent. Acrescente isso ao seu 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");
  });
});

Como funciona

Streaming de áudio simplificado: O áudio do Deepgram é enviado diretamente para a Vonage na forma de mensagens binárias. Não é necessário nenhum buffer manual nem sincronização — a Vonage cuida do buffer interno automaticamente.

Limpar mensagem de controle do buffer: Quando o Deepgram detecta que o usuário começou a falar (UserStartedSpeaking (evento), o aplicativo envia uma mensagem de controle CLEAR para a Vonage: {"action": "clear"}. Isso instrui a Voice API da Vonage a descartar imediatamente quaisquer quadros de áudio armazenados no buffer, criando uma funcionalidade de interrupção instantânea sem a necessidade de gerenciamento manual do buffer.

Confirmação do evento: A Vonage responde com um websocket:cleared evento para confirmar que o buffer foi esvaziado com sucesso. Isso permite que você acompanhe quando ocorrem interrupções.

Comunicação bidirecional: O áudio do usuário é transmitido da Vonage para a Deepgram na forma de mensagens binárias do WebSocket, enquanto o áudio do agente e as transcrições são transmitidos da Deepgram para a Vonage em tempo real.

Transcrições em tempo real: O Deepgram envia mensagens JSON contendo transcrições tanto da fala do usuário quanto das respostas do agente, que você pode registrar ou processar para fins de análise e garantia de qualidade.

Teste o aplicativo

  1. Certifique-se de que seu private.key O arquivo está no diretório do projeto.
  2. Inicie o ngrok em um terminal:
ngrok http 3000
  1. Execute seu servidor em outro terminal:
node server.js
  1. Ligue para o seu número da Vonage usando seu celular.
  2. O assistente de voz irá cumprimentá-lo e responder às suas perguntas por meio de uma conversa baseada em inteligência artificial.

Adicionar recurso de chamadas de saída

Para permitir que seu aplicativo faça chamadas de saída, adicione este endpoint ao seu 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 iniciar uma chamada de saída, abra seu navegador e acesse:

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

Substituir 15551234567 com o número de telefone para o qual você deseja ligar (no formato E.164, sem o + (sinal).

Personalize seu agente de voz

Você pode personalizar vários aspectos do agente de voz modificando o dgVoiceAgentSettings objeto:

Alterar o 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."
}

Alterar a voz

Atualize o DEEPGRAM_AGENT_SPEAK variável no seu .env arquivo. Veja Documentação dos modelos de TTS do Deepgram para ver as opções de voz disponíveis.

Personalizar o aviso do sistema

Modificar o prompt campo no think seção para alterar a personalidade e o comportamento do seu 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 passos