-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Данила Беляков
authored and
Данила Беляков
committed
Jul 30, 2024
1 parent
550642d
commit 4f8e86d
Showing
14 changed files
with
309 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
## Машина состояний | ||
|
||
> Finite-state machine (FSM) или finite-state automation (FSA), конечный автомат — это математическая модель вычислений. | ||
> | ||
> Это абстрактная машина, которая в любой момент времени может находиться ровно в одном из конечного числа состояний. Конечный автомат может переходить из одного состояния в другое в ответ на некоторые входные данные; переход из одного состояния в другое называется переходом. | ||
> | ||
> Конечный автомат определяется списком его состояний, его начальным состоянием и входными данными, которые запускают каждый переход. | ||
> | ||
> [Википедия](https://en.wikipedia.org/wiki/Finite-state_machine) | ||
### Проблема | ||
|
||
Не вся функциональность навыка может быть реализована в одном хэндлере. | ||
Если вам нужно получить некоторую информацию от пользователя в несколько шагов или нужно направить его в зависимости от ответа, то вам надо использовать FSM. | ||
|
||
### Решение | ||
|
||
Для начала определите возможные состояния в вашем навыке: | ||
```kotlin | ||
enum class InfoState { | ||
SET_NAME, | ||
SET_AGE, | ||
SET_INFO | ||
} | ||
``` | ||
|
||
Начальное Состояние | ||
Когда начинается новая сессия, установите начальное состояние: | ||
```kotlin | ||
message({ message.session.new }) { | ||
state.setState(InfoState.SET_NAME.name) | ||
response { | ||
text = "Добро пожаловать в навык, как вас зовут?" | ||
} | ||
} | ||
``` | ||
- `Условие`: Обрабатывается только при начале новой сессии. | ||
- `Действие`: Устанавливает состояние в `SET_NAME` и запрашивает у пользователя имя. | ||
|
||
После получения имени от пользователя, сохраняем его и переходим к следующему состоянию: | ||
```kotlin | ||
message({ state == InfoState.SET_NAME.name }) { | ||
val username = message.request.originalUtterance.toString() | ||
state.updateData("name" to username) | ||
state.setState(InfoState.SET_AGE.name) | ||
response { | ||
text = "Рад познакомиться $username, сколько вам лет?" | ||
} | ||
} | ||
``` | ||
- `Условие`: Обрабатывается, если текущее состояние `SET_NAME`. | ||
- `Действие`: Сохраняет имя в состоянии, устанавливает следующее состояние `SET_AGE`, и запрашивает возраст. | ||
|
||
После получения возраста от пользователя, сохраняем его и переходим к последнему состоянию: | ||
```kotlin | ||
message({ state == InfoState.SET_AGE.name }) { | ||
val age = message.request.originalUtterance.toString() | ||
state.updateData("age" to age) | ||
state.setState(InfoState.SET_INFO.name) | ||
response { | ||
text = "Супер, расскажите о себе" | ||
} | ||
} | ||
``` | ||
|
||
- `Условие`: Обрабатывается, если текущее состояние `SET_AGE`. | ||
- `Действие`: Сохраняет возраст в состоянии, устанавливает следующее состояние `SET_INFO`, и запрашивает дополнительную информацию. | ||
|
||
На заключительном этапе, после получения дополнительной информации, формируем окончательный ответ и завершаем сессию: | ||
```kotlin | ||
message({state == InfoState.SET_INFO.name}) { | ||
val info = message.request.originalUtterance.toString() | ||
val data = state.getData() | ||
state.clear() | ||
response { | ||
text = "Вот что мне удалось узнать\n\nИмя-${data["name"]}\nВозраст-${data["age"]}\nИнформация-$info" | ||
endSession = true | ||
} | ||
} | ||
``` | ||
|
||
- `Условие`: Обрабатывается, если текущее состояние `SET_INFO`. | ||
- `Действие`: Формирует текст ответа на основе собранной информации, очищает состояние и завершает сессию. | ||
|
||
> ⚠️ **Внимание** | ||
> Чтобы использовать хранилище на стороне Алисы, включите его в настройках навыка. | ||
> | ||
> ![storage](resources/storage.png) | ||
### Заключение | ||
Использование машины состояний позволяет структурировать взаимодействие с пользователем и управлять процессом сбора информации через последовательные шаги. Это особенно полезно для сложных сценариев, требующих многократного взаимодействия и сохранения состояния между шагами. | ||
|
||
### Примеры | ||
- [Info.kt](../examples/src/main/kotlin/com/github/examples/Info.kt) | ||
- [FsmForm.kt](../examples/src/main/kotlin/com/github/examples/FsmForm.kt) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
## Мидлвари | ||
|
||
Мидлварь - это код, который активируется при каждом событии, полученном от API Алисы. | ||
|
||
### Теория | ||
> Мидлвари — это повторно используемое программное обеспечение, которое использует шаблоны и платформы для устранения разрыва между функциональными требованиями приложений и базовыми операционными системами, стеками сетевых протоколов и базами данных | ||
#### Есть два типа Мидлвари | ||
|
||
1. Внешняя (outer) область - вызывается перед обработкой фильтров | ||
2. Внутренняя (inner) область - вызывается после обработки фильтров, но перед обработчиком | ||
|
||
> ❗ **Важно** | ||
> Мидлварь должен всегда возвращать `null` чтобы передать событие следующему мидлварю/хэндлеру. | ||
> Если вы хотите завершить обработку события, вы должны вернуть `MessageResponse` | ||
### Практика | ||
|
||
```kotlin | ||
fun main() { | ||
skill { | ||
id = "..." | ||
webServer = ktorWebServer { | ||
port = 8080 | ||
path = "/alice" | ||
} | ||
dispatch { | ||
outerMiddleware { | ||
if(message.session.user == null) { | ||
return@outerMiddleware response { | ||
text = "У вас нет аккаунта в Яндексе" | ||
} | ||
} | ||
null | ||
} | ||
} | ||
}.run() | ||
} | ||
``` | ||
|
||
В этом примере показано, как использовать внешнюю мидлварь, чтобы проверять, есть ли у пользователя аккаунт в Яндексе. | ||
Если аккаунта нет, мидлварь возвращает ответ с сообщением "У вас нет аккаунта в Яндексе". | ||
В противном случае, мидлварь возвращает null, позволяя передать событие следующему мидлварю или обработчику. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
## Начало | ||
|
||
Для того чтобы интерактивно работать с примерами фреймворка, необходимо создать тестовый навык в Алисе. | ||
|
||
### Создание навыка | ||
|
||
1. Зарегистрируйтесь на Яндексе или войдите в свою учетную запись. | ||
2. Перейдите в [панель разработчика](https://dialogs.yandex.ru/developer/). | ||
3. Нажмите на кнопку "Создать диалог". | ||
![create_dialogue](resources/create_dialogue.png) | ||
4. Выберите "Навык в Алисе". | ||
![type_dialogue](resources/type_dialogue.png) | ||
5. Заполните все необходимые поля и в поле Backend выберите Webhook URL. | ||
### Ngrok | ||
|
||
Для первого запуска необходимо подключить вебхук к Диалогам. Это можно сделать с помощью ngrok — сервиса, который позволяет сделать локальный порт доступным из интернета без настройки NAT, роутера, DDNS и других протоколов. Программа создает туннель между вашим компьютером и удалённым сервером, предоставляя доступ через уникальный домен. | ||
|
||
- [Установите](https://dashboard.ngrok.com/get-started/setup), разархивируйте и запустите ngrok. | ||
- Добавьте ваш [auth-token](https://dashboard.ngrok.com/get-started/your-authtoken) в консоль ngrok. | ||
- Запустите ngrok с командой: | ||
```shell | ||
ngrok http 8080 | ||
``` | ||
- Через несколько секунд появится длинная ссылка. Укажите её как URL вебхука и добавьте /alice в конце ссылки: | ||
|
||
> ⚠️ **Внимание** | ||
> Не выключайте ngrok, иначе туннель закроется. | ||
- ![ngrok](resources/ngrok_runnable.png) | ||
![webhook_url.png](resources/webhook_url.png) | ||
|
||
6. Сохраните изменения внизу страницы. | ||
|
||
### ID навыка | ||
Скоро вам понадобится ID навыка. Его можно найти на вкладке "Общие сведения" в панели разработчика. | ||
|
||
> ℹ️ **Примечание** | ||
> Для запуска в прод вам нужен свой домен и [SSL сертификат](https://wiki.yaboard.com/s/zc), об этом подробнее в [официальной документации](https://yandex.ru/dev/dialogs/alice/doc/deploy-overview.html). | ||
### [Первый навык](Первый_навык.md) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
## Первый навык | ||
|
||
Прежде чем приступить к разработке навыка для Алисы, важно ознакомиться с [официальной документацией](https://yandex.ru/dev/dialogs/alice/doc/) и понять принцип работы навыков. | ||
|
||
### Первые шаги | ||
Этот кодовый фрагмент иллюстрирует создание и запуск навыка с использованием библиотеки для работы с сервером на базе [Ktor](https://ktor.io). | ||
Навык обрабатывает сообщения и отвечает на них в зависимости от состояния сессии. | ||
|
||
```kotlin | ||
fun main() { | ||
val skill = skill { | ||
id = "" | ||
webServer = ktorWebServer { | ||
port = 8080 | ||
path = "/alice" | ||
} | ||
|
||
dispatch { | ||
message({ message.session.new }) { | ||
response { | ||
text = "Привет!" | ||
} | ||
} | ||
|
||
message { | ||
response { | ||
text = message.request.originalUtterance.toString() | ||
} | ||
} | ||
} | ||
} | ||
} | ||
``` | ||
|
||
- `id` - Уникальный идентификатор скилла. Оставьте пустым, если не требуется уникальный идентификатор. | ||
- `webServer` - Конфигурация веб-сервера с использованием Ktor. | ||
- `port` - Порт, на котором будет запущен сервер. В данном случае используется порт 8080. | ||
- `path` - Путь, по которому сервер будет доступен. В данном случае это /alice. | ||
|
||
#### Диспетчеризация сообщений | ||
```kotlin | ||
message({ message.session.new }) { | ||
response { | ||
text = "Привет!" | ||
} | ||
} | ||
``` | ||
Этот блок кода обрабатывает новые сессии. Если сессия новая `message.session.new`, то в ответ отправляется текст "Привет!". | ||
|
||
#### Обработка всех остальных сообщений | ||
```kotlin | ||
message { | ||
response { | ||
text = message.request.originalUtterance.toString() | ||
} | ||
} | ||
``` | ||
Этот блок кода обрабатывает все остальные сообщения. В ответ отправляется оригинальный текст запроса пользователя. | ||
|
||
#### Запускает сконфигурированный скилл. | ||
|
||
```kotlin | ||
skill.run() | ||
``` | ||
|
||
### [Мидлвари](Мидлвари.md) | ||
|
||
### Примеры | ||
- [Echo.kt](../examples/src/main/kotlin/com/github/examples/Echo.kt) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package com.github.examples | ||
|
||
import com.github.alice.ktx.dispatch | ||
import com.github.alice.ktx.handlers.message | ||
import com.github.alice.ktx.models.response.response | ||
import com.github.alice.ktx.server.impl.ktorWebServer | ||
import com.github.alice.ktx.skill | ||
|
||
private enum class InfoState { | ||
SET_NAME, | ||
SET_AGE, | ||
SET_INFO | ||
} | ||
|
||
fun main() { | ||
|
||
skill { | ||
id = "..." | ||
webServer = ktorWebServer { | ||
port = 8080 | ||
path = "/alice" | ||
} | ||
dispatch { | ||
message({ message.session.new }) { | ||
state.setState(InfoState.SET_NAME.name) | ||
response { | ||
text = "Добро пожаловать в навык, как вас зовут?" | ||
} | ||
} | ||
message({ state == InfoState.SET_NAME.name }) { | ||
val username = message.request.originalUtterance.toString() | ||
state.updateData("name" to username) | ||
state.setState(InfoState.SET_AGE.name) | ||
response { | ||
text = "Рад познакомиться $username, сколько вам лет?" | ||
} | ||
} | ||
message({ state == InfoState.SET_AGE.name }) { | ||
val age = message.request.originalUtterance.toString() | ||
state.updateData("age" to age) | ||
state.setState(InfoState.SET_INFO.name) | ||
response { | ||
text = "Супер, расскажите о себе" | ||
} | ||
} | ||
message({state == InfoState.SET_INFO.name}) { | ||
val info = message.request.originalUtterance.toString() | ||
val data = state.getData() | ||
state.clear() | ||
response { | ||
text = "Вот что мне удалось узнать\n\nИмя-${data["name"]}\nВозраст-${data["age"]}\nИнформация-$info" | ||
endSession = true | ||
} | ||
} | ||
} | ||
}.run() | ||
} |