From dadad2b96f63dfe320e040aebd7ee11062e9b32d Mon Sep 17 00:00:00 2001 From: Matheus Faria <matheus.sousa.faria@gmail.com> Date: Wed, 24 Jan 2018 11:15:28 -0200 Subject: [PATCH] Extracting natural related code to external file Signed-off-by: Matheus Faria <matheus.sousa.faria@gmail.com> Signed-off-by: Eduardo Nunes <eduardonunes2525@gmail.com> --- .hubot_history | 1 - launch.sh | 16 --- scripts/bot/brain.coffee | 180 +++++++++++++++++++++++++++++++++ scripts/bot/index.coffee | 209 ++------------------------------------- 4 files changed, 189 insertions(+), 217 deletions(-) delete mode 100644 .hubot_history delete mode 100755 launch.sh create mode 100644 scripts/bot/brain.coffee diff --git a/.hubot_history b/.hubot_history deleted file mode 100644 index 1385f26..0000000 --- a/.hubot_history +++ /dev/null @@ -1 +0,0 @@ -hey diff --git a/launch.sh b/launch.sh deleted file mode 100755 index c920a94..0000000 --- a/launch.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -export HUBOT_ADAPTER=rocketchat -export HUBOT_OWNER=RocketChat -export HUBOT_NAME='CatBot' -export HUBOT_DESCRIPTION="Processamento de linguagem natural com hubot" -export ROCKETCHAT_URL=https://chat.dorgam.it -export ROCKETCHAT_ROOM=GENERAL -export RESPOND_TO_DM=true -export RESPOND_TO_LIVECHAT=true -export ROCKETCHAT_USER=catbot -export ROCKETCHAT_PASSWORD='botPassword' -export ROCKETCHAT_AUTH=password -export HUBOT_LOG_LEVEL=debug -export HUBOT_CORPUS='catbot-en.yml' -export HUBOT_LANG='en' -bin/hubot -a rocketchat diff --git a/scripts/bot/brain.coffee b/scripts/bot/brain.coffee new file mode 100644 index 0000000..e7510b1 --- /dev/null +++ b/scripts/bot/brain.coffee @@ -0,0 +1,180 @@ +fs = require 'fs' +path = require 'path' +natural = require 'natural' + +brain = {} + +lang = (process.env.HUBOT_LANG || 'en') + +PorterStemmer = natural.PorterStemmer +if lang != 'en' + PorterStemmer = require '../../node_modules/natural/lib/natural/stemmers/porter_stemmer_' + lang + '.js' + +events = {} +nodes = {} +error_count = 0 +err_nodes = 0 + +eventsPath = path.join __dirname, '..', 'events' +for event in fs.readdirSync(eventsPath).sort() + events[event.replace /\.coffee$/, ''] = require path.join eventsPath, event + +classifyInteraction = (interaction, classifier) -> + if Array.isArray interaction.expect + for doc in interaction.expect + if interaction.multi == true + classifier.addDocument(doc, interaction.name+'|'+doc) + else + classifier.addDocument(doc, interaction.name) + + if Array.isArray interaction.next?.interactions + interaction.next.classifier = new natural.LogisticRegressionClassifier(PorterStemmer) + for nextInteractionName in interaction.next.interactions + nextInteraction = global.config.interactions.find (n) -> + return n.name is nextInteractionName + if not nextInteraction? + console.log 'No valid interaction for', nextInteractionName + continue + classifyInteraction nextInteraction, interaction.next.classifier + interaction.next.classifier.train() + + if interaction.multi == true + interaction.classifier = new natural.LogisticRegressionClassifier(PorterStemmer) + for doc in interaction.expect + interaction.classifier.addDocument(doc, doc) + interaction.classifier.train() + +brain.train = () -> + console.log 'Processing interactions' + console.time 'Processing interactions (Done)' + + global.nodes = {} + global.classifier = new natural.LogisticRegressionClassifier(PorterStemmer) + + for interaction in global.config.interactions + {name, event} = interaction + global.nodes[name] = new events[event] interaction + # count error nodes + if name.substr(0,5) == "error" + err_nodes++ + if interaction.level != 'context' + classifyInteraction interaction, global.classifier + + global.classifier.train() + + console.timeEnd 'Processing interactions (Done)' + +setContext = (res, context) -> + key = 'context_'+res.envelope.room+'_'+res.envelope.user.id + console.log 'set context', context + res.robot.brain.set(key, context) + +getContext = (res) -> + key = 'context_'+res.envelope.room+'_'+res.envelope.user.id + return res.robot.brain.get(key) + +isDebugMode = (res) -> + key = 'configure_debug-mode_'+res.envelope.room + return (res.robot.brain.get(key) == 'true') + +getDebugCount = (res) -> + key = 'configure_debug-count_'+res.envelope.room + return if res.robot.brain.get(key) then res.robot.brain.get(key) - 1 else false + +buildClassificationDebugMsg = (res, classifications) -> + list = '' + debugCount = getDebugCount(res) + + if debugCount + classifications = classifications[0..debugCount] + + for classification, i in classifications + list = list.concat 'Label: ' + classification.label + ' Score: ' + classification.value + '\n' + + newMsg = { + channel: res.envelope.user.roomID, + msg: "Classifications considered:", + attachments: [{ + text: list + }] + } + + return newMsg + +incErrors = (res) -> + key = 'errors_'+res.envelope.room+'_'+res.envelope.user.id + errors = res.robot.brain.get(key) or 0 + errors++ + console.log 'inc errors ', errors + res.robot.brain.set(key, errors) + return errors + +clearErrors = (res) -> + console.log 'clear errors' + key = 'errors_'+res.envelope.room+'_'+res.envelope.user.id + res.robot.brain.set(key, 0) + +brain.processMessage = (res, msg) -> + context = getContext(res) + currentClassifier = global.classifier + trust = global.config.trust + interaction = undefined + debugMode = isDebugMode(res) + console.log 'context ->', context + + if context + interaction = global.config.interactions.find (interaction) -> interaction.name is context + if interaction? and interaction.next?.classifier? + currentClassifier = interaction.next.classifier + + if interaction.next.trust? + trust = interaction.next.trust + + classifications = currentClassifier.getClassifications(msg) + + console.log 'classifications ->', classifications[0..4] + + if debugMode + newMsg = buildClassificationDebugMsg(res, classifications) + robot.adapter.chatdriver.customMessage(newMsg); + + if classifications[0].value >= trust + clearErrors res + [node_name, sub_node_name] = classifications[0].label.split('|') + console.log({node_name, sub_node_name}) + int = global.config.interactions.find (interaction) -> + interaction.name is node_name + if int.classifier? + subClassifications = int.classifier.getClassifications(msg) + else + if Array.isArray interaction?.next?.error + error_count = incErrors res + error_node_name = interaction.next.error[error_count - 1] + if not error_node_name? + clearErrors res + error_node_name = interaction.next.error[0] + else if interaction?.next? + setContext(res, undefined) + return brain.processMessage(res, msg) + else + error_count = incErrors res + if error_count > err_nodes + clearErrors res + error_node_name = "error-" + error_count + + currentInteraction = global.config.interactions.find (interaction) -> + interaction.name is node_name or interaction.name is error_node_name + + if not currentInteraction? + clearErrors res + return console.log 'Invalid interaction ['+node_name+']' + + if currentInteraction.context == 'clear' + setContext(res, undefined) + else if node_name? + setContext(res, node_name) + + currentNode = global.nodes[node_name or error_node_name] + currentNode.process.call @, res, msg, subClassifications + +module.exports = brain diff --git a/scripts/bot/index.coffee b/scripts/bot/index.coffee index 77f7ecf..0687466 100644 --- a/scripts/bot/index.coffee +++ b/scripts/bot/index.coffee @@ -1,30 +1,10 @@ -fs = require 'fs' -path = require 'path' -natural = require 'natural' - -lang = (process.env.HUBOT_LANG || 'en') - -if lang == "en" - PorterStemmer = require path.join '..', '..', 'node_modules', 'natural', 'lib', - 'natural', 'stemmers', 'porter_stemmer.js' -else - PorterStemmer = require path.join '..', '..', 'node_modules', 'natural', 'lib', - 'natural', 'stemmers', 'porter_stemmer_' + lang + '.js' - -debug_mode = ((process.env.HUBOT_NATURAL_DEBUG_MODE == 'true') || false) - -config = {} -events = {} -nodes = {} -error_count = 0 -err_nodes = 0 +require 'coffeescript/register' -{ regexEscape, loadConfigfile } = require path.join '..', 'lib', 'common.coffee' -{ getUserRoles, checkRole } = require path.join '..', 'lib', 'security.coffee' +path = require 'path' -eventsPath = path.join __dirname, '..', 'events' -for event in fs.readdirSync(eventsPath).sort() - events[event.replace /\.coffee$/, ''] = require path.join eventsPath, event +{regexEscape, loadConfigfile} = require '../lib/common' +{getUserRoles, checkRole} = require '../lib/security' +brain = require './brain' typing = (res, t) -> res.robot.adapter.callMethod 'stream-notify-room', @@ -56,89 +36,6 @@ sendWithNaturalDelay = (msgs, elapsed = 0) -> cb?() , delay -# setUserName = (res, name) -> -# res.robot.adapter.callMethod 'livechat:saveInfo', -# _id: res.envelope.user.id -# name: name -# , -# _id: res.envelope.room -# # - -classifyInteraction = (interaction, classifier) -> - if Array.isArray interaction.expect - for doc in interaction.expect - if interaction.multi == true - classifier.addDocument(doc, interaction.name + '|' + doc) - else - classifier.addDocument(doc, interaction.name) - - if Array.isArray interaction.next?.interactions - interaction.next.classifier = new natural.LogisticRegressionClassifier(PorterStemmer) - for nextInteractionName in interaction.next.interactions - nextInteraction = global.config.interactions.find (n) -> - return n.name is nextInteractionName - if not nextInteraction? - console.log 'No valid interaction for', nextInteractionName - continue - classifyInteraction nextInteraction, interaction.next.classifier - interaction.next.classifier.train() - - if interaction.multi == true - interaction.classifier = new natural.LogisticRegressionClassifier(PorterStemmer) - for doc in interaction.expect - interaction.classifier.addDocument(doc, doc) - interaction.classifier.train() - -setContext = (res, context) -> - key = 'context_' + res.envelope.room + '_' + res.envelope.user.id - console.log 'set context', context - res.robot.brain.set(key, context) - -getContext = (res) -> - key = 'context_' + res.envelope.room + '_' + res.envelope.user.id - return res.robot.brain.get(key) - -isDebugMode = (res) -> - key = 'configure_debug-mode_' + res.envelope.room - return (res.robot.brain.get(key) == 'true') - -getDebugCount = (res) -> - key = 'configure_debug-count_' + res.envelope.room - return if res.robot.brain.get(key) then res.robot.brain.get(key) - 1 else false - -buildClassificationDebugMsg = (res, classifications) -> - list = '' - debugCount = getDebugCount(res) - - if debugCount - classifications = classifications[0..debugCount] - - for classification, i in classifications - list = list.concat 'Label: ' + classification.label + ' Score: ' + classification.value + '\n' - - newMsg = { - channel: res.envelope.user.roomID, - msg: "Classifications considered:", - attachments: [{ - text: list - }] - } - - return newMsg - -incErrors = (res) -> - key = 'errors_' + res.envelope.room + '_' + res.envelope.user.id - errors = res.robot.brain.get(key) or 0 - errors++ - console.log 'inc errors ', errors - res.robot.brain.set(key, errors) - return errors - -clearErrors = (res) -> - console.log 'clear errors' - key = 'errors_' + res.envelope.room + '_' + res.envelope.user.id - res.robot.brain.set(key, 0) - module.exports = (_config, robot) -> global.config = _config @@ -151,95 +48,7 @@ module.exports = (_config, robot) -> robot.logger.warning 'No trust level configured.' return - classifier = new natural.LogisticRegressionClassifier(PorterStemmer) - - global.train = () -> - console.log 'Processing interactions' - console.time 'Processing interactions (Done)' - - global.nodes = {} - global.classifier = new natural.LogisticRegressionClassifier(PorterStemmer) - - for interaction in global.config.interactions - { name, event } = interaction - global.nodes[name] = new events[event] interaction - # count error nodes - if name.substr(0, 5) == "error" - err_nodes++ - if interaction.level != 'context' - classifyInteraction interaction, global.classifier - console.log('\tProcessing interaction: ' + name) - - console.log 'Training Hubot (This could be take a while...)' - global.classifier.train() - console.log '\n' - - console.timeEnd '\nProcessing interactions (Done)' - - global.train() - - processMessage = (res, msg) -> - context = getContext(res) - currentClassifier = global.classifier - trust = global.config.trust - interaction = undefined - debugMode = isDebugMode(res) - console.log 'context ->', context - - if context - interaction = global.config.interactions.find (interaction) -> interaction.name is context - if interaction? and interaction.next?.classifier? - currentClassifier = interaction.next.classifier - - if interaction.next.trust? - trust = interaction.next.trust - - classifications = currentClassifier.getClassifications(msg) - - console.log 'classifications ->', classifications[0..4] - - if debugMode - newMsg = buildClassificationDebugMsg(res, classifications) - robot.adapter.chatdriver.customMessage(newMsg) - - if classifications[0].value >= trust - clearErrors res - [node_name, sub_node_name] = classifications[0].label.split('|') - console.log({ node_name, sub_node_name }) - int = global.config.interactions.find (interaction) -> - interaction.name is node_name - if int.classifier? - subClassifications = int.classifier.getClassifications(msg) - else - if Array.isArray interaction?.next?.error - error_count = incErrors res - error_node_name = interaction.next.error[error_count - 1] - if not error_node_name? - clearErrors res - error_node_name = interaction.next.error[0] - else if interaction?.next? - setContext(res, undefined) - return processMessage(res, msg) - else - error_count = incErrors res - if error_count > err_nodes - clearErrors res - error_node_name = "error-" + error_count - - currentInteraction = global.config.interactions.find (interaction) -> - interaction.name is node_name or interaction.name is error_node_name - - if not currentInteraction? - clearErrors res - return console.log 'Invalid interaction [' + node_name + ']' - - if currentInteraction.context == 'clear' - setContext(res, undefined) - else if node_name? - setContext(res, node_name) - - currentNode = global.nodes[node_name or error_node_name] - currentNode.process.call @, res, msg, subClassifications + brain.train() robot.hear /(.+)/i, (res) -> res.sendWithNaturalDelay = sendWithNaturalDelay.bind(res) @@ -249,7 +58,7 @@ module.exports = (_config, robot) -> # check if robot should respond if res.envelope.user.roomType in ['c', 'p'] if (res.message.text.match new RegExp('\\b' + res.robot.name + '\\b', 'i')) or (res.message.text.match new RegExp('\\b' + res.robot.alias + '\\b', 'i')) - processMessage res, msg + brain.processMessage res, msg # TODO: Add engaged user conversation recognition/tracking - else if res.envelope.user.roomType in ['d', 'l'] - processMessage res, msg + else if res.envelope.user.roomType in ['d','l'] + brain.processMessage res, msg -- GitLab