thomaskekeisen.de

Aus dem Leben eines Bildschirmarbyters

Kleiner Skill zur Übung

In meinem Freundeskreis ist das von Google-Translate gesprochene "Nörgel Nörgel" eine gängige Reaktion auf mitgeteilte First-World-Problems. Grund genug für mich, das ganze als praktischen Skill für Amazon Echo zu implementieren. Natürlich zuletzt auch, um Erfahrungen mit der neuen Plattform zu sammeln.

Die Grundfunktion des Skills soll sein, mit ein bis fünf "Nörgel" auf jede Anfrage des Nutzers zu antworten. Außerdem soll der Skill auf den "Hilfe"- sowie "Stop"-Befehl reagieren können und auf die Frage "Wer ist nerviger?", gefolgt von zwei beliebigen deutschen Namen, einen dieser Namen zurückgeben.

Alexa, wer ist nerviger? Thomas oder Cornelia? Beispielhafte Frage für meinen Alexa Übugsskill

Alexa Skill: Nörgelgenerator

Smart Home 31. März 2017 Video öffnen

Die Basics

Ein Skill für Amazon Echo besteht prinzipiell aus vier Dingen: Einem Rufnamen, einer Liste mit Beispielsätzen, einer zweiten Liste, die die Beispielsätze mit Funktionsaufrufen im Code verknüpft und natürlich dem eigentlichen Programmcode. Die Spracherkennung und -ausgabe übernimmt der Code von Amazon. Als Entwickler ist ein Skill nichts weiter als ein Chat-Bot auf Textbasis mit der Ausnahme, dass es Dialekte gibt, auf die man softwareseitig eingehen kann.

Ich habe mich für die Implementierung in node.js sowie das Hosting auf AWS Lambda entschieden. Die Dokumentation von Alexa-Skills-Kit-SDK für Node.js macht schon beim Überfliegen einen guten Eindruck. Abgesehen von etwas optionalem Session-Handling scheint die grundsätzliche Entwicklung eines Skills mit der sauberen Formulierung möglichst vieler Beispielsätze sowie dem Feuern des Events :tell oder :ask erledigt zu sein. Eine freundliches "Guten Morgen!" würde also wie folgt geschickt:

            
                this.emit(':tell', 'Guten Morgen!');
            
        

Das Interaction Model

Als auf der Amazon Developer Console registrierter Entwickler wird man beim Anlegen eines neuen Alexa-Skills direkt um einen Rufnamen sowie ein "Intent Schema" und "Sample Utterances" gebeten. Diese drei einfachen Textfelder steuern die Funktionalität und Spracherkennung auf Seitens des Codes und der Server von Amazon. Als Rufname (oder auch "Invocation Name") wird der Name des Skills vergeben - in meinem Fall beispielsweise "Nörgelgenerator".

Das sogenannte "Intent Schema" beschreibt Aktionen, die der Skill ausführen soll und beschreibt alle möglichen Ereignisse, die im Gespräch passieren können, in einer abstrakten Form. In meinem Beispiel reagiere ich auf interne Ereignisse wie AMAZON.CancelIntent (Die Unterhaltung wurde abgebrochen) oder AMAZON.HelpIntent (Der Nutzer hat um Hilfe gebeten) sowie eigene Ereignisse wie NoergelIntent (Der Nutzer sagt "Nörgel nörgel") oder PersonIntent (Der Nutzer fragt, wer nerviger ist).

Letzterer Intent ist außerdem mit sogenannten Slots angereichert, die Variablen darstellen. In meinem Fall habe ich zwei Variablen mit dem Typ AMAZON.DE_FIRST_NAME gewählt. Alexa reagiert also nur mit meinem Skill, wenn deutsche Vornamen verglichen werden, nicht etwa bei Buchtiteln.

            
                {
                    "intents": [
                        {
                            "intent": "AMAZON.CancelIntent",
                            "slots": []
                        },
                        {
                            "intent": "AMAZON.HelpIntent",
                            "slots": []
                        },
                        {
                            "intent": "AMAZON.NoIntent",
                            "slots": []
                        },
                        {
                            "intent": "AMAZON.StopIntent",
                            "slots": []
                        },
                        {
                            "intent": "AMAZON.YesIntent",
                            "slots": []
                        },
                        {
                            "intent": "NoergelIntent",
                            "slots": []
                        },
                        {
                            "intent": "PersonIntent",
                            "slots": [
                                {
                                    "name": "FirstPerson",
                                    "type": "AMAZON.DE_FIRST_NAME"
                                },
                                {
                                    "name": "SecondPerson",
                                    "type": "AMAZON.DE_FIRST_NAME"
                                }
                            ]
                        }
                    ]
                }
            
        

