interactive bots: Create DialogFlow bot.

This commit is contained in:
fredfishgames 2017-12-09 20:11:18 +00:00 committed by showell
parent 277b384379
commit 657c6d7b9c
13 changed files with 242 additions and 1 deletions

View file

@ -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.")

View file

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

View file

@ -0,0 +1,3 @@
[dialogflow]
key=YOUR_PUBLIC_API_KEY_HERE
bot_info=BOT_INFO_HERE

View 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

View 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.

View file

@ -0,0 +1,12 @@
{
"response": {
"status": {
"errorType": "fail",
"code": "403",
"errorDetails": "Access Denied"
}
},
"request": {
"query": "hello"
}
}

View file

@ -0,0 +1,20 @@
{
"response": {
"result": {
"fulfillment": {
"speech": ""
}
},
"alternateResult": {
"fulfillment": {
"speech": "alternate result"
}
},
"status": {
"errorType": "success"
}
},
"request": {
"query": "hello"
}
}

View file

@ -0,0 +1,20 @@
{
"response": {
"result": {
"fulfillment": {
"speech": ""
}
},
"alternateResult": {
"fulfillment": {
"speech": ""
}
},
"status": {
"errorType": "success"
}
},
"request": {
"query": "hello"
}
}

View file

@ -0,0 +1,15 @@
{
"response": {
"foo": {
"fulfillment": {
"speech": "how are you?"
}
},
"bar": {
"errorType": "success"
}
},
"request": {
"query": "hello"
}
}

View file

@ -0,0 +1,15 @@
{
"response": {
"result": {
"fulfillment": {
"speech": "how are you?"
}
},
"status": {
"errorType": "success"
}
},
"request": {
"query": "hello"
}
}

View 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

View file

@ -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