Routing in komplexen Chatbots mit dem Hobot-Framework



Nachdem ich vor einigen Jahren mit der Entwicklung von Bots für Telegram begonnen hatte, entdeckte ich die Leistung, Einfachheit und Flexibilität der Arbeit mit ihnen als Sonderfall der Befehlszeilenschnittstelle. Diese Eigenschaften, die vielen heute zur Verfügung stehen, sind größtenteils auf das beliebte telegraf.js-Framework und dergleichen zurückzuführen, das vereinfachte Methoden für die Arbeit mit der Telegramm-API bietet.

Gleichzeitig liegt die Architektur des Projekts vollständig auf den Schultern des Entwicklers, und gemessen an der bescheidenen Anzahl komplexer und multifunktionaler Bots haben wir in dieser Hinsicht noch Raum für Wachstum.

In diesem Artikel möchte ich über ein kleines Chatbot-Routing-Framework sprechen, ohne das die Entwicklung unseres Projekts unmöglich wäre.

Einige grundlegende Informationen


In Chatbots und CLIs besteht die Ausführung einer logischen Aktion häufig aus mehreren Verfeinerungsschritten oder Verzweigungsschritten. Dazu muss das Programm eine bestimmte Koordinate speichern, um sich zu merken, wo sich der Benutzer im Fluss befindet, und seinen Befehl gemäß dieser Koordinate ausführen.

Die einfachste Darstellung ist die Ausführung des Befehls npm init , bei dem Sie vom Programm aufgefordert werden, nacheinander die einen oder anderen Daten für package.json anzugeben.

Im ersten Schritt teilt sie dem Benutzer mit, dass sie auf die Texteingabe des Paketnamens wartet - und was der Benutzer ihr mit dem nächsten Befehl sendet, wird dank der Variablen, in die diese Wartezeit geschrieben ist, als Paketname gespeichert.

Wir nennen diesen variablen Pfad - den Pfad, an den dieser oder jener Code logisch angehängt ist. Es wird immer benötigt, wenn der Bot in wenigen Schritten über eine Navigation oder Befehle verfügt.

Die heutige Praxis in der Bot-Architektur


Der Ansatz , dass ich zum ersten Mal auf von anderen Entwicklern sah sah wie folgt aus : für jedes Update vom Benutzer kommt, wird eine Liste von Schecks für einen bestimmten Wert des Pfad - Variable geschrieben und Geschäftslogik und die weitere Navigation innerhalb dieser Überprüfungen in der elementarsten Form gelegt:

onUserInput(ctx, input) {
    switch(ctx.session.path) {
        case 'firstPath':
	    if (input === '!') {
               // -  
	        ctx.reply('!');
	        ctx.session.path = 'secondPath';
	    } else {
	        ctx.reply(' "!"');
	    }
	    break;
        case '...':
       	    //   
    }
}

Wenn Sie nur ein paar Teams und ein paar Schritte für jedes Team haben, ist diese Lösung optimal. Erreichen Sie das dritte und das siebte Team, wenn Sie glauben, dass etwas schief geht.

In einem der Bots, mit denen wir zu einem späten Zeitpunkt arbeiten konnten, bestand der Kern der Funktion aus einem Blatt mit 4000 Zeilen und ~ 70 Bedingungen nur der obersten Ebene, die aus zwei Wenns hervorgegangen waren, mit einer Überprüfung dessen, was uns zu Herzen ging - manchmal Pfade, manchmal Befehle, manchmal Pfade und Befehle. Alle diese Bedingungen wurden für jede Benutzeraktion überprüft und auf Hilfsfunktionen von einem benachbarten Blattobjekt zugegriffen, das ebenfalls aus mehreren Zeilen wuchs. Muss ich sagen, wie langsam und wattig dieses Projekt ging?

Hobot-Rahmen


Beim Starten von ActualizeBot haben wir uns bereits vorgestellt, wie groß es sein würde, und unsere erste Aufgabe bestand darin, die Erweiterbarkeit und Geschwindigkeit der Entwicklung beizubehalten.

Zu diesem Zweck haben wir die Client-Logik in Controller unterteilt, die den Pfaden zugewiesen sind, und eine kleine Abstraktion geschrieben, um zwischen diesen Controllern zu navigieren und die vom Benutzer in ihnen empfangenen Nachrichten zu verarbeiten.

All dies, basierend auf großen Projekten, wurde in TypeScript geschrieben und erhielt den eleganten Namen Hobot, was natürlich auf Navigationspipelines anspielt.

