Wie man einen KI-Sprachagenten mit Vonage Voice API und Deepgram erstellt

Einführung

Dieser Leitfaden beschreibt den Prozess der Erstellung eines Echtzeit-KI-Sprachagenten mit der Voice API von Vonage und der Voice Agent-Plattform von Deepgram. Sie werden einen intelligenten Sprachassistenten erstellen, der Telefonanrufe beantwortet, Benutzern über automatische Spracherkennung (ASR) zuhört, Anfragen mit einem Large Language Model (LLM) verarbeitet und mit natürlich klingender Text-to-Speech-Sprache antwortet - alles in Echtzeit. Außerdem unterstützt das Setup die Unterbrechung von Gesprächen, auch bekannt als Barge-in.

Voraussetzungen

Bevor Sie beginnen, vergewissern Sie sich, dass Sie alles haben:

Einrichten Ihrer lokalen Umgebung

Erstellen Sie ein neues Verzeichnis für Ihr Projekt und installieren Sie die erforderlichen Abhängigkeiten:

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

Lokalen Server freilegen

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.

Bereitstellung Ihrer Vonage-Ressourcen

Melden Sie sich bei der Vonage Dashboard zu beginnen.

Erstellen einer Vonage-Applikation

Generieren Sie Ihre Anmeldedaten über das Dashboard und speichern Sie sie in dem soeben erstellten Ordner.

  1. Gehe zu Applications > Erstellen Sie eine neue Anwendung.
  2. Geben Sie Ihrer Anwendung einen Namen.
  3. Authentifizierung: Klicken Sie auf Öffentliche und private Schlüssel generieren.
    • Eine Datei namens private.key herunterladen wird.
    • Verschieben Sie diese private.key vonage-deepgram-voice-agent Ordner.
  4. Unter Fähigkeitenaktivieren Stimme.
  5. Legen Sie in den Spracheinstellungen die folgenden Webhooks fest:
    • Antwort-URL: https://{ngrok-url}/answer (Methode:
      GET
      )
    • URL der Veranstaltung: https://{ngrok-url}/event (Methode:
      POST
      )
  6. Klicken Sie auf Neue Anwendung generieren am unteren Ende.

Verknüpfung einer Number

  1. Gehe zu Phone Numbers > Numbers kaufen und kaufen Sie eine sprachaktivierte Nummer.
  2. Gehe zu Applications, wählen Sie Ihre Bot-Anwendung aus, und klicken Sie auf bearbeiten.
  3. Im Rahmen der Numbers Registerkarte, klicken Sie auf Link neben Ihrer neu erworbenen Nummer.

Umgebungsvariablen konfigurieren

Erstellen einer .env Datei in Ihrem Projektverzeichnis mit den folgenden Variablen:

#==== 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

Wichtig: Speichern Sie Ihre API-Schlüssel in Umgebungsvariablen, anstatt sie aus Sicherheitsgründen in Ihrem Quellcode zu kodieren.

Erstellen Sie den Voice Agent Connector

Erstellen Sie eine Datei mit dem Namen server.js und fügen Sie den folgenden Code hinzu. Diese Anwendung fungiert als Konnektor zwischen Vonage Voice API und 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!`);
});

Hinweis: Wenn man lokal mit ngrok arbeitet, wird die req.hostname nicht mit Ihrer öffentlichen Tunnel-URL übereinstimmen. Wenn Webhooks fehlschlagen, setzen Sie Ihre ngrok-Basis-URL als Umgebungsvariable und verwenden Sie sie, um die eventUrl und wsUri stattdessen.

Hinzufügen der WebSocket-Connector-Logik

Fügen Sie nun die zentrale Verbindungslogik hinzu, die die Vonage Voice API mit dem Deepgram Voice Agent verbindet. Hängen Sie dies an Ihre 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");
  });
});

Wie es funktioniert

Vereinfachtes Audio-Streaming: Audio von Deepgram wird direkt als Binärnachricht an Vonage gesendet. Es ist keine manuelle Pufferung oder Zeitsteuerung erforderlich - Vonage übernimmt die interne Pufferung automatisch.

Puffer löschen Steuermeldung: Wenn Deepgram erkennt, dass der Benutzer zu sprechen begonnen hat (UserStartedSpeaking Ereignis), sendet die Anwendung eine CLEAR-Kontrollnachricht an Vonage: {"action": "clear"}. Dadurch wird die Vonage Voice API angewiesen, alle gepufferten Audio-Frames sofort zu verwerfen, wodurch eine sofortige Barge-in-Funktionalität ohne manuelle Pufferverwaltung geschaffen wird.

Event-Bestätigung: Vonage antwortet mit einer websocket:cleared Ereignis, um zu bestätigen, dass der Puffer erfolgreich geleert wurde. So können Sie verfolgen, wann Unterbrechungen auftreten.

Bidirektionale Kommunikation: Benutzer-Audio fließt von Vonage → Deepgram als binäre WebSocket-Nachrichten, während Agenten-Audio und Transkripte von Deepgram → Vonage in Echtzeit fließen.

Abschriften in Echtzeit: Deepgram sendet JSON-Nachrichten, die Transkripte sowohl der Benutzerrede als auch der Agentenantworten enthalten, die Sie protokollieren oder zu Analyse- und Qualitätssicherungszwecken verarbeiten können.

Testen Sie die Applikation

  1. Stellen Sie sicher, dass Ihr private.key Datei befindet sich im Projektverzeichnis.
  2. Starten Sie ngrok in einem Terminal:
ngrok http 3000
  1. Starten Sie Ihren Server in einem anderen Terminal:
node server.js
  1. Rufen Sie Ihre Vonage-Telefonnummer von Ihrem Mobiltelefon aus an.
  2. Der Voice-Agent begrüßt Sie und beantwortet Ihre Fragen mithilfe von KI-gestützter Konversation.

Ausgehende Anrufe hinzufügen

Damit Ihre Anwendung ausgehende Anrufe tätigen kann, fügen Sie diesen Endpunkt zu Ihrem 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));
  }
});

Um einen abgehenden Anruf auszulösen, öffnen Sie Ihren Browser und navigieren Sie zu:

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

Ersetzen Sie 15551234567 mit der Rufnummer, die Sie anrufen möchten (im E.164-Format ohne die + Zeichen).

Passen Sie Ihren Sprachagenten an

Sie können verschiedene Aspekte des Sprachagenten anpassen, indem Sie die dgVoiceAgentSettings Objekt:

Ändern Sie das AI-Modell

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

Ändern Sie die Stimme

Aktualisieren Sie die DEEPGRAM_AGENT_SPEAK Variable in Ihrer .env Datei. Siehe Dokumentation der TTS-Modelle von Deepgram für die verfügbaren Sprachoptionen.

Anpassen der System-Eingabeaufforderung

Ändern Sie die prompt Feld in der think Abschnitt, um die Persönlichkeit und das Verhalten Ihres Agenten zu ändern:

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

Nächste Schritte