Skip to content

Instantly share code, notes, and snippets.

@grepto
Last active March 16, 2022 11:46
Show Gist options
  • Save grepto/83c723a946a87cead07bbf9befbdd963 to your computer and use it in GitHub Desktop.
Save grepto/83c723a946a87cead07bbf9befbdd963 to your computer and use it in GitHub Desktop.

Использование библиотеки dialogflow для работы с второй версией API сервиса Dialogflow

Однажды мне нужно было написать telegram бота, который мог бы ответить на типовые вопросы. Для тренировки бота использовался Dialogflow. Помимо обучения через консоль, нужно было написать скрипт для дообучения бота из локального json-файла.

Disclaimer

В этом документе я собрал информацию о шишках, которые набил в процессе работы с второй версией API сервиса Dialogflow. Мои способы достижения цели могут показаться кому-то неканоничными, варварскими и вообще антипаттерном. Я пишу это как шпаргалку, когда будущему мне понадобится еще раз управлять настройками агента dialogflow через api

Про Dialogflow

Про библиотеку

Работа с python-dialogflow

Получить ответ на фразу или вопрос

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

В документации к библиотеке есть пример создания 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'...}

Получить intent

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

Удаляется 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
{
"Устройство на работу": {
"questions": [
"Как устроиться к вам на работу?",
"Как устроиться к вам?",
"Как работать у вас?",
"Хочу работать у вас",
"Хочу работать с вами",
"Возможно-ли устроиться к вам?",
"Можно-ли мне поработать у вас?",
"Хочу работать редактором у вас"
],
"answer": "Если вы хотите устроиться к нам, напишите на почту [email protected] мини-эссе о себе и прикрепите ваше портфолио."
},
"Забыл пароль": {
"questions": [
"Не помню пароль",
"Не могу войти",
"Проблемы со входом",
"Забыл пароль",
"Забыл логин",
"Восстановить пароль",
"Как восстановить пароль",
"Неправильный логин или пароль",
"Ошибка входа",
"Не могу войти в аккаунт"
],
"answer": "Если вы не можете войти на сайт, воспользуйтесь кнопкой «Забыли пароль?» под формой входа. Вам на почту прийдёт письмо с дальнейшими инструкциями. Проверьте папку «Спам», иногда письма попадают в неё."
},
"Удаление аккаунта": {
"questions": [
"Хочу удалить аккаунт",
"Удалить аккаунт",
"Как удалить аккаунт",
"Как удалить данные обо мне",
"Удалить мои статьи",
"Как снести свой аккаунт"
],
"answer": "Если вы хотите удалить аккаунт, это можно сделать в вашем профиле в разделе «Настройки». Пролистайте этот раздел до зоны, выделенной красным."
},
"Вопросы от забаненных": {
"questions": [
"Меня забанили",
"Я в бане",
"Разбаньте",
"Разбаньте моего друга",
"Разбаньте меня",
"Почему я забанен",
"За что меня забанили",
"Можно-ли купить разбан?",
"Хочу купить разбан",
"Мой друг ничего не сделал, а его забанили"
],
"answer": "Если вы забанены, вы нарушили правила нашего сообщества. При входе на сайт вы можете увидеть доказательства ваших нарушений и ссылку на нарушенное правило. Разбан не продаётся. Если вы ознакомились с правилами и доказательствами вашей вины и у вас всё ещё есть претензии — воспользуйтесь формой «Не виновен» под сообщением о бане."
},
"Вопросы от действующих партнёров": {
"questions": [
"Где проходит совещание",
"Когда переведёте деньги по контракту",
"Скоро переведу деньги по контракту",
"Задерживаемся на совещание",
"Высылаю итоги совещания",
"Когда подписываем контракт?",
"Контракт уже в силе?"
],
"answer": "Простите, в этом чате сидит SMM-отдел, мы не знаем ответа на этот вопрос. Обратитесь напрямую к сотруднику, с которым работаете."
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment