Sprachmuster – Wie Chatbots kommunizieren (sollten)
Jede Implementation eines Softwaresystems ist lediglich so gut wie ihr Entwurf. Je nach Kontext gilt dasselbe für Unterhaltungen – und zwar wenn diese nicht der bloßen Unterhaltung dienen, sondern einem zielorientierten Gespräch. Werden Dialogsysteme im Geschäftskontext eingesetzt, sollten demnach die modellierten Konversationen durchdachten Entwurfsmustern folgen. Dies kann dabei helfen Frustration bei Nutzern vorzubeugen und unerwartete Szenarien zu handhaben. Im Folgenden werden zwei zentrale Entwurfsmuster vorgestellt und mit einer Beispielimplementationen in Rasa illustriert, um Chatbots der Stufe 3 zu entwickeln. Mehr zu den Klassifikationsstufen von Dialogsystemen können Sie im vorherigen Beitrag nachlesen.
Fragespielchen | Slot-Filling
Häufig werden zur Ausführung einer Transaktion mehrere verknüpfte Informationen benötigt. Um diese in einer strukturierten Art und Weise vom Nutzer zu erfragen, wird das Slot-Filling verwendet. Dabei werden die benötigten Informationen in einer Routine definiert und der Nutzer so lange aufgefordert diese bereitzustellen, bis diese vollständig vorhanden sind. Die extrahierten Slots sollten dabei direkt validiert und, falls nötig, mit spezifischen Nachfragen korrigiert werden. Um Frustration beim Nutzer zu vermeiden, sollte sowohl ein Ausbruch aus der Schleife, als auch eine verlustfreie Rückkehr in den Vorgang ermöglicht werden.
Rasa bietet hierfür eine generalisierte Methode namens Forms an. Diese wird im Folgenden anhand eines Beispiels illustriert. In unserem Beispiel soll ein Nutzer die Möglichkeit haben eine Pizza zu bestellen und dabei Größe und Belag angeben dürfen.
Um eine Form zu nutzen, muss diese, mitsamt der verwendeten Slots, in der Domain.yml des Rasa-Projekts deklariert werden:
intents: - order_pizza - stop - deny entitites: - pizza_size - pizza_topping slots: pizza_size: type: text mappings: - type: from_entity entity: pizza_size pizza_topping: type: categorical values: - Salami - Pilze - Peperoni mappings: - type: from_entity entity: pizza_topping forms: pizza_form: required_slots: - pizza_size - pizza_topping
Um die Form auszulösen, könnte beispielsweise die folgende Story angelegt werden:
stories: story: Nutzer möchte Pizza bestellen steps: - intent: order_pizza - action: pizza_form - active_loop: pizza_form
Dem Nutzer sollte allerdings immer die Möglichkeit gegeben werden aus der Form auszubrechen, was wie folgt modelliert werden kann:
stories: story: Nutzer möchte Pizzabestellung abbrechen steps: - intent: order_pizza - action: pizza_form - active_loop: pizza_form - intent: stop - action: utter_ask_continue - intent: deny - action: action_deactive_loop - active_loop: null
Diese Stories sollten iterativ ergänzt und ausgeweitet werden, um Sonderfälle behandeln zu können, die während der ursprünglichen Modellierung nicht bedacht wurden.
Um das Verhalten innerhalb der Forms anzupassen, können in einer Custom Action die Standardfunktionen überschrieben werden. Dies kann genutzt werden, um die Ausgaben des Chatbots zu individualisieren, das Extraktions- und Validierungsverhalten anzupassen oder dynamisch Slots zu erfragen, welche in Abhängigkeit zu gewissen Ausprägungen vorheriger Slots stehen. Jeder Slot kann dabei in einer eigenen Methode der Klasse behandelt werden. Im Folgenden ist eine beispielhafte Implementation einer Custom Form abgebildet. Die Klasse „ValidateForm" kümmert sich dabei um die Extraktion und Validierung der Slots und die Klasse „AskForSlotAction" gibt die korrekte Formulierung zur Nachfrage des Slots aus.
class ValidateForm(FormValidationAction): def name(self) -> Text: return “validate_form_name“ async def extract_slot_name( self, dispatcher: CollectingDispatcher, tracker: Tracker, domain: DomainDict ) -> Dict[Text, Any]: # Geschäftslogik zur Extraktion und Zuordnung der Slots slots = extract_and_validate_slots(tracker.latest_message) return slots async def required_slots( self, domain_slots: List[Text], dispatcher: CollectingDispatcher, tracker: Tracker, domain: DomainDict ) -> List[Text]: # individuelle Geschäftslogik zur dynamischen Ergänzung von Slots additional_slots = add_slots(tracker.slots) return additional_slots + domain_slots class AskForSlotAction(Action): def name(self) -> Text: return “action_ask_slot_name“ def run( self, dispatcher: CollectingDispatcher, tracker: Tracker, domain: DomainDict ) -> List[EventType]: # Geschäftslogik zur Bestimmung der Formulierung response = formulate_slot_request(tracker) dispatcher.utter_message(text=response) return []
Sobald der entsprechende Intent erkannt wurde, wird die Form gestartet. Zunächst werden potenzielle Entitäten aus der Nutzernachricht extrahiert und in Slots überführt. Diese können in der „extract_slot“-Methode validiert werden. Sollte der Slot nicht ordnungsgemäß gefüllt worden sein, kann der Slot innerhalb der „required_slots“-Methode erneut angefragt werden. Andernfalls wird der nächste benötigte Slot ausgewählt. Danach wird die „action_ask_slot“-Methode aufgerufen, welche die Formulierung zur Abfrage des nächsten Slots bildet. Dieser Prozess wird so lange in einer Schleife ausgeführt, bis alle nötigen Slots gefüllt wurden oder der Prozess vom Nutzer abgebrochen wird.
Human in the loop | Two-Stage-Fallback
Unabhängig vom Detailgrad der Dialogmodellierung und des Funktionsumfangs des Dialogsystems werden unerwartete Nutzerinteraktionen auftreten. Um diese in angemessener Art und Weise bearbeiten zu können, muss zum einen dem Dialogsystem die Chance gegeben werden die Anfrage zu disambiguieren und einem eventuell bekannten Intent zuzuordnen und zum anderen die Nutzeranfrage erfolgreich bearbeitet werden können, auch wenn diese nicht durch das Dialogsystem erfasst werden kann.
Dies kann beispielsweise erreicht werden, indem das System zunächst eine Rückfrage stellt, welche die, als am wahrscheinlichsten klassifizierten, Intents in natürlicher Sprache listet und so dem Nutzer direkt die Möglichkeit bietet, die Interaktion zu steuern. Falls der gewünschte Intent nicht enthalten sein sollte, kann im zweiten Schritt die Abgabe an einen menschlichen Mitarbeiter erfolgen. Diese Abgabe an einen Menschen wird auch als Human Handoff betitelt und ist eine Variante des „Human in the loop“-Konzepts innerhalb von Dialogsystemen.
Das Framework Rasa unterstützt dieses Konzept standardmäßig, wobei das Verhalten wie immer individualisiert werden kann. Zunächst muss in der config.yml der FallbackClassifier aktiviert werden, welcher bei Unterschreitung des Konfidenzwerts zur Intentklassifizierung, den Standard-Intent „nlu_fallback“ setzt:
pipeline: - name: FallbackClassifier Threshold: 0.4
Anschließend muss eine Regel hinzugefügt werden, welche die Fallback-Strategie auslöst, nachdem der entsprechende Intent erkannt wurde.
rules: - rule: Trigger two-stage-fallback steps: - intent: nlu_fallback - action: action_two_stage_fallback - active_loop: action_two_stage_fallback
Zur Individualisierung der Aktionen können die Standard-Aktionen „action_default_ask_rephrase“ und „action_default_fallback“ überschrieben werden, wie im Folgenden gezeigt:
class ActionDefaultAskRephrase(Action): def name(self) -> Text: return “action_default_ask_rephrase“ def run( self, dispatcher: CollectingDispatcher, tracker: Tracker, domain: DomainDict ) -> List[EventType]: # individuelle Geschäftslogik zur Formulierung der Nachfrage response = formulate_rephrase_request(tracker) dispatcher.utter_message(text=response) return [] class ActionDefaultFallback(Action): def name(self) -> Text: return “action_default_fallback“ def run( self, dispatcher: CollectingDispatcher, tracker: Tracker, domain: DomainDict ) -> List[EventType]: # Geschäftslogik zur Bestimmung der Formulierung dispatcher.utter_message( text=handoff_notification, json_message={ “handoff_host“: “https://support.company.de“, “title“: “Helpdesk“ } ) return [ConversationPaused(), UserUtteranceReverted()]
Die Klasse „ActionDefaultAskRephrase“ gibt nun eine individuelle Nachricht aus, um dem Nutzer die Möglichkeit zu geben den korrekten Intent zu wählen. Die Klasse „ActionDefaultFallback“ übergibt bei erneutem Misserfolg die Konversation an einen menschlichen Mitarbeiter, indem ein JSON-Request gesendet wird, welcher die Host-Adresse des Supports an das Frontend übergibt, welches so das Konversationsbackend zum Support-Mitarbeiter wechseln kann.
Ein Frontend, welches diese Funktionalität direkt ermöglicht und Rasa unterstützt, ist Chatroom.
Fazit
Mittels der vorgestellten Entwurfsmuster des Slot-Fillings und des Two-Stage-Fallbacks mit Human Handoff kann der Implementationsaufwand eines Dialogsystems reduziert und gleichzeitig die Nutzerfrustration deutlich gesenkt werden, da stets die Möglichkeit besteht mit einem Menschen zu sprechen, sollte das Dialogsystem nicht in der Lage sein die Anfrage zu bearbeiten. Das Framework Rasa implementiert Standardfunktionen, auf Grundlage derer diese Entwurfsmuster auf individuelle Anwendungsfälle angepasst und um zusätzliche Funktionen erweitert werden können. Wie Sie Ihren nun gut gerüsteten Chatbot dazu bringen sich selbstständig noch weiter zu verbessern, erfahren Sie im nächsten Blogbeitrag.
Seminarempfehlungen
AI/ACTIVE LEARNING - KOSTENLOSES WEBINAR W-AI-02
Zum SeminarMACHINE LEARNING BASICS W-AI-01
Zum SeminarBei Updates im Blog, informieren wir per E-Mail.
Kommentare