NLU Practice - Denire/jaicf-template-for-jaicp-developers GitHub Wiki

Практическое задание для закрепления знаний по CAILA и JAICF

CAILA - мощный NLU движок с кучей возможностей. Для того, чтобы познакомиться с ним получше, предлагаем выполнить несколько заданий.

Простое - Подключите к проекту два Кайла-активатора

Необходимо:

  1. Создать два проекта в Админке
  2. Подключить к JAICF-боту два кайла-классификатора
  3. Сделать так, чтобы один классификатор занимался интентом покупки коз:
    • Пример запроса: Купить козу за 100 рублей.
    • Классификатор понимает сущность коза и количество денег.
  4. Второй классификатор занимался арендой коров:
    • Пример запроса: Я бы хотел взять в аренду корову в пятницу
    • Классификатор понимает сущность корова и время, на которое ее хотят взять в аренду.

Посложнее - CAILA Direct API Spec

Зная, как работать с CAILA напрямую, можно не ограничиваться тем, что предлагает вам платформа в своих встроенных методах.

Работать с кайлой можно из GUI, а можно с помощью REST-API-Интерфейса. Например, можно написать код, который будет ходить по HTTP для вызова conform - метода, схожего по функционалу с $nlp.conform. Вот пример кода (все необходимые зависимости есть в текущем темплейте):

class CailaAPIClient(
    val accessToken: String,
    val baseUrl: String = "https://app.jaicp.com/cailapub/api/caila/p",
) : WithLogger {

    fun conform(text: String, number: Int): String = runBlocking {
        httpClient.get("$baseUrl/$accessToken/nlu/conform") {
            parameter("text", text)
            parameter("number", number)
            header("accept", "text/plain")
        }
    }
}

Во время встреч в зуме был задан вопрос - а есть ли в JAICF метод $nlp.inflect. Ответ - в JAICF его сейчас нет, возможно, он появится позже.

Ваша задача - написать $nlp.inflect. Ожидается, что это займет не больше 20 строк кода, если делать по аналогии с confirm. Спецификация кайлы здесь.

Самое сложное - Пишем CailaEntityActivator

JAICF может работать с любой NLU системой. К некоторым (CAILA/RASA/Lex) мы предосталяем готовые клиенты. Для некоторых, если это необходимо Вам, активатор напишете Вы сами.

JAICF - фреймворк, а не библиотека. Вы можете кастомизировать его, встравать свою логику в любое место выполнения запроса.

Процесс написания активатора состоит из нескольких составляющих:

  1. Пишем класс, который имплементирует интерфейс Activator
  2. Пишем к нему companion object, имплементирующий интерфейс ActivatorFactory, чтобы актиавтор с моделью сценария мог создавать BotEngine.
  3. Пишем класс, который наследуется от ActivatorContext. Этот класс передается в сценарий. Посмотрим на CailaIntentActivatorContext:
    • В самом обьекте мы говорим о полях и классах, которые будут доступны из сценария
    • Мы добавляем extension-функцию, чтобы в сценарии можно было написать activator.caila?.slots и получить найденные слоты. Без нее пользователю пришлось бы писать что-то в стиле (activator as CailaIntentActivatorContext).slots.
data class CailaIntentActivatorContext(
    val result: CailaAnalyzeResponseData,
    val intentData: CailaInferenceResultData
) : IntentActivatorContext(
    intent = intentData.intent.name,
    confidence = intentData.confidence.toFloat()
), java.io.Serializable {

    val topIntent = intentData.intent

    var slots = intentData.slots?.map { it.name to it.value }?.toMap() ?: emptyMap()

    val entities = result.entitiesLookup.entities

    companion object {
        private const val serialVersionUID = 4934755046273038374L
    }
}

val ActivatorContext.caila
    get() = this as? CailaIntentActivatorContext

В качестве референса вы можете смотреть на класс CailaIntentActivator.

На момент написания статьи разработчки JAICF не успели воплотить свои идеи по кастомизации активаторов. Будь наша задача выполнена - задча бы решалась значительно проще. Однако, это не повод не воплотить функционал, аналогичный функционалу JAICP DSL с именовванными паттернами.

Описание задачи

Вот недописанная реализация CailaEntityActivator. Ваша задача - дописать его и использовать в вашем проекте.

class CailaEntityActivator(token: String, model: ScenarioModel) : BaseActivator(model) {
    override val name: String = "cailaEntityActivator"
    private val client = CailaKtorClient(token, inferenceNBest = 5, url = "$DEFAULT_CAILA_URL/api/caila/p")

    override fun provideRuleMatcher(botContext: BotContext, request: BotRequest): ActivationRuleMatcher {
        val entities: List<CailaEntityMarkupData> = detectEntities(request)
        return ruleMatcher<CailaEntityActivatorRule> { rule ->
            TODO()
        }
    }

    private fun detectEntities(request: BotRequest): List<CailaEntityMarkupData> = TODO()

    override fun canHandle(request: BotRequest) = request.hasQuery()

    class Factory(private val token: String) : ActivatorFactory {
        override fun create(model: ScenarioModel): Activator = TODO()
    }
}

class CailaEntityActivatorRule(val entityName: String) : ActivationRule

class CailaEntityActivatorContext(
    val entity: CailaEntityMarkupData,
    override val confidence: Float = 1.0F,
) : ActivatorContext

fun ActivationRulesBuilder.entity(entityName: String) = TODO()

val ActivatorContext.entity get() = TODO()