bots: Create Wit.ai Bot.

Wit.ai Bot communicates with the Wit.ai API. It can be configured with
any Wit.ai token and allows for setting up a custom handler function to
handle Wit.ai responses.
This commit is contained in:
Marco Burstein 2017-12-17 18:23:22 -08:00 committed by showell
parent 26f76dc9b1
commit 878691a745
6 changed files with 196 additions and 0 deletions

View file

@ -67,6 +67,8 @@ force_include = [
"zulip_bots/zulip_bots/bots/chess/test_chess.py",
"zulip_bots/zulip_bots/bots/xkcd/xkcd.py",
"zulip_bots/zulip_bots/bots/xkcd/test_xkcd.py",
"zulip_bots/zulip_bots/bots/witai/witai.py",
"zulip_bots/zulip_bots/bots/witai/test_witai.py",
"zulip_bots/zulip_bots/bots/wikipedia/wikipedia.py",
"zulip_bots/zulip_bots/bots/wikipedia/test_wikipedia.py",
"zulip_bots/zulip_bots/bots/yoda/yoda.py",

View file

@ -54,6 +54,7 @@ setuptools_info = dict(
'lxml', # for bots/googlesearch
'requests', # for bots/link_shortener
'python-chess[engine,gaviota]', # for bots/chess
'wit', # for bots/witai
'apiai' # for bots/dialogflow
],
)

View file

@ -0,0 +1,52 @@
# Wit.ai Bot
Wit.ai Bot uses [wit.ai](https://wit.ai/) to parse natural language.
## Usage
1. Go to https://wit.ai/ and sign up.
2. Create your Wit.ai app, or follow
[this quickstart guide](https://wit.ai/docs/quickstart).
3. Create a `.conf` file containing a `token` field for your Wit.ai token,
and a `help_message` field for a message to display to confused users,
e.g.,
```
[witai]
token = QWERTYUIOP1234
help_message = Ask me about my favorite food!
```
4. Create a new file named `witai_handler.py`, and inside of it, create a
function called `handle` with one parameter `response`. Inside of `handle`,
write code for whatever you want to do with the Wit.ai response. It should
return a `string` to respond to the user with. For example,
```python
def handle(response):
if response['entities']['intent'][0]['value'] == 'favorite_food':
return 'pizza'
if response['entities']['intent'][0]['value'] == 'favorite_drink':
return 'coffee'
```
5. Add `witai_handler.py`'s location as `handler_location` in your
configuration file, e.g.,
```
[witai]
token = QWERTYUIOP1234
handler_location = /Users/you/witai_handler_directory/witai_handler.py
```
6. Call
```bash
zulip-run-bot witai \
--config-file <your zuliprc> \
--bot-config-file <the config file>
```
to start the bot.

View file

@ -0,0 +1,59 @@
from mock import patch
import sys
from typing import Dict, Any, Optional
from zulip_bots.test_lib import BotTestCase, get_bot_message_handler, StubBotHandler
class TestWitaiBot(BotTestCase):
bot_name = 'witai'
MOCK_CONFIG_INFO = {
'token': '12345678',
'handler_location': '/Users/abcd/efgh',
'help_message': 'Qwertyuiop!'
}
MOCK_WITAI_RESPONSE = {
'_text': 'What is your favorite food?',
'entities': {
'intent': [{
'confidence': 1.0,
'value': 'favorite_food'
}]
}
}
def test_normal(self) -> None:
with self.mock_config_info(self.MOCK_CONFIG_INFO):
get_bot_message_handler(self.bot_name).initialize(StubBotHandler())
with patch('wit.Wit.message') as message:
message.return_value = self.MOCK_WITAI_RESPONSE
with patch('zulip_bots.bots.witai.witai.get_handle') as handler:
handler.return_value = mock_handle
self.verify_reply(
'What is your favorite food?',
'pizza'
)
# This overrides the default one in `BotTestCase`.
def test_bot_responds_to_empty_message(self) -> None:
with self.mock_config_info(self.MOCK_CONFIG_INFO):
get_bot_message_handler(self.bot_name).initialize(StubBotHandler())
with patch('wit.Wit.message') as message:
message.return_value = self.MOCK_WITAI_RESPONSE
with patch('zulip_bots.bots.witai.witai.get_handle') as handler:
handler.return_value = mock_handle
self.verify_reply('', 'Qwertyuiop!')
def mock_handle(res: Dict[str, Any]) -> Optional[str]:
if res['entities']['intent'][0]['value'] == 'favorite_food':
return 'pizza'
if res['entities']['intent'][0]['value'] == 'favorite_drink':
return 'coffee'
return None

View file

@ -0,0 +1,4 @@
[witai]
token = <your token>
handler_location = <your handler location>
help_message = <your help message>

View file

@ -0,0 +1,78 @@
# See readme.md for instructions on running this code.
from typing import Dict, Any, Optional, Callable
import wit
import sys
import importlib.util
class WitaiHandler(object):
def usage(self) -> str:
return '''
Wit.ai bot uses pywit API to interact with Wit.ai. In order to use
Wit.ai bot, `witai.conf` must be set up. See `doc.md` for more details.
'''
def initialize(self, bot_handler: Any) -> None:
config = bot_handler.get_config_info('witai')
token = config.get('token')
if not token:
raise KeyError('No `token` was specified')
# `handler_location` should be the location of a module which contains
# the function `handle`. See `doc.md` for more details.
handler_location = config.get('handler_location')
if not handler_location:
raise KeyError('No `handler_location` was specified')
self.handle = get_handle(handler_location)
help_message = config.get('help_message')
if not help_message:
raise KeyError('No `help_message` was specified')
self.help_message = help_message
self.client = wit.Wit(token)
def handle_message(self, message: Dict[str, str], bot_handler: Any) -> None:
if message['content'] == '' or message['content'] == 'help':
bot_handler.send_reply(message, self.help_message)
return
try:
res = self.client.message(message['content'])
message_for_user = self.handle(res)
if message_for_user:
bot_handler.send_reply(message, message_for_user)
except wit.wit.WitError:
bot_handler.send_reply(message, 'Sorry, I don\'t know how to respond to that!')
except Exception as e:
bot_handler.send_reply(message, 'Sorry, there was an internal error.')
print(e)
return
handler_class = WitaiHandler
def get_handle(location: str) -> Callable[[Dict[str, Any]], Optional[str]]:
'''Returns a function to be used when generating a response from Wit.ai
bot. This function is the function named `handle` in the module at the
given `location`. For an example of a `handle` function, see `doc.md`.
For example,
handle = get_handle('/Users/someuser/witai_handler.py') # Get the handler function.
res = witai_client.message(message['content']) # Get the Wit.ai response.
message_res = self.handle(res) # Handle the response and find what to show the user.
bot_handler.send_reply(message, message_res) # Send it to the user.
Parameters:
- location: The absolute path to the module to look for `handle` in.
'''
try:
spec = importlib.util.spec_from_file_location('module.name', location)
handler = importlib.util.module_from_spec(spec)
spec.loader.exec_module(handler)
return handler.handle # type: ignore
except Exception as e:
print(e)
return None