Alexa – Teil 2

In Teil 1 (Alexa Blog Teil 1) dieser Blogreihe haben wir Alexa im Allgemeinen vorgestellt, in diesem Artikel wird es technischer: wie entwickelt man einen Alexa Skill?

Der Ablauf eines Alexa Skills

Im nachfolgenden Schaubild wird ein Ablaufdiagramm eines Alexa Skills dargestellt. Der Nutzer aktiviert durch Spracheingabe den Skill. Über das Alexa Skill Interface, die Verbindung aller Konfigurationen in der Amazon Developer Console, wird der Skill in einen Intent umgewandelt und an den Skill Service weitergegeben. Ein solcher Skill Service ist ein Endpunkt, der Intents verarbeiten und eine Alexa-kompatible Responses generieren kann, also der eigentliche Code des Skills. In unserem Fall verwenden wir AWS Lambda als Skill Service.

 

 

 

 

Die Sprache der Wahl: Node.js

AWS Lambda unterstützt Code in Python, Java, C# und eben Node.js. Vorteil der letzteren Programmiersprache ist es, dass verschiedene Bibliotheken bereits in Lambda integriert sind und direkt genutzt werden können. Das Alexa SDK wird von Amazon ebenfalls für (unter anderem) Node.js zur Verfügung gestellt.

Nach der Initialisierung des SDKs wird diesem ein Handler übergeben, der dann die Verarbeitung der Requests übernimmt. Die Keys der Requests sind teilweise vordefiniert („LaunchRequest“ wird beispielsweise immer beim Start eines Skills aufgerufen), für die Hauptfunktionen müssen eigene Keys im Interaction Model festgelegt werden.

 

exports.handler = function(event, context) {
    var alexa = Alexa.handler(event, context);
    alexa.appId = "amzn1.ask.skill.123456789";
    alexa.registerHandlers(handlers);
    alexa.execute();
}

var handlers = {
    'LaunchRequest': function() {
        this.emit(':ask', ‘Welcome to NMLunch’);
    },

    'AddLunchOption': function() {
        const personSlot = this.event.request.intent.slots.person;
        const destinationSlot = this.event.request.intent.slots.destination;
        
        if (personSlot == undefined || personSlot.value == undefined || destinationSlot == undefined || destinationSlot.value == undefined) {
            this.emit('AMAZON.HelpIntent');
            return false;
        }

        const self = this;
        updateLunchOptions(destinationSlot.value, personSlot.value, function(error) {
            if (error) {
                self.emit(':tell', 'Failed to update today\'s lunch options');
            } else {
                self.emit(':tell', 'Successfully added ' + personSlot.value + ' to team ' + destinationSlot.value);
            }
        });
   }
}

 

Das Alexa SDK stellt zwei Methoden bereit, um Antworten zu senden, „ask“ und „tell“. Der Unterschied beider Methoden ist, dass „ask“ auf eine weitere Eingabe wartet, während mit „tell“ die Session beendet wird. Innerhalb der Handler Funktionen stehen die Variablen (genannt „Slots“) zur Verfügung und können ausgelesen und weiterverarbeitet werden. Da der Nutzer ggf. nicht immer alle von uns definierten Slots in seiner Anfrage inkludiert, müssen wir davon ausgehen, dass diese leer bzw. „undefined“ sind, und darauf entsprechend reagieren (im Beispiel wird die Hilfefunktion aufgerufen, um dem Nutzer zu erklären, wie er den Skill korrekt aufruft).

 

Die Datenbank

Um die Daten nun persistent zu speichern, verwenden wir „DynamoDB“, die NoSQL Datenbanklösung von Amazon. Die Bibliothek, die notwendig ist, um auf DynamoDB zuzugreifen, ist bereits standardmäßig in Lambda integriert und kann direkt verwendet werden. Das nachfolgende Schema zeigt den Aufbau der Datenbank:

 

{
  "date": "today",
  "options": {
    "frischemarkt": {
      "person1": true,
      "person2": true
    },
    "kebab": {
      "person3": true,
      "person4": true
    },
    "pizza": {
      "person5": true
    }
  }
}

 

Der Key „date“ ist in diesem Prototypen immer mit dem Wert „today“ belegt, in den „options“ finden sich die verschiedenen Ziele mit den jeweiligen zugeordneten Personen. Da es bei DynamoDB nicht möglich ist reine Listen zu speichern, verwenden wir als Workaround ein Dictionary und weisen jeder Person den Wert „true“ zu.

Um nun von Lambda aus Daten in die Datenbank zu speichern, haben wir nachfolgende Hilfsfunktion gebaut, die aus dem Ziel und dem Namen der Person ein sogenanntes „DnyamoDB JSON“ baut, ein JSON, das in DynamoDB importiert werden kann und einer speziellen, von Amazon vorgegebenen, Struktur folgt.

 

function getJsonForAddingNameToLunchOption(destination, name) {
    var params = {
        TableName: "NMLunch",
        Key: {
            "date": "today"
        },
        UpdateExpression: "SET #o.#d.#n = if_not_exists(#o.#d.#n, :v)",
        ExpressionAttributeNames: {
            "#o": "options",
            "#d": destination.toLowerCase(),
            "#n": name
        },
        ExpressionAttributeValues: {
            ":v": true
        }
    };
    return params;
}

 

Wichtiger Hinweis an dieser Stelle: um die Komplexität des Codes zu verringern, werden in diesem Artikel bestimmte Szenarien übersprungen, so wird beispielsweise in obigem DynamoDB JSON davon ausgegangen, dass das Ziel bereits in der Datenbank existiert und der Name der Person nur noch zur Liste hinzugefügt werden muss. Ein doppeltes Hinzufügen zum gleichen Ziel wird in dem Snippet jedoch verhindert.

Abschließend muss nun noch das DynamoDB JSON in die Datenbank geschrieben werden. Das passiert mit der Funktion updateItem(). Nach Vollendung des Schreibvorgangs können wir dem Nutzer mitteilen, ob alles erfolgreich geklappt hat. Hierfür ist das Callback zuständig, das über „tell“ (vgl. erstes Code Snippet) den Nutzer informiert.

 

function updateLunchOptions(destination, name, callback) {
    const item = getJsonForAddingNameToLunchOption(destination, name);
    dynamo.updateItem(item, function(error, data) {
        if (error) {
            callback(error);
        } else {
            callback(null);
        }
    });
}

 

Fazit

Wir hoffen, wir konnten Ihnen einen guten Überblick über die einzelnen Schritte der (Custom) Skill Entwicklung geben. Die Code Snippets entspringen unserem lauffähigen Prototypen und zeigen beispielhaft die Ausschnitte, die für das Verständnis der Materie notwendig sind. Es gibt noch einige weitere Arten von Alexa Skills, die für unterschiedliche Szenarien verwendet werden können, beispielsweise den „Home Skill“ der sich besonders gut für die Integrierung von Smart Home Geräten eignet.

Falls Sie sich für das Thema interessieren, oder einen tieferen Einblick in die Entwicklung von Alexa Skills erhalten möchten, schicken Sie uns doch einfach eine Nachricht an skills@next-munich.com.

Tobias Fonfara, Technischer Projektmanager, tf@next-munich.com