initial support for Rasa

This commit is contained in:
Dave Horton
2021-09-01 21:06:32 -04:00
committed by Dave Horton
parent ac8827c885
commit 19f3cbaa43
6 changed files with 134 additions and 5 deletions

View File

@@ -202,7 +202,6 @@ class CallSession extends Emitter {
*/ */
getSpeechCredentials(vendor, type) { getSpeechCredentials(vendor, type) {
const {writeAlerts, AlertType} = this.srf.locals; const {writeAlerts, AlertType} = this.srf.locals;
this.logger.debug({vendor, type, speech: this.accountInfo.speech}, `searching for speech for vendor ${vendor}`);
if (this.accountInfo.speech && this.accountInfo.speech.length > 0) { if (this.accountInfo.speech && this.accountInfo.speech.length > 0) {
const credential = this.accountInfo.speech.find((s) => s.vendor === vendor); const credential = this.accountInfo.speech.find((s) => s.vendor === vendor);
if (credential && ( if (credential && (

View File

@@ -10,7 +10,7 @@ const makeTask = require('./make_task');
const assert = require('assert'); const assert = require('assert');
class TaskGather extends Task { class TaskGather extends Task {
constructor(logger, opts) { constructor(logger, opts, parentTask) {
super(logger, opts); super(logger, opts);
this.preconditions = TaskPreconditions.Endpoint; this.preconditions = TaskPreconditions.Endpoint;
@@ -40,6 +40,8 @@ class TaskGather extends Task {
if (this.say) this.sayTask = makeTask(this.logger, {say: this.say}, this); if (this.say) this.sayTask = makeTask(this.logger, {say: this.say}, this);
if (this.play) this.playTask = makeTask(this.logger, {play: this.play}, this); if (this.play) this.playTask = makeTask(this.logger, {play: this.play}, this);
this.parentTask = parentTask;
} }
get name() { return TaskName.Gather; } get name() { return TaskName.Gather; }
@@ -154,7 +156,6 @@ class TaskGather extends Task {
}); });
ep.addCustomEventListener(AwsTranscriptionEvents.Transcription, this._onTranscription.bind(this, cs, ep)); ep.addCustomEventListener(AwsTranscriptionEvents.Transcription, this._onTranscription.bind(this, cs, ep));
} }
this.logger.debug({vars: opts}, 'setting freeswitch vars');
await ep.set(opts) await ep.set(opts)
.catch((err) => this.logger.info(err, 'Error setting channel variables')); .catch((err) => this.logger.info(err, 'Error setting channel variables'));
@@ -235,7 +236,11 @@ class TaskGather extends Task {
await this.performAction({digits: this.digitBuffer}); await this.performAction({digits: this.digitBuffer});
} }
else if (reason.startsWith('speech')) { else if (reason.startsWith('speech')) {
await this.performAction({speech: evt}); if (this.parentTask) this.parentTask.emit('transcription', evt);
else await this.performAction({speech: evt});
}
else if (reason.startsWith('timeout') && this.parentTask) {
this.parentTask.emit('timeout', evt);
} }
this.notifyTaskDone(); this.notifyTaskDone();
} }

View File

@@ -48,6 +48,9 @@ function makeTask(logger, obj, parent) {
case TaskName.Message: case TaskName.Message:
const TaskMessage = require('./message'); const TaskMessage = require('./message');
return new TaskMessage(logger, data, parent); return new TaskMessage(logger, data, parent);
case TaskName.Rasa:
const TaskRasa = require('./rasa');
return new TaskRasa(logger, data, parent);
case TaskName.Say: case TaskName.Say:
const TaskSay = require('./say'); const TaskSay = require('./say');
return new TaskSay(logger, data, parent); return new TaskSay(logger, data, parent);

109
lib/tasks/rasa.js Normal file
View File

@@ -0,0 +1,109 @@
const Task = require('./task');
const {TaskName, TaskPreconditions} = require('../utils/constants');
const makeTask = require('./make_task');
const bent = require('bent');
class Rasa extends Task {
constructor(logger, opts) {
super(logger, opts);
this.preconditions = TaskPreconditions.Endpoint;
this.prompt = this.data.prompt;
this.eventHook = this.data?.eventHook;
this.actionHook = this.data?.actionHook;
this.post = bent('POST', 'json', 200);
}
get name() { return TaskName.Rasa; }
async exec(cs, ep) {
await super.exec(cs);
this.ep = ep;
try {
/* set event handlers */
this.on('transcription', this._onTranscription.bind(this, cs, ep));
this.on('timeout', this._onTimeout.bind(this, cs, ep));
/* start the first gather */
this.gatherTask = this._makeGatherTask(this.prompt);
this.gatherTask.exec(cs, ep, this)
.catch((err) => this.logger.info({err}, 'Rasa gather task returned error'));
await this.awaitTaskDone();
} catch (err) {
this.logger.error({err}, 'Rasa error');
throw err;
}
}
async kill(cs) {
super.kill(cs);
if (this.ep.connected) {
this.logger.debug('Rasa:kill');
this.performAction({rasaResult: 'caller hungup'})
.catch((err) => this.logger.error({err}, 'rasa - error w/ action webook'));
await this.ep.api('uuid_break', this.ep.uuid).catch((err) => this.logger.info(err, 'Error killing audio'));
}
this.removeAllListeners();
this.notifyTaskDone();
}
_makeGatherTask(prompt) {
let opts = {
input: ['speech'],
timeout: this.data.timeout || 10,
recognizer: this.data.recognizer || {
vendor: 'default',
language: 'default'
}
};
if (prompt) {
const sayOpts = this.data.tts ?
{text: prompt, synthesizer: this.data.tts} :
{text: prompt};
opts = {
...opts,
say: sayOpts
};
}
this.logger.debug({opts}, 'constructing a nested gather object');
const gather = makeTask(this.logger, {gather: opts}, this);
return gather;
}
async _onTranscription(cs, ep, evt) {
this.logger.debug({evt}, `Rasa: got transcription for callSid ${cs.callSid}`);
const utterance = evt.alternatives[0].transcript;
try {
const payload = {
sender: cs.callSid,
message: utterance
};
this.logger.debug({payload}, 'Rasa:_onTranscription - sending payload to Rasa');
const response = await this.post(this.data.url, payload);
this.logger.debug({response}, 'Rasa:_onTranscription - got response from Rasa');
const botUtterance = Array.isArray(response) ?
response.reduce((prev, current) => `${prev} ${current.text}`, '') :
null;
if (botUtterance) {
this.logger.debug({botUtterance}, 'playing out bot utterance');
this.gatherTask = this._makeGatherTask(botUtterance);
this.gatherTask.exec(cs, ep, this)
.catch((err) => this.logger.info({err}, 'Rasa gather task returned error'));
}
} catch (err) {
this.logger.error({err}, 'Error sending');
}
}
_onTimeout(cs, ep, evt) {
this.logger.debug({evt}, 'Rasa: got timeout');
}
}
module.exports = Rasa;

View File

@@ -78,7 +78,6 @@
"say": "#say" "say": "#say"
}, },
"required": [ "required": [
"actionHook"
] ]
}, },
"conference": { "conference": {
@@ -228,6 +227,19 @@
"length" "length"
] ]
}, },
"rasa": {
"properties": {
"url": "string",
"recognizer": "#recognizer",
"tts": "#synthesizer",
"prompt": "string",
"actionHook": "object|string",
"eventHook": "object|string"
},
"required": [
"url"
]
},
"redirect": { "redirect": {
"properties": { "properties": {
"actionHook": "object|string" "actionHook": "object|string"

View File

@@ -14,6 +14,7 @@
"Message": "message", "Message": "message",
"Pause": "pause", "Pause": "pause",
"Play": "play", "Play": "play",
"Rasa": "rasa",
"Redirect": "redirect", "Redirect": "redirect",
"RestDial": "rest:dial", "RestDial": "rest:dial",
"SipDecline": "sip:decline", "SipDecline": "sip:decline",