Last active
October 27, 2020 07:29
-
-
Save nileshtrivedi/8c1a2864efcb3d8d0910a9861422b152 to your computer and use it in GitHub Desktop.
A stateful chatbot to capture data in 50 lines of Ruby
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# https://medium.com/cleartax-engineering/a-simple-rule-based-stateful-chatbot-for-data-capture-ebfad9271388 | |
require 'ostruct' | |
# Track the current state of the conversation | |
state = {pointer: ""} | |
# Metadata is used to fill the outgoing templates AND store the captured data from conversation | |
metadata = {name: "Calvin"} | |
# Build the conversation tree from bottom up | |
# Each node has the outgoing message and incoming message patterns | |
# next_node pointer is where the conversation will jump to if the pattern matches | |
last = OpenStruct.new({ | |
outgoing: "Congrats on your %{age}th year", | |
incoming: [] | |
}) | |
second = OpenStruct.new({ | |
outgoing: "Ok %{name}, how old are you?", | |
incoming: [ | |
OpenStruct.new({pattern: /(?<age>\d+)/, next_node: last}) | |
] | |
}) | |
# Root of the conversation tree | |
$chat_tree = OpenStruct.new({ | |
outgoing: "Hi %{name}, is that you?", | |
incoming: [ | |
OpenStruct.new({pattern: /(y|Y|yes|YES)/, next_node: second}), | |
OpenStruct.new({pattern: /(no|NO)/, next_node: last}) | |
] | |
}) | |
def tree_lookup(node_pointer) | |
node = $chat_tree | |
node_pointer.split("/").reject(&:empty?).each do |idx| | |
resp = node.incoming[idx.to_i] | |
node = resp.next_node || tree_lookup(resp.next_node_id) | |
end | |
return node | |
end | |
def process(message, state, metadata) | |
node = tree_lookup(state[:pointer]) | |
resp = node.incoming.select { |r| message.match(r.pattern) }.first | |
if resp | |
resp_index = node.incoming.index(resp) | |
state.merge! message.match(resp.pattern).named_captures.inject({}){|memo, (k,v)| memo[k.to_sym] = v; memo} | |
state[:pointer] += "/#{resp_index}" | |
puts "state = #{state.inspect}" | |
puts resp.next_node.outgoing % state.merge(metadata) | |
else | |
puts "Sorry, I didn't understand. Try again?" | |
end | |
end | |
puts $chat_tree.outgoing % state.merge(metadata) | |
while true do | |
msg = gets.chomp | |
process(msg, state, metadata) | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment