Ein Blick auf das Microsoft Bot Framework

Einen eigenen Chatbot bauen

Roman Schacherl, Daniel Sklenitzka

© Shutterstock / kirill_makarov

Seit März hat Microsoft ein eigens Bot Framework. Entwicklern soll es damit leicht gemacht werden, eigene Bots zu schreiben – also möglichst intelligente Programme, die in einem Chat Antworten geben und Aktionen durchführen können. Was steckt dahinter? Wo hilft das Framework? Und: Wozu eigentlich?

Jetzt haben wir uns gerade erst damit abgefunden, dass Konsolenanwendungen (CLI, Command Line Interfaces) nicht der Gipfel der Usability sind. Nach GUI kam NUI (Natural User Interface) – wobei sich die Interpretation zum Teil nur auf die Verwendung des Fingers statt der Maus reduzierte. Und dann kamen Siri, Google Now und Cortana. Plötzlich finden Benutzer Gefallen daran, mit Software zu kommunizieren – sei es mündlich oder schriftlich. Nicht die TV-App zu starten, den Sender auszuwählen, ins Hauptabendprogramm zu scrollen und die Beschreibung zu öffnen – sondern das Vorhaben einfach in Worte zu gießen: „Was läuft heute Abend auf 3sat?“.

Genau in diese Kerbe schlagen Conversational User Interfaces (CUI). Die Schnittstelle zwischen Mensch und Computer nähert sich immer mehr einer Mensch-zu-Mensch-Kommunikation an und verdient immer öfter die Bezeichnung „intelligent“. Damit das zuverlässig funktioniert, ist ein tiefes Verständnis von Sprache erforderlich. Gesprochene Sätze müssen analysiert und in Text umgewandelt werden, bei geschriebenen Texten dürfen auch kleinere Tipp- oder Rechtschreibfehler der Erkennung keinen Abbruch tun. Darüber hinaus muss ein CUI über den aktuellen Kontext Bescheid wissen: Wer spricht? Was wurde bereits gesagt? Was muss nicht gesagt werden, kann aber aus anderen Informationsquellen (Internet, Kalender, Kontakte) ermittelt werden? Was wurde bereits erlernt und kann als gegeben hingenommen werden? Es ist das Ende von harten Fakten, Interpretation ist angesagt: „um 10 Uhr“ bedeutet am Morgen etwas anderes als am späten Abend.

Kurzum: Das Entwickeln von Conversational User Interfaces erfordert weitaus mehr als ein paar aneinandergereihte if-Statements, die dem Benutzer vordefinierte Phrasen zurückwerfen. Es geht um das Verständnis von Sprache – mit allem, was dazu gehört.

Das Microsoft Bot Framework

Das Microsoft Bot Framework [1] stellt einen Werkzeugkasten für die Entwicklung derartiger Schnittstellen – eben Bots – zur Verfügung. Es unterstützt bei der Entwicklung (Bot Builder SDK), der Verteilung/Bewerbung (Bot Directory) und der Integration von Bots in andere Kommunikationsplattformen (Bot Connector). Darüber hinaus stehen Cognitive Services [2] bereit, um beispielsweise beim Verständnis von Texten oder Bildern assistierend zur Seite zu stehen – nicht ausschließlich für Bots, aber gerade dort sehr hilfreich.

Bot Builder SDK

Das SDK steht auf GitHub unter einer Open-Source-Lizenz sowohl für C# als auch für Node.js zur Verfügung. Dieser Artikel beschäftigt sich im Folgenden mit der C#-Variante. Im Wesentlichen ist ein Bot nichts anderes als ein REST-Service, der ein bestimmtes Protokoll spricht:

public async Task Post([FromBody]Message message)
{
...
}

Man bekommt also eine Message, und muss auch wieder eine Message zurückliefern, wobei immer sämtliche Informationen des Gesprächs hin- und hergeschickt werden – die Implementierung muss zustandslos erfolgen, damit der Bot später beliebig skaliert werden kann. Darüber hinaus beinhaltet eine Message diverse Metainformationen (Sprache, Teilnehmer, …).

