Skip to content

Commit

Permalink
Merge pull request #183 from salute-developers/statemachine
Browse files Browse the repository at this point in the history
Statemachine
  • Loading branch information
SyrexMinus authored Mar 11, 2024
2 parents c794057 + 82e7d50 commit 4563cc7
Show file tree
Hide file tree
Showing 300 changed files with 17,728 additions and 80 deletions.
96 changes: 96 additions & 0 deletions nlpf_statemachine/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
"""
# Пакет NLPF StateMachine.
*Фреймворк для удобного написания сценариев для голосовых ассистентов Салют на Python.*
В основе своей фреймворк имеет [SmartApp Framework](https://github.com/sberdevices/smart_app_framework),
он же **NLP Framework** или **NLPF**.
![sber](static/assistant.png){:.sber-icon style="width: 100px"}
## Основные понятия
### Canvas App
--- web-приложение с голосовым управлением.
([документация](https://developers.sber.ru/docs/ru/salute/basics/canvasapp),
[видео на youtube](https://www.youtube.com/watch?v=2_AbcyfzHTo&t=0s))
### Фронт
--- JS-вёрстка, которая крутится на клиенте. (девайс, мобильное приложение, ...)
#### Экран (или страница)
--- Раздел приложения, ограниченный конкретной логикой.
**Примеры экранов:**
* Промо-экран;
* Экран авторизации;
* Экран корзины;
* ...
### Запрос (message)
--- Сообщение, которое прилетает в нашу систему.
Все запросы/ответы описываются в виде [pydantic моделей](https://pydantic-docs.helpmanual.io/).
Подробнее все модели можно посмотреть в разделе `nlpf_statemachine.models`.
**Запросы могут быть разные:**
* Голосовой запрос от пользователя (Создаётся объект наследние `nlpf_statemachine.models.MessageToSkill`);
* ServerAction --- событие, прилетевшее с фронта
(Создаётся объект наследник `nlpf_statemachine.models.ServerActionMessage`);
* Интеграцонные сообщения --- события, прилетевшие от интеграций, памяти ассистента, сторонних систем
(наследник `nlpf_statemachine.models.IntegrationMessage`);
### Слот
--- Параметр в голосовом запросе. Это может быть город, время, любая другая структурированная информация, которую может
сказать пользователь.
### Форма
--- Набор слотов, извлечённых из запроса.
Подробнее про экшены можно посмотреть в разделе `nlpf_statemachine.kit.form`.
### Событие (event)
--- любое событие, которое произошло в системе, на что наше приложение может отреагировать. *Определяется из запроса!*
**Примеры событий**
* У пользователя есть потребность (интент), он выражает её вербально.
К нам прилетает запрос `nlpf_statemachine.models.MessageToSkill`.
Из него мы можем определить интент, который, в свою очередь, и является событием;
* Прилетел `nlpf_statemachine.models.ServerActionMessage` с фронта;
* В системе сработал таймер; (этот тоже `nlpf_statemachine.models.ServerActionMessage`)
* Произошёл таймаут --- прилетел запрос `nlpf_statemachine.models.LocalTimeout`.
* ...
Как правило событие можно описать конкретным ключом (строкой).
### Классификатор
--- объект, который может из запроса определить конкретное событие, которое произошло.
То есть вернуть его ключ.
Подробнее про классификаторы можно посмотреть в разделе `nlpf_statemachine.kit.classifier`.
### Экшен (Action)
--- обработчик конкертного запроса.
Подробнее про экшены можно посмотреть в разделе `nlpf_statemachine.kit.actions`.
### Сценарий (Scenario)
--- коллекция классификаторов, экшенов, форм.
Подробнее про экшены можно посмотреть в разделе `nlpf_statemachine.kit.scenario`.
### Контекстный менеджер.
--- объект, отвечающий за основную логику работы приложения.
Подробнее про экшены можно посмотреть в разделе `nlpf_statemachine.kit.context_manager`.
"""

__version__ = "0.0.1"

__all__ = [
__version__,
]
20 changes: 20 additions & 0 deletions nlpf_statemachine/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
"""
# Описание конфигов.
Конфиги можно настраивать в своём проекте через переменные окружения.
"""
from decouple import config

from nlpf_statemachine.models.enums import ResponseMessageName


