Alice on Kotlin:将代码转换为Yandex.Station



6月,Yandex在语音技能开发人员中组织了一次在线黑客马拉松Just AI上的我们只是在Kotlin上更新了我们的开源框架,以支持新的酷炫Alice功能。并且有必要为自述文件提供一些简单的示例...



关于Kotlin上的几百行代码如何变成Yandex.Station



爱丽丝+科特林= JAICF



Just AI具有用于开发语音应用程序和文本聊天机器人JAICF的开源和完全免费的框架。它是用Kotlin编写的,这是JetBrains的一种编程语言,所有编写血腥企业(或者从Java重写企业)的android和服务器都众所周知。该框架旨在促进为各种语音,文本甚至电话助手创建精确的对话应用程序。



Yandex拥有Alice,它是语音助手,具有令人愉悦的语音和面向第三方开发人员的开放API。也就是说,任何开发人员都可以为数百万用户扩展Alice的功能,甚至可以从Yandex那里获得收益



我们当然与Alice成为JAICF的正式朋友,因此,您现在可以在Kotlin中编写技能。这就是它的样子。



脚本-> Webhook->对话





Alicia的任何技能都是用户与数字助理之间的语音对话。该对话在JAICF中以脚本的形式描述,然后在webhook服务器上运行,该服务器已在Yandex.Dialogues中注册。



情境



让我们来学习一下黑客马拉松的技巧。在商店购物时,这有助于节省金钱。首先,看看它是如何工作的。





在这里,您可以看到用户如何问爱丽丝- “告诉我什么更有利可图-这么多,这么多或那么多的卢布?”



爱丽丝立即启动我们的技能(因为它被称为“什么是更有利可图的”),并将所有必要的信息传递给它-用户的意图和请求中的数据



该技能反过来对意图做出反应,处理数据并返回有用的响应。爱丽丝说出答案并关闭,因为该技能结束了会议(他们称此为“一次通过技能”)。



这是一个简单的场景,但是,您可以通过它快速计算出一种产品比另一种产品更有利可图。同时赢得Yandex的演讲专栏。




Kotlin看起来像什么?
object MainScenario: Scenario() {
    init {
        state("profit") {
            activators {
                intent("CALCULATE.PROFIT")
            }

            action {
                activator.alice?.run {
                    val a1 = slots["first_amount"]
                    val a2 = slots["second_amount"]
                    val p1 = slots["first_price"]
                    val p2 = slots["second_price"]
                    val u1 = slots["first_unit"]
                    val u2 = slots["second_unit"] ?: firstUnit

                    context.session["first"] = Product(a1?.value?.double ?: 1.0, p1!!.value.int, u1!!.value.content)
                    context.session["second"] = p2?.let {
                        Product(a2?.value?.double ?: 1.0, p2.value.int, u2!!.value.content)
                    }

                    reactions.go("calculate")
                }
            }

            state("calculate") {
                action {
                    val first = context.session["first"] as? Product
                    val second = context.session["second"] as? Product

                    if (second == null) {
                        reactions.say("   ?")
                    } else {
                        val profit = try {
                            ProfitCalculator.calculateProfit(first!!, second)
                        } catch (e: Exception) {
                            reactions.say("   , .   .")
                            return@action
                        }

                        if (profit == null || profit.percent == 0) {
                            reactions.say("     .")
                        } else {
                            val variant = when {
                                profit.product === first -> ""
                                else -> ""
                            }

                            var reply = "$variant   "

                            reply += when {
                                profit.percent < 10 -> "   ${profit.percent}%."
                                profit.percent < 100 -> " ${profit.percent}%."
                                else -> "  ${profit.percent}%."
                            }

                            context.client["last_reply"] = reply
                            reactions.say(reply)
                            reactions.alice?.endSession()
                        }
                    }
                }
            }

            state("second") {
                activators {
                    intent("SECOND.PRODUCT")
                }

                action {
                    activator.alice?.run {
                        val a2 = slots["second_amount"]
                        val p2 = slots["second_price"]
                        val u2 = slots["second_unit"]

                        val first = context.session["first"] as Product
                        context.session["second"] = Product(
                            a2?.value?.double ?: 1.0,
                            p2!!.value.int,
                            u2?.value?.content ?: first.unit
                        )

                        reactions.go("../calculate")
                    }
                }
            }
        }

        fallback {
            reactions.say(",   . " +
                    "  :  , 2   230   3   400.")
        }
    }
}




完整脚本可在Github上获得



如您所见,这是一个常规对象,它扩展了JAICF库中的Scenario类。基本上,脚本是状态机,其中每个节点都是会话的可能状态。因为对话的上下文是任何语音应用程序中非常重要的组成部分,所以这就是我们在上下文中实现工作的方式。



