Cómo crear un IVR / Bot de voz avanzado

Esta guía muestra cómo crear un agente de IA basado en voz utilizando la Voice API de Vonage y OpenAI. Crearás un Bot de voz que responde a las llamadas entrantes, escucha la pregunta de un usuario utilizando Reconocimiento automático del habla (ASR)y responde con una respuesta inteligente generada por un LLM.

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

Exponga su servidor local

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

ngrok http 3000

ngrok will forward your local port 3000 to a public URL like https://{random-id}.ngrok.app.

Important: Keep this terminal window open while developing and testing. If you close ngrok, you’ll need to update your webhook URLs with the new address.

Copy this URL. You’ll need it when configuring your Vonage application in the next step.

Note: The free version of ngrok generates a new random URL each time you restart it. For a consistent URL during development, consider using ngrok reserved domains or upgrading to a paid plan.

Aprovisiona tus recursos de Vonage

Inicie sesión en Panel de Vonage para empezar.

Crear una aplicación de voz

  1. Vaya a Applications > Crear una nueva aplicación.
  2. Dale un nombre (por ejemplo, Voice AI Bot).
  3. En Capacidadesactivar Voz.
  4. En el Respuesta URL introduzca la URL de su base ngrok seguida de /webhooks/answer (por ejemplo https://{random-id}.ngrok.app/webhooks/answer). Establezca el método en
    GET
    .
  5. En el URL del evento introduzca la URL de su base ngrok seguida de /webhooks/events (por ejemplo https://{random-id}.ngrok.app/webhooks/events). Establezca el método en
    POST
    .
  6. Haga clic en Generar clave pública y privada. Guarde el private.key en la carpeta del proyecto (aunque no lo utilizaremos para este flujo ASR básico, es necesario para la creación de la aplicación).
  7. Haga clic en Guardar cambios.

Vincular un número

  1. Ir a Numbers > Comprar Numbers y adquiera un número vocal.
  2. Ir a Sus aplicacionesseleccione 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.

Construir el bot de voz

Cree un archivo llamado index.js y añada el siguiente código. Sustituya YOUR_OPENAI_API_KEY con tu llave real.

Nota: Cuando se ejecuta localmente con ngrok, req.protocol/req.get('host') puede no coincidir con la URL de su túnel público. Si los webhooks fallan, establezca la URL base de su túnel en config (por ejemplo una var env) y construya eventUrl de eso en su lugar.

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'));

Probar la aplicación

  1. Ejecute su servidor:

    node index.js
  2. Marca tu número de Vonage desde tu teléfono.

  3. Cuando se le solicite, formule una pregunta (por ejemplo ¿Por qué el cielo es azul? o Cuéntame un chiste).

  4. El robot capturará tu discurso, lo enviará a OpenAI y te leerá la respuesta utilizando Texto a voz.

Habilitar la conversación contextual

Para que la conversación resulte natural, debemos modificar la aplicación para que recuerde los intercambios anteriores y vuelva a preguntar al usuario.

Nota: Cuando se ejecuta localmente con ngrok, req.get('host') puede no coincidir con su host de túnel público. Si los webhooks fallan, construya eventUrl utilizando la URL base de tu túnel público (por ejemplo desde config/env) en lugar del host de la petición.

Actualice su index.js con esta lógica de 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);
});

Qué ha cambiado

  • El mapa de la sesión: Utilizamos el uuid para mantener separados los historiales de las distintas personas que llaman.
  • NCCO recursiva: en lugar de una simple talk ahora devolvemos un talk seguido de un input acción. Esto mantiene la línea abierta.
  • Memoria: Al pasar toda la history según OpenAI, el robot entiende ahora preguntas de seguimiento como Cuénteme más sobre eso.

Prueba la aplicación actualizada reiniciando tu servidor y marcando el número de Vonage vinculado a tu aplicación, como en la sección Probar la aplicación paso.

Añadir la herramienta "Conectar con humanos

Este paso consiste en actualizar las definiciones de las herramientas y añadir una rama a la lógica ASR que devuelva la información de Vonage connect acción.

Actualización index.js

Añada la nueva definición de herramienta y modifique la asr para gestionar la transferencia:

// 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')));
});

Cómo funciona

  • La intención: Cuando el usuario dice Quiero hablar con un gestor o Ayúdame, esto es muy difícil el LLM reconoce la intención y activa el connect_to_human función.
  • El Hand-off: Su servidor detiene el bucle ASR y envía el connect acción a Vonage.
  • La conexión: Vonage crea un nuevo tramo saliente hacia el HUMAN_AGENT_NUMBER y conecta las dos llamadas. Una vez establecida la conexión, la IA deja de "escuchar".

Reinicia tu servidor y llama al número de Vonage de tu aplicación. Cuando le pidas al bot que hable con un humano, debería decir la frase Por favor espere mientras le comunico con un representante humanoy, a continuación, le conectará con el número de teléfono que haya configurado como HUMAN_AGENT_NUMBER.

Próximos pasos

  • Voces personalizadas: Cambiar el nombre de la voz en el talk acción para una experiencia más marcada.
  • WebSocket Streaming: Para una latencia más baja, utilice WebSockets para transmitir audio en tiempo real.
  • Puntos finales: Conéctese a su centralita o Contact Center vía SIP o cree su propia interfaz web para un agente humano con Client SDK.
  • Versión .NET: Vea el mismo escenario IVR/voice-bot implementado en .NET en este entrada del blog.