@bavard/agent-config
Advanced tools
Comparing version 0.0.54 to 0.0.55
@@ -12,7 +12,20 @@ import { AgentConfig } from '../agent-config'; | ||
} | ||
export interface IEmailService { | ||
sendEmail(html: string, subject: string, to: string, fromName: string, fromEmail: string): Promise<any>; | ||
} | ||
export declare class DialogueManager { | ||
private config; | ||
private nluService; | ||
constructor(config: AgentConfig, nluService: INLUService); | ||
private emailService; | ||
constructor(config: AgentConfig, nluService: INLUService, emailService: IEmailService); | ||
processUserAction(userAction: IUserAction, conversation: IConversation): Promise<IConversation>; | ||
private isRawUtterance; | ||
/** | ||
* Uses nluService to determine the tags and intent of a raw utterance action. | ||
*/ | ||
private parseUtterance; | ||
/** | ||
* Sends the email associated with an IEmailUserAction. | ||
*/ | ||
private handleEmailAction; | ||
} |
@@ -11,2 +11,5 @@ "use strict"; | ||
}; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
@@ -18,29 +21,80 @@ exports.DialogueManager = void 0; | ||
const graph_policy_executor_1 = require("./policy-executors/graph-policy-executor"); | ||
const user_1 = require("../actions/user"); | ||
const lodash_1 = require("lodash"); | ||
const sanitize_html_1 = __importDefault(require("sanitize-html")); | ||
class DialogueManager { | ||
constructor(config, nluService) { | ||
constructor(config, nluService, emailService) { | ||
this.config = config; | ||
this.nluService = nluService; | ||
this.emailService = emailService; | ||
} | ||
processUserAction(userAction, conversation) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
switch (this.config.getDefaultPolicyType()) { | ||
case agent_config_1.EPolicyType.GRAPH: | ||
try { | ||
const conv = graph_policy_executor_1.graphPolicyExecutor(this.config, userAction, conversation); | ||
return conv; | ||
// Preprocess the action. | ||
if (this.isRawUtterance(userAction)) { | ||
userAction = yield this.parseUtterance(userAction); | ||
} | ||
// Update the conversation according to the agent's policy. | ||
let updatedConv; | ||
const policyType = this.config.getDefaultPolicyType(); | ||
if (policyType === agent_config_1.EPolicyType.GRAPH) { | ||
try { | ||
updatedConv = graph_policy_executor_1.graphPolicyExecutor(this.config, userAction, conversation); | ||
} | ||
catch (error) { | ||
if (error instanceof graph_policy_1.UnrecognizedUserActionError) { | ||
// Fall back to the default policy. | ||
updatedConv = default_executor_1.defaultPolicyExecutor(this.config, userAction, conversation); | ||
} | ||
catch (error) { | ||
if (!(error instanceof graph_policy_1.UnrecognizedUserActionError)) { | ||
throw error; | ||
} | ||
else { | ||
throw error; | ||
} | ||
case agent_config_1.EPolicyType.DEFAULT_ACTION: { | ||
return yield default_executor_1.defaultPolicyExecutor(this.config, userAction, conversation, this.nluService); | ||
} | ||
default: | ||
throw new Error(`Invalid policy type: ${this.config.getDefaultPolicyType()}`); | ||
} | ||
else if (policyType === agent_config_1.EPolicyType.DEFAULT_ACTION) { | ||
updatedConv = default_executor_1.defaultPolicyExecutor(this.config, userAction, conversation); | ||
} | ||
else { | ||
throw new Error(`Invalid policy type: ${this.config.getDefaultPolicyType()}`); | ||
} | ||
// Perform any follow-up work still needed on the action. | ||
if (userAction.type == user_1.EUserActionType.EMAIL_ACTION) { | ||
this.handleEmailAction(userAction); | ||
} | ||
return updatedConv; | ||
}); | ||
} | ||
isRawUtterance(userAction) { | ||
return userAction.type === user_1.EUserActionType.UTTERANCE_ACTION && !userAction.isDefaultResponse; | ||
} | ||
/** | ||
* Uses nluService to determine the tags and intent of a raw utterance action. | ||
*/ | ||
parseUtterance(userAction) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const updatedUserAction = lodash_1.cloneDeep(userAction); | ||
const nluResult = yield this.nluService.parseUtterance(this.config.getIntents().map(x => x.name), Array.from(this.config.getTagTypes()), userAction.utterance); | ||
updatedUserAction.intent = nluResult.intent; | ||
updatedUserAction.tags = nluResult.tags; | ||
return updatedUserAction; | ||
}); | ||
} | ||
/** | ||
* Sends the email associated with an IEmailUserAction. | ||
*/ | ||
handleEmailAction(userAction) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const textOnlyMessage = sanitize_html_1.default(userAction.message, { | ||
allowedTags: [], | ||
allowedAttributes: {}, | ||
}); | ||
return this.emailService.sendEmail(` | ||
<div> | ||
<p>The following message is from ${userAction.email}</p> | ||
<p>${textOnlyMessage}</p> | ||
</div> | ||
`, `Bavard Bot: Message from ${userAction.email}`, userAction.to, 'bavard', userAction.from || userAction.email); | ||
}); | ||
} | ||
} | ||
exports.DialogueManager = DialogueManager; |
import { AgentConfig } from '../..'; | ||
import { IUserAction } from '../../actions/user'; | ||
import { IConversation } from '../conversation'; | ||
import { INLUService } from '../dialogue-manager'; | ||
export declare const defaultPolicyExecutor: (config: AgentConfig, userAction: IUserAction, conversation: IConversation, nluService: INLUService) => Promise<IConversation>; | ||
export declare const defaultPolicyExecutor: (config: AgentConfig, userAction: IUserAction, conversation: IConversation) => IConversation; |
"use strict"; | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
var __importDefault = (this && this.__importDefault) || function (mod) { | ||
@@ -18,25 +9,13 @@ return (mod && mod.__esModule) ? mod : { "default": mod }; | ||
const utterance_action_1 = require("../../actions/agent/utterance-action"); | ||
const user_1 = require("../../actions/user"); | ||
const default_action_policy_helpers_1 = require("../utils/default-action-policy-helpers"); | ||
exports.defaultPolicyExecutor = (config, userAction, conversation, nluService) => __awaiter(void 0, void 0, void 0, function* () { | ||
exports.defaultPolicyExecutor = (config, userAction, conversation) => { | ||
var _a; | ||
if (!userAction) { | ||
console.log('No user input.'); | ||
return handleNoUserInput(config, conversation); | ||
} | ||
const updatedUserAction = userAction; | ||
let intent; | ||
const updatedConv = lodash_1.default.cloneDeep(conversation); | ||
// Run NLU if user input is a raw utterance | ||
if (userAction.type === user_1.EUserActionType.UTTERANCE_ACTION && !userAction.isDefaultResponse) { | ||
const nluResult = yield nluService.parseUtterance(config.getIntents().map(x => x.name), Array.from(config.getTagTypes()), userAction.utterance); | ||
updatedUserAction.intent = nluResult.intent; | ||
updatedUserAction.tags = nluResult.tags; | ||
intent = nluResult.intent; | ||
} | ||
else { | ||
intent = (_a = userAction.intent) !== null && _a !== void 0 ? _a : ''; | ||
} | ||
// execute the default action policy | ||
const agentAction = yield default_action_policy_helpers_1.generateNextAction(config, intent); | ||
const intent = (_a = userAction.intent) !== null && _a !== void 0 ? _a : ''; | ||
const agentAction = default_action_policy_helpers_1.generateNextAction(config, intent); | ||
// update the conversation | ||
@@ -46,8 +25,8 @@ updatedConv.turns.push({ actor: 'USER', userAction: updatedUserAction, timestamp: new Date() }); | ||
return updatedConv; | ||
}); | ||
const handleNoUserInput = (config, conversation) => __awaiter(void 0, void 0, void 0, function* () { | ||
var _b, _c; | ||
}; | ||
const handleNoUserInput = (config, conversation) => { | ||
var _a, _b; | ||
// If the conversation is empty, play the greeting action. | ||
if (conversation.turns.length === 0) { | ||
const greetingAction = (_b = config.getActionByName('greeting')) === null || _b === void 0 ? void 0 : _b.toJsonObj(); | ||
const greetingAction = (_a = config.getActionByName('greeting')) === null || _a === void 0 ? void 0 : _a.toJsonObj(); | ||
conversation.turns.push({ | ||
@@ -63,3 +42,3 @@ actor: 'AGENT', | ||
if ((lastTurn === null || lastTurn === void 0 ? void 0 : lastTurn.actor) === 'AGENT') { | ||
const waitingAction = (_c = config.getActionByName('waiting')) === null || _c === void 0 ? void 0 : _c.toJsonObj(); | ||
const waitingAction = (_b = config.getActionByName('waiting')) === null || _b === void 0 ? void 0 : _b.toJsonObj(); | ||
conversation.turns.push({ | ||
@@ -72,2 +51,2 @@ actor: 'AGENT', | ||
return conversation; | ||
}); | ||
}; |
import { AgentConfig, IAgentAction } from '../..'; | ||
export declare const generateNextAction: (config: AgentConfig, intentValue: string) => Promise<IAgentAction>; | ||
export declare const generateNextAction: (config: AgentConfig, intentValue: string) => IAgentAction; |
"use strict"; | ||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } | ||
return new (P || (P = Promise))(function (resolve, reject) { | ||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } | ||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } | ||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } | ||
step((generator = generator.apply(thisArg, _arguments || [])).next()); | ||
}); | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.generateNextAction = void 0; | ||
const __1 = require("../.."); | ||
exports.generateNextAction = (config, intentValue) => __awaiter(void 0, void 0, void 0, function* () { | ||
exports.generateNextAction = (config, intentValue) => { | ||
var _a; | ||
@@ -24,3 +15,3 @@ const intent = config.getIntents().find((x) => x.name === intentValue); | ||
if (intent && intent.defaultActionName) { | ||
const maybeAction = yield ((_a = config.getActionByName(intent.defaultActionName)) === null || _a === void 0 ? void 0 : _a.toJsonObj()); | ||
const maybeAction = (_a = config.getActionByName(intent.defaultActionName)) === null || _a === void 0 ? void 0 : _a.toJsonObj(); | ||
if (maybeAction) { | ||
@@ -31,2 +22,2 @@ return maybeAction; | ||
return action; | ||
}); | ||
}; |
@@ -105,3 +105,3 @@ "use strict"; | ||
if (!!userAction && !nextNode) { | ||
throw new custom_errors_1.UnrecognizedUserActionError(`Unrecognizer user action: ${JSON.stringify(userAction)}.`); | ||
throw new custom_errors_1.UnrecognizedUserActionError(`Unrecognized user action: ${JSON.stringify(userAction)}.`); | ||
} | ||
@@ -108,0 +108,0 @@ while (nextNode) { |
{ | ||
"name": "@bavard/agent-config", | ||
"version": "0.0.54", | ||
"version": "0.0.55", | ||
"description": "", | ||
@@ -5,0 +5,0 @@ "main": "dist/index.js", |
@@ -6,3 +6,5 @@ import { AgentConfig, EPolicyType } from '../agent-config'; | ||
import { graphPolicyExecutor } from './policy-executors/graph-policy-executor'; | ||
import { IUserAction } from '../actions/user'; | ||
import { IUserAction, EUserActionType, IEmailUserAction, IUtteranceUserAction } from '../actions/user'; | ||
import { cloneDeep } from 'lodash'; | ||
import sanitize from 'sanitize-html'; | ||
@@ -18,5 +20,9 @@ export interface IParsedUtterance { | ||
export interface IEmailService { | ||
sendEmail(html: string, subject: string, to: string, fromName: string, fromEmail: string): Promise<any> | ||
} | ||
export class DialogueManager { | ||
constructor( | ||
private config: AgentConfig, private nluService: INLUService) { | ||
private config: AgentConfig, private nluService: INLUService, private emailService: IEmailService) { | ||
} | ||
@@ -28,19 +34,81 @@ | ||
): Promise<IConversation> { | ||
switch (this.config.getDefaultPolicyType()) { | ||
case EPolicyType.GRAPH: | ||
// Preprocess the action. | ||
if (this.isRawUtterance(userAction)) { | ||
userAction = await this.parseUtterance(userAction as IUtteranceUserAction); | ||
} | ||
// Update the conversation according to the agent's policy. | ||
let updatedConv; | ||
const policyType = this.config.getDefaultPolicyType(); | ||
if (policyType === EPolicyType.GRAPH) { | ||
try { | ||
const conv = graphPolicyExecutor(this.config, userAction, conversation); | ||
return conv; | ||
updatedConv = graphPolicyExecutor(this.config, userAction, conversation); | ||
} catch (error) { | ||
if (!(error instanceof UnrecognizedUserActionError)) { | ||
if (error instanceof UnrecognizedUserActionError) { | ||
// Fall back to the default policy. | ||
updatedConv = defaultPolicyExecutor(this.config, userAction, conversation); | ||
} else { | ||
throw error; | ||
} | ||
} | ||
case EPolicyType.DEFAULT_ACTION: { | ||
return await defaultPolicyExecutor(this.config, userAction, conversation, this.nluService); | ||
} | ||
default: | ||
} else if (policyType === EPolicyType.DEFAULT_ACTION) { | ||
updatedConv = defaultPolicyExecutor(this.config, userAction, conversation); | ||
} else { | ||
throw new Error(`Invalid policy type: ${this.config.getDefaultPolicyType()}`); | ||
} | ||
// Perform any follow-up work still needed on the action. | ||
if (userAction.type == EUserActionType.EMAIL_ACTION) { | ||
this.handleEmailAction(userAction); | ||
} | ||
return updatedConv; | ||
} | ||
private isRawUtterance(userAction: IUserAction): boolean { | ||
return userAction.type === EUserActionType.UTTERANCE_ACTION && !userAction.isDefaultResponse; | ||
} | ||
/** | ||
* Uses nluService to determine the tags and intent of a raw utterance action. | ||
*/ | ||
private async parseUtterance(userAction: IUtteranceUserAction): Promise<IUserAction> { | ||
const updatedUserAction = cloneDeep(userAction); | ||
const nluResult = await this.nluService.parseUtterance( | ||
this.config.getIntents().map(x => x.name), | ||
Array.from(this.config.getTagTypes()), | ||
userAction.utterance, | ||
); | ||
updatedUserAction.intent = nluResult.intent; | ||
updatedUserAction.tags = nluResult.tags; | ||
return updatedUserAction; | ||
} | ||
/** | ||
* Sends the email associated with an IEmailUserAction. | ||
*/ | ||
private async handleEmailAction( | ||
userAction: IEmailUserAction, | ||
): Promise<any> { | ||
const textOnlyMessage = sanitize( | ||
userAction.message, { | ||
allowedTags: [], | ||
allowedAttributes: {}, | ||
} | ||
); | ||
return this.emailService.sendEmail( | ||
` | ||
<div> | ||
<p>The following message is from ${userAction.email}</p> | ||
<p>${textOnlyMessage}</p> | ||
</div> | ||
`, | ||
`Bavard Bot: Message from ${userAction.email}`, | ||
userAction.to, | ||
'bavard', | ||
userAction.from || userAction.email | ||
); | ||
} | ||
} |
import _ from 'lodash'; | ||
import { AgentConfig } from '../..'; | ||
import { UtteranceAction } from '../../actions/agent/utterance-action'; | ||
import { EUserActionType, IUserAction, IUtteranceUserAction } from '../../actions/user'; | ||
import { IUserAction, IUtteranceUserAction } from '../../actions/user'; | ||
import { IConversation } from '../conversation'; | ||
import { INLUService } from '../dialogue-manager'; | ||
import { generateNextAction } from '../utils/default-action-policy-helpers'; | ||
export const defaultPolicyExecutor = async ( | ||
export const defaultPolicyExecutor = ( | ||
config: AgentConfig, | ||
userAction: IUserAction, | ||
conversation: IConversation, | ||
nluService: INLUService, | ||
): Promise<IConversation> => { | ||
conversation: IConversation | ||
): IConversation => { | ||
if (!userAction) { | ||
console.log('No user input.'); | ||
return handleNoUserInput(config, conversation); | ||
@@ -21,22 +18,7 @@ } | ||
const updatedUserAction = userAction; | ||
let intent: string; | ||
const updatedConv = (_.cloneDeep(conversation) as IConversation); | ||
// Run NLU if user input is a raw utterance | ||
if (userAction.type === EUserActionType.UTTERANCE_ACTION && !userAction.isDefaultResponse) { | ||
const nluResult = await nluService.parseUtterance( | ||
config.getIntents().map(x => x.name), | ||
Array.from(config.getTagTypes()), | ||
userAction.utterance, | ||
); | ||
(updatedUserAction as IUtteranceUserAction).intent = nluResult.intent; | ||
(updatedUserAction as IUtteranceUserAction).tags = nluResult.tags; | ||
intent = nluResult.intent; | ||
} | ||
else { | ||
intent = ((userAction as unknown) as IUtteranceUserAction).intent ?? ''; | ||
} | ||
// execute the default action policy | ||
const agentAction = await generateNextAction(config, intent); | ||
const intent = ((userAction as unknown) as IUtteranceUserAction).intent ?? ''; | ||
const agentAction = generateNextAction(config, intent); | ||
@@ -49,6 +31,6 @@ // update the conversation | ||
const handleNoUserInput = async ( | ||
const handleNoUserInput = ( | ||
config: AgentConfig, | ||
conversation: IConversation, | ||
): Promise<IConversation> => { | ||
): IConversation => { | ||
// If the conversation is empty, play the greeting action. | ||
@@ -55,0 +37,0 @@ if (conversation.turns.length === 0) { |
import { AgentConfig, EAgentActionTypes, IAgentAction } from '../..'; | ||
export const generateNextAction = async (config: AgentConfig, intentValue: string): Promise<IAgentAction> => { | ||
export const generateNextAction = (config: AgentConfig, intentValue: string): IAgentAction => { | ||
const intent = config.getIntents().find((x) => x.name === intentValue); | ||
@@ -14,3 +14,3 @@ | ||
if (intent && intent.defaultActionName) { | ||
const maybeAction = await config.getActionByName(intent.defaultActionName)?.toJsonObj(); | ||
const maybeAction = config.getActionByName(intent.defaultActionName)?.toJsonObj(); | ||
if (maybeAction) { | ||
@@ -17,0 +17,0 @@ return maybeAction; |
@@ -138,3 +138,3 @@ import GraphPolicyNode from './nodes/base-node'; | ||
throw new UnrecognizedUserActionError( | ||
`Unrecognizer user action: ${JSON.stringify(userAction)}.` | ||
`Unrecognized user action: ${JSON.stringify(userAction)}.` | ||
); | ||
@@ -141,0 +141,0 @@ } |
@@ -9,5 +9,8 @@ import { assert } from 'chai'; | ||
import { | ||
makeNLUService, | ||
makeMockNLUService, | ||
MockEmailService, | ||
testConversation, | ||
testUtteranceUserActions, | ||
testOptionUserActions, | ||
testEmailUserAction, | ||
createTestConfigWithDefaultPolicy, | ||
@@ -21,3 +24,3 @@ createTestConfigWithGraphPolicy | ||
const config = new AgentConfig('test', 'test'); | ||
const dm = new DialogueManager(config, makeNLUService()); | ||
const dm = new DialogueManager(config, makeMockNLUService(), new MockEmailService()); | ||
@@ -31,3 +34,3 @@ const updatedConv = await dm.processUserAction(testUtteranceUserActions.thankYou, testConversation); | ||
const config = createTestConfigWithDefaultPolicy(); | ||
const dm = new DialogueManager(config, makeNLUService({ intent: 'wanting_thanks', tags: [] })); | ||
const dm = new DialogueManager(config, makeMockNLUService({ intent: 'wanting_thanks', tags: [] }), new MockEmailService()); | ||
const conversation: IConversation = { id: 0, turns: [] }; | ||
@@ -44,3 +47,3 @@ | ||
const config = createTestConfigWithDefaultPolicy(); | ||
const dm = new DialogueManager(config, makeNLUService({ intent: '', tags: [] })); | ||
const dm = new DialogueManager(config, makeMockNLUService({ intent: '', tags: [] }), new MockEmailService()); | ||
const conversation: IConversation = { id: 0, turns: [] }; | ||
@@ -55,3 +58,3 @@ | ||
const config = createTestConfigWithDefaultPolicy(); | ||
const dm = new DialogueManager(config, makeNLUService({ intent: 'wanting_thanks', tags: [] })); | ||
const dm = new DialogueManager(config, makeMockNLUService({ intent: 'wanting_thanks', tags: [] }), new MockEmailService()); | ||
const conversation: IConversation = { id: 0, turns: [] }; | ||
@@ -64,2 +67,13 @@ const convBeforeSnapshot = _.cloneDeep(conversation); | ||
}); | ||
it('Email gets sent', async () => { | ||
const config = createTestConfigWithDefaultPolicy(); | ||
const mockEmailService = new MockEmailService(); | ||
const dm = new DialogueManager(config, makeMockNLUService({ intent: 'wanting_thanks', tags: [] }), mockEmailService); | ||
const conversation: IConversation = { id: 0, turns: [] }; | ||
assert.equal(mockEmailService.callCount, 0); | ||
await dm.processUserAction(testEmailUserAction, conversation); | ||
assert.equal(mockEmailService.callCount, 1); | ||
}); | ||
}); | ||
@@ -70,3 +84,3 @@ | ||
const config = createTestConfigWithGraphPolicy(); | ||
const dm = new DialogueManager(config, makeNLUService()); | ||
const dm = new DialogueManager(config, makeMockNLUService({ intent: '30_day_challenge', tags: [] }), new MockEmailService()); | ||
const conversation: IConversation = { id: 0, turns: [] }; | ||
@@ -85,6 +99,6 @@ | ||
const config = createTestConfigWithGraphPolicy(); | ||
const dm = new DialogueManager(config, makeNLUService()); | ||
const dm = new DialogueManager(config, makeMockNLUService(), new MockEmailService()); | ||
const policy = (config.getActiveGraphPolicy() as GraphPolicy); | ||
const conversation: IConversation = { id: 0, turns: [] }; | ||
const updatedConv = await dm.processUserAction(testUtteranceUserActions.thirtyDayChallenge, conversation); | ||
const updatedConv = await dm.processUserAction(testOptionUserActions.dailyLifeChallenge, conversation); | ||
// The policy should have been attached to the conversation. | ||
@@ -96,3 +110,3 @@ assert.deepEqual(policy.toJsonObj(), updatedConv.policy); | ||
const config = createTestConfigWithGraphPolicy(); | ||
const dm = new DialogueManager(config, makeNLUService()); | ||
const dm = new DialogueManager(config, makeMockNLUService({ intent: '30_day_challenge', tags: [] }), new MockEmailService()); | ||
const conversation: IConversation = { id: 0, turns: [] }; | ||
@@ -103,3 +117,3 @@ | ||
// Move from there to another place down the graph. | ||
const finalConvState = await dm.processUserAction(testUtteranceUserActions.sleepingBetter, updatedConv); | ||
const finalConvState = await dm.processUserAction(testOptionUserActions.sleepingBetter, updatedConv); | ||
assert.equal(finalConvState.turns.length, 4); | ||
@@ -114,3 +128,3 @@ | ||
const config = createTestConfigWithGraphPolicy(); | ||
const dm = new DialogueManager(config, makeNLUService()); | ||
const dm = new DialogueManager(config, makeMockNLUService(), new MockEmailService()); | ||
const conversation: IConversation = { id: 0, turns: [] }; | ||
@@ -123,2 +137,40 @@ const convBeforeSnapshot = _.cloneDeep(conversation); | ||
}); | ||
it('Falls back to default policy', async () => { | ||
// DialogueManager should fall back to the default policy when | ||
// the graph policy recieves a user action it doesn't recognize. | ||
const config = createTestConfigWithGraphPolicy(true); | ||
const dm = new DialogueManager(config, makeMockNLUService(), new MockEmailService()); | ||
const conversation: IConversation = { id: 0, turns: [] }; | ||
// When an action is seen that doesn't exist in the graph policy, | ||
// the default policy should be tried. | ||
const updatedConv = await dm.processUserAction(testOptionUserActions.wantingThanks, conversation); | ||
// Agent response should be the default policy's response. | ||
assert.equal(updatedConv.turns.length, 2); | ||
const turn1 = (updatedConv.turns[1] as IAgentDialogueTurn); | ||
assert.equal(turn1.actor, 'AGENT'); | ||
assert.equal(turn1.agentAction.name, 'thank'); | ||
}); | ||
it('Handle unknown intent', async () => { | ||
const config = createTestConfigWithGraphPolicy(); | ||
const dm = new DialogueManager(config, makeMockNLUService({ intent: '', tags: [] }), new MockEmailService()); | ||
const conversation: IConversation = { id: 0, turns: [] }; | ||
const updatedConv = await dm.processUserAction(testUtteranceUserActions.wantingThanks, conversation); | ||
// Should be able to handle an unknown intent. | ||
assert.equal((updatedConv.turns[1] as IAgentDialogueTurn).agentAction.name, 'invalid'); | ||
}); | ||
it('Email gets sent', async () => { | ||
const config = createTestConfigWithGraphPolicy(); | ||
const mockEmailService = new MockEmailService(); | ||
const dm = new DialogueManager(config, makeMockNLUService(), mockEmailService); | ||
const conversation: IConversation = { id: 0, turns: [] }; | ||
assert.equal(mockEmailService.callCount, 0); | ||
await dm.processUserAction(testEmailUserAction, conversation); | ||
assert.equal(mockEmailService.callCount, 1); | ||
}); | ||
}); |
@@ -8,3 +8,3 @@ import UtteranceNode from '../src/graph-policy/nodes/utterance-node'; | ||
import { INLUService, IParsedUtterance } from '../src/dialogue-manager'; | ||
import { EUserActionType, IUtteranceUserAction } from '../src/actions/user'; | ||
import { EUserActionType, IOptionUserAction, IUtteranceUserAction, IEmailUserAction } from '../src/actions/user'; | ||
import { AgentConfig } from '../src/agent-config'; | ||
@@ -196,3 +196,3 @@ import { EAgentActionTypes } from '../src/types'; | ||
export const createTestConfigWithGraphPolicy = (): AgentConfig => { | ||
export const createTestConfigWithGraphPolicy = (includeDefaultPolicy = false): AgentConfig => { | ||
const config = new AgentConfig('test', 'test'); | ||
@@ -202,2 +202,12 @@ const graph = createLifeBot(); | ||
config.setActivePolicyName(graph.policyName); | ||
if (includeDefaultPolicy) { | ||
// Add a one action, one intent default policy. | ||
config.addAction({ | ||
utterance: 'Thank you so much.', | ||
type: EAgentActionTypes.UTTERANCE_ACTION, | ||
name: 'thank', | ||
options: [] | ||
}); | ||
config.addIntent('wanting_thanks', 'thank'); | ||
} | ||
return config; | ||
@@ -210,3 +220,3 @@ }; | ||
*/ | ||
export const makeNLUService = (parsedUtterance?: IParsedUtterance): INLUService => { | ||
export const makeMockNLUService = (parsedUtterance?: IParsedUtterance): INLUService => { | ||
const parseUtterance = async (intents: string[], tagTypes: string[], utterance: string): Promise<IParsedUtterance> => { | ||
@@ -227,2 +237,15 @@ if (parsedUtterance) { | ||
export class MockEmailService { | ||
callCount: number; | ||
constructor() { | ||
this.callCount = 0; | ||
} | ||
async sendEmail (html: string, subject: string, to: string, fromName: string, fromEmail: string) { | ||
this.callCount += 1; | ||
return {html, subject, to, fromName, fromEmail}; | ||
} | ||
} | ||
export const testConversation: IConversation = { | ||
@@ -276,13 +299,42 @@ id: 0, | ||
isDefaultResponse: false, | ||
utterance: 'I want to do the 30 day challenge.', | ||
intent: '30_day_challenge', | ||
tags: [] | ||
}, | ||
utterance: 'I want to do the 30 day challenge.' | ||
} | ||
}; | ||
interface ITestOptionUserActions { | ||
[key: string]: IOptionUserAction; | ||
} | ||
export const testOptionUserActions: ITestOptionUserActions = { | ||
sleepingBetter: { | ||
type: EUserActionType.UTTERANCE_ACTION, | ||
isDefaultResponse: false, | ||
utterance: 'I would like to sleep better.', | ||
type: EUserActionType.OPTION_ACTION, | ||
isDefaultResponse: true, | ||
utterance: 'Sleeping Better', | ||
intent: 'sleeping_better', | ||
tags: [] | ||
}, | ||
dailyLifeChallenge: { | ||
type: EUserActionType.OPTION_ACTION, | ||
isDefaultResponse: true, | ||
utterance: 'Start Now', | ||
intent: 'daily_life_challenge', | ||
tags: [] | ||
}, | ||
wantingThanks: { | ||
type: EUserActionType.OPTION_ACTION, | ||
isDefaultResponse: true, | ||
utterance: 'I want a thank you.', | ||
intent: 'wanting_thanks', | ||
tags: [] | ||
} | ||
}; | ||
export const testEmailUserAction: IEmailUserAction = { | ||
type: EUserActionType.EMAIL_ACTION, | ||
isDefaultResponse: false, | ||
email: 'email@test.com', | ||
message: 'this is an email message', | ||
to: 'to@test.com', | ||
from: 'from@test.com', | ||
}; |
204935
5765