Como criar um IVR avançado / bot de voz

Este guia mostra como criar um agente de IA baseado em voz usando a Voice API da Vonage e a OpenAI. Você criará um Bot de voz que atende chamadas recebidas, ouve a pergunta do usuário por meio do Reconhecimento Automático de Fala (ASR) e responde com uma resposta inteligente gerada por um LLM.

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-voice-bot cd vonage-voice-bot npm init -y npm install express openai

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 um aplicativo de voz

  1. Acesse Applications > Criar um novo aplicativo.
  2. Dê um nome a ele (por exemplo, Bot de IA por voz).
  3. Clique Gerar chave pública e chave privada. Salve o private.key arquivo na pasta do seu projeto (embora não vamos usá-lo neste fluxo básico do ASR, ele é necessário para a criação do aplicativo).
  4. Sob Recursos, ativar Voz.
  5. No URL da resposta campo, digite sua URL base do ngrok seguida de /webhooks/answer (por exemplo, https://{random-id}.ngrok.app/webhooks/answer). Defina o método como GET.
  6. No URL do evento campo, digite sua URL base do ngrok seguida de /webhooks/events (por exemplo, https://{random-id}.ngrok.app/webhooks/events). Defina o método como POST.
  7. 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.

Crie o bot de voz

Crie um arquivo chamado index.js e adicione o código a seguir. Substitua YOUR_OPENAI_API_KEY com sua chave real.

Observação: Ao executar localmente com o ngrok, req.protocol/req.get('host') pode não corresponder à URL do seu túnel público. Se os webhooks falharem, defina a URL base do seu túnel na configuração (por exemplo, em uma variável de ambiente) e compile eventUrl a partir disso, então.

const express = require('express');
const { OpenAI } = require('openai');

const app = express();
app.use(express.json());

const openai = new OpenAI({ apiKey: 'YOUR_OPENAI_API_KEY' });

// 1. Handle the initial call
app.get('/webhooks/answer', (req, res) => {
  const ncco = [
    {
      action: 'talk',
      text: 'Hi, I am your AI assistant. How can I help you today?'
    },
    {
      action: 'input',
      eventUrl: [`${req.protocol}://${req.get('host')}/webhooks/asr`],
      type: ['speech'],
      speech: {
        language: 'en-us',
        endOnSilence: 1
      }
    }
  ];
  res.json(ncco);
});

// 2. Process the Speech-to-Text result and query OpenAI
app.post('/webhooks/asr', async (req, res) => {
  const speechResults = req.body.speech?.results;

  if (!speechResults || speechResults.length === 0) {
    return res.json([{ action: 'talk', text: 'I am sorry, I didn\'t catch that. Goodbye.' }]);
  }

  const userText = speechResults[0].text;
  console.log(`User said: ${userText}`);

  try {
    // Request a completion from OpenAI
    const completion = await openai.chat.completions.create({
      model: "gpt-4o",
      messages: [
        { role: "system", content: "You are a helpful assistant on a phone call. Keep answers concise." },
        { role: "user", content: userText }
      ],
    });

    const aiResponse = completion.choices[0].message.content;

    // Respond back to the user
    res.json([{ action: 'talk', text: aiResponse }]);
    
  } catch (error) {
    console.error("OpenAI Error:", error);
    res.json([{ action: 'talk', text: 'I encountered an error processing your request.' }]);
  }
});

// 3. Log call events
app.post('/webhooks/events', (req, res) => {
  console.log('Event:', req.body.status);
  res.sendStatus(200);
});

app.listen(3000, () => console.log('Server running on port 3000'));

Teste o aplicativo

  1. Execute seu servidor:

    node index.js
  2. Ligue para o seu número da Vonage pelo seu telefone.

  3. Quando for solicitado, faça uma pergunta (por exemplo, Por que o céu é azul? ou Conta-me uma piada).

  4. O bot vai capturar o que você disser, enviar para a OpenAI e ler a resposta para você usando Conversão de texto em fala.

Ativar conversação contextual

Para que a conversa pareça natural, precisamos modificar o aplicativo para que ele se lembre das trocas anteriores e solicite novas informações ao usuário.

Observação: Ao executar localmente com o ngrok, req.get('host') pode não corresponder ao host do seu túnel público. Se os webhooks falharem, crie eventUrl usando a URL base do seu túnel público (por exemplo, do arquivo config/env) em vez do host da solicitação.

Atualize seu index.js com essa lógica com estado:

// 1. Add a Map to store conversation history by Call UUID
const sessions = new Map();

// Helper to generate a NCCO that "loops" back to ASR
const getConversationalNCCO = (text, host) => [
  { action: 'talk', text: text },
  {
    action: 'input',
    eventUrl: [`https://${host}/webhooks/asr`],
    type: ['speech'],
    speech: { language: 'en-us', endOnSilence: 1 }
  }
];

app.get('/webhooks/answer', (req, res) => {
  const uuid = req.query.uuid;
  // Initialize history for this specific caller
  sessions.set(uuid, [{ role: "system", content: "You are a helpful, concise assistant." }]);
  
  res.json(getConversationalNCCO('Hello! What is on your mind?', req.get('host')));
});

app.post('/webhooks/asr', async (req, res) => {
  const { uuid, speech } = req.body;
  const userText = speech?.results?.[0]?.text;

  if (!userText) {
    sessions.delete(uuid);
    return res.json([{ action: 'talk', text: 'Goodbye!' }]);
  }

  // Retrieve history and append the new question
  let history = sessions.get(uuid) || [];
  history.push({ role: "user", content: userText });

  const completion = await openai.chat.completions.create({
    model: "gpt-4o",
    messages: history,
  });

  const aiResponse = completion.choices[0].message.content;
  history.push({ role: "assistant", content: aiResponse });
  sessions.set(uuid, history);

  // Return the AI response AND listen for the next question
  res.json(getConversationalNCCO(aiResponse, req.get('host')));
});

// Clean up memory when the call ends
app.post('/webhooks/events', (req, res) => {
  if (req.body.status === 'completed') sessions.delete(req.body.uuid);
  res.sendStatus(200);
});

O que mudou

  • O mapa da sessão: Utilizamos o uuid para manter separados os históricos de diferentes chamadores.
  • NCCO recursivo: Em vez de um simples talk ação, agora retornamos um talk seguido por um input ação. Isso mantém a linha aberta.
  • Memória: Ao passar todo o history Graças à integração com a OpenAI, o bot agora compreende perguntas complementares como Conta-me mais sobre isso.

Experimente o aplicativo atualizado reiniciando seu servidor e discando o número da Vonage vinculado ao seu aplicativo, conforme indicado no Teste o aplicativo passo.

Adicionar a ferramenta “Conectar-se a uma pessoa”

Esta etapa envolve atualizar as definições de suas ferramentas e adicionar uma ramificação à sua lógica ASR que retorne o Vonage connect ação.

Atualização index.js

Adicione a nova definição da ferramenta e modifique o asr webhook para lidar com a transferência:

// Define the transfer tool
const tools = [
  {
    type: "function",
    function: {
      name: "connect_to_human",
      description: "Call this when the user wants to speak to a real person or a human agent.",
      parameters: { type: "object", properties: {} } // No arguments needed
    }
  }
];

const HUMAN_AGENT_NUMBER = '15551234567'; // Replace with your phone number

app.post('/webhooks/asr', async (req, res) => {
  const { uuid, speech } = req.body;
  const userText = speech?.results?.[0]?.text;

  if (!userText) return res.json([{ action: 'talk', text: 'Goodbye.' }]);

  let history = sessions.get(uuid) || [];
  history.push({ role: "user", content: userText });

  const response = await openai.chat.completions.create({
    model: "gpt-4o",
    messages: history,
    tools: tools // Provide the tool to the LLM
  });

  const message = response.choices[0].message;

  // Check if the AI wants to transfer the call
  if (message.tool_calls && message.tool_calls[0].function.name === 'connect_to_human') {
    console.log(`Transferring call ${uuid} to human agent...`);
    
    // Clean up session since the AI is leaving the call
    sessions.delete(uuid);

    // Return the "connect" NCCO
    return res.json([
      { 
        action: 'talk', 
        text: 'Please hold while I connect you to a human representative.' 
      },
      {
        action: 'connect',
        from: 'YOUR_VONAGE_NUMBER', // Your linked Vonage number
        endpoint: [{ type: 'phone', number: HUMAN_AGENT_NUMBER }]
      }
    ]);
  }

  // Regular conversational flow
  const aiResponse = message.content;
  history.push({ role: "assistant", content: aiResponse });
  sessions.set(uuid, history);

  res.json(getConversationalNCCO(aiResponse, req.get('host')));
});

Como funciona

  • A intenção: Quando o usuário diz Quero falar com um gerente ou Me ajude, isso é muito difícil O LLM reconhece a intenção e aciona o connect_to_human função.
  • A transferência: Seu servidor interrompe o ciclo de ASR e envia o connect ação contra a Vonage.
  • A conexão: a Vonage cria uma nova rota de saída para o HUMAN_AGENT_NUMBER e une as duas chamadas. A IA deixa de “ouvir” assim que a conexão é estabelecida.

Reinicie seu servidor e ligue para o número da Vonage do seu aplicativo. Quando você pedir ao bot para falar com uma pessoa, ele deve dizer a frase Aguarde, por favor, enquanto eu o transfiro para um atendente humano, e, em seguida, conectá-lo ao número de telefone que você definiu como HUMAN_AGENT_NUMBER.

Próximos passos

  • Vozes personalizadas: Alterar o nome da voz no talk medida para proporcionar uma experiência mais alinhada à marca.
  • Transmissão via WebSocket: Para reduzir a latência, use WebSockets para transmitir áudio em tempo real.
  • Terminais: Conecte-se ao seu PBX ou à sua central de atendimento por meio do SIP ou crie sua própria interface web para um agente humano com Client SDK.
  • Versão .NET: Veja o mesmo cenário de IVR/bot de voz implementado em .NET neste postagem no blog.