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:
parent
26f76dc9b1
commit
878691a745
|
@ -67,6 +67,8 @@ force_include = [
|
||||||
"zulip_bots/zulip_bots/bots/chess/test_chess.py",
|
"zulip_bots/zulip_bots/bots/chess/test_chess.py",
|
||||||
"zulip_bots/zulip_bots/bots/xkcd/xkcd.py",
|
"zulip_bots/zulip_bots/bots/xkcd/xkcd.py",
|
||||||
"zulip_bots/zulip_bots/bots/xkcd/test_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/wikipedia.py",
|
||||||
"zulip_bots/zulip_bots/bots/wikipedia/test_wikipedia.py",
|
"zulip_bots/zulip_bots/bots/wikipedia/test_wikipedia.py",
|
||||||
"zulip_bots/zulip_bots/bots/yoda/yoda.py",
|
"zulip_bots/zulip_bots/bots/yoda/yoda.py",
|
||||||
|
|
|
@ -54,6 +54,7 @@ setuptools_info = dict(
|
||||||
'lxml', # for bots/googlesearch
|
'lxml', # for bots/googlesearch
|
||||||
'requests', # for bots/link_shortener
|
'requests', # for bots/link_shortener
|
||||||
'python-chess[engine,gaviota]', # for bots/chess
|
'python-chess[engine,gaviota]', # for bots/chess
|
||||||
|
'wit', # for bots/witai
|
||||||
'apiai' # for bots/dialogflow
|
'apiai' # for bots/dialogflow
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
52
zulip_bots/zulip_bots/bots/witai/doc.md
Normal file
52
zulip_bots/zulip_bots/bots/witai/doc.md
Normal 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.
|
59
zulip_bots/zulip_bots/bots/witai/test_witai.py
Normal file
59
zulip_bots/zulip_bots/bots/witai/test_witai.py
Normal 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
|
4
zulip_bots/zulip_bots/bots/witai/witai.conf
Normal file
4
zulip_bots/zulip_bots/bots/witai/witai.conf
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
[witai]
|
||||||
|
token = <your token>
|
||||||
|
handler_location = <your handler location>
|
||||||
|
help_message = <your help message>
|
78
zulip_bots/zulip_bots/bots/witai/witai.py
Normal file
78
zulip_bots/zulip_bots/bots/witai/witai.py
Normal 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
|
Loading…
Reference in a new issue