class SMConfig:
default_integration_behaviour_id = config("DEFAULT_INTEGRATION_BEHAVIOR_ID", cast=str, default="nlpf_statemachine")
transaction_timeout = config("TRANSACTION_TIMEOUT", cast=int, default=10)
transaction_massage_name_finish_list = config(
"TRANSACTION_MESSAGE_NAME_FINISH_LIST",
cast=lambda scenarios: [
message_name.strip() for message_name in scenarios.split(",")
],
default=", ".join(list(ResponseMessageName)),
)
35 changes: 35 additions & 0 deletions nlpf_statemachine/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""
# Общие константы, удобные для использования.
1. Список всех ассистентов: AssistantJoy, AssistantAthena, AssistantSber.
"""

from nlpf_statemachine.models import AssistantAppeal, AssistantGender, AssistantId, AssistantName, Character

AssistantJoy = Character(
id=AssistantId.joy,
name=AssistantName.joy,
gender=AssistantGender.male,
appeal=AssistantAppeal.no_official,
)

AssistantAthena = Character(
id=AssistantId.athena,
name=AssistantName.athena,
gender=AssistantGender.female,
appeal=AssistantAppeal.official,
)

AssistantSber = Character(
id=AssistantId.sber,
name=AssistantName.sber,
gender=AssistantGender.male,
appeal=AssistantAppeal.official,
)

CONTEXT_MANAGER_ID = "StateMachineScenarios"
DEFAULT = "DEFAULT"
DEFAULT_ACTION = "DEFAULT_ACTION"
GLOBAL_NODE_NAME = "GLOBAL_NODE"
INTEGRATION_TIMEOUT = 5
STATE_MACHINE_REPOSITORY_NAME = "StateMachineScenarios"
3 changes: 3 additions & 0 deletions nlpf_statemachine/example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Тестовое приложение

Данный сценарий является примером по использованию NLPF StateMachine .
7 changes: 7 additions & 0 deletions nlpf_statemachine/example/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
"""
# Пример работы с проектом на NLPF StateMachine.
В данном разделе собраны примеры подходов по использованию пакета.
Коллекция будет постоянно пополняться.
"""
3 changes: 3 additions & 0 deletions nlpf_statemachine/example/app/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""
# Бизнес-логика всего приложения.
"""
49 changes: 49 additions & 0 deletions nlpf_statemachine/example/app/context_manager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"""
# Пример использования ContextManager.
"""
from core.logging.logger_utils import behaviour_log
from nlpf_statemachine.example.app.sc.example_1_static_storage import scenario as static_storage_scenario
from nlpf_statemachine.example.app.sc.example_2_integration import scenario as integration_scenario
from nlpf_statemachine.example.app.sc.example_3_server_action_and_commands import scenario as server_action_scenario
from nlpf_statemachine.example.app.sc.example_4_form_and_intersection_clf import scenario as form_filling_scenario
from nlpf_statemachine.example.app.sc.example_5_isolated_scenario import run_app_condition
from nlpf_statemachine.example.app.sc.example_5_isolated_scenario import scenario as run_app_scenario
from nlpf_statemachine.example.app.sc.example_6_pre_process import pre_process_condition
from nlpf_statemachine.example.app.sc.example_6_pre_process import scenario as pre_process_scenario
from nlpf_statemachine.example.app.sc.fallback import fallback
from nlpf_statemachine.example.app.sc.pre_post_process import post_process, pre_process
from nlpf_statemachine.kit import ContextManager, Form

behaviour_log("==== ИНИЦИАЛИЗАЦИЯ ContextManager ====")

# 1. Создаём инстанс ContextManager для нашего аппа.
context_manager = ContextManager()

# 2. Создаём глобальную форму для нашего аппа.
# Не обязательный шаг, но удобен для отдельных кейсов.
form = Form()
context_manager.add_form(form=form)

# 3. Добавление изолированных сценариев в ContextManager.
context_manager.add_isolated_scenario(
condition=run_app_condition,
scenario=run_app_scenario,
)
context_manager.add_isolated_scenario(
condition=pre_process_condition,
scenario=pre_process_scenario,
)

# 4. Добавление сценариев в ContextManager.
context_manager.add_scenario(scenario=static_storage_scenario)
context_manager.add_scenario(scenario=integration_scenario)
context_manager.add_scenario(scenario=server_action_scenario)
context_manager.add_scenario(scenario=form_filling_scenario)

# 4. Добавление Fallback Action.
context_manager.add_fallback_action(action=fallback)

# 5. Добавление Pre и Post процессов
context_manager.add_pre_process(process=pre_process)
context_manager.add_post_process(process=post_process)
behaviour_log("==== ИНИЦИАЛИЗАЦИЯ ContextManager ОКОНЧЕНА ====")
17 changes: 17 additions & 0 deletions nlpf_statemachine/example/app/model_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""
# Пример SmartAppResources.
Для работы проекта необходимо относледовать свой `SmartAppResources` от базового класса
`nlpf_statemachine.override.model_config.SMSmartAppResources`.
И указать в параметре CONTEXT_MANAGER ссылку на инстанс вашего ContextManager.
"""
from nlpf_statemachine.example.app.context_manager import context_manager
from nlpf_statemachine.override.model_config import SMSmartAppResources


class ExampleResources(SMSmartAppResources):
"""
# Переопределение SmartAppResources для проекта.
"""

CONTEXT_MANAGER = context_manager
5 changes: 5 additions & 0 deletions nlpf_statemachine/example/app/sc/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""
# Пример модуля со сценариями.
Сценарии - сердце нашего приложения. Все обработчики запросов, классификаторы и формы лежат именно в этом модуле.
"""
3 changes: 3 additions & 0 deletions nlpf_statemachine/example/app/sc/classifiers/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""
# Коллекция классификаторов для работы сценария.
"""
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""
# Пример простого классификатора (анализ запроса).
"""

