Auftragsausführung mithilfe eines Webhooks erstellen

Auftragsausführungen in Dialogflow können sein: ein Dienst, eine Anwendung, ein Feed, eine Unterhaltung oder eine andere Logik, die eine Nutzeranfrage lösen kann. Im vorliegenden Fall wird eine Auftragsausführung benötigt, die einen Termin für einen Fahrradhändler vereinbaren kann. Uhrzeit und Datum werden dabei von dem Intent zur Terminvereinbarung Make Appointment vorgegeben.

Für dieses Szenario wird ein Webhook als Back-End-Dienst bereitgestellt. Dieser kann die Parameter für Uhrzeit und Datum vom Intent empfangen und mithilfe der API einen Termin in Google Kalender erstellen. Dazu müssen Sie zwei Aufgaben ausführen:

  • Anmeldedaten für die Google Calendar API abrufen
  • Einen neuen Kalender erstellen und den Code im Webhook konfigurieren.

Webhook mit dem Inline-Editor erstellen

Dialogflow hat in der Konsole einen Inline-Editor, mit dem Sie NodeJS-Code schreiben können, der dann als Webhook in Firebase bereitgestellt werden kann.

So erstellen Sie einen Webhook mit dem Inline-Editor von Dialogflow:

  1. Klicken Sie auf den Intent Make Appointment.
  2. Aktivieren Sie nun im Bereich Fulfillment die Funktion Enable Webhook call for this intent.
  3. Klicken Sie auf SAVE.
  4. Klicken Sie auf den Tab Fulfillment in der Navigationsleiste, um zur Seite für Auftragsausführungen zu gelangen.
  5. Schalten Sie nun den Schieberegler des Inline-Editors auf ENABLED.
  6. Löschen Sie den vorhandenen Inhalt im Tab package.json des Inline-Editors.
  7. Kopieren Sie dann den folgenden JSON-Code und fügen Sie ihn in den Tab package.json des Inline-Editors ein:

    {
      "name": "DialogflowFirebaseWebhook",
      "description": "Firebase Webhook dependencies for a Dialogflow agent.",
      "version": "0.0.1",
      "private": true,
      "license": "Apache Version 2.0",
      "author": "Google Inc.",
      "engines": {
        "node": "6"
      },
      "scripts": {
        "lint": "semistandard --fix \"**/*.js\"",
        "start": "firebase deploy --only functions",
        "deploy": "firebase deploy --only functions"
      },
      "dependencies": {
        "firebase-functions": "^2.0.2",
        "firebase-admin": "^5.13.1",
        "googleapis": "^27.0.0",
        "actions-on-google": "2.2.0",
        "dialogflow-fulfillment": "0.5.0"
      }
    }
    
  8. Löschen Sie den vorhandenen Code im Tab index.js des Inline-Editors.

  9. Kopieren Sie anschließend den folgenden Code und fügen Sie ihn in den Tab index.js des Inline-Editors ein:

    /**
     * Copyright 2017 Google Inc. All Rights Reserved.
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *      http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    
    'use strict';
    
    const functions = require('firebase-functions');
    const {google} = require('googleapis');
    const {WebhookClient} = require('dialogflow-fulfillment');
    
    // Enter your calendar ID and service account JSON below.
    const calendarId = '<INSERT CALENDAR ID HERE>'; // Example: 6ujc6j6rgfk02cp02vg6h38cs0@group.calendar.google.com
    const serviceAccount = {}; // The JSON object looks like: { "type": "service_account", ... }
    
    // Set up Google Calendar service account credentials
    const serviceAccountAuth = new google.auth.JWT({
      email: serviceAccount.client_email,
      key: serviceAccount.private_key,
      scopes: 'https://www.googleapis.com/auth/calendar'
    });
    
    const calendar = google.calendar('v3');
    process.env.DEBUG = 'dialogflow:*'; // It enables lib debugging statements
    
    const timeZone = 'America/Los_Angeles';  // Change it to your time zone
    const timeZoneOffset = '-07:00';         // Change it to your time zone offset
    
    exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
      const agent = new WebhookClient({ request, response });
    
      function makeAppointment (agent) {
        // Use the Dialogflow's date and time parameters to create Javascript Date instances, 'dateTimeStart' and 'dateTimeEnd',
        // which are used to specify the appointment's time.
        const appointmentDuration = 1;// Define the length of the appointment to be one hour.
        const dateTimeStart = convertParametersDate(agent.parameters.date, agent.parameters.time);
        const dateTimeEnd = addHours(dateTimeStart, appointmentDuration);
        const appointmentTimeString = getLocaleTimeString(dateTimeStart);
        const appointmentDateString = getLocaleDateString(dateTimeStart);
        // Check the availability of the time slot and set up an appointment if the time slot is available on the calendar
        return createCalendarEvent(dateTimeStart, dateTimeEnd).then(() => {
          agent.add(`Got it. I have your appointment scheduled on ${appointmentDateString} at ${appointmentTimeString}. See you soon. Good-bye.`);
        }).catch(() => {
          agent.add(`Sorry, we're booked on ${appointmentDateString} at ${appointmentTimeString}. Is there anything else I can do for you?`);
        });
      }
      let intentMap = new Map();
      intentMap.set('Make Appointment', makeAppointment);  // It maps the intent 'Make Appointment' to the function 'makeAppointment()'
      agent.handleRequest(intentMap);
    });
    
    function createCalendarEvent (dateTimeStart, dateTimeEnd) {
      return new Promise((resolve, reject) => {
        calendar.events.list({  // List all events in the specified time period
          auth: serviceAccountAuth,
          calendarId: calendarId,
          timeMin: dateTimeStart.toISOString(),
          timeMax: dateTimeEnd.toISOString()
        }, (err, calendarResponse) => {
          // Check if there exists any event on the calendar given the specified the time period
          if (err || calendarResponse.data.items.length > 0) {
            reject(err || new Error('Requested time conflicts with another appointment'));
          } else {
            // Create an event for the requested time period
            calendar.events.insert({ auth: serviceAccountAuth,
              calendarId: calendarId,
              resource: {summary: 'Bike Appointment',
                start: {dateTime: dateTimeStart},
                end: {dateTime: dateTimeEnd}}
            }, (err, event) => {
              err ? reject(err) : resolve(event);
            }
            );
          }
        });
      });
    }
    
    // A helper function that receives Dialogflow's 'date' and 'time' parameters and creates a Date instance.
    function convertParametersDate(date, time){
      return new Date(Date.parse(date.split('T')[0] + 'T' + time.split('T')[1].split('-')[0] + timeZoneOffset));
    }
    
    // A helper function that adds the integer value of 'hoursToAdd' to the Date instance 'dateObj' and returns a new Data instance.
    function addHours(dateObj, hoursToAdd){
      return new Date(new Date(dateObj).setHours(dateObj.getHours() + hoursToAdd));
    }
    
    // A helper function that converts the Date instance 'dateObj' into a string that represents this time in English.
    function getLocaleTimeString(dateObj){
      return dateObj.toLocaleTimeString('en-US', { hour: 'numeric', hour12: true, timeZone: timeZone });
    }
    
    // A helper function that converts the Date instance 'dateObj' into a string that represents this date in English.
    function getLocaleDateString(dateObj){
      return dateObj.toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric', timeZone: timeZone });
    }
    
  10. Klicken Sie auf DEPLOY (Bereitstellen).

    Abbildung 7. Flussdiagramm, das die Verbindung zur Webhook-Funktion makeAppointment() zeigt.

Der Intent Make Appointment ist jetzt mit der Funktion makeAppointment() im Webhook verbunden. Betrachten wir einmal den folgenden Code-Teil im Webhook:

exports.dialogflowFirebaseFulfillment = functions.https.onRequest((request, response) => {
  const agent = new WebhookClient({ request, response });

  function makeAppointment (agent) {
    // Calculate appointment start and end datetimes (end = +1hr from start)
    const dateTimeStart = convertParametersDate(agent.parameters.date, agent.parameters.time);
    const dateTimeEnd = addHours(dateTimeStart, 1);
    const appointmentTimeString = getLocaleTimeString(dateTimeStart);
    const appointmentDateString = getLocaleDateString(dateTimeStart);
    // Check the availability of the time, and make an appointment if there is time on the calendar
    return createCalendarEvent(dateTimeStart, dateTimeEnd).then(() => {
      agent.add(`Got it. I have your appointment scheduled on ${appointmentDateString} at ${appointmentTimeString}. See you soon. Good-bye.`);
    }).catch(() => {
      agent.add(`Sorry, we're booked on ${appointmentDateString} at ${appointmentTimeString}. Is there anything else I can do for you?`);
    });
  }
  let intentMap = new Map();
  intentMap.set('Make Appointment', makeAppointment);
  agent.handleRequest(intentMap);
});

Beachten Sie im obigen Code die folgende Zeile:

intentMap.set('Make Appointment', makeAppointment);

Die Funktion set() wird für das Map-Objekt intentMap aufgerufen. Diese Funktion verknüpft einen Intent mit einer bestimmten Funktion im Code. Hier wird mit dem Aufruf die Zuordnung zwischen dem Intent Make Appointment und der Funktion makeAppointment() hergestellt.

Die Funktion makeAppointment(agent){} liest die Parameterwerte für Datum und Uhrzeit aus dem Eingabeobjekt agent über agent.parameters.date und agent.parameters.time. Nachdem die Datums- und Uhrzeitwerte geparst und formatiert wurden, ruft die Funktion eine benutzerdefinierte Funktion createCalendarEvent() auf, die einen API-Aufruf an Google Kalender ausführt, um einen Termin im Kalender zu erstellen.

Schließlich wird mit der Funktion agent.add() ein benutzerdefinierter String als Antwort an den Nutzer gesendet. Antworten können auch über die Dialogflow-Konsole bereitgestellt werden. Der Unterschied besteht darin, dass Sie mit einem Webhook die Logik des Codes nutzen können, um hochdynamische Antworten zu erstellen. Wenn der Agent beispielsweise einen Termin erfolgreich plant, antwortet er mit der folgenden Antwort:

Got it. I have your appointment scheduled on ${appointmentDateString} at ${appointmentTimeString}. See you soon. Good-bye.

Kann der Agent zum angegebenen Zeitpunkt und Datum jedoch keinen Termin vereinbaren, gibt er die folgende Antwort zurück:

Sorry, we're booked on ${appointmentDateString} at ${appointmentTimeString}. Is there anything else I can do for you?

In diesem Moment können wir den Code nicht richtig testen, da er keinen Zugriff auf die Google Calendar API hat. Im Code zur Personalisierung der Google Calendar API finden Sie die folgenden Variablen:

const calendarId = '<INSERT CALENDAR ID HERE>'; // Example: 6ujc6j6rgfk02cp02vg6h38cs0@group.calendar.google.com
const serviceAccount = {}; // The JSON object looks like: { "type": "service_account", ... }

Als Nächstes müssen sie den Code konfigurieren, um auf die Google Calendar API zuzugreifen.

Anmeldedaten für die Google Calendar API abrufen

Gehen Sie folgendermaßen vor, um Anmeldedaten für die Google Calendar API abzurufen:

  1. Klicken Sie in der Navigationsleiste von Dialogflow auf die Schaltfläche für Einstellungen (⚙) neben dem Namen des Agents.
  2. Klicken Sie dann in der Tabelle GOOGLE PROJECT auf den Link der Projekt-ID, um die Google Cloud Platform Console zu öffnen.
  3. Klicken Sie nun in der Google Cloud Platform Console auf das Dreistrich-Menü ☰ und wählen Sie APIs & Services > Library aus.
  4. Klicken Sie auf die Karte Google Calendar API.

  5. Klicken Sie dann auf der Seite der Google Calendar API auf ACTIVATE.

  6. Klicken Sie in der Navigationsleiste auf Credentials.

  7. Klicken Sie dann auf Create credentials und wählen Sie aus dem Drop-down-Menü den Service account key aus.

  8. Klicken Sie auf das Drop-down-Menü Service account und wählen Sie New service account aus.

  9. Geben Sie im Eingabefeld Name des Dienstkontos den Wert bike-shop-calendar ein.

  10. Wählen Sie für das Feld Role (Rolle) aus dem Drop-down-Menü Project > Owner (Projekt > Eigentümer) aus.

  11. Klicken Sie auf Erstellen. (Es wird nun eine JSON-Datei heruntergeladen, die Ihren Dienstkontoschlüssel enthält.)

Neuen Kalender erstellen und den Code im Webhook konfigurieren

Als Nächstes müssen Sie einen neuen Kalender für den Fahrradhändler erstellen, damit Sie Termine erfassen können. Verwenden Sie dabei die Informationen aus der heruntergeladenen JSON-Datei, um den Webhook-Code des Agents in den neuen Google Kalender zu integrieren. Bevor Sie mit den nächsten Anleitungsschritten fortfahren, prüfen Sie besser noch, ob Sie den Inhalt der JSON-Datei sehen und kopieren können.

So erstellen Sie einen neuen Kalender und schließen die Integration ab:

  1. Öffnen Sie den Google Kalender.
  2. Klicken Sie in der Navigationsleiste von Google Kalender neben dem Eingabefeld Add a friend's calendar auf die Schaltfläche +.
  3. Wählen Sie aus dem Drop-down-Menü die Option New calendar aus.
  4. Geben Sie im Feld Name die Option Mary's Bike Shop ein.
  5. Klicken Sie auf den Link CREATE CALENDAR (Kalender erstellen). Der neue Kalender Mary's Bike Shop wird im Fenster Settings for my calendars (Einstellungen für meine Kalender) der Navigationsleiste erstellt.
  6. Klicken Sie auf Mary's Bike Shop und wählen Sie Share with specific people (Für bestimmte Personen freigeben) aus.
  7. Klicken Sie im Bereich Share with specific people auf ADD PEOPLE. Das Pop-up-Fenster Share with specific people wird nun auf dem Bildschirm angezeigt.
  8. Öffnen Sie die zuvor heruntergeladene JSON-Datei und kopieren Sie die E-Mail-Adresse ohne Anführungszeichen in das Feld client_email:

    {
      "type": "service_account",
      "project_id": "marysbikeshop-...",
      "private_key_id": "...",
      "private_key": "...",
      "client_email": "bike-shop-calendar@<project-id>.iam.gserviceaccount.com",
      "client_id": "...",
      "auth_uri": "https://accounts.google.com/o/oauth2/auth",
      "token_uri": "https://accounts.google.com/o/oauth2/token",
      "auth_provider_x509_cert_url": "https://www.googleapis.com...",
      "client_x509_cert_url": "https://www.googleapis.com/robot/v..."
    }
    
  9. Geben Sie im Pop-up-Fenster Share with specific people (Für bestimmte Personen freigeben) im Eingabefeld Add email or name (E-Mail oder Namen hinzufügen) die E-Mail-Adresse aus client_email ein.

  10. Wählen Sie dann im Drop-down-Menü Permissions (Berechtigungen) das Element Make changes to events (Änderungen an Ereignissen vornehmen) aus.

  11. Klicken Sie schließlich auf SEND.

  12. Scrollen Sie im Abschnitt Integrate calendar nach unten und kopieren Sie den Inhalt der Kalender-ID.

  13. Wechseln Sie dann in der Dialogflow-Konsole zur Seite Fulfillment des Agents.

  14. Suchen Sie im Tab index.js des Inline-Editors nach der Variable calendarId.

    const calendarId = '<INSERT CALENDAR ID HERE>';
    
  15. Fügen Sie den Inhalt der Calendar ID (Kalender-ID) ein, um <INSERT CALENDAR ID HERE> zu ersetzen, wie im folgenden Code gezeigt:

    const calendarId = 'fh5kgikn3t4vvmc73423875rjc@group.calendar.google.com';
    
  16. Suchen Sie im Tab index.js des Inline-Editors nach der Variable serviceAccount.

    const serviceAccount = {};
    
  17. Öffnen Sie die zuvor heruntergeladene JSON-Datei noch einmal und kopieren Sie den gesamten Inhalt, auch die geschweiften Klammern ({}) ganz außen.

  18. Fügen Sie den Inhalt wie im folgenden Code-Snippet gezeigt ein, um den Wert von serviceAccount zu ersetzen:

    const serviceAccount = {
      "type": "service_account",
      "project_id": "marysbikeshop-...",
      "private_key_id": "...",
      "private_key": "...",
      "client_email": "bike-shop-calendar@<project-id>.iam.gserviceaccount.com",
      "client_id": "...",
      "auth_uri": "https://accounts.google.com/o/oauth2/auth",
      "token_uri": "https://accounts.google.com/o/oauth2/token",
      "auth_provider_x509_cert_url": "https://www.googleapis.com...",
      "client_x509_cert_url": "https://www.googleapis.com/robot/v..."
    };
    
  19. Klicken Sie dann auf DEPLOY (Bereitstellen).

  20. Testen Sie den Agent, um zu überprüfen, ob neue Termine im Kalender korrekt erstellt werden.

Weitere Verbesserung

Aktuell können Agents Dienste nur bereitstellen, wenn die Nutzer gut kooperieren. Unterhaltungen werden nicht gut verarbeitet, wenn Nutzer Dinge sagen, die nicht den skriptbasierten Dialogen entsprechen. Bei Unterhaltungsschnittstellen besteht ein großer Teil der Arbeit darin, den Agent so zu gestalten, dass er auf unerwartete Äußerungen von Nutzern so reagieren kann, dass er wiederhergestellt wird. Wir empfehlen Ihnen, sich mit dem leistungsstarken Feature Kontexte von Dialogflow vertraut zu machen. Damit können Agents den Gesprächsfluss effektiv steuern.