interactive bots: Create DialogFlow bot.
This commit is contained in:
parent
277b384379
commit
657c6d7b9c
|
@ -69,6 +69,8 @@ force_include = [
|
||||||
"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",
|
||||||
"zulip_bots/zulip_bots/bots/yoda/test_yoda.py",
|
"zulip_bots/zulip_bots/bots/yoda/test_yoda.py",
|
||||||
|
"zulip_bots/zulip_bots/bots/dialogflow/dialogflow.py",
|
||||||
|
"zulip_bots/zulip_bots/bots/dialogflow/test_dialogflow.py"
|
||||||
]
|
]
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description="Run mypy on files tracked by git.")
|
parser = argparse.ArgumentParser(description="Run mypy on files tracked by git.")
|
||||||
|
|
|
@ -53,7 +53,8 @@ setuptools_info = dict(
|
||||||
'BeautifulSoup4', # for bots/googlesearch
|
'BeautifulSoup4', # for bots/googlesearch
|
||||||
'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
|
||||||
|
'apiai' # for bots/dialogflow
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
0
zulip_bots/zulip_bots/bots/dialogflow/__init__.py
Normal file
0
zulip_bots/zulip_bots/bots/dialogflow/__init__.py
Normal file
3
zulip_bots/zulip_bots/bots/dialogflow/dialogflow.conf
Normal file
3
zulip_bots/zulip_bots/bots/dialogflow/dialogflow.conf
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[dialogflow]
|
||||||
|
key=YOUR_PUBLIC_API_KEY_HERE
|
||||||
|
bot_info=BOT_INFO_HERE
|
57
zulip_bots/zulip_bots/bots/dialogflow/dialogflow.py
Normal file
57
zulip_bots/zulip_bots/bots/dialogflow/dialogflow.py
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
# See readme.md for instructions on running this code.
|
||||||
|
from __future__ import print_function
|
||||||
|
import logging
|
||||||
|
from six.moves.urllib import parse
|
||||||
|
import json
|
||||||
|
|
||||||
|
import apiai
|
||||||
|
|
||||||
|
from typing import Dict, Any, List
|
||||||
|
|
||||||
|
help_message = '''DialogFlow bot
|
||||||
|
This bot will interact with dialogflow bots.
|
||||||
|
Simply send this bot a message, and it will respond depending on the configured bot's behaviour.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def get_bot_result(message_content: str, config: Dict[str, str], sender_id: str) -> str:
|
||||||
|
if message_content.strip() == '' or message_content.strip() == 'help':
|
||||||
|
return config['bot_info']
|
||||||
|
ai = apiai.ApiAI(config['key'])
|
||||||
|
try:
|
||||||
|
request = ai.text_request()
|
||||||
|
request.session_id = sender_id
|
||||||
|
request.query = message_content
|
||||||
|
response = request.getresponse()
|
||||||
|
res_str = response.read().decode('utf8', 'ignore')
|
||||||
|
res_json = json.loads(res_str)
|
||||||
|
if res_json['status']['errorType'] != 'success':
|
||||||
|
return 'Error {}: {}.'.format(res_json['status']['code'], res_json['status']['errorDetails'])
|
||||||
|
if res_json['result']['fulfillment']['speech'] == '':
|
||||||
|
if res_json['alternateResult']['fulfillment']['speech'] == '':
|
||||||
|
return 'Error. No result.'
|
||||||
|
return res_json['alternateResult']['fulfillment']['speech']
|
||||||
|
return res_json['result']['fulfillment']['speech']
|
||||||
|
except Exception as e:
|
||||||
|
logging.exception(str(e))
|
||||||
|
return 'Error. {}.'.format(str(e))
|
||||||
|
|
||||||
|
class DialogFlowHandler(object):
|
||||||
|
'''
|
||||||
|
This plugin allows users to easily add their own
|
||||||
|
DialogFlow bots to zulip
|
||||||
|
'''
|
||||||
|
|
||||||
|
def initialize(self, bot_handler: Any) -> None:
|
||||||
|
self.config_info = bot_handler.get_config_info('dialogflow')
|
||||||
|
|
||||||
|
def usage(self) -> str:
|
||||||
|
return '''
|
||||||
|
This plugin will allow users to easily add their own
|
||||||
|
DialogFlow bots to zulip
|
||||||
|
'''
|
||||||
|
|
||||||
|
def handle_message(self, message: Dict[str, str], bot_handler: Any) -> None:
|
||||||
|
result = get_bot_result(message['content'], self.config_info, message['sender_id'])
|
||||||
|
bot_handler.send_reply(message, result)
|
||||||
|
|
||||||
|
handler_class = DialogFlowHandler
|
24
zulip_bots/zulip_bots/bots/dialogflow/doc.md
Normal file
24
zulip_bots/zulip_bots/bots/dialogflow/doc.md
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
# DialogFlow bot
|
||||||
|
This bot allows users to easily add their own DialogFlow bots to zulip.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
To add your DialogFlow bot:
|
||||||
|
Add the V1 Client access token from your agent's settings in the DialogFlow console to
|
||||||
|
`dialogflow.conf`, and write a short sentence describing what your bot does in the same file
|
||||||
|
as `bot_info`.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Run this bot as described
|
||||||
|
[here](https://zulipchat.com/api/running-bots#running-a-bot).
|
||||||
|
|
||||||
|
Mention the bot in order to say things to it.
|
||||||
|
|
||||||
|
For example: `@weather What is the weather today?`
|
||||||
|
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
When creating your DialogFlow bot, please consider these things:
|
||||||
|
|
||||||
|
- Empty input will not be sent to the bot.
|
||||||
|
- Only text can be sent to, and recieved from the bot.
|
12
zulip_bots/zulip_bots/bots/dialogflow/fixtures/test_403.json
Normal file
12
zulip_bots/zulip_bots/bots/dialogflow/fixtures/test_403.json
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"response": {
|
||||||
|
"status": {
|
||||||
|
"errorType": "fail",
|
||||||
|
"code": "403",
|
||||||
|
"errorDetails": "Access Denied"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"request": {
|
||||||
|
"query": "hello"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"response": {
|
||||||
|
"result": {
|
||||||
|
"fulfillment": {
|
||||||
|
"speech": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"alternateResult": {
|
||||||
|
"fulfillment": {
|
||||||
|
"speech": "alternate result"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"errorType": "success"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"request": {
|
||||||
|
"query": "hello"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"response": {
|
||||||
|
"result": {
|
||||||
|
"fulfillment": {
|
||||||
|
"speech": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"alternateResult": {
|
||||||
|
"fulfillment": {
|
||||||
|
"speech": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"errorType": "success"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"request": {
|
||||||
|
"query": "hello"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"response": {
|
||||||
|
"foo": {
|
||||||
|
"fulfillment": {
|
||||||
|
"speech": "how are you?"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bar": {
|
||||||
|
"errorType": "success"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"request": {
|
||||||
|
"query": "hello"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"response": {
|
||||||
|
"result": {
|
||||||
|
"fulfillment": {
|
||||||
|
"speech": "how are you?"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"status": {
|
||||||
|
"errorType": "success"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"request": {
|
||||||
|
"query": "hello"
|
||||||
|
}
|
||||||
|
}
|
71
zulip_bots/zulip_bots/bots/dialogflow/test_dialogflow.py
Normal file
71
zulip_bots/zulip_bots/bots/dialogflow/test_dialogflow.py
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
from zulip_bots.test_lib import StubBotTestCase, read_bot_fixture_data
|
||||||
|
|
||||||
|
from contextlib import contextmanager
|
||||||
|
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from typing import Any, ByteString
|
||||||
|
|
||||||
|
import json
|
||||||
|
|
||||||
|
class MockTextRequest():
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.session_id = ""
|
||||||
|
self.query = ""
|
||||||
|
self.response = ""
|
||||||
|
|
||||||
|
def getresponse(self) -> Any:
|
||||||
|
return MockHttplibRequest(self.response)
|
||||||
|
|
||||||
|
class MockHttplibRequest():
|
||||||
|
def __init__(self, response: str) -> None:
|
||||||
|
self.response = response
|
||||||
|
|
||||||
|
def read(self) -> ByteString:
|
||||||
|
return json.dumps(self.response).encode()
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def mock_dialogflow(test_name: str, bot_name: str) -> Any:
|
||||||
|
response_data = read_bot_fixture_data(bot_name, test_name)
|
||||||
|
df_request = response_data.get('request')
|
||||||
|
df_response = response_data.get('response')
|
||||||
|
|
||||||
|
with patch('apiai.ApiAI.text_request') as mock_text_request:
|
||||||
|
request = MockTextRequest()
|
||||||
|
request.response = df_response
|
||||||
|
mock_text_request.return_value = request
|
||||||
|
yield
|
||||||
|
|
||||||
|
class TestDialogFlowBot(StubBotTestCase):
|
||||||
|
bot_name = 'dialogflow'
|
||||||
|
|
||||||
|
def _test(self, test_name: str, message: str, response: str) -> None:
|
||||||
|
with self.mock_config_info({'key': 'abcdefg', 'bot_info': 'bot info foo bar'}), \
|
||||||
|
mock_dialogflow(test_name, 'dialogflow'):
|
||||||
|
self.verify_reply(message, response)
|
||||||
|
|
||||||
|
def test_normal(self) -> None:
|
||||||
|
self._test('test_normal', 'hello', 'how are you?')
|
||||||
|
|
||||||
|
def test_403(self) -> None:
|
||||||
|
self._test('test_403', 'hello', 'Error 403: Access Denied.')
|
||||||
|
|
||||||
|
def test_empty_response(self) -> None:
|
||||||
|
self._test('test_empty_response', 'hello', 'Error. No result.')
|
||||||
|
|
||||||
|
def test_exception(self) -> None:
|
||||||
|
with patch('logging.exception'):
|
||||||
|
self._test('test_exception', 'hello', 'Error. \'status\'.')
|
||||||
|
|
||||||
|
def test_help(self) -> None:
|
||||||
|
self._test('test_normal', 'help', 'bot info foo bar')
|
||||||
|
self._test('test_normal', '', 'bot info foo bar')
|
||||||
|
|
||||||
|
def test_alternate_response(self) -> None:
|
||||||
|
self._test('test_alternate_result', 'hello', 'alternate result')
|
||||||
|
|
||||||
|
def test_bot_responds_to_empty_message(self) -> None:
|
||||||
|
with self.mock_config_info({'key': 'abcdefg', 'bot_info': 'bot info foo bar'}):
|
||||||
|
pass
|
|
@ -114,6 +114,7 @@ class StubBotTestCase(TestCase):
|
||||||
display_recipient='foo_stream',
|
display_recipient='foo_stream',
|
||||||
sender_email='foo@example.com',
|
sender_email='foo@example.com',
|
||||||
sender_full_name='Foo Test User',
|
sender_full_name='Foo Test User',
|
||||||
|
sender_id='123',
|
||||||
content=content,
|
content=content,
|
||||||
)
|
)
|
||||||
return message
|
return message
|
||||||
|
|
Loading…
Reference in a new issue