Im Juni veranstaltete Yandex einen Online-Hackathon unter Sprachentwicklern. Wir bei Just AI haben gerade unser Open Source Framework auf Kotlin aktualisiert , um neue coole Alice-Funktionen zu unterstützen. Und es war notwendig, ein einfaches Beispiel für die README-Datei zu finden ...
Wie aus ein paar hundert Codezeilen auf Kotlin Yandex.Station wurde
Alice + Kotlin = JAICF
Nur AI hat ein Open Source und ein völlig kostenloses Framework für die Entwicklung von Sprachanwendungen und Text-Chatbots - JAICF . Es ist in Kotlin geschrieben , einer Programmiersprache von JetBrains, die allen Androiden und Servern bekannt ist, die ein blutiges Unternehmen schreiben (oder es von Java aus neu schreiben). Das Framework soll die Erstellung präziser Konversationsanwendungen für verschiedene Sprach-, Text- und sogar Telefonassistenten erleichtern.
Yandex hat Alice, eine Sprachassistentin mit einer angenehmen Stimme und einer offenen API für Entwickler von Drittanbietern. Das heißt, jeder Entwickler kann die Funktionalität von Alice für Millionen von Benutzern erweitern und dafür sogar Geld von Yandex erhalten .
Wir natürlichJAICF hat sich offiziell mit Alice angefreundet , sodass Sie jetzt Fähigkeiten in Kotlin schreiben können. Und so sieht es aus.
Skript -> Webhook -> Dialog
Jede Alicia-Fähigkeit ist ein Sprachdialog zwischen einem Benutzer und einem digitalen Assistenten. Der Dialog wird in JAICF in Form von Skripten beschrieben, die dann auf dem in Yandex.Dialogues registrierten Webhook-Server ausgeführt werden.
Szenario
Nehmen wir eine Fähigkeit, die wir uns für einen Hackathon ausgedacht haben. Es hilft, beim Einkaufen in Geschäften Geld zu sparen. Sehen Sie zuerst, wie es funktioniert.
Hier können Sie sehen, wie der Benutzer Alice fragt: "Sagen Sie mir, was rentabler ist - so viele Rubel für so und so viel oder so viel dafür?"
Alice startet sofort unsere Fähigkeit (weil sie "Was ist rentabler" genannt wird) und überträgt alle erforderlichen Informationen darauf - die Absicht und die Daten des Benutzers aus seiner Anfrage .
Die Fertigkeit reagiert wiederum auf die Absicht, verarbeitet die Daten und gibt eine nützliche Antwort zurück. Alice sagt die Antwort und schaltet sich aus, weil die Fertigkeit die Sitzung beendet (sie nennen dies eine "One-Pass-Fertigkeit").
Hier ist ein so einfaches Szenario, mit dem Sie jedoch schnell berechnen können, wie viel ein Produkt rentabler ist als ein anderes. Nun, und gleichzeitig eine sprechende Kolumne von Yandex gewinnen.
Wie sieht es in Kotlin aus?
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.")
}
}
}
Das vollständige Skript ist auf Github verfügbar .
Wie Sie sehen können, ist dies ein reguläres Objekt, das die Scenario-Klasse aus der JAICF-Bibliothek erweitert. Grundsätzlich ist das Skript eine Zustandsmaschine, bei der jeder Knoten ein möglicher Zustand der Konversation ist. So implementieren wir die Arbeit mit dem Kontext, da der Kontext des Dialogs ein sehr wichtiger Bestandteil jeder Sprachanwendung ist.
Angenommen, derselbe Satz kann je nach Kontext des Dialogs unterschiedlich interpretiert werden. Dies ist übrigens einer der Gründe, warum wir Kotlin für unser Framework ausgewählt haben - es ermöglicht Ihnen, ein lakonisches DSL zu erstellen , in dem es bequem ist, solche verschachtelten Kontexte und Übergänge zwischen ihnen zu verwalten.
Der Status wird mit aktiviertAktivator (zum Beispiel eine Absicht ) und führt den verschachtelten Codeblock aus - die Aktion . Und innerhalb der Aktion können Sie tun, was Sie wollen, aber die Hauptsache ist, dem Benutzer eine nützliche Antwort zurückzugeben oder etwas abzufragen. Dies geschieht durch Reaktionen . Folgen Sie den Links, um eine detaillierte Beschreibung jeder dieser Entitäten zu erhalten.
Absichten und Slots
Eine Absicht ist eine sprachunabhängige Darstellung einer Benutzeranforderung. Tatsächlich ist dies die Kennung dessen, was der Benutzer von Ihrer Konversationsanwendung erhalten möchte.
Alice hat kürzlich gelernt, wie man Absichten für Ihre Fähigkeiten automatisch definiert, wenn Sie zuerst eine spezielle Grammatik beschreiben. Darüber hinaus weiß sie, wie man die notwendigen Daten aus der Phrase in Form von Slots extrahiert - zum Beispiel den Preis und das Volumen der Waren, wie in unserem Beispiel.
Damit alles funktioniert, müssen Sie diese Grammatik und die Slots beschreiben . Dies ist die Grammatik unserer Fähigkeiten, und dies sind die Slotswir benutzen es darin. Dies ermöglicht es unserem Können, am Eingang nicht nur eine Zeile einer Benutzeranfrage in russischer Sprache zu erhalten, sondern zusätzlich eine bereits sprachunabhängige Kennung und konvertierte Slots (den Preis jedes Produkts und sein Volumen).
JAICF unterstützt natürlich jede andere NLU-Engine (z. B. Caila oder Dialogflow ), aber in unserem Beispiel wollten wir diese spezielle Alice-Funktion verwenden, um zu zeigen, wie sie funktioniert.
Webhook
Okay, wir haben das Drehbuch. Wie überprüfen wir, ob es funktioniert?
Anhänger des testgetriebenen Entwicklungsansatzes werden natürlich den integrierten Mechanismus zum automatisierten Testen von Dialogskripten in JAICF zu schätzen wissen , den wir persönlich ständig verwenden, da wir große Projekte durchführen und es schwierig ist, alle Änderungen von Hand zu überprüfen. Da unser Beispiel jedoch recht klein ist, sollten wir den Server sofort starten und versuchen, mit Alice zu sprechen.
Zum Ausführen des Skripts benötigen Sie einen Webhook - einen Server, der eingehende Anforderungen von Yandex akzeptiert, wenn der Benutzer mit Ihren Fähigkeiten spricht. Der Server ist überhaupt nicht schwer zu starten - Sie müssen nur Ihren Bot konfigurieren und einen Endpunkt daran hängen.
val skill = BotEngine(
model = MainScenario.model,
activators = arrayOf(
AliceIntentActivator,
BaseEventActivator,
CatchAllActivator
)
)
So wird der Bot konfiguriert - hier beschreiben wir, welche Skripte darin verwendet werden, wo Benutzerdaten gespeichert werden und welche Aktivatoren wir benötigen, damit das Skript funktioniert (möglicherweise gibt es mehrere davon).
fun main() {
embeddedServer(Netty, System.getenv("PORT")?.toInt() ?: 8080) {
routing {
httpBotRouting("/" to AliceChannel(skill, useDataStorage = true))
}
}.start(wait = true)
}
Aber so startet ein Server mit einem Webhook einfach so - Sie müssen nur angeben, welcher Kanal an welchem Endpunkt funktionieren soll. Wir führen hier den JetBrains Ktor-Server aus, aber Sie können jeden anderen in JAICF verwenden .
Hier haben wir eine weitere Funktion von Alice verwendet - das Speichern von Benutzerdaten in ihrer internen Datenbank (Option useDataStorage ). JAICF speichert automatisch den Kontext von dort und alles, was unser Skript dort schreibt. Die Serialisierung ist transparent.
Dialog
Endlich können wir alles testen! Der Server wird lokal ausgeführt, daher benötigen wir eine temporäre öffentliche URL für Alices Anfragen, um unseren Webhook über das Internet zu erreichen. Zu diesem Zweck können Sie bequem das kostenlose Tool ngrok verwenden , indem Sie einfach einen Befehl im Terminal ausführen , z. B.
ngrok http 8080
Alle Anforderungen werden in Echtzeit auf Ihrem PC eingehen. So können Sie den Code debuggen und bearbeiten.
Jetzt können Sie die empfangene https-URL verwenden, wenn Sie einen neuen Aliego-Dialog auf Yandex erstellen. Dialoge . Dort können Sie den Dialog auch mit Text testen. Wenn Sie jedoch mit einer Stimme mit einer Fertigkeit sprechen möchten, kann Alice jetzt schnell private Fertigkeiten veröffentlichen, die zum Zeitpunkt der Entwicklung nur für Sie verfügbar sind. Ohne eine lange Moderation durch Yandex zu durchlaufen, können Sie bereits direkt aus Alices Anwendung oder über einen intelligenten Lautsprecher mit Ihren Fähigkeiten sprechen.
Veröffentlichung
Wir haben alles getestet und sind bereit, die Fähigkeit für alle Alice-Benutzer zu veröffentlichen! Dazu muss unser Webhook irgendwo auf einem öffentlichen Server mit einer konstanten URL gehostet werden. Grundsätzlich können Anwendungen auf JAICF überall dort ausgeführt werden, wo Java unterstützt wird (sogar auf einem Android-Smartphone).
Wir haben unser Beispiel auf Heroku ausgeführt . Wir haben gerade eine neue Anwendung erstellt und die Adresse unseres Github-Repositorys registriert, in dem der Skill-Code gespeichert ist. Heroku baut und betreibt alles von der Quelle selbst. Wir müssen nur die resultierende öffentliche URL im Yandex registrieren. Dialoge und senden Sie alles zur Moderation .
Gesamt
Dieses kleine Tutorial folgt den Spuren des Yandex-Hackathons , bei dem das obige Szenario „ Was rentabler ist “ eine von drei Yandex.Stations gewann! Hier können Sie übrigens sehen, wie es war .
Das JAICF-Framework auf Kotlin hat mir geholfen, das Dialogskript schnell zu implementieren und zu debuggen, ohne mich um die Arbeit mit Alices API, Kontexten und Datenbanken zu kümmern , ohne die Möglichkeiten einzuschränken (wie dies häufig bei ähnlichen Bibliotheken der Fall ist).
Nützliche Links
Das vollständige JAICF-Dokument finden Sie hier .
Anweisungen zum Erstellen von Fähigkeiten für Alice finden Sie hier .
Die Quelle der Fähigkeit selbst gefunden werden kann dort .
Und wenn es dir gefällt
Fühlen Sie sich frei, einen Beitrag zu JAICF zu leisten , wie es Kollegen von Yandex bereits tun , oder hinterlassen Sie einfach ein Sternchen auf Github .
Und wenn Sie Fragen haben, beantworten wir diese sofort in unserem gemütlichen Slack .