Um diese Intents mit der entsprechenden Sprache zu verknüpfen, werden die Utterances (zu deutsch: Äußerungen) definiert. Eine Äußerung besteht immer aus einem Ereignisnamen (also Beispielsweise NoergelIntent oder PersonIntent ), gefolgt vom gesprochenen Satz ohne Satzzeichen. Variablen werden mit geschweiften Klammern (also { und } ) umschlossen. In meinem Skill-Code definiere ich, was der Nutzer sagen kann, um einen der Intents zu aktivieren. Auch verwende ich die zuvor definierten Slots FirstPerson und SecondPerson .

            
                NoergelIntent nörgel
                NoergelIntent nörgel nörgel
                NoergelIntent nörgel nörgel nörgel
                NoergelIntent nörgel nörgel nörgel nörgel
                NoergelIntent nörgel nörgel nörgel nörgel nörgel
                PersonIntent Wer nervt mehr {FirstPerson} oder {SecondPerson}
                PersonIntent Wer nerviger ist {FirstPerson} oder {SecondPerson}
                PersonIntent Ist {FirstPerson} oder {SecondPerson} nerviger
                PersonIntent Wer ist eigentlich nerviger {FirstPerson} oder {SecondPerson}
                PersonIntent Wer geht einem mehr auf die Nerven {FirstPerson} oder {SecondPerson}
                PersonIntent Wer kann einem mehr auf die nerven gehen {FirstPerson} oder {SecondPerson}
            
        

Hosting bei Amazon

Gehostet wird die Alexa-Erweiterung in den eigenen Reihen. Per AWS Lambda kann der Skill quasi unbegrenzt skalierbar gehostet werden. Mit AWS Lambda ist es möglich, einfache Code-Schnipsel in einer bereits bestehenden Cloud auszuführen, es werden also keine eigenen Server benötigt. Für das Anlegen der neuen Lambda-Funktion muss lediglich ein funktionierender Name sowie ein Trigger gesetzt werden. Der Trigger "Alexa Skills Kit" muss mindestens gesetzt sein damit der Amazon Echo die Funktion auch aufrufen darf.
So gibt Amazon die ARN (also Adresse) der Lambda-Funktion preis
Die weitere Konfiguration sollte an dieser Stelle aus der node.js-Welt bekannt sein. Wer sich unsicher ist kann auch aus den Templates, die für Lambda-Funktionen zur Verfügung stehen, bedienen. Diese funktionieren gut, solange der Code im Web-Editor in der AWS Console verwendet wird. Updates über das AWS CLIüberschreiben im Zweifel geladene, externe Bibliotheken.

Die eigene Logik

Der Skill selbst ist aus Javascript-Sicht ein schlankes Stück Code. Das Alexa-Skills-Kit-SDK für Node.js ist sehr einfach gestrickt. Prinzipiell muss nur eine Definition erstellt werden, die das SDK lädt und auf die entsprechenden Intents mit Aufrufen von Javascript-Funktionen reagieren kann. Hier werden pro zuvor definierten Intent verschiedene Funktionen aufgerufen. Es muss also nur noch die Antwort formuliert oder die Eingabe verarbeitet werden - also meistens Code, der sich auf ein paar einfache Zeilen beschränkt.