Ein Controller ist ein einfaches Objekt mit drei Eigenschaften:

  • Pfad - Zeichenfolgenkennung des Pfads, der zum Initialisieren und Navigieren der initialisierten Pfade verwendet wird
  • get — , , hobot.gotoPath(ctx, path, data?). — data ,
  • post — . , , . updateType — , : text, callback_query . updateType ctx

Controller-Beispiel:

const anotherController = {
    path: 'firstPath',
    get: async (ctx, data) => 
        await ctx.reply('Welcome to this path! Say "Hi"'),
    post: async (ctx, updateType) => {
        //     : text / callback_query / etc...
        if (updateType === updateTypes.text && ctx.update.message.text === 'Hi') {
            await ctx.reply("Thank you!");
            // hobot       this:
            this.hobot.gotoPath(ctx, 'secondPath', { userJustSaid: "Hi" });
        } else {
            //     ,       
            await ctx.reply('We expect "Hi" text message here');
        }
    }
}

Es sieht etwas komplizierter aus als am Anfang, aber die Schwierigkeit bleibt gleich, wenn Sie 100 oder 200 Pfade haben.

Die interne Logik ist trivial und es ist überraschend, dass dies noch niemand getan hat :
Diese Controller werden einem Objekt hinzugefügt, dessen Schlüssel die Werte der Pfadeigenschaft sind, und werden von diesen Schlüsseln während Benutzeraktionen oder beim Navigieren mit hobot.gotoPath (ctx, path, data? ) .

Die Navigation wird in einer separaten Methode zugewiesen, um den variablen Pfad und die Navigationslogik nicht zu berühren, sondern nur an die Geschäftslogik zu denken, obwohl Sie ctx.session.path jederzeit mit Ihren Händen ändern können, was natürlich nicht empfohlen wird.

Alles, was Sie tun müssen, um Ihren neuen Bot mit unzerstörbarer Struktur zum Laufen zu bringen, ist, einen regulären Telegraf-Bot zu starten und ihn und das Konfigurationsobjekt an den Hobot-Konstruktor zu übergeben. Das Konfigurationsobjekt besteht aus den Controllern, die Sie initialisieren möchten, dem Standardpfad und den Befehls- / Controller-Paaren.

//   telegraf-
const bot = new Telegraf('_');

//  
export const hobot = new Hobot(bot, {
    defaultPath: 'firstPath',
    commands: [
        //  ,     
        // get    :
        { command: 'start', path: 'firstPath' }
    ],
    controllers: [
        // -,    
        startController,
        nextController
    ]
});

// C telegraf-,       
bot.launch();

Abschließend


Implizite Vorteile der Aufteilung eines Blattes in Controller:

  • Die Möglichkeit, isolierte Funktionen, Methoden und Schnittstellen, die an die Logik dieses Controllers gebunden sind, in separaten Dateien in der Nähe der Controller abzulegen
  • Deutlich reduziertes Risiko, versehentlich alles zu beschädigen
  • Modularität: Ein- / Ausschalten / Geben eines bestimmten Teils des Publikums Diese oder jene Logik kann einfach durch Hinzufügen und Entfernen von Controllern zum Array erfolgen, einschließlich durch Aktualisieren der Konfiguration ohne Programmierung - dafür müssen wir natürlich ein paar Buchstaben schreiben, da wir dies noch nicht getan haben erreicht
  • Die Fähigkeit, dem Benutzer klar zu sagen, was genau von ihm erwartet wird, wenn er (was häufig vorkommt) am Ende der Verarbeitung nach der Methode etwas falsch macht - und dies dort, wo dies der Ort ist

Unsere Pläne :

Dieser Artikel beschreibt das grundlegende Skript für die Arbeit mit Hobot für Textnachrichten.
Wenn sich das Thema als relevant herausstellt, werden wir in zukünftigen Artikeln weitere technische Feinheiten des Frameworks und unsere Beobachtungen aus der Praxis der Entwicklung und Verwendung von Telegramm-Bots teilen.

Links : Die

Installation des Bots sieht folgendermaßen aus: npm i -s hobot
Repository mit exemplarischer Anleitung in README.MD und dem Sandbox
Ready-Bot , der in der Produktion auf der Basis von Hobot arbeitet.

Vielen Dank für Ihre Aufmerksamkeit. Ich freue mich über Ihre Fragen, Vorschläge oder Ideen für neue Bots!

All Articles