Однажды мне нужно было написать telegram бота, который мог бы ответить на типовые вопросы. Для тренировки бота использовался Dialogflow. Помимо обучения через консоль, нужно было написать скрипт для дообучения бота из локального json-файла.
В этом документе я собрал информацию о шишках, которые набил в процессе работы с второй версией API сервиса Dialogflow. Мои способы достижения цели могут показаться кому-то неканоничными, варварскими и вообще антипаттерном. Я пишу это как шпаргалку, когда будущему мне понадобится еще раз управлять настройками агента dialogflow через api
- Официальная документация
- Описание REST API (пригодится для подсматривания структуры моделей объектов и названия атрибутов)
- Quickstart по работе с диалогом через API
- Чат-бот понимающий человеческую речь на Dialogflow - статья на хабре
- Официальная документация
- Репозиторий на гитхабе
- Вопросы на Stackoverflow с тегами
dialogflow
иpython
import dialogflow_v2 as dialogflow
def get_dialog_response(session_id, text, language_code='ru', project_id=DIALOGFLOW_PROJECT_ID):
session_client = dialogflow.SessionsClient()
session = session_client.session_path(project_id, session_id)
text_input = dialogflow.types.TextInput(text=text, language_code=language_code)
query_input = dialogflow.types.QueryInput(text=text_input)
dialogflow_response = session_client.detect_intent(session=session, query_input=query_input)
response = {
'query_text': dialogflow_response.query_result.query_text,
'intent': dialogflow_response.query_result.intent.display_name,
'confidence': dialogflow_response.query_result.intent_detection_confidence,
'response_text': dialogflow_response.query_result.fulfillment_text,
}
return response
session_id = 1
text = 'Как восстановить пароль?'
print(get_dialog_response(session_id, text))
>>> {'query_text': 'Как восстановить пароль?', 'intent': 'Забыл пароль', 'confidence': 1.0, 'response_text': 'Если вы не можете войти на сайт, воспользуйтесь кнопкой «Забыли пароль?» под формой входа. Вам на почту прийдёт письмо с дальнейшими инструкциями. Проверьте папку «Спам», иногда письма попадают в неё.'}
В документации к библиотеке есть пример создания intent'а. Путем экспериментов и перелопачивания StackOverflow я написал более простую конструкцию. Правда, в training_phrases
и messages
нужно передавать специальным образом подготовленные списки.
В ответе возвращается созданный intent - объект класса google.cloud.dialogflow_v2.types.Intent
Для сериализации в словарь нужно использовать MessageToDict()
, а MessageToJson()
выдаст ответ в виде json'а. Подробнее в исходниках
import dialogflow_v2 as dialogflow
from google.protobuf.json_format import MessageToDict
def create_intent(name, training_phrases, messages, parameters=None, project_id=DIALOGFLOW_PROJECT_ID):
intents_client = dialogflow.IntentsClient()
parent = intents_client.project_agent_path(project_id)
intent = {
'display_name': name,
'training_phrases': training_phrases,
'parameters': parameters,
'messages': messages,
}
intent = intents_client.create_intent(parent, intent, intent_view=dialogflow.enums.IntentView.INTENT_VIEW_FULL)
return MessageToDict(intent, preserving_proto_field_name=True)
user_phrases = [
'Как устроиться к вам на работу?',
'Как устроиться к вам?',
'Как работать у вас?',
'Хочу работать у вас',
'Хочу работать с вами',
'Возможно-ли устроиться к вам?',
'Можно-ли мне поработать у вас?',
'Хочу работать редактором у вас',
]
bot_answers = [
'Если вы хотите устроиться к нам, напишите на почту [email protected] мини-эссе о себе и прикрепите ваше портфолио.',
'Для работы у нас, пришлите ваше портфолио и напишите мини-эссе о себе на почту [email protected]',
]
training_phrases = [{'type': 'EXAMPLE', 'parts': [{'text': phrase}]} for phrase in user_phrases]
messages = [{'text': {'text': [answer]}} for answer in bot_answers]
print(create_intent('Устроиться на работу', training_phrases, messages))
>>> {'name': 'projects/dvmn-gdrive/agent/intents/96f8c77e-7385-4ad9-b2c9-6ef821cfabac', 'display_name': 'Устроиться на работу', 'priority': 500000, 'training_phrases': [{'name': '1b7a0839-36a0-4dc5-9552-a744a338919a', 'type': 'EXAMPLE', 'parts': [{'text': 'Как устроиться к вам на работу?'}]}, {'name': '33f5a014-4ffc-484c-81fe-2a8f4a8781ad', 'type': 'EXAMPLE', 'parts': [{'text': 'Как устроиться к вам?'}]}, {'name': '6a121783-fdf8-4682-b911-0ba9821eca87'...}
import dialogflow_v2 as dialogflow
from google.protobuf.json_format import MessageToDict
def get_intent(name, project_id=DIALOGFLOW_PROJECT_ID):
client = dialogflow.IntentsClient()
parent = client.project_agent_path(project_id)
intents = client.list_intents(parent, intent_view=dialogflow.enums.IntentView.INTENT_VIEW_FULL)
try:
intent = [intent for intent in intents if intent.display_name == name][0]
except IndexError:
return None
return MessageToDict(intent, preserving_proto_field_name=True)
print(get_intent('Устроиться на работу')
>>> {'name': 'projects/dvmn-gdrive/agent/intents/96f8c77e-7385-4ad9-b2c9-6ef821cfabac', 'display_name': 'Устроиться на работу', 'priority': 500000, 'training_phrases': [{'name': '1b7a0839-36a0-4dc5-9552-a744a338919a', 'type': 'EXAMPLE', 'parts': [{'text': 'Как устроиться к вам на работу?'}]}, {'name': '33f5a014-4ffc-484c-81fe-2a8f4a8781ad', 'type': 'EXAMPLE', 'parts': [{'text': 'Как устроиться к вам?'}]}, {'name': '6a121783-fdf8-4682-b911-0ba9821eca87'...}
Удаляется intent по его id, который можно получить из содержимого удаляемого intent'а с помощью get_intent()
import dialogflow_v2 as dialogflow
def delete_intent(name, project_id=DIALOGFLOW_PROJECT_ID):
intent = get_intent(name, project_id)
intent_id = intent['name'].split('/')[-1]
intents_client = dialogflow.IntentsClient()
intent_path = intents_client.intent_path(project_id, intent_id)
intents_client.delete_intent(intent_path)
delete_intent('Устроиться на работу')
>>> Process finished with exit code 0
Собственно, процесс переобучения бота строится на комибинации функций get_intent()
, delete_intent()
и create_intent()
.
Скрипту скармливается json файл содержащий тренировочный сет для обучения бота. Если в сет добавится новый вопрос, скрипт просто создаст новый intent с фразами и ответом из нового блока сета. Если в существующем сете добавляются вопросы или ответы, скрипт объединяет их и создает новый intent. Параметр is_rewrite_answers
определяет нужно ли перезаписывать ответ из файла или оставить тот что сейчас используется в intent'е.
import json
import logging
import dialogflow_v2 as dialogflow
def train_bot(training_set_file, is_rewrite_answers=False):
logger.info('Training bot process has been started')
with open(training_set_file, 'r') as training_set_file:
training_set = json.load(training_set_file)
for intent_name, intent_training_set in training_set.items():
training_phrases = []
messages = []
parameters = []
logger.info(f'Starting training for set {intent_name}')
intent = get_intent(intent_name)
if intent:
training_phrases.extend(intent.get('training_phrases', []))
messages = intent.get('messages', [])
parameters = intent.get('parameters', [])
delete_intent(intent_name)
training_set_questions = [{'type': 'EXAMPLE', 'parts': [{'text': question}]} for question in
intent_training_set['questions']]
training_phrases.extend(training_set_questions)
training_set_answer = intent_training_set['answer']
if not messages or is_rewrite_answers:
messages = [{'text': {'text': [training_set_answer]}}]
create_intent(intent_name, training_phrases, messages, parameters)
logger.info(f'Bot has been trained on set {intent_name}')
logger.info('Training bot process has been finished')
train_bot(QUESTIONS_FILE, True)
>>> 2019-07-24 10:29:08,120 - train_bot - INFO - Training bot process has been started
>>> 2019-07-24 10:29:08,121 - train_bot - INFO - Starting training for set Устройство на работу
>>> 2019-07-24 10:29:11,763 - train_bot - INFO - Bot has been trained on set Устройство на работу
>>> 2019-07-24 10:29:11,763 - train_bot - INFO - Starting training for set Забыл пароль
>>> 2019-07-24 10:29:15,082 - train_bot - INFO - Bot has been trained on set Забыл пароль
>>> 2019-07-24 10:29:15,082 - train_bot - INFO - Starting training for set Удаление аккаунта
>>> 2019-07-24 10:29:18,415 - train_bot - INFO - Bot has been trained on set Удаление аккаунта
>>> 2019-07-24 10:29:18,415 - train_bot - INFO - Starting training for set Вопросы от забаненных
>>> 2019-07-24 10:29:21,775 - train_bot - INFO - Bot has been trained on set Вопросы от забаненных
>>> 2019-07-24 10:29:21,775 - train_bot - INFO - Starting training for set Вопросы от действующих партнёров
>>> 2019-07-24 10:29:25,407 - train_bot - INFO - Bot has been trained on set Вопросы от действующих партнёров
>>> 2019-07-24 10:29:25,407 - train_bot - INFO - Training bot process has been finished