Собственно, после одного из недавних постов @IBM возникла идея скрестить ежа с ужом Dialog с Natural Language Classifier. Причём тут Токио? А при наличии возможности определить его как сущность типа «город» в dialog и сохранить в профиле для обработки. Впрочем, именно получения погоды под катом не будет. Однако, по идее, можно прицепить обработку соответствующей «команды».
Перед началом работы понадобится зарегистрироваться в Bluemix, создать приложение и получить учётные данные для Dialog и Natural Language Classifer. Само же приложение может быть локальным.
Код полученной библиотеки (на Kotlin, должно легко сочетаться с Java и, вероятно, другими языками на платформе JVM) — тут. Бинарная же сборка — тут (кстати, куда её лучше забросить? CVS всё же про исходники). Вкратце, основные моменты:
- Сценарий описывается XML-файлом (как «надмножество» Dialog-й разметки). Классы, соответственно, определены в нём же.
- После классификации наиболее подходящий класс будет передан перед пользовательским вводом в Dialog (например — «isn't it too cold in Moscow» — «temperature isn't it too cold in Moscow»). Есть возможность подмены «команды» в выводе результатом её выполнения. Например, [Text:$City] заменится на содержимое переменной City (впрочем, нужна реализация команды — стандартных нет).
Пример кода сценария показан далее. Обратите внимание — ссылки на классы в input/grammar оформлены как [имяКласса]:
<dialog xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="WatsonDialogDocument_1.0.xsd">
<flow>
<folder label="Main">
<output>
<prompt selectionType="RANDOM">
<item>Hello, I'm Watson-based chatbot and can tell you about weather.</item>
</prompt>
<goto ref="introSearch" />
</output>
<output>
<prompt selectionType="RANDOM">
<item>Goodbye.</item>
</prompt>
<getUserInput id="introSearch">
<search ref="introSearchFolder" />
<default>
<output>
<prompt selectionType="RANDOM">
<item>I don't understand you.</item>
</prompt>
</output>
</default>
</getUserInput>
</output>
</folder>
<folder label="Library">
<folder id="introSearchFolder" label="Live Content">
<input>
<grammar>
<item>[temperature] *</item>
</grammar>
<input>
<grammar>
<item>$ (City)={City}</item>
</grammar>
<output>
<prompt selectionType="SEQUENTIAL">
<item>I processed your request. Temperature in [Text:$City] is [Temperature:$City]</item>
</prompt>
</output>
</input>
<input>
<grammar>
<item>*</item>
</grammar>
<output>
<prompt selectionType="SEQUENTIAL">
<item>Send a city name.</item>
</prompt>
<getUserInput id="temperatureCitySelectionInput">
<search ref="temperatureCitySelectionSearch" />
<default>
<output>
<prompt selectionType="RANDOM">
<item>I can't process this request.</item>
</prompt>
</output>
</default>
</getUserInput>
</output>
</input>
</input>
<input>
<grammar>
<item>[conditions] *</item>
</grammar>
<input>
<grammar>
<item>$ (City)={City}</item>
</grammar>
<output>
<prompt selectionType="SEQUENTIAL">
<item>I processed your request. Conditions in [Text:$City] is [Conditions:$City]</item>
</prompt>
</output>
</input>
<input>
<grammar>
<item>*</item>
</grammar>
<output>
<prompt selectionType="SEQUENTIAL">
<item>Send a city name.</item>
</prompt>
<getUserInput id="conditionsCitySelectionInput">
<search ref="conditionsCitySelectionSearch" />
<default>
<output>
<prompt selectionType="RANDOM">
<item>I can't process your request</item>
</prompt>
</output>
</default>
</getUserInput>
</output>
</input>
</input>
</folder>
<folder id="temperatureCitySelectionSearch" label="Live Content">
<input>
<grammar>
<item>$ (City)={City}</item>
</grammar>
<output>
<prompt selectionType="SEQUENTIAL">
<item>I processed your request. Temperature in [Text:$City] is [Temperature:$City]</item>
</prompt>
</output>
</input>
</folder>
<folder id="conditionsCitySelectionSearch" label="Live Content">
<input>
<grammar>
<item>$ (City)={City}</item>
</grammar>
<output>
<prompt selectionType="SEQUENTIAL">
<item>I processed your request. Conditions in [Text:$City] is [Conditions:$City]</item>
</prompt>
</output>
</input>
</folder>
</folder>
<folder label="Global" />
<folder label="Concepts" />
</flow>
<entities>
<entity name="City">
<value name="Tokyo" value="Tokyo">
<grammar>
<item>Tokyo</item>
</grammar>
</value>
<value name="Ottawa" value="Ottawa">
<grammar>
<item>Ottawa</item>
</grammar>
</value>
<value name="Moscow" value="Moscow">
<grammar>
<item>Moscow</item>
</grammar>
</value>
</entity>
</entities>
<variables>
<var_folder name="Home">
<var name="City" type="TEXT" />
</var_folder>
</variables>
<settings />
<specialSettings />
<classes>
<class name="conditions">
<item>Is it windy?</item>
<item>Will it rain today?</item>
<item>What are the chances for rain?</item>
<item>Will we get snow?</item>
<item>Are we expecting sunny?</item>
<item>Is it overcast?</item>
<item>Will it be cloudy?</item>
<item>How much rain will fall today?</item>
<item>How much snow are we expecting?</item>
<item>Is it windy outside?</item>
<item>How much snow do we expect?</item>
<item>Is the forecast calling for snow today?</item>
<item>Will we see some sun?</item>
<item>When will the rain subside?</item>
<item>Is it cloudy?</item>
<item>Is it sunny now?</item>
<item>Will it rain?</item>
<item>Will we have much snow?</item>
<item>Are the winds dangerous?</item>
<item>What is the expected snowfall today?</item>
<item>Will it be dry?</item>
<item>Will it be breezy?</item>
<item>Will it be humid?</item>
<item>What is today's expected humidity?</item>
<item>Will the blizzard hit us?</item>
<item>Is it drizzling?</item>
</class>
<class name="temperature">
<item>How hot is it today?</item>
<item>Is it hot outside?</item>
<item>Will it be uncomfortably hot?</item>
<item>Will it be sweltering?</item>
<item>How cold is it today?</item>
<item>Is it cold outside?</item>
<item>Will it be uncomfortably cold?</item>
<item>Will it be frigid?</item>
<item>What is the expected high for today?</item>
<item>What is the expected temperature?</item>
<item>Will high temperatures be dangerous?</item>
<item>Is it dangerously cold?</item>
<item>When will the heat subside?</item>
<item>Is it hot?</item>
<item>Is it cold?</item>
<item>How cold is it now?</item>
<item>Will we have a cold day today?</item>
<item>When will the cold subside?</item>
<item>What highs are we expecting?</item>
<item>What lows are we expecting?</item>
<item>Is it warm?</item>
<item>Is it chilly?</item>
<item>What's the current temp in Celsius?</item>
<item>What is the temperature in Fahrenheit?</item>
</class>
</classes>
</dialog>
Как видно — в сценарии определены классы [temperature, conditions] и тип сущности City с возможными значениями [Tokyo, Ottava, Moscow].
Пример кода, который запустит обучение и выдаст «вводное» сообщение по его завершению:
package com.alex4321.botdemo
import com.alex4321.bot.*
fun main(args: Array<String>) {
if (args.size != 7 && args.size != 6) {
println("Need arguments [path, dialogUsername, dialogPassword, nlcUsername, nlcPassword, dialogID, nlcID] or " +
"[path, dialogUsername, dialogPassword, nlcUsername, nlcPassword, robotName]")
return
}
val path = args[0]
val dialogUsername = args[1]
val dialogPassword = args[2]
val nlcUsername = args[3]
val nlcPassword = args[4]
val robotName = if (args.size == 6) {
args[5]
} else {
""
}
val dialogId = if (args.size == 7) {
args[5]
} else {
""
}
val nlcId = if (args.size == 7) {
args[6]
} else {
""
}
val new = dialogId == "" && nlcId == ""
val code = WatsonProgramPath(path).read()
val program = WatsonNlcDialogProgram(code)
val auth = WatsonRobotAuth(dialogUsername, dialogPassword, nlcUsername, nlcPassword)
val robot = WatsonRobot(program, dialogId, nlcId, auth)
if (new) {
robot.train(robotName)
print("Dialog ID : ")
println(robot.dialogID)
print("NLC ID : ")
println(robot.nlcID)
println("Training")
while (! robot.available) {
Thread.sleep(10000)
}
}
println("Ready")
val conversation = robot.converse()
val intro =conversation.intro()
println(intro.response)
println(intro.profile)
}
Для начала работы нужно передать аргументы:
- path — путь к разметке сценария
- dialogUsername, dialogPassword, nlcUsername, nlcPassword — см. ваши данные для доступа в Bluemix
- robotName — уникальное наименование. Используется для именования диалогов и классификаторов
После запуска и обучения (не самый быстрый процесс, да) получим что-то типа:
Dialog ID : 93791233-53fb-42c7-8581-6bc2bb761e6a
NLC ID : 2374f9x68-nlc-7617
Training
Ready
[Hello, I'm Watson-based chatbot and can tell you about weather.]
{}
Добавим пользовательский ввод:
package com.alex4321.botdemo
import com.alex4321.bot.*
fun main(args: Array<String>) {
if (args.size != 7 && args.size != 6) {
println("Need arguments [path, dialogUsername, dialogPassword, nlcUsername, nlcPassword, dialogID, nlcID] or " +
"[path, dialogUsername, dialogPassword, nlcUsername, nlcPassword, robotName]")
return
}
val path = args[0]
val dialogUsername = args[1]
val dialogPassword = args[2]
val nlcUsername = args[3]
val nlcPassword = args[4]
val robotName = if (args.size == 6) {
args[5]
} else {
""
}
val dialogId = if (args.size == 7) {
args[5]
} else {
""
}
val nlcId = if (args.size == 7) {
args[6]
} else {
""
}
val new = dialogId == "" && nlcId == ""
val code = WatsonProgramPath(path).read()
val program = WatsonNlcDialogProgram(code)
val auth = WatsonRobotAuth(dialogUsername, dialogPassword, nlcUsername, nlcPassword)
val robot = WatsonRobot(program, dialogId, nlcId, auth)
if (new) {
robot.train(robotName)
print("Dialog ID : ")
println(robot.dialogID)
print("NLC ID : ")
println(robot.nlcID)
println("Training")
while (! robot.available) {
Thread.sleep(10000)
}
}
println("Ready")
val conversation = robot.converse()
val intro = conversation.intro()
println(intro.response)
println(intro.profile)
while(true) {
val input = readLine() as String
if (input == "exit") {
break
}
val answer = conversation.answer(input)
println(answer.response)
println(answer.profile)
}
}
В результате — что-то типа такого:
Ready
[Hello, I'm Watson-based chatbot and can tell you about weather.]
{}
Isn't it too cold in Moscow?
[I processed your request. Temperature in Text is Temperature, , Goodbye.]
{City=Moscow}
Или
Ready
[Hello, I'm Watson-based chatbot and can tell you about weather.]
{}
Isn't it cold now?
[Send a city name.]{}
Moscow
[I processed your request. Temperature in Text is Temperature.]
{City=Moscow}
Ну и собственно обработка команд. Обработчики имеют тип BiFunction<List, String, String>. На входе — полученный при разборе аргументов список и пользовательский ввод. На выходе — текст, полученный при обработке команды. Например:
package com.alex4321.botdemo
import com.alex4321.bot.*
import java.util.function.BiFunction
fun main(args: Array<String>) {
CommandHandler.register("Text", BiFunction { args, input -> args[0] })
if (args.size != 7 && args.size != 6) {
println("Need arguments [path, dialogUsername, dialogPassword, nlcUsername, nlcPassword, dialogID, nlcID] or " +
"[path, dialogUsername, dialogPassword, nlcUsername, nlcPassword, robotName]")
return
}
val path = args[0]
val dialogUsername = args[1]
val dialogPassword = args[2]
val nlcUsername = args[3]
val nlcPassword = args[4]
val robotName = if (args.size == 6) {
args[5]
} else {
""
}
val dialogId = if (args.size == 7) {
args[5]
} else {
""
}
val nlcId = if (args.size == 7) {
args[6]
} else {
""
}
val new = dialogId == "" && nlcId == ""
val code = WatsonProgramPath(path).read()
val program = WatsonNlcDialogProgram(code)
val auth = WatsonRobotAuth(dialogUsername, dialogPassword, nlcUsername, nlcPassword)
val robot = WatsonRobot(program, dialogId, nlcId, auth)
if (new) {
robot.train(robotName)
print("Dialog ID : ")
println(robot.dialogID)
print("NLC ID : ")
println(robot.nlcID)
println("Training")
while (! robot.available) {
Thread.sleep(10000)
}
}
println("Ready")
val conversation = robot.converse()
val intro = conversation.intro()
println(intro.response)
println(intro.profile)
while(true) {
val input = readLine() as String
if (input == "exit") {
break
}
val answer = conversation.answer(input)
println(answer.response)
println(answer.profile)
}
}
Теперь вывод выглядит так:
Ready
[Hello, I'm Watson-based chatbot and can tell you about weather.]
{}
Isn't it too cold in Moscow?
[I processed your request. Temperature in Moscow is Temperature, , Goodbye.]
{City=Moscow}
P.S.: и да, код явно не лучший, согласен. Жду критики.
Автор: alex4321