Skip to content
This repository was archived by the owner on Jan 15, 2025. It is now read-only.

Commit 8f22a9d

Browse files
Shuai Wangfeich-ms
andauthored
Add options to support doing luis and qna cross-train seperately (#1161)
* add flag for controling cross train * change naming * add more tests and docs * rename to trainingOpt * minor fixes * add options for luRecognizerID * fix tests * retrriger * fix lint problem * fix flags naming and add options * remove lu recognizer id * fix format * remove no need doc * remove a console.log statement Co-authored-by: Fei Chen <[email protected]>
1 parent de71c51 commit 8f22a9d

File tree

16 files changed

+4516
-15
lines changed

16 files changed

+4516
-15
lines changed

packages/cli/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -839,6 +839,10 @@ OPTIONS
839839
--intentName=intentName [default: _Interruption] Interruption intent name
840840
841841
--log Writes out log messages to console
842+
843+
--inner-dialog Only performs the inner dialog cross train, defalt is true, to set it as false, use --no-inner-dialog
844+
845+
--intra-dialog Only performs the intra dialog cross train, defalt is true, to set it as false, use --no-intra-dialog
842846
```
843847

844848
_See code: [@microsoft/bf-luis-cli](https://github.com/microsoft/botframework-cli/tree/master/packages/luis/src/commands/luis/cross-train.ts)_

packages/lu/src/parser/cross-train/cross-train.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@ module.exports = {
1515
* @param {string} intentName interruption intent name. Default value is _Interruption.
1616
* @param {string} config path to config file of mapping rules.
1717
* @param {boolean} verbose verbose to indicate whether log warnings and errors or not when parsing cross-train files.
18+
* @param {inner: boolean, intra: boolean} trainingOpt trainingOpt indicates whether you want to control do the inner or intra dialog training seperately
1819
* @returns {luResult: any, qnaResult: any} trainedResult of luResult and qnaResult or undefined if no results.
1920
*/
20-
train: async function (input, intentName, config, verbose) {
21+
train: async function (input, intentName, config, verbose, trainingOpt) {
2122
// Get all related file content.
2223
const luContents = await file.getFilesContent(input, fileExtEnum.LUFile)
2324
const qnaContents = await file.getFilesContent(input, fileExtEnum.QnAFile)
@@ -53,7 +54,8 @@ module.exports = {
5354
configId: configContent.id,
5455
intentName,
5556
verbose,
56-
importResolver
57+
importResolver,
58+
trainingOpt
5759
})
5860

5961
return trainedResult

packages/lu/src/parser/cross-train/crossTrainer.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ module.exports = {
2727
* @param {any[]} luContents the lu content array whose element includes path and content
2828
* @param {any[]} qnaContents the qna content array whose element includes path and content
2929
* @param {any} configObject cross train config json object
30-
* @param {any} options some optional parameters including configId, intentName, verbose, importResolver
30+
* @param {any} options some optional parameters including configId, intentName, verbose, importResolver, trainingOpt
3131
* @returns {Map<string, LUResource>} map of file id and luResource
3232
* @throws {exception} throws errors
3333
*/
@@ -38,18 +38,19 @@ module.exports = {
3838
const crossTrainConfig = fileHelper.getConfigObject(
3939
configObject,
4040
options.intentName || '_Interruption',
41-
options.verbose || true)
41+
options.verbose || true,
42+
options.trainingOpt || {inner: true, intra: true})
4243

4344
let {luObjectArray, qnaObjectArray} = pretreatment(luContents, qnaContents)
44-
const {rootIds, triggerRules, intentName, verbose} = crossTrainConfig
45+
const {rootIds, triggerRules, intentName, verbose, trainingOpt} = crossTrainConfig
4546

4647
// parse lu content to LUResource object
4748
let {fileIdToResourceMap: luFileIdToResourceMap, allEmpty: allLuEmpty} = await parseAndValidateContent(luObjectArray, verbose, importResolver, fileExtEnum.LUFile)
4849

4950
// parse qna content to LUResource object
5051
let {fileIdToResourceMap: qnaFileIdToResourceMap, allEmpty: allQnAEmpty} = await parseAndValidateContent(qnaObjectArray, verbose, importResolver, fileExtEnum.QnAFile)
5152

52-
if (!allLuEmpty) {
53+
if (!allLuEmpty && trainingOpt.intra) {
5354
// construct resource tree to build the father-children relationship among lu files
5455
let resources = constructResoureTree(luFileIdToResourceMap, triggerRules)
5556

@@ -67,7 +68,7 @@ module.exports = {
6768
}
6869
}
6970

70-
if (!allQnAEmpty) {
71+
if (!allQnAEmpty && trainingOpt.inner) {
7172
// do qna cross training with lu files
7273
qnaCrossTrain(qnaFileIdToResourceMap, luFileIdToResourceMap, intentName, allLuEmpty)
7374
}

packages/lu/src/utils/filehelper.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ export function getParsedObjects(contents: {id: string, content: string}[], extT
229229
return parsedObjects
230230
}
231231

232-
export function getConfigObject(configObject: any, intentName: string, verbose: boolean) {
232+
export function getConfigObject(configObject: any, intentName: string, verbose: boolean, trainingOpt: {inner: boolean; intra: boolean}) {
233233
let finalConfigObj = Object.create(null)
234234
let rootFileIds: string[] = []
235235
if (configObject) {
@@ -272,7 +272,8 @@ export function getConfigObject(configObject: any, intentName: string, verbose:
272272
rootIds: rootFileIds,
273273
triggerRules: finalConfigObj,
274274
intentName,
275-
verbose
275+
verbose,
276+
trainingOpt
276277
}
277278

278279
return crossTrainConfig

packages/lu/test/parser/cross-train/crossTrainer.test.js

Lines changed: 192 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,6 @@ describe('luis:cross training tests among lu and qna contents', () => {
199199
const trainedResult = await crossTrainer.crossTrain(luContentArray, qnaContentArray, configObject)
200200
const luResult = trainedResult.luResult
201201
const qnaResult = trainedResult.qnaResult
202-
203202
let foundIndex = luResult.get('Main').Sections.findIndex(s => s.Name === 'DeferToRecognizer_QnA_Main')
204203
assert.isTrue(foundIndex > -1)
205204
assert.equal(luResult.get('Main').Sections[foundIndex].Body, `- user guide${NEWLINE}- tell joke`)
@@ -1036,4 +1035,196 @@ describe('luis:cross training tests among lu and qna contents', () => {
10361035
assert.equal(qnaResult.get('main').Sections[foundIndex].Questions.length, 1)
10371036
assert.equal(qnaResult.get('main').Sections[foundIndex].Questions[0], 'book a hotel for me')
10381037
})
1038+
1039+
it('luis:cross training can do inner or intra training seperately', async () => {
1040+
let luContentArray = []
1041+
let qnaContentArray = []
1042+
1043+
luContentArray.push({
1044+
content:
1045+
`# dia1_trigger
1046+
- book a hotel for me
1047+
1048+
# dia2_trigger
1049+
- book a flight for me
1050+
- book a train ticket for me`,
1051+
id: 'Main'})
1052+
1053+
qnaContentArray.push({
1054+
content:
1055+
`# ?user guide
1056+
1057+
**Filters:**
1058+
- aa=bb
1059+
1060+
\`\`\`
1061+
Here is the [user guide](http://contoso.com/userguide.pdf)
1062+
\`\`\`
1063+
1064+
# ?tell joke
1065+
\`\`\`
1066+
tell a funny joke
1067+
\`\`\``,
1068+
id: 'Main'}
1069+
)
1070+
1071+
luContentArray.push({
1072+
content:
1073+
`> !# @app.name = my luis application
1074+
1075+
# hotelLevel
1076+
- I need a four star hotel
1077+
1078+
# hotelLocation
1079+
- can I book a hotel near Space Needle`,
1080+
id: 'Dia1'}
1081+
)
1082+
1083+
qnaContentArray.push({
1084+
content:
1085+
`> !# @qna.pair.source = xyz
1086+
<a id = "1"></a>
1087+
1088+
# ?tell Joke
1089+
- tell me a joke
1090+
1091+
\`\`\`
1092+
ha ha ha
1093+
\`\`\`
1094+
1095+
# ?can I book a hotel near space needle
1096+
\`\`\`
1097+
of course you can
1098+
\`\`\``,
1099+
id: 'dia1'}
1100+
)
1101+
1102+
luContentArray.push({
1103+
content:
1104+
`# dia3_trigger
1105+
- book a flight from {fromLocation = Seattle} to {toLocation = Beijing}
1106+
1107+
# dia4_trigger
1108+
- book a train ticket from Seattle to Portland`,
1109+
id: 'Dia2'}
1110+
)
1111+
1112+
qnaContentArray.push({
1113+
content:
1114+
`# ?sing song
1115+
\`\`\`
1116+
sure, I can sing song.
1117+
\`\`\`
1118+
1119+
# ?tell a joke
1120+
\`\`\`
1121+
ha ha ha
1122+
\`\`\``,
1123+
id: 'dia2'}
1124+
)
1125+
1126+
luContentArray.push({
1127+
content:
1128+
`# flightDestination
1129+
- book a flight to {toLocation = Beijing}
1130+
1131+
# flightTime
1132+
- which {flightTime} do you prefer`,
1133+
id: 'dia3'}
1134+
)
1135+
1136+
qnaContentArray.push({
1137+
content: ``,
1138+
id: 'dia3'
1139+
})
1140+
1141+
luContentArray.push({
1142+
content:
1143+
`# railwayStation
1144+
- which station will you leave from
1145+
1146+
# railwayTime
1147+
- when do you want to leave from Seattle train station`,
1148+
id: 'dia4'})
1149+
1150+
qnaContentArray.push({
1151+
content:
1152+
`# ? there is only qna for this dialog
1153+
\`\`\`
1154+
should add filter meta data here
1155+
\`\`\``,
1156+
id: 'dia5'
1157+
})
1158+
1159+
const configObject = {
1160+
'main': {
1161+
'rootDialog': true,
1162+
'triggers': {
1163+
'dia1_trigger': 'dia1',
1164+
'dia2_trigger': 'dia2'
1165+
}
1166+
},
1167+
'dia2': {
1168+
'triggers': {
1169+
'dia3_trigger': 'dia3',
1170+
'dia4_trigger': 'dia4'
1171+
}
1172+
}
1173+
}
1174+
1175+
const options = {trainingOpt: {inner: true, intra: false}}
1176+
1177+
const trainedResult = await crossTrainer.crossTrain(luContentArray, qnaContentArray, configObject, options)
1178+
const luResult = trainedResult.luResult
1179+
const qnaResult = trainedResult.qnaResult
1180+
1181+
let luisContent = luResult.get('Main').Content;
1182+
assert.isTrue(luisContent.includes('DeferToRecognizer_QnA') === true)
1183+
assert.isTrue(luisContent.includes('_Interruption') === false)
1184+
1185+
luisContent = luResult.get('Dia1').Content;
1186+
assert.isTrue(luisContent.includes('DeferToRecognizer_QnA') === true)
1187+
assert.isTrue(luisContent.includes('_Interruption') === false)
1188+
1189+
1190+
1191+
luisContent = luResult.get('Dia2').Content;
1192+
assert.isTrue(luisContent.includes('DeferToRecognizer_QnA') === true)
1193+
assert.isTrue(luisContent.includes('_Interruption') === false)
1194+
1195+
1196+
let qnaContent = qnaResult.get('Main').Content;
1197+
assert.isTrue(qnaContent.includes('DeferToRecognizer_') === true)
1198+
assert.isTrue(qnaContent.includes('_Interruption') === false)
1199+
1200+
qnaContent = qnaResult.get('dia1').Content;
1201+
assert.isTrue(qnaContent.includes('DeferToRecognizer_') === true)
1202+
assert.isTrue(qnaContent.includes('_Interruption') === false)
1203+
1204+
qnaContent = qnaResult.get('dia2').Content;
1205+
assert.isTrue(qnaContent.includes('DeferToRecognizer_') === true)
1206+
assert.isTrue(qnaContent.includes('_Interruption') === false)
1207+
1208+
const optionsIntra = {trainingOpt: {inner: false, intra: true}}
1209+
1210+
const trainedResult2 = await crossTrainer.crossTrain(luContentArray, qnaContentArray, configObject, optionsIntra)
1211+
const luResult2 = trainedResult2.luResult
1212+
const qnaResult2 = trainedResult2.qnaResult
1213+
1214+
luisContent = luResult2.get('Dia1').Content;
1215+
assert.isTrue(luisContent.includes('# DeferToRecognizer_') === false)
1216+
assert.isTrue(luisContent.includes('_Interruption') === true)
1217+
1218+
luisContent = luResult2.get('Dia2').Content;
1219+
assert.isTrue(luisContent.includes('# DeferToRecognizer_') === false)
1220+
assert.isTrue(luisContent.includes('_Interruption') === true)
1221+
1222+
qnaContent = qnaResult2.get('dia1').Content;
1223+
assert.isTrue(qnaContent.includes('DeferToRecognizer_') === false)
1224+
assert.isTrue(qnaContent.includes('_Interruption') === false)
1225+
1226+
qnaContent = qnaResult2.get('dia2').Content;
1227+
assert.isTrue(qnaContent.includes('DeferToRecognizer_') === false)
1228+
assert.isTrue(qnaContent.includes('_Interruption') === false)
1229+
})
10391230
})