Man kann entweder mit einer leeren ASP.NET-Anwendung starten, das SDK über NuGet hinzufügen (Microsoft.Bot.Connector) und einen entsprechenden HTTP-Endpunkt implementieren – oder man installiert das Bot Application Template [3], das einem bei der Erstellung eines Projekts diese Schritte abnimmt.

Lesen Sie auch: Interview mit Bernd Fondermann: „Wir sehen Anzeichen dafür, dass Machine-Learning-Systeme eigenen kreativen Output liefern“

Für die weitere Implementierung dieses Service stellt das SDK über ein weiteres NuGet-Package Microsoft.Bot.Builder zwei verschiedene Hilfestellungen zur Verfügung: Dialoge und Form Flow. Diese sollen nun anhand desselben Beispiels vorgestellt werden: Ziel ist es, einen Bot für ein Projektmanagement- und Zeiterfassungssystem zu entwickeln, der einem bei der Einrichtung von Benachrichtigungen behilflich ist. Diese sollen versandt werden, wenn in einem Projekt ein bestimmter Teil des (Zeit-)Budgets aufgebraucht ist. Der Bot muss also in Erfahrung bringen, für welches Projekt und nach welchem Zeitraum (in Stunden oder Tagen) die Benachrichtigung erstellt werden soll. Das Listing zeigt eine entsprechende Klasse.

public enum AmountType
{
Hours,
Days
}

[Serializable]
public class Alert
{
public string Project { get; set; }
public int Amount { get; set; }
public AmountType AmountType { get; set; }
}

Dialoge

Der Begriff „Dialog“ ist im Bot Framework im ursprünglichen Wortsinn zu verstehen – und nicht wie sonst in der Softwareentwicklung üblich als Synonym für ein Anwendungsfenster. Ein Dialog ist eine – im besten Falle wiederverwendbare – kurze Konversation zwischen Benutzer und Bot, die eine bestimmte Information als Ergebnis liefert. Ein Dialog kann dabei weitere Dialoge aufrufen, sodass sich ein Gespräch mit einem Bot meist aus mehreren Dialogen zusammensetzt. Das Framework definiert ein (recht überschaubares) Interface für einen Dialog:

public interface IDialog
{
Task StartAsync(IDialogContext context);
}

Listing 2 zeigt die erste Version der Klasse AlertDialog, die dieses Interface implementiert.

[Serializable]
public class AlertDialog : IDialog
{
private Alert alert;

public async Task StartAsync(IDialogContext context)
{
context.Wait(MessageReceivedAsync);
}

private async Task MessageReceivedAsync(IDialogContext context, IAwaitable argument)
{
var message = await argument;
await context.PostAsync("Ich habe Sie leider nicht verstanden.");
context.Wait(MessageReceivedAsync);
}
}

Das Pattern, das dabei verwendet wird, sieht auf den ersten Blick etwas ungewöhnlich aus, man gewöhnt sich aber schnell daran: Durch den Aufruf von context.Wait wartet man auf die nächste Nachricht und gibt eine Callback-Methode an. In unserem einfachen Beispiel wird diese Methode sofort aufgerufen, da ja bereits eine Nachricht verfügbar ist. Bemerkenswert ist, dass auch der Zugriff auf die Message mittels await erfolgen muss – mit dem Warten auf die Nachricht hat das aber nichts mehr zu tun. Mit PostAsync kann man eine Antwort senden, um danach wieder mittels Wait auf die nächste Antwort zu warten.

Gespräche mit dieser ersten Version unseres Bots sind vielleicht noch etwas eintönig (böse Zungen behaupten allerdings, für den Einsatz im Kundendienst mancher Unternehmen würde es bereits reichen). Das Listing zeigt den nächsten Schritt: Wir prüfen, ob die eingegangene Message gleich dem Text „Neuer Alarm“ ist. Wenn dem so ist, starten wir einen neuen Dialog, um den Namen des Projekts zu erfragen.

