Entwicklung eines Alexa-Skills #2
Lesedauer: 5 Minuten

Im letzten Teil haben wir bereits das Grundgerüst unseres Skill in der Amazon Developer Console gebaut und einen Account für die Amazon Web Services angelegt. In diesem Teil wollen wir nun die Logik unseres Skills entwickeln. Dazu nutzen wir Visual Studio und das AWS Toolkit, welches ich bereits hier vorgestellt habe und für den weiteren Verlauf dieses Beitrags installiert und eingerichtet sein sollte.

Wir starten nun also Visual Studio und legen ein neues Projekt an. Nach der Installation des AWS Toolkits stehen einem neue Templates im Abschnitt AWS Lambda zur Verfügung. Wir wählen hier AWS Lambda Project (.NET Core) aus und vergeben einen Namen für die Solution, z.B. ComplimentButler.

Es öffnet sich nun ein Dialog, wo man eine Blaupause für das Anlegen der Lambda-Funktion auswählen kann. Wir wollen unseren Skill selbst entwickeln und wählen daher den Eintrag Empty Function und bestätigen mit Next.

Im ersten Schritt wollen wir ein NuGet-Package einbinden, welches die Entwicklung unseres Skills vereinfacht. Wir benötigen nämlich Alexa.NET von Tim Heuer.

Zunächst schreiben wir eine Methode MakeSkillResponse, welche einen Sprachausgabe auf dem Echo durchführt und gegebenenfalls die Session beendet. Diese Methode werden wir später verwenden, um für die verschiedenen Intents eine passende Ausgabe zu generieren.

private SkillResponse MakeSkillResponse(string outputSpeech,
  bool shouldEndSession, string repromtText = "")
{
  var response = new ResponseBody
  {
    ShouldEndSession = shouldEndSession,
    OutputSpeech = new PlainTextOutputSpeech
      {
        Text = outputSpeech 
      }
  };

  if (repromtText != null)
  {
    response.Reprompt = new Reprompt
    {
      OutputSpeech = new PlainTextOutputSpeech
        {
          Text = repromtText 
        } 
    };
  }

  var skillResponse = new SkillResponse
  {
    Response = response,
    Version = "1.0"
  };

  return skillResponse;
}

Wir wollen nun im folgenden ein paar Strings definieren, welche wir später verwenden wollen, um unsere Intents zu identifizieren. Dazu fügen wir in die Function.cs folgende Fields ein.

private const string INVOCATION_NAME = 
  "Kompliment-Butler";

private const string GETCOMPLIMENT_INTENT = 
  "GetCompliment";
private const string STOP_INTENT =
  "AMAZON.StopIntent";
private const string CANCEL_INTENT =
  "AMAZON.CancelIntent";
private const string HELP_INTENT =
  "AMAZON.HelpIntent";

Im nächsten Schritt wollen wir den Stop-, den Cancel- und den Help-Intent abarbeiten. Dazu schreiben wir die beiden folgenden beiden Methoden.

private SkillResponse HelpMessage()
{
  return MakeSkillResponse($"Hier ist der {INVOCATION_NAME}. " +
    "Lasse dir ein Kompliment ausgeben, " + 
    "z.B. mit sage mir ein Kompliment", false);
}

private SkillResponse StopMessage()
{
  return MakeSkillResponse($"Vielen Dank für die Verwendung von " +
    $"{INVOCATION_NAME}.", true);
}

Im nächsten Schritt definieren wir nun unsere Komplimente, aus denen später eines ausgewählt und zurückgegeben wird. Unterhalb der Definition unserer Intents fügen wir das folgende Code-Snippet ein.

private readonly List<string> COMPLIMENTS = new List<string>
{
  "Du siehst heute aber gut aus!",
  "Hast du abgenommen?",
  "Deine Frisur steht dir fantastisch!",
  "Mit deinen strahlend weißen Zähnen könntest du Werbung machen!",
  "Du hast ein tolles Gespür für Farben und Stil!"
};

Nun können wir die Methode HandleComplimentIntent() schreiben, welche per Zufall ein Kompliment aus der Liste auswählt und dann dem Nutzer ausgibt.

private SkillResponse HandleComplimentIntent()
{
  var index = new Random().Next(0, COMPLIMENTS.Count);
  var currentCompliment = COMPLIMENTS[index];

  return MakeSkillResponse($"Hier ist ein Kompliment für dich: " +
    $"{currentCompliment}", true);
}

Abschließend fehlt noch die Methode FunctionHandler, welche entscheidet, welchen Intent der Nutzer gerne haben möchte und entsprechend unserer Konfiguration die passende Antwort liefert.

public SkillResponse FunctionHandler(SkillRequest input, ILambdaContext context)
{
  var requestType = input.GetRequestType();

  if (requestType == typeof(LaunchRequest))
  {
    return HelpMessage();
  }
  else if (requestType == typeof(IntentRequest))
  {
    var intentRequest = input.Request as IntentRequest;
    switch (intentRequest.Intent.Name)
    {
      case GETCOMPLIMENT_INTENT:
        return HandleComplimentIntent();
      case CANCEL_INTENT:
      case STOP_INTENT:
        return StopMessage();
      case HELP_INTENT:
        return HelpMessage();
      default:
        return HelpMessage();
    }
  }
  else
  {
    return MakeSkillResponse($"Leider ging hier etwas schief.", true);
  }
}

Wie man dieser Methode entnehmen kann, wird geschaut, welchen Intent der Nutzer gewählt hat und entsprechend unsere Methoden mit der passenden Antwort aufgerufen.

An dieser Stelle habe ich nun noch einmal die gesamte Function.cs Datei eingefügt.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