packages/luis/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,11 @@ OPTIONS
425425
--intentName=intentName [default: _Interruption] Interruption intent name
426426
427427
--log Writes out log messages to console
428+
429+
--inner-dialog Only performs the inner dialog cross train, defalt is true, to set it as false, use --no-inner-dialog
430+
431+
--intra-dialog Only performs the intra dialog cross train, defalt is true, to set it as false, use --no-intra-dialog
432+
428433
```
429434

430435
_See code: [src/commands/luis/cross-train.ts](https://github.com/microsoft/botframework-cli/tree/master/packages/luis/src/commands/luis/cross-train.ts)_

packages/luis/src/commands/luis/cross-train.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,14 @@ export default class LuisCrossTrain extends Command {
2020
config: flags.string({description: 'Path to config file of mapping rules'}),
2121
intentName: flags.string({description: 'Interruption intent name', default: '_Interruption'}),
2222
force: flags.boolean({char: 'f', description: 'If --out flag is provided with the path to an existing file, overwrites that file', default: false}),
23-
log: flags.boolean({description: 'Writes out log messages to console', default: false})
23+
log: flags.boolean({description: 'Writes out log messages to console', default: false}),
24+
'inner-dialog': flags.boolean({description: 'Only do inner dialog cross train', default: true, allowNo: true}),
25+
'intra-dialog': flags.boolean({description: 'Only do intra dialog cross train', default: true, allowNo: true})
2426
}
2527

2628
async run() {
2729
try {
2830
const {flags} = this.parse(LuisCrossTrain)
29-
3031
if (!flags.in) {
3132
throw new CLIError('Missing input. Please specify a folder with --in flag')
3233
}
@@ -39,7 +40,13 @@ export default class LuisCrossTrain extends Command {
3940
throw new CLIError('Missing cross train config. Please provide config file path by --config.')
4041
}
4142

42-
const trainedResult = await crossTrain.train(flags.in, flags.intentName, flags.config, flags.log)
43+
let trainingOpt = {}
44+
trainingOpt = {
45+
inner: flags['inner-dialog'],
46+
intra: flags['intra-dialog']
47+
}
48+
49+
const trainedResult = await crossTrain.train(flags.in, flags.intentName, flags.config, flags.log, trainingOpt)
4350

4451
if (flags.out === undefined) {
4552
flags.out = path.join(process.cwd(), 'cross-trained')

packages/luis/test/commands/luis/crossTrain.test.ts

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ describe('luis:cross-train tests for lu and qna contents', () => {
8787
expect(await compareLuFiles('./../../../interruptionGen/dia2.lu', './../../fixtures/verified/interruption3/dia2.lu')).to.be.true
8888
expect(await compareLuFiles('./../../../interruptionGen/dia3.lu', './../../fixtures/verified/interruption3/dia3.lu')).to.be.true
8989
})
90-
90+
9191
test
9292
.stdout()
9393
.command(['luis:cross-train',
@@ -114,4 +114,38 @@ describe('luis:cross-train tests for lu and qna contents', () => {
114114
.it('displays a warning if log is set true', ctx => {
115115
expect(ctx.stdout).to.contain('[WARN] line 1:0 - line 1:15: no utterances found for intent definition: "# hotelLocation"')
116116
})
117-
})
117+
118+
test
119+
.stdout()
120+
.command(['luis:cross-train',
121+
'--in', `${path.join(__dirname, './../../fixtures/testcases/interruption5')}`,
122+
'--intentName', '_Interruption',
123+
'--config', `${path.join(__dirname, './../../fixtures/testcases/interruption5/mapping_rules.json')}`,
124+
'--out', './interruptionGen',
125+
'--force',
126+
'--intra-dialog',
127+
'--no-inner-dialog'])
128+
.it('luis:cross training only do inner dialog', async () => {
129+
expect(await compareLuFiles('./../../../interruptionGen/main.lu', './../../fixtures/verified/interruption6/main.lu')).to.be.true
130+
expect(await compareLuFiles('./../../../interruptionGen/main.qna', './../../fixtures/verified/interruption6/main.qna')).to.be.true
131+
expect(await compareLuFiles('./../../../interruptionGen/dia1.lu', './../../fixtures/verified/interruption6/dia1.lu')).to.be.true
132+
expect(await compareLuFiles('./../../../interruptionGen/dia1.qna', './../../fixtures/verified/interruption6/dia1.qna')).to.be.true
133+
})
134+
135+
test
136+
.stdout()
137+
.command(['luis:cross-train',
138+
'--in', `${path.join(__dirname, './../../fixtures/testcases/interruption5')}`,
139+
'--intentName', '_Interruption',
140+
'--config', `${path.join(__dirname, './../../fixtures/testcases/interruption5/mapping_rules.json')}`,
141+
'--out', './interruptionGen',
142+
'--force',
143+
'--inner-dialog',
144+
'--no-intra-dialog'])
145+
.it('luis:cross training only do intra dialog', async () => {
146+
expect(await compareLuFiles('./../../../interruptionGen/main.lu', './../../fixtures/verified/interruption7/main.lu')).to.be.true
147+
expect(await compareLuFiles('./../../../interruptionGen/main.qna', './../../fixtures/verified/interruption7/main.qna')).to.be.true
148+
expect(await compareLuFiles('./../../../interruptionGen/dia1.lu', './../../fixtures/verified/interruption7/dia1.lu')).to.be.true
149+
expect(await compareLuFiles('./../../../interruptionGen/dia1.qna', './../../fixtures/verified/interruption7/dia1.qna')).to.be.true
150+
})
151+
})

0 commit comments

Comments
 (0)