假设根据对话的上下文,同一短语的解释可能有所不同。顺便说一下,这就是我们选择Kotlin作为我们的框架的原因之一-它允许您创建简单的DSL,在其中可以方便地管理此类嵌套上下文和它们之间的过渡。



状态被激活激活器(例如,一个intent)并执行嵌套的代码块-action在操作内部,您可以做任何想做的事情,但是最主要的是向用户返回一些有用的答案或询问某些事情。这是通过反应来完成的单击链接以找到每个实体的详细描述。



意向和广告位







目的用户请求语言无关表示。实际上,这是用户希望从您的会话应用程序中获取的内容的标识符。如果您先描述一种特殊的语法,那么



爱丽丝最近学会了如何自动定义技能的意图。此外,她知道如何从形式短语提取必要的数据插槽-例如,价格和货物的体积,在我们的例子。



要使其全部工作,您需要描述此语法slot这是我们技能的语法这些是空位我们在其中使用它。这使我们的技能不仅可以在入口处接收俄语的用户请求行,而且还可以接收已经独立于语言的标识符和转换后的广告位(每种产品的价格及其数量)。



JAICF当然支持任何其他NLU引擎(例如CailaDialogflow),但是在我们的示例中,我们希望使用此特定的Alice功能来展示其工作原理。



Webhook



好的,我们有脚本。我们如何检查它是否有效?



当然,测试驱动开发方法的拥护者会喜欢JAICF中针对交互式脚本内置自动测试机制的存在,由于我们从事大型项目,因此我们个人经常使用该机制,因为我们很难手动检查所有更改。但是我们的示例很小,因此我们最好立即启动服务器,并尝试与Alice交谈。



要运行脚本,您需要一个Webhook-一个服务器,当用户开始与您的技能交谈时,该服务器接受Yandex的传入请求。服务器一点也不难启动-您只需要配置bot并在其上挂一些端点即可。



val skill = BotEngine(
    model = MainScenario.model,
    activators = arrayOf(
        AliceIntentActivator,
        BaseEventActivator,
        CatchAllActivator
    )
)


这是机器人的配置方式-我们在这里描述其中使用了哪些脚本,在何处存储用户数据以及该脚本起作用所需的激活器(可能有多个)。



fun main() {
    embeddedServer(Netty, System.getenv("PORT")?.toInt() ?: 8080) {
        routing {
            httpBotRouting("/" to AliceChannel(skill, useDataStorage = true))
        }
    }.start(wait = true)
}


但这就是带有Webhook的服务器如何启动的方式-您只需要指定端点应在哪个通道上工作即可。我们在这里运行JetBrains Ktor服务器,但是您可以在JAICF中使用任何其他服务器



在这里,我们使用了Alice的另一个功能-将用户数据存储在她的内部数据库中useDataStorage选项)。JAICF将自动从那里保存和恢复上下文以及脚本编写的所有内容。序列化是透明的。



对话



终于我们可以测试全部了!该服务器在本地运行,因此我们需要一个临时的公共URL,以便来自Alice的请求可以从Internet到达我们的Webhook。为此,使用免费的ngrok工具非常方便,只需在终端中运行命令即可,例如“ngrok http 8080







所有请求”将实时到达您的PC上-因此您可以调试和编辑代码。



现在,您可以使用接收到的https URL并在Yandex上创建新的Aliego对话框时指定它。对话。在这里,您还可以测试带有文本的对话框。但是,如果您想用声音与某个技能对话,那么爱丽丝现在可以迅速发布私人技能,在开发时仅向您提供。因此,无需经过Yandex的长时间审核,您就可以直接从Alice的应用程序或智能扬声器开始与您的技能进行交谈。







出版物



我们已经测试了所有内容,并准备向所有Alice用户发布该技能!为此,我们的webhook必须托管在具有恒定URL的公共服务器上的某个位置。原则上,JAICF上的应用程序可以在支持Java的任何地方运行(甚至在Android智能手机上)。



我们在Heroku上运行了示例我们刚刚创建了一个新应用程序,并注册了存储技能代码的Github存储库地址Heroku从源代码本身构建并运行一切。我们只需要在Yandex中注册生成的公共URL。对话,然后将其发送给所有人





本小教程紧随Yandex黑客马拉松的脚步,上面的场景“哪个更有利可图”赢得了三个Yandex.Stations之一!顺便说一下,在这里,您会看到它的样子



Kotlin上的JAICF框架帮助我快速实现和调试对话框脚本,而不必费心使用Alice的API,上下文和数据库,同时又不限制可能性(与相似的库通常如此)。



有用的链接



JAICF的完整文档在这里

说明爱丽丝创建它的技能是在这里

该技术本身的来源可以发现那里



如果你喜欢



就像Yandex的同事们所做的一样 随时为JAICF做出贡献,或在Github上留下一个星号 如果您有任何问题,我们都会在舒适的Slack中立即回答






All Articles