botkit-middleware-watson
Advanced tools
Comparing version 1.3.1 to 1.4.0
@@ -0,1 +1,10 @@ | ||
## v1.4.0 | ||
The following changes were introduced with the v1.4.0 release: | ||
* Added `updateContext` function to middleware. | ||
* `interpret` is just an alias of `receive`. | ||
* `sendToWatson` is a new alias of `receive`. | ||
* Fixed error handling in `utils.updateContext`. | ||
* If any error happens in `receive`, it is assigned to `message.watsonError` | ||
## v1.3.1 | ||
@@ -47,2 +56,2 @@ | ||
* Added hears function to middleware | ||
* Added `hears` function to middleware |
@@ -62,50 +62,2 @@ /** | ||
interpret: function(bot, message, callback) { | ||
var before = Promise.promisify(middleware.before); | ||
var after = Promise.promisify(middleware.after); | ||
if (!middleware.conversation) { | ||
debug('Creating Conversation object with parameters: ' + JSON.stringify(config, 2, null)); | ||
middleware.conversation = new ConversationV1(config); | ||
} | ||
if (!message.text || ignoreType.indexOf(message.type) !== -1 || message.reply_to || message.bot_id) { | ||
// Ignore messages initiated by Slack. Reply with dummy output object | ||
message.watsonData = { | ||
output: { | ||
text: [] | ||
} | ||
}; | ||
callback(); | ||
} | ||
middleware.storage = bot.botkit.storage; | ||
readContext(message.user, middleware.storage).then(function(userContext) { | ||
var payload = { | ||
workspace_id: config.workspace_id, | ||
input: { | ||
text: message.text | ||
} | ||
}; | ||
if (userContext) { | ||
payload.context = userContext; | ||
} | ||
return payload; | ||
}).then(function(payload) { | ||
return before(message, payload); | ||
}).then(function(watsonRequest) { | ||
return postMessage(middleware.conversation, watsonRequest); | ||
}).then(function(watsonResponse) { | ||
return after(message, watsonResponse); | ||
}).then(function(watsonResponse) { | ||
message.watsonData = watsonResponse; | ||
return updateContext(message.user, middleware.storage, watsonResponse); | ||
}).catch(function(error) { | ||
debug('Error: %s', JSON.stringify(error, null, 2)); | ||
}).done(function(response) { | ||
callback(null); | ||
}); | ||
}, | ||
receive: function(bot, message, next) { | ||
@@ -127,3 +79,3 @@ var before = Promise.promisify(middleware.before); | ||
}; | ||
next(); | ||
return next(); | ||
} | ||
@@ -154,2 +106,3 @@ | ||
}).catch(function(error) { | ||
message.watsonError = error; | ||
debug('Error: %s', JSON.stringify(error, Object.getOwnPropertyNames(error), 2)); | ||
@@ -162,4 +115,16 @@ }).done(function(response) { | ||
middleware.interpret = middleware.receive; | ||
middleware.sendToWatson = middleware.receive; | ||
middleware.updateContext = function(user, context, callback) { | ||
watsonUtils.updateContext( | ||
user, | ||
middleware.storage, | ||
{ context: context }, | ||
callback | ||
); | ||
}; | ||
debug('Middleware: ' + JSON.stringify(middleware, null, 2)); | ||
return middleware; | ||
}; |
@@ -20,2 +20,7 @@ /** | ||
storage.users.get(userId, function(err, user_data) { | ||
if (err) { | ||
//error is returned if nothing is stored yet, so it is the best to ignore it | ||
debug('User: %s, read context error: %s', userId, err); | ||
} | ||
if (user_data && user_data.context) { | ||
@@ -32,4 +37,4 @@ debug('User: %s, Context: %s', userId, JSON.stringify(user_data.context, null, 2)); | ||
var updateContext = function(userId, storage, watsonResponse, callback) { | ||
readContext(userId, storage, function(error, user_data) { | ||
if (error) return callback(error); | ||
readContext(userId, storage, function(err, user_data) { | ||
if (err) return callback(err); | ||
@@ -44,5 +49,6 @@ if (!user_data) { | ||
if (err) return callback(err); | ||
debug('User: %s, Updated Context: %s', userId, JSON.stringify(watsonResponse.context, null, 2)); | ||
callback(null, watsonResponse); | ||
}); | ||
debug('User: %s, Updated Context: %s', userId, JSON.stringify(watsonResponse.context, null, 2)); | ||
callback(null, watsonResponse); | ||
}); | ||
@@ -54,8 +60,6 @@ }; | ||
conversation.message(payload, function(err, response) { | ||
if (err) { | ||
callback(err); | ||
} else { | ||
debug('Conversation Response: %s', JSON.stringify(response, null, 2)); | ||
callback(null, response); | ||
} | ||
if (err) return callback(err); | ||
debug('Conversation Response: %s', JSON.stringify(response, null, 2)); | ||
callback(null, response); | ||
}); | ||
@@ -62,0 +66,0 @@ }; |
{ | ||
"name": "botkit-middleware-watson", | ||
"version": "1.3.1", | ||
"version": "1.4.0", | ||
"description": "A middleware for using Watson Conversation in a Botkit-powered bot.", | ||
@@ -26,10 +26,11 @@ "main": "lib/middleware/index.js", | ||
"botkit": "^0.2.2", | ||
"nock": "^8.1.0", | ||
"mocha": "^3.1.0" | ||
"mocha": "^3.4.2", | ||
"nock": "^8.2.1", | ||
"sinon": "^2.3.5" | ||
}, | ||
"dependencies": { | ||
"bluebird": "^3.4.6", | ||
"debug": "^2.2.0", | ||
"watson-developer-cloud": "^2.4.1" | ||
"bluebird": "^3.5.0", | ||
"debug": "^2.6.8", | ||
"watson-developer-cloud": "^2.32.1" | ||
} | ||
} |
173
README.md
@@ -11,5 +11,13 @@ # Use IBM Watson's Conversation service to chat with your Botkit-powered Bot! [](https://travis-ci.org/watson-developer-cloud/botkit-middleware) | ||
* Exposes the following functions to developers: | ||
* `before`: pre-process requests before sending to Watson Conversation (Conversation). | ||
* `after` : post-process responses before forwarding them to Botkit. | ||
## Function Overview | ||
* `receive`: used as [middleware in Botkit](#bot-setup). | ||
* `interpret`: an alias of `receive`, used in [message-filtering](#message-filtering) and [implementing app actions](#implementing-app-actions). | ||
* `sendToWatson`: another alias of `receive`, use the one that looks the best in context. | ||
* `hear`: used for [intent matching](#intent-matching). | ||
* `updateContext`: used in [implementing app actions](#implementing-app-actions). | ||
* `before`: [pre-process](#before-and-after) requests before sending to Watson Conversation (Conversation). | ||
* `after`: [post-process](#before-and-after) responses before forwarding them to Botkit. | ||
## Installation | ||
@@ -33,3 +41,3 @@ ```sh | ||
Otherwise, follow [Botkit's instructions](https://github.com/howdyai/botkit/blob/master/readme-slack.md) to create your Slack bot from scratch. When your bot is ready, you are provided with a Slack token. | ||
Otherwise, follow [Botkit's instructions](https://github.com/howdyai/botkit/blob/master/docs/readme-slack.md) to create your Slack bot from scratch. When your bot is ready, you are provided with a Slack token. | ||
@@ -58,3 +66,3 @@ ### Bot setup | ||
workspace_id: YOUR_WORKSPACE_ID, | ||
version_date: '2016-09-20', | ||
version_date: '2017-05-26', | ||
minimum_confidence: 0.50, // (Optional) Default is 0.75 | ||
@@ -73,55 +81,150 @@ }); | ||
slackController.hears(['.*'], ['direct_message', 'direct_mention', 'mention'], function(bot, message) { | ||
if (message.watsonError) { | ||
bot.reply(message, "I'm sorry, but for technical reasons I can't respond to your message"); | ||
} else { | ||
bot.reply(message, message.watsonData.output.text.join('\n')); | ||
} | ||
}); | ||
``` | ||
The middleware attaches the `watsonData` object to _message_. This contains the text response from Conversation. | ||
If any error happened in middleware, error is assigned to `watsonError` property of the _message_. | ||
Then you're all set! | ||
### Middleware Functions | ||
The _watsonMiddleware_ object provides some useful functions which can be used for customizing the question-answering pipeline. | ||
## Features | ||
They come in handy to: | ||
- Respond to incoming messages | ||
- Make database updates | ||
- Update the context in the payload | ||
- Call some external service before/after calling Conversation | ||
- Filter out irrelevant intents by overwriting Botkit's hears function | ||
### Message filtering | ||
When middleware is registered, the receive function is triggered on _every_ message. | ||
If you would like to make your bot to only respond to _direct messages_ using Conversation, you can achieve this in 2 ways: | ||
#### `receive` | ||
The _receive_ function is the one which gets triggered on incoming bot messages. One needs to bind it to the Botkit's receive middleware in order for it to work. | ||
#### Using interpret function instead of registering middleware | ||
```js | ||
slackController.hears(['.*'], ['direct_message'], function(bot, message) { | ||
middleware.interpret(bot, message, function() { | ||
if (message.watsonError) { | ||
bot.reply(message, "I'm sorry, but for technical reasons I can't respond to your message"); | ||
} else { | ||
bot.reply(message, message.watsonData.output.text.join('\n')); | ||
} | ||
}); | ||
}); | ||
``` | ||
#### Using middleware wrapper | ||
```js | ||
// Connect to Watson middleware | ||
slackController.middleware.receive.use(middleware.receive); | ||
var receiveMiddleware = function (bot, message, next) { | ||
if (message.type === 'direct_message') { | ||
watsonMiddleware.receive(bot, message, next); | ||
} else { | ||
next(); | ||
} | ||
}; | ||
slackController.middleware.receive.use(receiveMiddleware); | ||
``` | ||
Then simply respond to messages as follows: | ||
### Implementing app actions | ||
Conversation side of app action is documented in [Developer Cloud](https://www.ibm.com/watson/developercloud/doc/conversation/develop-app.html#implementing-app-actions) | ||
A common scenario of processing actions is | ||
* Send message to user "Please wait while I ..." | ||
* Perform action | ||
* Persist results in conversation context | ||
* Send message to Watson with updated context | ||
* Send result message(s) to user. | ||
#### Using middleware.after and controller | ||
Before v1.4.0 only middleware.after callback can update context, and only controller can send replies to user. | ||
The downside is that it is impossible to send "Please wait message". | ||
```js | ||
slackController.hears(['.*'], ['direct_message', 'direct_mention', 'mention'], function(bot, message) { | ||
bot.reply(message, message.watsonData.output.text.join('\n')); | ||
}); | ||
function checkBalance(watsonResponse, callback) { | ||
//middleware.after function must pass a complete Watson respose to callback | ||
watsonResponse.context.validAccount = true; | ||
watsonResponse.context.accountBalance = 95.33; | ||
callback(null, watsonResponse); | ||
} | ||
watsonMiddleware.after = function(message, watsonResponse, callback) { | ||
//real action happens in middleware.after | ||
if (typeof watsonResponse !== 'undefined' && typeof watsonResponse.output !== 'undefined') { | ||
if (watsonResponse.output.action === 'check_balance') { | ||
return checkBalance(watsonResponse, callback); | ||
} | ||
} | ||
callback(null, watsonResponse); | ||
}; | ||
var processWatsonResponse = function(bot, message) { | ||
if (message.watsonError) { | ||
return bot.reply(message, "I'm sorry, but for technical reasons I can't respond to your message"); | ||
} | ||
if (typeof message.watsonData.output !== 'undefined') { | ||
//send "Please wait" to users | ||
bot.reply(message, message.watsonData.output.text.join('\n')); | ||
if (message.watsonData.output.action === 'check_balance') { | ||
var newMessage = clone(message); | ||
newMessage.text = 'balance result'; | ||
//send to watson | ||
watsonMiddleware.interpret(bot, newMessage, function() { | ||
//send results to user | ||
processWatsonResponse(bot, newMessage); | ||
}); | ||
} | ||
} | ||
}; | ||
controller.on('message_received', processWatsonResponse); | ||
``` | ||
#### Using updateContext in controller (available since v1.4.0) | ||
Since 1.4.0 it is possible to update context from controller code. | ||
```js | ||
Note: The receive function is triggered on _every_ message. Please consult the [Botkit's guide](https://github.com/howdyai/botkit#receive-middleware) to the receive middleware to know more about it. | ||
function checkBalance(context, callback) { | ||
//this version of function updates only the context object | ||
context.validAccount = true; | ||
context.accountBalance = 95.33; | ||
callback(null, watsonResponse); | ||
} | ||
#### `interpret` | ||
Promise.promisifyAll(watsonMiddleware); | ||
var checkBalanceAsync = Promise.promisify(checkBalance); | ||
The `interpret()` function works very similarly to the receive function but unlike the receive function, | ||
- it is not mapped to a Botkit function so doesn't need to be added as a middleware to Botkit | ||
- doesn't get triggered on all events | ||
var processWatsonResponse = function (bot, message) { | ||
if (message.watsonError) { | ||
return bot.reply(message, "I'm sorry, but for technical reasons I can't respond to your message"); | ||
} | ||
if (typeof message.watsonData.output !== 'undefined') { | ||
//send "Please wait" to users | ||
bot.reply(message, message.watsonData.output.text.join('\n')); | ||
The _interpret_ function only gets triggered when an event is _heard_ by the controller. For example, one might want your bot to only respond to _direct messages_ using Conversation. In such scenarios, one would use the interpret function as follows: | ||
if (message.watsonData.output.action === 'check_balance') { | ||
var newMessage = clone(message); | ||
newMessage.text = 'balance result'; | ||
```js | ||
slackController.hears(['.*'], ['direct_message'], function(bot, message) { | ||
middleware.interpret(bot, message, function(err) { | ||
if (!err) | ||
bot.reply(message, message.watsonData.output.text.join('\n')); | ||
}); | ||
}); | ||
//check balance | ||
checkBalanceAsync(message.watsonData.context).then(function (context) { | ||
//update context in storage | ||
return watsonMiddleware.updateContextAsync(message.user, context); | ||
}).then(function () { | ||
//send message to watson (it reads updated context from storage) | ||
return watsonMiddleware.sendToWatsonAsync(bot, newMessage); | ||
}).catch(function (error) { | ||
newMessage.watsonError = error; | ||
}).then(function () { | ||
//send results to user | ||
return processWatsonResponse(bot, newMessage); | ||
}); | ||
} | ||
} | ||
}; | ||
controller.on('message_received', processWatsonResponse); | ||
``` | ||
#### `hear` | ||
### Intent matching | ||
The Watson middleware also includes a `hear()` function which provides a mechanism to | ||
@@ -165,3 +268,3 @@ developers to fire handler functions based on the most likely intent of the user. | ||
```js | ||
middleware.before = function(message, conversationPayload, callback) { | ||
middleware.before = function(message, conversationPayload, callback) { | ||
// Code here gets executed before making the call to Conversation. | ||
@@ -168,0 +271,0 @@ callback(null, customizedPayload); |
30180
280
5
166
Updatedbluebird@^3.5.0
Updateddebug@^2.6.8