using Amazon.Lambda.Core;
using Alexa.NET.Response;
using Alexa.NET.Request;
using Alexa.NET.Request.Type;

// Assembly attribute to enable the Lambda function's JSON input to be converted into a .NET class.
[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.Json.JsonSerializer))]

namespace ComplimentButler
{
    public class Function
    {
        private const string INVOCATION_NAME = "Kompliment-Butler";

        private const string GETCOMPLIMENT_INTENT = "GetCompliment";
        private const string STOP_INTENT = "AMAZON.StopIntent";
        private const string CANCEL_INTENT = "AMAZON.CancelIntent";
        private const string HELP_INTENT = "AMAZON.HelpIntent";

        private readonly List&lt;string&gt; COMPLIMENTS = new List&lt;string&gt;
        {
            "Du siehst heute aber gut aus!",
            "Hast du abgenommen?",
            "Deine Frisur steht dir fantastisch!",
            "Mit deinen strahlend weißen Zähnen könntest du Werbung machen!",
            "Du hast ein tolles Gespür für Farben und Stil!"
        };

        public SkillResponse FunctionHandler(SkillRequest input, ILambdaContext context)
        {
            var requestType = input.GetRequestType();

            if (requestType == typeof(LaunchRequest))
            {
                return HelpMessage();
            }
            else if (requestType == typeof(IntentRequest))
            {
                var intentRequest = input.Request as IntentRequest;
                switch (intentRequest.Intent.Name)
                {
                    case GETCOMPLIMENT_INTENT:
                        return HandleComplimentIntent();
                    case CANCEL_INTENT:
                    case STOP_INTENT:
                        return StopMessage();
                    case HELP_INTENT:
                        return HelpMessage();
                    default:
                        return HelpMessage();
                }
            }
            else
            {
                return MakeSkillResponse($"Leider ging hier etwas schief.", true);
            }
        }

        private SkillResponse HandleComplimentIntent()
        {
            var index = new Random().Next(0, COMPLIMENTS.Count);
            var currentCompliment = COMPLIMENTS[index];

            return MakeSkillResponse($"Hier ist ein Kompliment für dich: {currentCompliment}", true);
        }

        private SkillResponse HelpMessage()
        {
            return MakeSkillResponse($"Hier ist der {INVOCATION_NAME}. " +
                "Lasse dir ein Kompliment ausgeben, z.B. mit sage mir ein Kompliment", false);
        }

        private SkillResponse StopMessage()
        {
            return MakeSkillResponse($"Vielen Dank für die Verwendung von {INVOCATION_NAME}.", true);
        }

        private SkillResponse MakeSkillResponse(string outputSpeech, bool shouldEndSession, string repromtText = "")
        {
            var response = new ResponseBody
            {
                ShouldEndSession = shouldEndSession,
                OutputSpeech = new PlainTextOutputSpeech { Text = outputSpeech }
            };

            if (repromtText != null)
            {
                response.Reprompt = new Reprompt { OutputSpeech = new PlainTextOutputSpeech { Text = repromtText } };
            }

            var skillResponse = new SkillResponse
            {
                Response = response,
                Version = "1.0"
            };

            return skillResponse;
        }
    }
}

Wenn wir nun mit einem Rechtsklick auf das Projekt klicken, können wir den Upload direkt zu AWS starten.

Dazu müssen wir nur einen Namen für die Funktion eingeben, welche beliebig ist. Ich habe mich hier für complimentButler entschieden. Ebenso habe ich die untere Checkbox markiert, damit meine Änderungen entsprechend im JSON-File gespeichert werden und für weitere Deploy-Vorgänge direkt zur Verfügung stehen.

Nach einem Klick auf Next sollten wir noch den Timeout auf 10 Sekunden und den Speicher auf 128MB setzen, damit wir im kostenlosen AWS-Slot bleiben.

Mit einem Klick auf den Upload-Button starten wir Uploadvorgang und unsere Funktion wird in den Amazon Web Services deployed.

Nun müssen wir noch die Verbindung zwischen unserer Lambda-Funktion und unseren Skill herstellen. Dazu wechseln wir wieder in die AWS-Konsole in unserem Browser und unter den Lambda-Services finden wir nun unsere gerade hochgeladene Funktion.

Wir klicken unsere Funktion nun an und im Reiter Triggers müssen wir noch eine neuen Trigger zum Alexa Skills Kit herstellen.

Anschließend finden wir im oberen rechten Bereich unseren ARN-Link. Diesen müssen wir kopieren und anschließend in die Amazon Developer Console wechseln.

Hier wählen wir unter dem Menüpunkt Configuration den Service-Endpunkt AWS Lambda ARN aus und fügen in das Feld den kopierten Link ein.

Anschließend können wir unseren Skill jetzt testen. Wenn ihr den gleichen Account verwendet wie auf eurem Dot ist der Skill bereits aktiv und kann getestet werden. Ansonsten könnt ihr auch unter dem Menüpunkt Test einen Beispielaufruf in textueller Form machen, wie der folgende Screenshot zeigt.

Damit habt ihr jetzt euren ersten eigenen Alexa-Skill geschrieben, welchen ihr jetzt sogar zur Zertifizierung an Amazon schicken könnt. Diesen Skill könnt ihr jetzt natürlich noch belieben erweitern oder sogar einen Web-Api aufrufen, welche euch entsprechend Daten liefert und diese dann von euch verarbeitet werden. Der Fantasie sind hier keine Grenzen gesetzt, aber ihr habt jetzt erst einmal das Grundgerüst für die Entwicklung eigener Skills.

Lokale WordPress Installation mit Docker #DiWoKiel: Rückblick auf meinen Alexa-Workshop WordPress-Seite als Xamarin.Forms App – Teil 1