Routage dans des chatbots complexes avec le framework Hobot



Ayant commencé à développer des bots pour Telegram il y a plusieurs années, j'ai découvert les performances, la simplicité et la flexibilité de travailler avec eux comme un cas particulier de l'interface de ligne de commande. Ces caractéristiques, disponibles pour beaucoup aujourd'hui, sont en grande partie dues au framework telegraf.js populaire et similaires, qui fournissent des méthodes simplifiées pour travailler avec l'API Telegram.

L'architecture du projet appartient en même temps entièrement au développeur et, à en juger par le nombre modeste de robots complexes et multifonctionnels, nous avons encore de la place pour grandir à cet égard.

Dans cet article, je veux parler d'un petit framework de routage de chatbot, sans lequel le développement de notre projet serait impossible.

Quelques informations de base


Dans les chatbots et les CLI, l'exécution d'une seule action logique consiste souvent en plusieurs étapes de raffinement ou étapes de branchement. Cela nécessite que le programme stocke une certaine coordonnée afin de se rappeler où se trouve l'utilisateur et d'exécuter sa commande conformément à cette coordonnée.

L'illustration la plus simple est l'exécution de la commande npm init , au cours de laquelle le programme vous demande de spécifier l'une ou l'autre des données pour package.json à son tour.

Dans la première étape, elle indique à l'utilisateur qu'elle attend la saisie de texte du nom du package - et ce que l'utilisateur lui envoie avec la commande suivante sera enregistré en tant que nom du package grâce à la variable dans laquelle cette attente est écrite.

Nous appelons ce chemin variable - le chemin auquel tel ou tel code est logiquement attaché. Il est toujours nécessaire si le bot a une navigation ou des commandes émises en quelques étapes.

La pratique d'aujourd'hui en architecture bot


L'approche que j'ai d'abord examinée par d'autres développeurs ressemblait à ceci: pour toute mise à jour provenant de l'utilisateur, une liste de vérifications pour une valeur particulière de la variable de chemin est écrite et la logique métier et une navigation supplémentaire sont placées à l'intérieur de ces vérifications sous la forme la plus élémentaire:

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

Si vous n'avez que quelques équipes et quelques étapes pour chaque équipe, cette solution est optimale. Rejoindre la troisième équipe et la septième si vous commencez à penser que quelque chose ne va pas.

Dans l'un des bots avec lesquels nous avons eu la chance de travailler à un stade avancé, le cœur de la fonctionnalité était une feuille de 4000 lignes et ~ 70 conditions uniquement du niveau supérieur qui était issue de deux ifs, avec une vérification de ce qui allait à cœur - parfois des chemins, parfois des commandes, parfois des chemins et des commandes. Toutes ces conditions ont été vérifiées pour chaque action de l'utilisateur et ont accédé aux fonctions auxiliaires à partir d'un objet feuille voisin, qui est également passé de plusieurs lignes. Dois-je dire à quelle vitesse et avec quelle lenteur ce projet s'est déroulé?

Cadre Hobot


En lançant ActualizeBot, nous avions déjà imaginé sa taille, et notre première tâche était de préserver l'extensibilité et la vitesse de développement.

Pour ce faire, nous avons divisé la logique client en contrôleurs affectés aux chemins et écrit une petite abstraction pour naviguer entre ces contrôleurs et traiter les messages reçus de l'utilisateur en eux.

Tout cela, basé sur de grands projets, a été écrit en TypeScript et a reçu le nom élégant de Hobot, qui fait allusion, bien sûr, aux pipelines de navigation.

Un contrôleur est un simple objet de trois propriétés:

  • path - identifiant de chaîne du chemin utilisé pour initialiser et parcourir les chemins initialisés
  • get — , , hobot.gotoPath(ctx, path, data?). — data ,
  • post — . , , . updateType — , : text, callback_query . updateType ctx

Exemple de contrôleur:

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');
        }
    }
}

Cela semble un peu plus compliqué qu'au début, mais la difficulté restera la même lorsque vous aurez 100 ou 200 chemins.

La logique interne est triviale et il est surprenant que personne ne l'ait encore fait :
ces contrôleurs sont ajoutés à un objet dont les clés sont les valeurs de la propriété path et sont appelés à partir de lui par ces clés lors des actions de l'utilisateur ou lors de la navigation à l'aide de hobot.gotoPath (ctx, path, data? ) .

La navigation est allouée dans une méthode distincte afin de ne pas toucher le chemin variable et la logique de navigation, mais de ne penser qu'à la logique métier, bien que vous puissiez toujours changer ctx.session.path avec vos mains, ce qui, bien sûr, n'est pas recommandé.

Tout ce que vous devez faire pour faire fonctionner votre nouveau bot avec une structure indestructible est de lancer un bot telegraf régulier et de le passer ainsi que l'objet de configuration au constructeur Hobot. L'objet config se compose des contrôleurs que vous souhaitez initialiser, du chemin par défaut et des paires commande / contrôleur.

//   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();

En conclusion


Avantages implicites de la division d'une feuille en contrôleurs:

  • La possibilité de placer des fonctions, méthodes et interfaces isolées qui sont verrouillées sur la logique de ce contrôleur dans des fichiers séparés à côté des contrôleurs
  • Risque considérablement réduit de tout casser accidentellement
  • Modularité: allumer / éteindre / donner à un certain segment de l'auditoire telle ou telle logique peut être faite simplement en ajoutant et en supprimant des contrôleurs du tableau, y compris en mettant à jour la configuration sans programmation - pour cela, bien sûr, nous devons écrire quelques lettres, car nous n'en avons toujours pas besoin atteint
  • La possibilité de dire clairement à l'utilisateur ce qu'on attend exactement de lui quand il (ce qui arrive souvent) fait quelque chose de mal - et de le faire où c'est le cas - à la fin du traitement de la méthode de publication

Nos plans :

Cet article décrit le scénario de base pour travailler avec la messagerie texte Hobot.
Si le sujet s'avère pertinent, nous partagerons d'autres subtilités techniques du cadre et nos observations de la pratique de développement et d'utilisation de robots télégrammes dans de futurs articles.

Liens : L'

installation du bot ressemble à ceci: npm i -s hobot
Référentiel avec procédure pas à pas dans README.MD et le bot Sandbox
Ready travaillant en production basé sur Hobot.

Merci de votre attention, je serai heureux d'entendre vos questions, suggestions ou idées de nouveaux bots!

All Articles