Die in noergelHandlers definierten Funktionen werden aufgerufen, wenn der Amazon Echo entschieden hat, dass die erkannte Nutzerspracheingabe zum Skill passt. Je nach Kontext versucht der Code, mit dem Nutzer in die Interkation zu kommen. Um aber in die von Amazon gestellten Richtlinien zu passen wird von mir nur im NoergelIntent mit einem :ask -Ereignis reagiert, was Alexa dazu veranlasst, auf eine weitere Antwort des Nutzers zu warten. In den sonstigen Fällen beendet der Skill mit einem :tell -Ereignis das Gespräch. Was mein textBuilder genau tut, kann auf GitHub nachgelesen werden.

            
                var Alexa       = require('alexa-sdk');
                var language    = require('./lib/language');
                var textBuilder = require('./lib/textBuilder');
                var util        = require('./lib/util');

                var noergelHandlers = {
                    'AMAZON.CancelIntent': function () {
                        this.emit(':tell', textBuilder.noergel());
                    },
                    'AMAZON.HelpIntent': function () {
                        this.emit(':tell', textBuilder.replacePlaceholders(language.help));
                    },
                    'AMAZON.NoIntent': function () {
                        this.emit(':tell', textBuilder.noergel());
                    },
                    'AMAZON.StopIntent': function () {
                        this.emit(':tell', textBuilder.replacePlaceholders(language.stop));
                    },
                    'AMAZON.YesIntent': function () {
                        this.emit(':tell', textBuilder.noergel());
                    },
                    'NoergelIntent': function (loud) {
                        this.emit(':ask', textBuilder.noergel());
                    },
                    'LaunchRequest': function () {
                        this.emit('NoergelIntent');
                    },
                    'PersonIntent': function () {
                        var person = util.getRandomArrayItem(
                        [
                            this.event.request.intent.slots.FirstPerson.value,
                            this.event.request.intent.slots.SecondPerson.value
                        ]);

                        this.emit(':tell', textBuilder.getPersonAnswer(person));
                    },
                    'Unhandled': function () {
                        this.emit('AMAZON.StopIntent');
                    }
                };

                exports.handler = function (event, context, callback) {
                    var alexa = Alexa.handler(event, context);
                    alexa.registerHandlers(noergelHandlers);
                    alexa.execute();
                };
            
        

Debugging

Das Debugging ist natürlich nicht so einfach, da der Code niemals auf dem eigenen Computer läuft und aufgrund der Sprachsteuerung auch nur schwer getestet werden kann. Die Entwickler-Konsole von Amazon bietet darum die Möglichkeit, Daten an die Lambda-Funktion zu senden, als wären sie von einem echten Amazon Echo erzeugt. Außerdem können zu sprechende Sätze und deren Aussprache ausgegeben werden, als hätte Sie der Amazon Echo gesagt. So kann man eventuell noch gegensteuern, wenn etwas falsch ausgesprochen wird.

Gegen Ende meiner Entwicklungsarbeiten habe ich aber primär einfach Log-Ausgaben ( console.log('test') ) erzeugt und diese im Log von AWS Lambda angeschaut. Der Link zu den Logs erscheint, sobald der "Test"-Button auf der Detail-Seite der entsprechenden Lambda-Funktion geklickt wurde. Dieser Log ist fast in Echtzeit und letztlich wie die gewohnte Javascript-Konsole.

Amazon hat zwar auch eine Anleitung zum Testen einer Lambda-Funktion auf einem lokalen Computer veröffentlicht, darauf habe ich allerdings aufgrund dem dahinterstehenden, augenscheinlich sehr hohen Konfigurationsaufwand bisher verzichtet.

Meine erste Ablehnung

Ich habe diese App natürlich sofort für den Skill-Store eingereicht - nicht zuletzt um noch einen Skill-Entwickler-Hoodie abzustauben. Diese Einreichung meiner Nörgel-App wurde abgelehnt. Allerdings hatte ich das auch erwartet. Insgesamt drei Gründe sind den Testern bei Amazon aufgefallen:

Unpassender Rufname: Mit "Nörgel" verstoße ich gegen diverse Guidelines , die Amazon vorgibt. Davon abgesehen, das Skill-Namen nicht nur aus einem Wort bestehen dürfen, war mein "Nörgel" wohl auch zu schwer verständlich um eine hohe Trefferquote zu erlauben. Ich werde den Skill in "Nörgelgenerator" umbenennen und es erneut versuchen.

Hilfe zu sarkastisch: Hat man meinen Skill mit einem einfachen "Hilfe" um eine Erklärung gebeten, hat er nur "Du nörgelst, ich nörgel. Ganz einfach." geantwortet. Das hat Amazon wohl nicht gereicht. Hier habe ich die Funktion um eine vollumfassende Hilfe erweitert.

Skill zu aufdringlich: Ich habe jedes Ereignis respektive jede Nutzereingabe aus technischer Sicht mit einer Frage beantwortet. Das hatte die Folge, dass man den Skill bis auf ein paar Ausnahmen nicht so einfach beenden konnte. Auch das habe ich angepasst. Der Nörgelgenerator ist jetzt zurückhaltender.

Abschließend

Der ganze Code für meinen Nörgel-Skill ist auf GitHub zu finden. Meinen Skill habe ich bereits in der aktualisierten Version eingereicht und hoffe, dass er zeitnah den Skill-Store erreicht. Wer schon jetzt den Nörgelgenerator nutzen möchte, müsste meinen Code herunterladen und selbst die entsprechende App anlegen.

Teilen

Kommentare