private async Task MessageReceivedAsync(IDialogContext context, IAwaitable argument)
{
var message = await argument;

if (message.Text.Equals("neuer alarm", StringComparison.CurrentCultureIgnoreCase))
{
PromptDialog.Text(context,
ProjectReceivedAsync,
"Für welches Projekt?",
"Entschuldigung - welches Projekt?");
}
else
{
await context.PostAsync("Ich habe Sie leider nicht verstanden");
context.Wait(MessageReceivedAsync);
}
}

Die Klasse PromptDialog bietet bereits mehrere wiederverwendbare Dialogimplementierungen, mit denen sich diverse Standarddatentypen erfragen lassen – in diesem Fall ein Text. Als Parameter anzugeben ist wiederrum ein Callback, außerdem der Text für die Frage und optional eine zweite Frage, falls die erste Antwort unverständlich ist. Aus diesen Bausteinen können wir nun den Alert-Dialog fertig implementieren, sodass nach und nach die alert-Variable unserer Klasse befüllt wird. Dabei wird immer das beschriebene Muster verwendet. In den Callback-Methoden kann jeweils mit await result auf den Rückgabewert des Dialogs zugegriffen werden. Durch den Aufruf von context.Done beenden wir schließlich den aktuellen Dialog.

private async Task ProjectReceivedAsync(IDialogContext context, IAwaitable result)
{
this.alert = new Alert()
{
Project = await result
};

PromptDialog.Choice(
context,
AmountTypeReceivedAsync,
new[] { "Tagen", "Stunden" },
$"Soll der Alarm für {this.alert.Project} nach Stunden oder Tagen erfolgen?");
}

private async Task AmountTypeReceivedAsync(IDialogContext context, IAwaitable result)
{
var amountType = await result;
this.alert.AmountType = (amountType == "Tage" ? AmountType.Days : AmountType.Hours);

PromptDialog.Number(
context,
AmountReceivedAsync,
$"Nach wie vielen {(this.alert.AmountType == AmountType.Days ? "Tagen" : "Stunden")} soll der Alarm eingestellt werden?");
}

private async Task AmountReceivedAsync(IDialogContext context, IAwaitable result)
{
this.alert.Amount = (int)await result;

PromptDialog.Confirm(
context,
FinishedAsync,
$"Möchtest du einen Alarm für das Projekt {alert.Project} nach {alert.Amount} {(alert.AmountType == AmountType.Days ? "Tagen" : "Stunden")} setzen?");
}

private async Task FinishedAsync(IDialogContext context, IAwaitable result)
{
if (await result)
{
// add alarm

context.Done(this.alert);
}
else
{
context.Done(null);
}
}

Im Service können wir den Alert-Dialog schließlich starten.

public async Task Post([FromBody]Message message)
{
if (message.Type == "Message")
{
return await Conversation.SendAsync(message, () => new DiaryDialog());
}
else
{
return HandleSystemMessage(message);
}
}

