Skip to content

Instantly share code, notes, and snippets.

@nileshtrivedi
Last active October 27, 2020 07:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nileshtrivedi/8c1a2864efcb3d8d0910a9861422b152 to your computer and use it in GitHub Desktop.
Save nileshtrivedi/8c1a2864efcb3d8d0910a9861422b152 to your computer and use it in GitHub Desktop.
A stateful chatbot to capture data in 50 lines of Ruby
# 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