botium-core 1.14.3 → 1.14.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/botium-cjs.js +87 -63
- package/dist/botium-cjs.js.map +1 -1
- package/dist/botium-es.js +105 -81
- package/dist/botium-es.js.map +1 -1
- package/package.json +1 -1
- package/src/scripting/CompilerMarkdown.js +2 -1
- package/src/scripting/CompilerTxt.js +1 -4
- package/src/scripting/MatchFunctions.js +30 -8
- package/src/scripting/ScriptingProvider.js +12 -10
- package/src/scripting/helper.js +3 -2
- package/test/compiler/compilermarkdown.spec.js +3 -3
- package/test/compiler/compilertxt.spec.js +1 -1
- package/test/compiler/convos/txt/convos_emptyrow_just_emptyrow.convo.txt +1 -1
- package/test/scripting/asserters/convoStepParameters.spec.js +11 -0
- package/test/scripting/asserters/convos/convo_step_parameter_matchmode_failed_wer.convo.txt +9 -0
- package/test/scripting/logichooks/convos/scripting_memory_overwrite_and_check.convo.txt +2 -2
package/package.json
CHANGED
|
@@ -89,7 +89,8 @@ module.exports = class CompilerMarkdown extends CompilerBase {
|
|
|
89
89
|
stepTag: 'Line ' + (step.map[0] + 1)
|
|
90
90
|
},
|
|
91
91
|
linesToConvoStep(step.children.map(child => child.content +
|
|
92
|
-
(child.children ? ' ' + child.children.map(child => child.content).join('|') : '')
|
|
92
|
+
(child.children && child.children.length > 0 ? ' ' + child.children.map(child => child.content).join('|') : '')
|
|
93
|
+
), sender, this.context, this.eol)
|
|
93
94
|
))
|
|
94
95
|
} else {
|
|
95
96
|
debug(`Expected sender ${validSenders.map(s => `'${s}'`).join(' or ')} but found ${sender}`)
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
const _ = require('lodash')
|
|
2
|
-
|
|
3
1
|
const Capabilities = require('../Capabilities')
|
|
4
2
|
const Constants = require('./Constants')
|
|
5
3
|
const CompilerBase = require('./CompilerBase')
|
|
@@ -37,7 +35,7 @@ module.exports = class CompilerTxt extends CompilerBase {
|
|
|
37
35
|
let scriptData = scriptBuffer
|
|
38
36
|
if (Buffer.isBuffer(scriptBuffer)) scriptData = scriptData.toString()
|
|
39
37
|
|
|
40
|
-
const lines =
|
|
38
|
+
const lines = scriptData.split(this.eol)
|
|
41
39
|
|
|
42
40
|
if (scriptType === Constants.SCRIPTING_TYPE_CONVO) {
|
|
43
41
|
return this._compileConvo(lines, false)
|
|
@@ -94,7 +92,6 @@ module.exports = class CompilerTxt extends CompilerBase {
|
|
|
94
92
|
|
|
95
93
|
lines.forEach((line) => {
|
|
96
94
|
currentLineIndex++
|
|
97
|
-
line = line.trim()
|
|
98
95
|
if (isValidTagLine(line)) {
|
|
99
96
|
pushPrev()
|
|
100
97
|
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
const _ = require('lodash')
|
|
2
|
+
const debug = require('debug')('botium-core-MatchFunctions')
|
|
2
3
|
|
|
3
4
|
const { toString, quoteRegexpString, calculateWer } = require('./helper')
|
|
4
5
|
|
|
5
|
-
const _normalize = (botresponse) => {
|
|
6
|
+
const _normalize = (botresponse, args, convoStepParameters) => {
|
|
7
|
+
if (!convoStepParameters) {
|
|
8
|
+
debug('Convo step parameters might be missing!')
|
|
9
|
+
}
|
|
6
10
|
if (_.isUndefined(botresponse) || _.isNil(botresponse)) return ''
|
|
7
11
|
if (_.isObject(botresponse) && _.has(botresponse, 'messageText')) {
|
|
8
12
|
return toString(botresponse.messageText) || ''
|
|
@@ -10,7 +14,10 @@ const _normalize = (botresponse) => {
|
|
|
10
14
|
return toString(botresponse)
|
|
11
15
|
}
|
|
12
16
|
|
|
13
|
-
const regexp = (ignoreCase) => (botresponse, utterance) => {
|
|
17
|
+
const regexp = (ignoreCase) => (botresponse, utterance, args, convoStepParameters) => {
|
|
18
|
+
if (!convoStepParameters) {
|
|
19
|
+
debug('Convo step parameters might be missing!')
|
|
20
|
+
}
|
|
14
21
|
if (_.isUndefined(botresponse)) return false
|
|
15
22
|
utterance = toString(utterance)
|
|
16
23
|
botresponse = _normalize(botresponse)
|
|
@@ -19,7 +26,10 @@ const regexp = (ignoreCase) => (botresponse, utterance) => {
|
|
|
19
26
|
return regexp.test(botresponse)
|
|
20
27
|
}
|
|
21
28
|
|
|
22
|
-
const wildcard = (ignoreCase) => (botresponse, utterance) => {
|
|
29
|
+
const wildcard = (ignoreCase) => (botresponse, utterance, args, convoStepParameters) => {
|
|
30
|
+
if (!convoStepParameters) {
|
|
31
|
+
debug('Convo step parameters might be missing!')
|
|
32
|
+
}
|
|
23
33
|
if (_.isUndefined(botresponse)) {
|
|
24
34
|
if (utterance.trim() === '*') return true
|
|
25
35
|
else return false
|
|
@@ -37,7 +47,10 @@ const wildcard = (ignoreCase) => (botresponse, utterance) => {
|
|
|
37
47
|
return regexp.test(botresponse)
|
|
38
48
|
}
|
|
39
49
|
|
|
40
|
-
const wildcardExact = (ignoreCase) => (botresponse, utterance) => {
|
|
50
|
+
const wildcardExact = (ignoreCase) => (botresponse, utterance, args, convoStepParameters) => {
|
|
51
|
+
if (!convoStepParameters) {
|
|
52
|
+
debug('Convo step parameters might be missing!')
|
|
53
|
+
}
|
|
41
54
|
if (_.isUndefined(botresponse)) {
|
|
42
55
|
if (utterance.trim() === '*') return true
|
|
43
56
|
else return false
|
|
@@ -55,7 +68,10 @@ const wildcardExact = (ignoreCase) => (botresponse, utterance) => {
|
|
|
55
68
|
return regexp.test(botresponse)
|
|
56
69
|
}
|
|
57
70
|
|
|
58
|
-
const include = (ignoreCase) => (botresponse, utterance) => {
|
|
71
|
+
const include = (ignoreCase) => (botresponse, utterance, args, convoStepParameters) => {
|
|
72
|
+
if (!convoStepParameters) {
|
|
73
|
+
debug('Convo step parameters might be missing!')
|
|
74
|
+
}
|
|
59
75
|
if (_.isUndefined(botresponse)) return false
|
|
60
76
|
utterance = toString(utterance)
|
|
61
77
|
botresponse = _normalize(botresponse)
|
|
@@ -67,7 +83,10 @@ const include = (ignoreCase) => (botresponse, utterance) => {
|
|
|
67
83
|
return botresponse.indexOf(utterance) >= 0
|
|
68
84
|
}
|
|
69
85
|
|
|
70
|
-
const equals = (ignoreCase) => (botresponse, utterance) => {
|
|
86
|
+
const equals = (ignoreCase) => (botresponse, utterance, args, convoStepParameters) => {
|
|
87
|
+
if (!convoStepParameters) {
|
|
88
|
+
debug('Convo step parameters might be missing!')
|
|
89
|
+
}
|
|
71
90
|
if (_.isUndefined(botresponse)) return false
|
|
72
91
|
utterance = toString(utterance)
|
|
73
92
|
botresponse = _normalize(botresponse)
|
|
@@ -79,11 +98,14 @@ const equals = (ignoreCase) => (botresponse, utterance) => {
|
|
|
79
98
|
return botresponse === utterance
|
|
80
99
|
}
|
|
81
100
|
|
|
82
|
-
const wer = () => (botresponse, utterance, args) => {
|
|
101
|
+
const wer = () => (botresponse, utterance, args, convoStepParameters) => {
|
|
102
|
+
if (!convoStepParameters) {
|
|
103
|
+
debug('Convo step parameters might be missing!')
|
|
104
|
+
}
|
|
83
105
|
botresponse = _normalize(botresponse || '')
|
|
84
106
|
utterance = toString(utterance || '')
|
|
85
107
|
|
|
86
|
-
const threshold = ([',', '.'].find(p => `${args[0]}`.includes(p)) ? parseFloat(args[0]) : parseInt(args[0]) / 100)
|
|
108
|
+
const threshold = !_.isNil(convoStepParameters?.matchingModeWer) ? (convoStepParameters?.matchingModeWer / 100) : ([',', '.'].find(p => `${args[0]}`.includes(p)) ? parseFloat(args[0]) : parseInt(args[0]) / 100)
|
|
87
109
|
return calculateWer(botresponse, utterance) <= threshold
|
|
88
110
|
}
|
|
89
111
|
|
|
@@ -130,19 +130,20 @@ module.exports = class ScriptingProvider {
|
|
|
130
130
|
resolveUtterance: ({ utterance, resolveEmptyIfUnknown }) => {
|
|
131
131
|
return this._resolveUtterance({ utterance, resolveEmptyIfUnknown })
|
|
132
132
|
},
|
|
133
|
-
assertBotResponse: (botresponse, tomatch, stepTag, meMsg, convoStepParameters) => {
|
|
133
|
+
assertBotResponse: (botresponse, tomatch, stepTag, meMsg, convoStepParameters = {}) => {
|
|
134
134
|
if (!_.isArray(tomatch)) {
|
|
135
135
|
tomatch = [tomatch]
|
|
136
136
|
}
|
|
137
137
|
debug(`assertBotResponse ${stepTag} ${meMsg ? `(${meMsg}) ` : ''}BOT: ${botresponse} = ${tomatch} ...`)
|
|
138
138
|
const matchFn = convoStepParameters.matchingMode ? (getMatchFunction(convoStepParameters.matchingMode) || this.matchFn) : this.matchFn
|
|
139
|
-
const found = _.find(tomatch, (utt) => matchFn(botresponse, utt, this.caps[Capabilities.SCRIPTING_MATCHING_MODE_ARGS]))
|
|
139
|
+
const found = _.find(tomatch, (utt) => matchFn(botresponse, utt, this.caps[Capabilities.SCRIPTING_MATCHING_MODE_ARGS], convoStepParameters))
|
|
140
140
|
const asserterType = this.caps[Capabilities.SCRIPTING_MATCHING_MODE] === 'wer' ? 'Word Error Rate Asserter' : 'Text Match Asserter'
|
|
141
141
|
if (_.isNil(found)) {
|
|
142
|
-
|
|
142
|
+
const matchingMode = convoStepParameters.matchingMode || this.caps[Capabilities.SCRIPTING_MATCHING_MODE]
|
|
143
|
+
if (matchingMode === 'wer') {
|
|
143
144
|
const wer = calculateWer(botresponse, tomatch[0])
|
|
144
145
|
const werArgs = this.caps[Capabilities.SCRIPTING_MATCHING_MODE_ARGS]
|
|
145
|
-
const threshold = ([',', '.'].find(p => `${werArgs[0]}`.includes(p)) ? parseFloat(werArgs[0]) : parseInt(werArgs[0]) / 100)
|
|
146
|
+
const threshold = !_.isNil(convoStepParameters.matchingModeWer) ? (convoStepParameters.matchingModeWer / 100) : ([',', '.'].find(p => `${werArgs[0]}`.includes(p)) ? parseFloat(werArgs[0]) : parseInt(werArgs[0]) / 100)
|
|
146
147
|
const message = `${stepTag}: Word Error Rate (${toPercent(wer)}) higher than accepted (${toPercent(threshold)})`
|
|
147
148
|
throw new BotiumError(
|
|
148
149
|
message,
|
|
@@ -150,7 +151,7 @@ module.exports = class ScriptingProvider {
|
|
|
150
151
|
type: 'asserter',
|
|
151
152
|
source: asserterType,
|
|
152
153
|
params: {
|
|
153
|
-
matchingMode
|
|
154
|
+
matchingMode,
|
|
154
155
|
args: this.caps[Capabilities.SCRIPTING_MATCHING_MODE_ARGS] || null
|
|
155
156
|
},
|
|
156
157
|
context: {
|
|
@@ -192,19 +193,20 @@ module.exports = class ScriptingProvider {
|
|
|
192
193
|
}
|
|
193
194
|
}
|
|
194
195
|
},
|
|
195
|
-
assertBotNotResponse: (botresponse, nottomatch, stepTag, meMsg, convoStepParameters) => {
|
|
196
|
+
assertBotNotResponse: (botresponse, nottomatch, stepTag, meMsg, convoStepParameters = {}) => {
|
|
196
197
|
if (!_.isArray(nottomatch)) {
|
|
197
198
|
nottomatch = [nottomatch]
|
|
198
199
|
}
|
|
199
200
|
debug(`assertBotNotResponse ${stepTag} ${meMsg ? `(${meMsg}) ` : ''}BOT: ${botresponse} != ${nottomatch} ...`)
|
|
200
201
|
const matchFn = convoStepParameters.matchingMode ? (getMatchFunction(convoStepParameters.matchingMode) || this.matchFn) : this.matchFn
|
|
201
|
-
const found = _.find(nottomatch, (utt) => matchFn(botresponse, utt, this.caps[Capabilities.SCRIPTING_MATCHING_MODE_ARGS]))
|
|
202
|
+
const found = _.find(nottomatch, (utt) => matchFn(botresponse, utt, this.caps[Capabilities.SCRIPTING_MATCHING_MODE_ARGS], convoStepParameters))
|
|
202
203
|
const asserterType = this.caps[Capabilities.SCRIPTING_MATCHING_MODE] === 'wer' ? 'Word Error Rate Asserter' : 'Text Match Asserter'
|
|
203
204
|
if (!_.isNil(found)) {
|
|
204
|
-
|
|
205
|
+
const matchingMode = convoStepParameters.matchingMode || this.caps[Capabilities.SCRIPTING_MATCHING_MODE]
|
|
206
|
+
if (matchingMode === 'wer') {
|
|
205
207
|
const wer = calculateWer(botresponse, nottomatch[0])
|
|
206
208
|
const werArgs = this.caps[Capabilities.SCRIPTING_MATCHING_MODE_ARGS]
|
|
207
|
-
const threshold = ([',', '.'].find(p => `${werArgs[0]}`.includes(p)) ? parseFloat(werArgs[0]) : parseInt(werArgs[0]) / 100)
|
|
209
|
+
const threshold = !_.isNil(convoStepParameters.matchingModeWer) ? (convoStepParameters.matchingModeWer / 100) : ([',', '.'].find(p => `${werArgs[0]}`.includes(p)) ? parseFloat(werArgs[0]) : parseInt(werArgs[0]) / 100)
|
|
208
210
|
const message = `${stepTag}: Word Error Rate (${toPercent(wer)}) lower than accepted (${toPercent(threshold)})`
|
|
209
211
|
throw new BotiumError(
|
|
210
212
|
message,
|
|
@@ -212,7 +214,7 @@ module.exports = class ScriptingProvider {
|
|
|
212
214
|
type: 'asserter',
|
|
213
215
|
source: asserterType,
|
|
214
216
|
params: {
|
|
215
|
-
matchingMode
|
|
217
|
+
matchingMode,
|
|
216
218
|
args: this.caps[Capabilities.SCRIPTING_MATCHING_MODE_ARGS] || null
|
|
217
219
|
},
|
|
218
220
|
context: {
|
package/src/scripting/helper.js
CHANGED
|
@@ -4,6 +4,7 @@ const speechScorer = require('word-error-rate')
|
|
|
4
4
|
const debug = require('debug')('botium-core-scripting-helper')
|
|
5
5
|
|
|
6
6
|
const { E_SCRIPTING_MEMORY_COLUMN_MODE } = require('../Enums')
|
|
7
|
+
const WHITE_SPACES_EXCEPT_SPACE_CHAR_AT_THE_END = /[\n\t\r]+$/
|
|
7
8
|
|
|
8
9
|
const normalizeText = (str, doCleanup) => {
|
|
9
10
|
if (str && _.isArray(str)) {
|
|
@@ -247,7 +248,7 @@ const linesToConvoStep = (lines, sender, context, eol, singleLineMode = false) =
|
|
|
247
248
|
if (eol === null) {
|
|
248
249
|
throw new Error('eol cant be null')
|
|
249
250
|
}
|
|
250
|
-
convoStep.messageText = textLines.join(eol).
|
|
251
|
+
convoStep.messageText = textLines.join(eol).replace(WHITE_SPACES_EXCEPT_SPACE_CHAR_AT_THE_END, '')
|
|
251
252
|
}
|
|
252
253
|
}
|
|
253
254
|
} else {
|
|
@@ -473,7 +474,7 @@ const convoStepToLines = (step) => {
|
|
|
473
474
|
lines.push(logicHook.name + _formatAppendArgs(logicHook.args))
|
|
474
475
|
})
|
|
475
476
|
}
|
|
476
|
-
return lines
|
|
477
|
+
return lines
|
|
477
478
|
}
|
|
478
479
|
|
|
479
480
|
const linesToScriptingMemories = (lines, columnMode = null) => {
|
|
@@ -161,7 +161,7 @@ describe('compiler.compilermarkdown', function () {
|
|
|
161
161
|
const compiler = new Compiler(context, Object.assign({}, DefaultCapabilities, caps))
|
|
162
162
|
|
|
163
163
|
compiler.Compile(scriptBuffer, 'SCRIPTING_TYPE_CONVO')
|
|
164
|
-
assert.equal(context.convos[0].conversation[1].messageText, 'hello meat bag
|
|
164
|
+
assert.equal(context.convos[0].conversation[1].messageText, 'hello meat bag\n!hello2')
|
|
165
165
|
assert.equal(context.convos[0].conversation[1].not, true)
|
|
166
166
|
})
|
|
167
167
|
})
|
|
@@ -191,7 +191,7 @@ describe('compiler.compilermarkdown', function () {
|
|
|
191
191
|
compiler.Compile(scriptBuffer, 'SCRIPTING_TYPE_CONVO')
|
|
192
192
|
assert.fail('expected error')
|
|
193
193
|
} catch (err) {
|
|
194
|
-
assert.equal(err.message, 'Failed to parse conversation. All element in convo step has to be optional or not optional: ["?hello meat bag
|
|
194
|
+
assert.equal(err.message, 'Failed to parse conversation. All element in convo step has to be optional or not optional: ["?hello meat bag","BUTTONS checkbutton|checkbutton2"]')
|
|
195
195
|
}
|
|
196
196
|
})
|
|
197
197
|
it('should read ?? as ?', async function () {
|
|
@@ -225,7 +225,7 @@ describe('compiler.compilermarkdown', function () {
|
|
|
225
225
|
const compiler = new Compiler(context, Object.assign({}, DefaultCapabilities, caps))
|
|
226
226
|
|
|
227
227
|
compiler.Compile(scriptBuffer, 'SCRIPTING_TYPE_CONVO')
|
|
228
|
-
assert.equal(context.convos[0].conversation[1].messageText, 'hello meat bag
|
|
228
|
+
assert.equal(context.convos[0].conversation[1].messageText, 'hello meat bag\n?hello2')
|
|
229
229
|
assert.equal(context.convos[0].conversation[1].optional, true)
|
|
230
230
|
})
|
|
231
231
|
})
|
|
@@ -236,7 +236,7 @@ describe('compiler.compilertxt', function () {
|
|
|
236
236
|
assert.equal(convo.conversation.length, 2)
|
|
237
237
|
assert.equal(convo.conversation[0].messageText, '')
|
|
238
238
|
assert.equal(convo.conversation[0].logicHooks.length, 0)
|
|
239
|
-
assert.equal(convo.conversation[1].messageText, 'Hi')
|
|
239
|
+
assert.equal(convo.conversation[1].messageText, 'Hi ')
|
|
240
240
|
})
|
|
241
241
|
it('should read nothing if there is nothing (even no separator)', async function () {
|
|
242
242
|
const scriptBuffer = fs.readFileSync(path.resolve(__dirname, CONVOS_DIR, 'convos_emptyrow_no_separator_row.convo.txt'))
|
|
@@ -64,6 +64,17 @@ describe('scripting.asserters.convoStepParametersForAssert', function () {
|
|
|
64
64
|
assert.isTrue(err.message.indexOf('"You said Hello" expected to match "Hello"') >= 0)
|
|
65
65
|
}
|
|
66
66
|
})
|
|
67
|
+
it('should not accept bad chatbot response on exact match defined on step even with WER asserter', async function () {
|
|
68
|
+
this.compiler.ReadScript(path.resolve(__dirname, 'convos'), 'convo_step_parameter_matchmode_failed_wer.convo.txt')
|
|
69
|
+
assert.equal(this.compiler.convos.length, 1)
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
await this.compiler.convos[0].Run(this.container)
|
|
73
|
+
assert.fail('should have failed')
|
|
74
|
+
} catch (err) {
|
|
75
|
+
assert.isTrue(err.message.indexOf('Word Error Rate (50%) higher than accepted (25%)') >= 0)
|
|
76
|
+
}
|
|
77
|
+
})
|
|
67
78
|
})
|
|
68
79
|
|
|
69
80
|
describe('scripting.asserters.convoStepParametersForAssert.retry', function () {
|