Dismal Tony is a conversational, virtual assistant I've been working on, that includes elements of Natural Language understanding, Web Integration, Text parsing and processing, and unique facets of the Ruby programming language. Writing code for Tony taught me a lot about Ruby, and programming in general. It's a project I'm very proud of, and one that I use myself.
Tony is designed to have mini-programs known as Directives written, which have known Criteria associated with them. This forms an internal structure of actions that can be taken when these directives are triggered. That's a very long winded way of saying that writing code for Tony allows him to understand new topics and actions based on what the words mean, and how to respond to them and reply.
module DismalTony::Directives
class DrinkMixDirective < DismalTony::Directive
include DismalTony::DirectiveHelpers::DataRepresentationHelpers
set_name :drinkmix
set_group :fun
expect_frags :drink
use_parsing_strategies do |use|
use << DismalTony::ParsingStrategies::ComprehendSyntaxStrategy
end
add_criteria do |qry|
qry << must { |q| q =~ /drink/i }
qry << must { |q| q.verb&.any_of?(/want/i, /like/i, /mix/i, /make/i, /pick/i, /choose/i) }
end
def run
frags[:drink] = ['Glass of Water', 'Old Fashioned', 'Zombie', 'Mai Tai', 'Rum & Coke', 'Gin & Tonic', 'Pepsi Crystal'].sample
moj = %w[martini pineapple think tropicaldrink beer cheers toast champagne].sample
return_data(frags[:drink])
DismalTony::HandledResponse.finish("~e:#{moj} Okay, #{query.user['nickname']}. Have a #{frags[:drink]}!")
end
end
end
Tony takes advantage of any number of modular Parsing Strategies, including AWS Comprehend, and a Google Tensorflow Parsey McParseface Parser. This allows the Directive to define its match logic requirements using powerful sentence structure analysis methods, conveniently inside a Ruby data structure for performing complex NLU matching simply.
add_criteria do |qry|
qry << uniquely { |q| q.contains?(/weather/i, /\brain/i) }
qry << must(&:location?)
qry << could { |q| q.key_phrases.any? { |ph| ph.text.match?(/\bweather\b/i) } }
end
This allows really simple means of handling complex tasks by breaking them into logical, lexical structures. Assisting the Virtual Agent in learning the language allows the programmer to understand their query better, and is a dually didactic process. Built in to the system are a few MatchLogic keyword methods which allow you to easier specify when a query should match the directive. This way, a couple match criteria can yield a huge variety of acceptable phrasings.
# Highest level of presence detection, used for key words and phrases,
# and other imperatives.
class Uniquely < MatchLogic
def initialize(pre)
# :nodoc:
super(pre)
@priority = :uniquely
@success_incr = 7
end
def on_failure
raise MatchLogicFailure
end
end
After executing the instructions, Tony will reply with a conversational output, featuring an emoji and a response string. These are defined within the Directive, and are thus context sensitive and dynamically settable within the code to use any information available to Tony.
def run
if /how are you/.match?(query)
DismalTony::HandledResponse.finish("~e:thumbsup I'm doing well!")
else
moj = random_emoji(
'wave',
'smile',
'rocket',
'star',
'snake',
'cat',
'octo',
'spaceinvader'
)
resp = "#{synonym_for('hello').capitalize}"
resp << ', ' << query.user[:nickname] if rand(4) <= 2
resp << ['!', '.'].sample
DismalTony::HandledResponse.finish("~e:#{moj} #{resp}")
You can include Tony in existing Ruby work for a quick text notification when long running processes are complete, but Tony is best utilized within a persistent web accessible application. A simple Rails application designed to receive and send Twilio messages allows the various directives to be utilized as a mobile service. Customize the conversation paths and directive executions, and Tony becomes the friendly face of info queries, backend operations, and custom scripts.