Roteando em chatbots complexos com a estrutura Hobot



Tendo começado a desenvolver bots para o Telegram há vários anos, descobri o desempenho, a simplicidade e a flexibilidade de trabalhar com eles como um caso especial da interface da linha de comando. Essas características, disponíveis para muitos hoje em dia, devem-se em grande parte à popular estrutura telegraf.js e similares, que fornecem métodos simplificados para trabalhar com a API do Telegram.

A arquitetura do projeto, ao mesmo tempo, cabe inteiramente ao desenvolvedor e, a julgar pelo número modesto de bots complexos e multifuncionais, ainda temos espaço para crescer nesse sentido.

Neste artigo, quero falar sobre uma pequena estrutura de roteamento de chatbot, sem a qual o desenvolvimento do nosso projeto seria impossível.

Algumas informações básicas


Em chatbots e CLIs, a execução de uma única ação lógica geralmente consiste em várias etapas de refinamento ou etapas de ramificação. Isso requer que o programa armazene uma determinada coordenada para lembrar em que ponto do fluxo o usuário está localizado e executar seu comando de acordo com essa coordenada.

A ilustração mais simples é a execução do comando npm init , durante o qual o programa solicita que você especifique um ou outro dado para package.json por vez.

Na primeira etapa, ela diz ao usuário que está aguardando a entrada de texto do nome do pacote - e o que o usuário envia a ela com o próximo comando será salvo como o nome do pacote, graças à variável na qual essa espera é gravada.

Chamamos esse caminho de variável - o caminho ao qual esse ou aquele código está logicamente conectado. É sempre necessário se o bot tiver navegação ou comandos emitidos em algumas etapas.

Prática de hoje em arquitetura de bot


A abordagem que eu olhei pela primeira vez por outros desenvolvedores tinha a seguinte aparência: para qualquer atualização vinda do usuário, uma lista de verificações para um valor específico da variável path é gravada e a lógica de negócios e a navegação adicional são colocadas nessas verificações da forma mais elementar:

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

Se você possui apenas algumas equipes e algumas etapas para cada equipe, esta solução é ideal. Chegar à terceira equipe e à sétima, se você começar a pensar que algo está errado.

Em um dos bots com os quais tivemos a chance de trabalhar em um estágio tardio, o núcleo do funcional era uma folha de 4000 linhas e ~ 70 condições apenas do nível superior que cresceu em dois ifs, com uma verificação do que deu certo - às vezes caminhos, às vezes comandos, às vezes caminhos e comandos. Todas essas condições foram verificadas para cada ação do usuário e acessadas funções auxiliares de um objeto de planilha vizinho, que também cresceu de várias linhas. Escusado será dizer que quão lento e esquisito foi esse projeto?

Quadro Hobot


Iniciando o ActualizeBot, já imaginávamos o tamanho, e nossa primeira tarefa foi preservar a extensibilidade e a velocidade do desenvolvimento.

Para fazer isso, dividimos a lógica do cliente em controladores atribuídos aos caminhos e escrevemos uma pequena abstração para navegar entre esses controladores e processar as mensagens recebidas do usuário neles.

Tudo isso, baseado em grandes projetos, foi escrito em TypeScript e recebeu o nome elegante Hobot, que sugere, é claro, pipelines de navegação.

Um controlador é um objeto simples de três propriedades:

  • path - identificador de string do caminho usado para inicializar e navegar pelos caminhos inicializados
  • get — , , hobot.gotoPath(ctx, path, data?). — data ,
  • post — . , , . updateType — , : text, callback_query . updateType ctx

Exemplo de controlador:

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

Parece um pouco mais complicado do que no começo, mas a dificuldade permanecerá a mesma quando você tiver 100 ou 200 caminhos.

A lógica interna é trivial e é surpreendente que ninguém tenha feito isso ainda :
esses controladores são adicionados a um objeto cujas chaves são os valores da propriedade path e são chamadas por essas chaves durante as ações do usuário ou ao navegar usando hobot.gotoPath (ctx, path, data? ) .

A navegação é alocada em um método separado para não tocar no caminho variável e na lógica de navegação, mas pensar apenas na lógica de negócios, embora você sempre possa alterar ctx.session.path com as mãos, o que, é claro, não é recomendado.

Tudo o que você precisa fazer para que seu novo bot com estrutura indestrutível funcione é iniciar um bot telegraf regular e passá-lo e o objeto de configuração ao construtor Hobot. O objeto de configuração consiste nos controladores que você deseja inicializar, no caminho padrão e nos pares comando / controlador.

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

Em conclusão


Vantagens implícitas de dividir uma planilha em controladores:

  • A capacidade de colocar funções, métodos e interfaces isolados que estão bloqueados na lógica deste controlador em arquivos separados ao lado dos controladores
  • Risco significativamente reduzido de quebrar acidentalmente tudo
  • Modularidade: ligar / desligar / atribuir a um determinado segmento da audiência esta ou aquela lógica pode ser feita simplesmente adicionando e removendo controladores da matriz, inclusive atualizando a configuração sem programação - para isso, é claro, precisamos escrever algumas letras, pois ainda não temos alcançado
  • A capacidade de dizer claramente ao usuário o que exatamente é esperado dele quando ele (o que geralmente acontece) faz algo errado - e faz isso onde é esse o lugar - no final do processamento do método pós

Nossos planos :

Este artigo descreve o script básico para trabalhar com as mensagens de texto Hobot.
Se o tópico for relevante, compartilharemos outras sutilezas técnicas da estrutura e nossas observações da prática de desenvolver e usar bots de telegrama em artigos futuros.

Links : A

instalação do bot se parece com isso: npm i -s hobot
Repositório com explicação em README.MD e o bot sandbox
Ready trabalhando na produção com base no Hobot.

Obrigado por sua atenção, ficarei feliz em ouvir suas perguntas, sugestões ou idéias para novos bots!

All Articles