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/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",
|
||||
|
|
|
@ -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
|
||||
],
|
||||
)
|
||||
|
|
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