Zeit für einen ersten Test: Starten Sie das Projekt und merken Sie sich den lokalen Port, unter dem die Website läuft (üblicherweise Port 3978). Natürlich könnten Sie jetzt mit Postman oder Fiddler Requests austauschen – einfacher geht es aber mit dem Bot Framework Emulator [4, Abb. 1]. Geben Sie in der oberen Leiste den URL (z. B.: http://localhost:3978/api/messages) an und achten Sie darauf, dass die App-ID und das App Secret den Werten aus der Web.config entsprechen – vorerst können wir die Standardwerte (YourAppId, YourAppSecret) belassen. Anschließend steht Ihnen das linke Chatfenster zur Kommunikation zur Verfügung, beim Klicken auf das blaue Icon in den Antworten sehen Sie auch das dazugehörige JSON-Objekt.

schacherl_1

Abb. 1: Bot Framework Emulator

Übrigens: wenn Sie CLI und CUI mischen möchten, dann können Sie auch ein Kommandozeilenprogramm als Emulator bauen; den entsprechenden Sourcecode finden Sie online unter [5].

Einfache Dialoge lassen sich also recht schnell implementieren, und auch komplexere Konversationen sind durch die Verschachtelung von mehreren Dialogen gut handhabbar. Bei der Erfassung von komplexeren Datenstrukturen als unserer Alert-Klasse kann es aber trotzdem schnell aufwendig werden, alle Eigenschaften „von Hand“ abzufragen. Hier kommt die zweite Variante ins Spiel.

Lesen Sie auch: Machine Learning Experten-Check: 6 Tipps für den Einstieg in das maschinelle Lernen

Form Flow

Die Grundidee von Form Flow ist, auf Basis einer C#-Klasse (Form) automatisch einen entsprechenden Dialog zu erstellen, der alle Felder und Eigenschaften befüllt. Dazu müssen wir die Alert-Klasse um eine statische Methode erweitern, die mittels der Klasse FormBuilder eine IForm erstellt:

public static IForm BuildForm()
{
return new FormBuilder().Build();
}

Mithilfe dieser können wir dann einen entsprechenden Dialog erzeugen:

return await Conversation.SendAsync(message, () => FormDialog.FromForm(Alert.BuildForm));

Die Form Flow Engine fragt dann Schritt für Schritt nach jedem Feld, wobei die Fragen je nach Datentyp unterschiedlich gestellt werden. Bei Enumerationen werden beispielsweise gleich alle Optionen aufgelistet (Abb. 2).

schacherl_2

Abb. 2: FormFlow: Je nach Datentyp werden unterschiedliche Fragen gestellt

Ist man mit diesem Standardverhalten nicht zufrieden, kann man auf verschiedene Arten eingreifen. So kann man mittels eines Fluent-API beispielsweise den Begrüßungstext und die Abschlussfrage formulieren oder mittels Attributen die Fragen nach den jeweiligen Feldern beeinflussen. Dabei kommt eine eigene Pattern Language mit Platzhaltern zum Einsatz, mit der etwa die Werte einzelner Felder oder die verfügbaren Optionen angezeigt werden können. Eine detaillierte Beschreibung aller Features ist unter [6] zu finden.
Auf diese Weise ist es mit minimalem Aufwand möglich, selbst große Objekte zu befüllen, auch wenn man im Vergleich zur manuellen Implementierung etwas Flexibilität verliert. Dank der zahlreichen Konfigurationsmöglichkeiten fällt das allerdings kaum ins Gewicht, und da auf Basis der Form ebenfalls ein Dialog erstellt wird, lassen sich die beiden Ansätze auch wunderbar kombinieren.

public enum AmountType
{
[Describe("Stunden")]
[Terms("Stunde", "Stunden")]
Hours,
[Describe("Tagen")]
[Terms("Tag", "Tage", "Tagen")]
Days
}
[Serializable]
public class Alert
{
[Describe("Projekt")]
[Prompt("Für welches Projekt?")]
public string Project { get; set; }

[Describe("Art")]
[Prompt("Wie soll der Alarm eingestellt werden? {||}")]
public AmountType? AmountType { get; set; }

[Numeric(1, 100)]
[Describe("Limit")]
[Prompt("Nach wie vielen {AmountType} soll der Alarm eingestellt werden?")]
public int Amount { get; set; }

public static IForm BuildForm()
{
return new FormBuilder()
.Message("Hallo, ich helfe dir bei der Erstellung.")
.AddRemainingFields()
.Confirm("Möchtest du einen Alarm für das Projekt {Project} nach {Amount} {AmountType} setzen?")
.Message("Der Alarm wurde erstellt.")
.Build();
}
}

Deployment und Registrierung

Da es sich bei einem Bot-Projekt technologisch um ein Web-API handelt, kann das Deployment wie bei herkömmlichen ASP.NET-Anwendungen erfolgen – also auch auf On-Premise-Servern. Dass die Integration in Microsoft Azure leicht gemacht wird, überrascht aber natürlich wenig. In Visual Studio kann mittels PUBLISH (Kontextmenü des Projekts) direkt ein neuer App-Service in Azure erstellt werden (API-App); der Bot ist nach wenigen Eingaben online (Abb. 3). Sollte Ihr Bot großen Anklang finden und von vielen Benutzern konsultiert werden, spricht nichts gegen ein Scale-out der Azure-Instanzen: Dank Zustandslosigkeit des Service können während einer Konversation problemlos die Server gewechselt werden.

schacherl_3

Abb. 3: Deployment als API-App in Azure

Im nächsten Schritt erfolgt die Registrierung des Bots. Unter [1] können Sie einen neuen Bot anlegen; vor allem der URL zum Message Endpoint ist relevant (achten Sie auf die komplette Angabe des URLs, also inkl. der Route auf /api/messages). Auch die App-ID ist wichtig; der hier eingegebene Wert muss mit dem Wert aus der Web.config im Bot-Projekt übereinstimmen und sollte spätestens jetzt auf einen sinnvollen Namen (ohne Leer- und Sonderzeichen) geändert werden. Die Aufnahme ins Bot Directory, die mittels Option beantragt werden kann, ist noch Zukunftsmusik. In diesem Verzeichnis werden später alle Bots aufgelistet, derzeit sind aber nur die Microsoft-eigenen Kandidaten gelistet.

schacherl_4

Abb. 4: Registrierung des Bots

Nach der Erstellung werden Sie zur Übersichtsseite weitergeleitet (Abb. 4), auf der Sie u. a. das Primary app secret finden. Kopieren Sie diesen Schlüssel, passen Sie damit die Web.config an und updaten Sie den Bot durch ein erneutes Deployment.

Bot Connector

Zusätzlich sehen Sie auf der Übersichtsseite auch noch den dritten Tätigkeitsbereich des Microsoft Bot Frameworks: den Bot Connector. Über welches Interface möchten Sie mit dem Bot sprechen? Bisher haben Sie nur den Emulator genutzt, für Endbenutzer ist das aber wenig sinnvoll. Ihr Bot muss dort sein, wo Ihre Benutzer sind: auf Skype, Slack oder GroupMe. Er soll per E-Mail oder SMS antworten können. Oder einfach auf Ihrer Website für Fragen aller Art zur Verfügung stehen. Alle diese Szenarien deckt der Bot Connector ab. Microsoft hat nicht nur Schnittstellen zu den Kommunikationsplattformen („Kanäle“) entwickelt; in bebilderten Schritt-für-Schritt-Anleitungen werden auch die notwendigen Einstellungen detailliert beschrieben. Eine Integration des Bots in Slack ist dadurch in weniger als 5 Minuten erledigt (Abb. 5). Für die ausgewählten Kanäle stehen auch fertige HTML-Einbettungscodes zur Verfügung, um das Chat-Control auf der eigenen Website platzieren zu können.

schacherl_5

Abb. 5: Verwendung des Bots in Slack

Fazit

Warum nicht neue Benutzer mit einem Bot um die Vervollständigung des Userprofils bitten (wie bei Slack hervorragend gelöst)? Warum nicht einfache Aktionen durch einen Bot abwickeln anstatt Wartungsmasken zu bauen? Warum ein FAQ nicht einmal anders lösen?

Das Microsoft Bot Framework ist eine faszinierende Spielwiese. Nicht für jede Anwendung sofort sinnvoll einsetzbar – aber auf alle Fälle einen Blick in die Zukunft wert. Viel Spaß und liebe Grüße an Ihren ersten Bot!

Verwandte Themen:

Geschrieben von
Roman Schacherl
Roman Schacherl
Roman Schacherl (MVP) ist Mitgründer der Firma softaware gmbh und entwickelt mit seinem Team individuelle Lösungen auf Basis von Microsoft-Technologien. Er ist Autor mehrerer Fachartikel, nebenberuflich Lehrender an der FH Hagenberg (Österreich) und als Sprecher auf Entwicklerkonferenzen tätig.
Daniel Sklenitzka
Daniel Sklenitzka
Daniel Sklenitzka ist Mitgründer der Firma softaware gmbh und entwickelt mit seinem Team individuelle Lösungen auf Basis von Microsoft-Technologien. Er ist Autor mehrerer Fachartikel, nebenberuflich Lehrender an der FH Hagenberg (Österreich) und als Sprecher auf Entwicklerkonferenzen tätig.
Kommentare

Hinterlasse einen Kommentar

Hinterlasse den ersten Kommentar!

avatar
400
  Subscribe  
Benachrichtige mich zu: