-
Notifications
You must be signed in to change notification settings - Fork 370
/
luisbot.ts
245 lines (208 loc) · 9.4 KB
/
luisbot.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
import { BotFrameworkAdapter, MemoryStorage, ConversationState, TurnContext, RecognizerResult } from 'botbuilder';
import { DialogSet, TextPrompt, DatetimePrompt, DialogContext } from 'botbuilder-dialogs';
import { LuisRecognizer, InstanceData, IntentData, DateTimeSpec } from 'botbuilder-ai';
import { CafeLUISModel, _Intents, _Entities, _Instance } from './CafeLUISModel';
import * as restify from 'restify';
const Resolver = require('@microsoft/recognizers-text-data-types-timex-expression').default.resolver;
const Creator = require('@microsoft/recognizers-text-data-types-timex-expression').default.creator;
const TimexProperty = require('@microsoft/recognizers-text-data-types-timex-expression').default.TimexProperty;
// Replace this appId with the ID of the app you create from cafeLUISModel.json
const appId = process.env.LUIS_APP_ID;
// Replace this with your authoring key
const subscriptionKey = process.env.LUIS_SUBSCRIPTION_KEY;
console.log(`process.env.LUIS_APP_ID=${process.env.LUIS_APP_ID}, process.env.LUIS_SUBSCRIPTION_KEY=${process.env.LUIS_SUBSCRIPTION_KEY}`);
// Default is westus
const serviceEndpoint = 'https://westus.api.cognitive.microsoft.com';
const luisRec = new LuisRecognizer({
appId: appId,
subscriptionKey: subscriptionKey,
serviceEndpoint: serviceEndpoint
});
// Enum for convenience
// intent names match CafeLUISModel.ts
enum Intents {
Book_Table = "Book_Table",
Greeting = "Greeting",
None = "None",
Who_are_you_intent = "Who_are_you_intent"
};
// Create server
let server = restify.createServer();
server.listen(process.env.port || process.env.PORT || 3978, function () {
console.log(`${server.name} listening to ${server.url}`);
});
// Create adapter
const adapter = new BotFrameworkAdapter( {
appId: process.env.MICROSOFT_APP_ID,
appPassword: process.env.MICROSOFT_APP_PASSWORD
});
// Add conversation state middleware
interface CafeBotConvState {
dialogStack: any[];
cafeLocation: string;
dateTime: string;
partySize: string;
Name: string;
}
const conversationState = new ConversationState<CafeBotConvState>(new MemoryStorage());
adapter.use(conversationState);
// Create empty dialog set
const dialogs = new DialogSet();
// Listen for incoming requests
server.post('/api/messages', (req, res) => {
// Route received request to adapter for processing
adapter.processActivity(req, res, async (context) => {
const isMessage = context.activity.type === 'message';
// Create dialog context
const state = conversationState.get(context);
const dc = dialogs.createContext(context, state);
if (!isMessage) {
await context.sendActivity(`[${context.activity.type} event detected]`);
}
// Check to see if anyone replied.
if (!context.responded) {
await dc.continue();
// if the dialog didn't send a response
if (!context.responded && isMessage) {
await luisRec.recognize(context).then(async (res : any) =>
{
var typedresult = res as CafeLUISModel;
let topIntent = LuisRecognizer.topIntent(res);
switch (topIntent)
{
case Intents.Book_Table: {
await dc.begin('reserveTable', typedresult);
break;
}
case Intents.Greeting: {
await context.sendActivity("Hello!");
break;
}
case Intents.Who_are_you_intent: {
await context.sendActivity("I'm the Contoso Cafe bot.");
break;
}
default: {
await dc.begin('default', topIntent);
break;
}
}
}, (err) => {
// there was some error
console.log(err);
}
);
}
}
});
});
// Add dialogs
dialogs.add('default', [
async function (dc, args) {
const state = conversationState.get(dc.context);
await dc.context.sendActivity(`Hi! I'm the Contoso Cafe reservation bot. Say something like make a reservation."`);
await dc.end();
}
]);
dialogs.add('textPrompt', new TextPrompt());
dialogs.add('dateTimePrompt', new DatetimePrompt());
dialogs.add('reserveTable', [
async function(dc, args, next){
var typedresult = args as CafeLUISModel;
// Call a helper function to save the entities in the LUIS result
// to dialog state
await SaveEntities(dc, typedresult);
await dc.context.sendActivity("Welcome to the reservation service.");
if (dc.activeDialog.state.dateTime) {
await next();
}
else {
await dc.prompt('dateTimePrompt', "Please provide a reservation date and time. We're open 4PM-8PM.");
}
},
async function(dc, result, next){
if (!dc.activeDialog.state.dateTime) {
// Save the dateTimePrompt result to dialog state
dc.activeDialog.state.dateTime = result[0].value;
}
// If we don't have party size, ask for it next
if (!dc.activeDialog.state.partySize) {
await dc.prompt('textPrompt', "How many people are in your party?");
} else {
await next();
}
},
async function(dc, result, next){
if (!dc.activeDialog.state.partySize) {
dc.activeDialog.state.partySize = result;
}
// Ask for the reservation name next
await dc.prompt('textPrompt', "Whose name will this be under?");
},
async function(dc, result){
dc.activeDialog.state.Name = result;
// Save data to conversation state
var state = conversationState.get(dc.context);
// Copy the dialog state to the conversation state
state = dc.activeDialog.state;
// TODO: Add in <br/>Location: ${state.cafeLocation}
var msg = `Reservation confirmed. Reservation details:
<br/>Date/Time: ${state.dateTime}
<br/>Party size: ${state.partySize}
<br/>Reservation name: ${state.Name}`;
await dc.context.sendActivity(msg);
await dc.end();
}
]);
// Helper function that saves any entities found in the LUIS result
// to the dialog state
async function SaveEntities( dc: DialogContext<TurnContext>, typedresult) {
// Resolve entities returned from LUIS, and save these to state
if (typedresult.entities)
{
let datetime = typedresult.entities.datetime;
if (datetime) {
console.log(`datetime entity found of type ${datetime[0].type}.`);
// Use the first date or time found in the utterance
if (datetime[0].timex) {
var timexValues = datetime[0].timex
// timexValues is the array of all resolutions of datetime[0]
// a datetime entity detected by LUIS is resolved to timex format.
// More information on timex can be found here:
// http://www.timeml.org/publications/timeMLdocs/timeml_1.2.1.html#timex3
// More information on the library which does the recognition can be found here:
// https://github.com/Microsoft/Recognizers-Text
if (datetime[0].type === "datetime") {
var resolution = Resolver.evaluate(
// array of timex values to evaluate. There may be more than one: "today at 6" can be 6AM or 6PM.
timexValues,
// Creator.evening constrains this to times between 4pm and 8pm
[Creator.evening]);
if (resolution[0]) {
// toNaturalLanguage takes the current date into account to create a friendly string
dc.activeDialog.state.dateTime = resolution[0].toNaturalLanguage(new Date());
// You can also use resolution.toString() to format the date/time.
} else {
// time didn't satisfy constraint.
dc.activeDialog.state.dateTime = null;
}
}
else {
console.log(`Type ${datetime[0].type} is not yet supported. Provide both the date and the time.`);
}
}
}
let partysize = typedresult.entities.partySize;
if (partysize) {
console.log(`partysize entity detected: ${partysize}`);
// use first partySize entity that was found in utterance
dc.activeDialog.state.partySize = partysize[0];
}
let cafelocation = typedresult.entities.cafeLocation;
if (cafelocation) {
console.log(`location entity detected: ${cafelocation}`);
// use first cafeLocation entity that was found in utterance
dc.activeDialog.state.cafeLocation = cafelocation[0][0];
}
}
}