from typing import Dict, Optional

from core.text_preprocessing.constants import NUM_TOKEN
from nlpf_statemachine.example.const import STATIC_PARAPHRASE
from nlpf_statemachine.kit import Classifier
from nlpf_statemachine.models import Context, MessageToSkill, SmartEnum


class Events(SmartEnum):
"""
# Список возможных событий.
События соответствуют конкретным кейсам в примере:
1. Фиксированный ответ на верхнем уровне в static-файле;
2. Ответ для соответствующего ассистента;
3. Ответ для любого ассистента;
4. Ответ с добавлением простой команды.
"""

SIMPLE_ASSISTANT_ANSWER = "SIMPLE_ASSISTANT_ANSWER"
CHOICE_ASSISTANT_ANSWER = "CHOICE_ASSISTANT_ANSWER"
ANY_ASSISTANT_ANSWER = "ANY_ASSISTANT_ANSWER"
ANSWER_WITH_COMMAND = "ANSWER_WITH_COMMAND"


class OriginalTextClassifier(Classifier):
"""
# Пример простого классификатора.
Данный классификатор проверяет наличие слова в запросе.
"""

def run(self, message: MessageToSkill, context: Context, form: Dict) -> Optional[str]:
"""
## Запуск классификации.
*Основной метод для переопределения*.
Args:
message (nlpf_statemachine.models.message.protocol.assistant_message.BaseMessage): Тело запроса.
context (nlpf_statemachine.models.context.Context): Контекст.
form (Dict[str, Any]): Форма
Returns:
str: результат классификации (event).
"""
if STATIC_PARAPHRASE in message.payload.message.original_text:
for token in message.payload.message.tokenized_elements_list:
if token.get("token_type") == NUM_TOKEN:
text = token.get("text")
if text == "1":
return Events.SIMPLE_ASSISTANT_ANSWER
elif text == "2":
return Events.CHOICE_ASSISTANT_ANSWER
elif text == "3":
return Events.ANY_ASSISTANT_ANSWER
elif text == "4":
return Events.ANSWER_WITH_COMMAND
38 changes: 38 additions & 0 deletions nlpf_statemachine/example/app/sc/commands.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
"""
# Генераторы команд.
"""
from typing import Dict, Union

from nlpf_statemachine.models import AssistantCommand, CanvasAppItem, SmartAppDataCommand
from .models.commands import CustomCommand


def generate_item(smart_app_data: Union[AssistantCommand, Dict]) -> CanvasAppItem:
"""
## Генерация item-команды для списка items в ответе.
Args:
smart_app_data (AssistantCommand): Команда для фронта;
Returns:
CanvasAppItem: Элемент списка items в ответе.
"""
return CanvasAppItem(
command=SmartAppDataCommand(
smart_app_data=smart_app_data,
),
)


def custom_command(field_1: str, field_2: int) -> CanvasAppItem:
"""
# Пример генерации кастомной команды c 2 полями.
Args:
field_1 (str): параметр 1 - строка;
field_2: параметр 2 - число;
Returns:
CanvasAppItem: Элемент списка items в ответе с данной командой.
"""
return generate_item(smart_app_data=CustomCommand(field_1=field_1, field_2=field_2))
3 changes: 3 additions & 0 deletions nlpf_statemachine/example/app/sc/enums/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
"""
# Коллекция SmartEnum-ов для работы сценария.
"""
13 changes: 13 additions & 0 deletions nlpf_statemachine/example/app/sc/enums/approve_values.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
"""
# Список возможных значений филлера из примера #4.
"""
from nlpf_statemachine.models import SmartEnum


class ApproveValues(SmartEnum):
"""
# Возможные значения слота ApproveFiller.
"""

AGREEMENT = "AGREEMENT"
REJECTION = "REJECTION"
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
"""Описание моделей для интеграций."""
from nlpf_statemachine.models import SmartEnum


class IntegrationRequestMessageName(SmartEnum):
"""Список наименований запросов в интеграцию."""

GENERATE_DATA = "GENERATE_DATA_REQUEST"


class IntegrationResponseMessageName(SmartEnum):
"""Список наименований ответов от интеграции."""

GENERATE_DATA = "GENERATE_DATA_RESPONSE"
Loading

0 comments on commit 4563cc7

Please sign in to comment.