Recognizing commands

When developing bots for Telegram and other messengers, the task of recognizing and fulfilling requests expressed in human language periodically arises. It is this "feature", in some opinion, is the main difference between bots and command line applications. Under the cut, a proprietary framework for executing arbitrary speech commands is described. Descriptions of key concepts are accompanied by examples in the Kotlin language.





Let's take normalized semantic representations as a basis for speech recognition. Their choice is primarily due to the simplicity and ease of implementation. Let's start with the basics, an example from the framework sources:





/**      */
typealias Rule = (String) -> Boolean

/**    */
open class Semnorm(vararg val rules: Rule)

/**       */
fun stem(vararg stems: String): Rule = { stems.any(it::startsWith) }

/**        */
fun word(vararg words: String): Rule = { words.any(it::equals) }

/**      */
fun String.matches(norm: Semnorm) = norm.rules.any { it(this) }
      
      



Now we have the ability to define predefined normalized semantic representations in the form of objects:





object Day : Semnorm(stem("day", "", "", "", "", ""))
      
      



The framework puts them in correspondence with the tokens of the incoming phrases, and the sentence starts to look like this:





assertThat(
  "   5 ".tokenize(), 
  equalTo(
   listOf(
     Token("", Ban), 
     Token("", null),
     Token("", null), 
     Token("5", Number),
     Token("", Minute)
   )
  )
)
      
      



We have dealt with speech recognition. The tokenizer code is attached in the repository available at the end of the article. Let's move on to executing the commands from the speech. And this is where the most interesting thing begins: the framework allows for each semantic representation to hang the specified behavior. Again, the simplest example of how to recognize a help request in two languages:





object Help : ExecutableSemnorm(stem(
  "", "", "", "help", 
  "rule", "faq", "start", "",
)) {
  override fun execute(bot: Bot, m: Message) {
    val faq = message.from.relatedFaq()
    bot.sendMessage(m.chat.id, faq)
  }
}
      
      



, ? , , , , :





object Ban : DurableSemonrm(stem(
  "ban", "block", "mute", "", "",
  "", "", "",
)) {
  override fun execute(
    bot: Bot, attackerMessage: Message, duration: Duration) {

    val victimMessage = attackerMessage.replyToMessage
    val victimId = victimMessage.from.id
    val untilSecond = now().epochSecond + duration.inWholeSeconds

    bot.restrictChatMember(
      attackerMessage.chat.id, victimId, untilSecond)
  }
}
      
      



? , . , :





object Week : Semnorm(stem("week", "")) {
  override fun toDuration(number: Long) = 
    days(number) * 7
}
      
      



, :





class DurableSemnorm(vararg rules: Rule) : ExecutableSemnorm(*rules) {

  final override fun execute(
    token: Iterator<Token>, bot: Bot, m: Message) = 
      execute(bot, message, token.parseDuration())

  abstract fun execute(bot: Bot, m: Message, duration: Duration)
}
      
      



, . . , , Github.








All Articles