Данная статья является продолжением заметки “Проектируем интенты с Apache NlpCraft” и содержит детальное описание возможностей языка определения интентов NlpCraft IDL, созданного для использования в NLP проектах основанных на системе Apache NlpCraft. Поддержка NlpCraft IDL добавлена в систему начиная с версии 0.7.5.
Новая версия декларативного языка определения интентов, получившая название NlpCraft IDL (NlpCraft Intents Definition Language), значительно упростила процесс работы с интентами в диалоговых и поисковых системах, построенных на базе проекта Apache NlpCraft и вместе с тем расширила возможности системы.
NlpCraft IDL - это декларативный язык, позволяющий создавать определения интентов для их последующего использования в моделях Apache NlpCraft.
Начнем с примеров, демонстрирующих общие возможности языка, приведем необходимые пояснения, а далее опишем конструкции языка чуть более формально.
Напомню, что в точке выбора интента, NLP системы как правило уже имеют на входе обработанный пользовательский запрос, включающий в себя все полученные при разборе запроса комбинации токенов и прочую сопутствующую информацию.
Примеры интентов, определенных с помощью NlpCraft IDL
intent=xa
flow="^(?:login)(^:logout)*$"
meta={'enabled': true}
term(a)={!(tok_id()) != "z"}[1,3]
term(b)={
meta_intent('enabled') == true &&
month() == 1
}
term(c)~{
@tokId = tok_id()
@usrTypes = meta_model('user_types')
(tokId == 'order' || tokId == 'order_cancel') &&
has_all(@usrTypes, list(1, 2, 3) &&
abs(meta_tok('order:size')) > 10
)
}
Пояснение:
Имя интента - “xa”.
Интент содержит три terms. Term - это элемент, определяющий правило, каждое из которых должно быть выполнено для срабатывания интента.
Правило первое (a) - разобранный запрос должен содержать от одного до трех токенов с идентификаторами отличными от “z”, без учетах данных из истории диалога (тип term =) .
Правило второе (b) - интент должен быть активен в момент срабатывания - флаг ”enabled” метаданных модели. Кроме того, такой интент может сработать лишь в январе - функция month().
Правило третье(c) - в запросе или в истории диалога (тип term ~) должен быть найден токен с идентификатором “order” или “order_cancel”. Также должны быть учтены ограничения, наложенные на значения метаданных модели и абсолютное значение размера ордера. В определении третьего правила используются переменные, о них мы поговорим чуть позже.
Flow. В данном разделе определено дополнительное правило, согласно которому для срабатывания интента необходимо, чтобы в рамках текущей сессии уже хотя бы один раз сработал интент с идентификатором “login”, и ни разу не срабатывал интент с идентификатором “logout”. Правило определено в виде регулярного выражения, основанного на идентификаторах предыдущих интентов пользовательской сессии. Ниже также будут рассмотрены другие способы создания подобных правил.
Meta. Для описываемого интента определен некий набор данных, в данном случае конфигурация, с помощью которой можно включить или выключить интент.
intent=xb
flow=/#flowModelMethod/
ordered=true
term(a)=/org.mypackage.MyClass#termMethod/?
fragment(frag)
Пояснение к следующему примеру:
Имя интента - “xb”.
Интент может содержать один term (”a”, опционально, согласно квантификатору “?“, детальные пояснения будут приведены ниже), определенный в коде - org.mypackage.MyClass#termMethod.
Fragment с идентификатором “frag” расширяет список terms интента, дополнительными, ранее определенными terms. Элемент “frag” должен быть определен выше в коде или доступен с помощью import.
Flow содержит условие, заданное в коде модели в методе flowModelMethod.
Больше примеров - здесь.
Разберем элементы языка более детально.
Хочу обратить ваше внимание на то, что данная статья является лишь кратким обзором возможностей NlpCraft IDL и не пытается быть исчерпывающим мануалом. Перед началом полноценной работы с системой рекомендуется изучить детальное описание синтаксиса и всех возможностей языка в соответствующих разделах документации.
Место определения интентов
Интенты, определяемые с помощью NlpCraft IDL могут быть объявлены непосредственно в файлах статического определения модели. Данный подход очень удобен для простых случаев. Пример доступен по ссылке.
Также интенты могут быть определены непосредственно в коде модели с помощью аннотаций. Пример по ссылке.
Кроме того, интенты могут быть определены в отдельных файлах. Модели при этом будет ссылаться на определение интентов согласно указанному пути к этим файлам или URL ресурсам с помощью элемента import. Данный подход удобен при работе с большими моделями, при редактировании определений которых может оказаться полезной подсветка синтаксиса и прочие возможности предоставляемые IDE (так, например, Intellij Idea обеспечивает подсветку ключевых слов, подсказки и проверку синтаксиса файлов произвольных типов согласно заданной конфигурации). Кроме того данный подход может быть полезен при работе специалистов, не имеющих возможности или желания редактировать код напрямую. Пример доступен по ссылкам: 1, 2.
Ключевые слова NlpCraft IDL
flow, fragment, import, intent, meta, ordered, term, true, false, null.
intent, flow, fragment, meta, ordered, term - составные части определения интента.
Ключевое слово fragment также используется для создания именованных списков terms, включаемых в определения интентов.
import - необходим при подключении внешних файлов для возможности использования определенных в них элементов fragment, intent или других import.
true, false, null - используются при работе со встроенными функциями, о которых мы поговорим чуть ниже.
Структура программы NlpCraft IDL
Программа содержит набор следующих необязательных элементов, расположенных в произвольном порядке:
import
fragment
intent
Отладка и запуск
Интент компилируется при запуске модели и может быть отлажен лишь в процессе отладки модели. Для задания сложных интентов рекомендуется создавать их в отдельных файлах и использовать возможности редактирования, предоставляемые IDE для обеспечения начальной валидации сложных конструкций. Элементы раздела import и fragment могут быть задействованы и проверены только совместно с использующим эти элементы интентом.
Структура определения импорта
Содержит ключевое слово “import” и имя файла или URL ресурса в круглых скобках.
Пример: import('http://mysite.com/nlp/idls/external.idl)
Структура определения фрагмента
Содержит ключевое слово “fragment” с именем и список terms. Terms в данном случае могут быть параметризованными.
Пример простого fragment:
fragment=buzz term~{tok_id() == 'x:alarm'}
Пример параметризованного fragment c аргументами ‘a’ и ‘b’:
fragment=p1
term={
meta_frag('a') &&
has_any(get(meta_frag('b'), 'Москва'), list(1, 2))
}
Ниже приведен пример использования данного fragment в интенте. Аргументам ‘a’ и ‘b’ ставятся в соответствие значения параметров.
intent=i1
fragment(p1, {'a': true, 'b': {'Москва': [1, 2, 3]}})
Структура определения интента
Имя интента
Обязательный элемент. Имя - это уникальный в рамках модели идентификатор, необходимый для создания в колбеках ссылок на интент. Ниже приведен пример использования ссылки на интент "timeIntent".
@NCIntentRef("timeIntent")
fun onTimeMatch(
ctx: NCIntentMatch,
@NCIntentTerm("t1") tok: NCToken
): NCResult { ... }
Набор terms
Обязательный элемент. Term - это основной элемент определения интента. В каждом term задается правило срабатывания интента на основе сконфигурированного условия, тела term. Составные части правил могут относиться к токену или опираться на какие-то иные факторы.
Как определятеся term:
Ключевое слово term. Обязательный элемент.
Имя в круглых скобках. Опционально. Служит для создания ссылок на найденный токен в аргументах колбека, смотри пример выше, токен “t1”.
Тип term. Обязательный элемент. Поддерживается два типа term:
“~“ - токен может быть получен из истории диалога или из текущего запроса.
“=“ - токен должен быть получен только из текущего запроса.
Пример:
term(nums)~{tok_id() == 'nlpcraft:num'}
Выбор типа term имеет смысл только для terms относящихся к токенам, в противном случае значение выставленного типа игнорируется.
Тело term. Обязательный элемент. Существуют два способа задания тела term: с помощью встроенных функций или с помощью программного кода.
Примеры:
term(nums)={tok_id() == 'nlpcraft:num'}
term(nums)~{true}
term~/org.mypackage.MyClass#termMethod/?
Обратите внимание на специальный синтаксис последнего term.
Квантификатор. Опционально. Поддерживаются следующие типы квантификаторов:
[M, N] - условие должно сработать от N до M раз.
* - условие должно сработать хотя бы один раз, эквивалентно [0, ?]
+ - условие должно сработать более одного раза, эквивалентно [1, ?]
? - условие должно сработать 0 или 1 раз, эквивалентно [0, 1]
Примеры:
term(nums)={tok_id() == 'nlpcraft:num'}[1,2]
- запрос должен содержать один или два токена с идентификатором “nlpcraft:num”.term(nums)={tok_id() == 'nlpcraft:num'}*
- запрос должен содержать один или более токенов с идентификатором “nlpcraft:num”.
Подробнее о встроенных функциях NlpCraft IDL.
Поддерживаемые в языке функции условно подразделяются на следующие типы:
Основанные на базовых свойствах токенов - идентификаторах, группах. Примеры: tok_id(), tok_groups(), tok_parent().
Основанные на NLP свойствах токенов - стеммах, леммах, частях речи, признаках стоп-слов. Примеры: tok_lemma(), tok_is_wordnet(), tok_swear().
Основанные на информации о том, как токен был обнаружен в пользовательском запросе - значениях синонимов и т.д. Примеры: tok_value(), tok_is_permutated(), tok_is_direct().
Основанные на данных о пользовательском запросе - времени запроса, типе user agent. Примеры: req_tstamp(), req_addr(), req_agent().
Основанные на различных метаданных - токенов, модели, запроса и т.д. Примеры: meta_model('my:prop'), meta_tok('nlpcraft:num:unit'), meta_user('my:prop').
Основанные на данных, предоставляемых NER провайдерами токенов. Пример, для “geo:city“ это может быть количество жителей или координаты, полученные из метаданных.
Основанные на данных пользователя системы и его компании - статусы, время регистрации. Примеры: user_admin(), comp_name(), user_signup_tstamp().
Основанные на системных данных - значениях переменных окружения, системном времени и т.д. Примеры: meta_sys('java.home'), now(), day_of_week().
Математические, текстовые функции, функции работы с коллекциями и т.д. Примеры: lowercase("TeXt"), abs(-1.5), distinct(list(1, 2, 2, 3, 1)).
Более детальная информация и описание каждой функции - по ссылке.
Тело term - это предикат, построенный на основе комбинации этих функций.
Для предотвращения необходимости повторных вычислений при работе с функциями, могут быть использованы локальные переменные, пример ниже:
term(t2)={
@a = meta_model('a')
@list = list(1, 2, 3, 4)
(@a == 42 || @a == 44) && has_all(@list, list(3, 2))
}
Локальные переменные определяются с помощью специального префикса @. Их использование помогает избежать повторных вычислений и сокращает запись.
Подразумевается, что существующего набора встроенных функций и средств NlpCraft IDL достаточно для определения большинства возможных term любого интента. Но при необходимости пользователь может создать свои собственные предикаты на java, scala, kotlin, groovy или другом java based языке и прописать их в теле term. То есть NlpCraft IDL может содержать ссылки на фрагменты кода, полностью написанного на других языках.
Пример: term(a)=/MyClass#myMethod/
На входе пользовательская функция получает аргумент, содержащий все имеющиеся данные о пользовательском запросе, а на выходе должна вернуть значение определенного типа.
Fragment
Fragment это поименованный набор terms, создаваемый для возможности повторного использования в разных интентах. Пример по ссылке.
Flow
Здесь мы определяем дополнительное правило срабатывания интента, опирающееся на данные по предыдущим срабатываниям в рамках сессии.
Данное правило может быть определено на основе regex, составленного на основе идентификаторов предыдущих сработавших интентов.
Пример такого определения: flow="^(?:login)(^:logout)*$"
Данное правило означает, что для срабатывания интента необходимо, чтобы в рамках текущей сессии уже было срабатывание интента с идентификатором “login”, и не было с идентификатором “logout”.
В случае необходимости задания более сложной логики, она также может быть вынесена в пользовательский код, написанный на Java-based языке, как и тело term.
Пример определения в интенте:
@NCIntent("intent=x
flow=/com.company.dialog.Flow#customFlow/
term~{tok_id() == 'some_id'}"
)
def onX(): NCResult = { .. }
Предикат, определенный в методе customFlow(), получает на входе список с информацией по всем интентам, ранее вызванным в рамках текущей сессии, и возвращает значение типа boolean.
Meta
Опциональный элемент. Набор данных для задания дополнительных условий срабатывания интента представленный в формате JSON.
Ordered flag
Опциональный элемент. По умолчанию false. Определяет правило - должны ли токены сработавших terms быть упорядочены в запросе.
Зачем вообще нужен отдельный язык
Еще раз обратите внимание на то, что вся логика создания интентов, определенная с помощью NlpCraft IDL, может быть написана на любом java based языке. Зачем тогда вообще нужен этот новый язык? Даже если его синтаксис краток, прост и понятен, все равно придется потратить какое-то время на его изучение.
Ниже приведем ряд аргументов в пользу использования NlpCraft IDL.
Краткость записи. Запись на специализированном DSL всегда короче записи той же логики на базовом языке. Для интентов с небанальными правилами это может быть важно.
В случае если NlpCraft IDL код был вынесен в отдельный файл, то редактирование IDL программы, например для изменения логики срабатывания интента, не влечет за собой необходимости пересобирать код модели и ее колбеков.
Разнесение логики написания колбеков и логики срабатывания интентов. Данными вопросами могут заниматься разные специалисты. В силу осознанной ограниченности языковых средств, DSL проще для изучения.
В настоящее время модели могут быть созданы на любом java based языке. В ближайших планах Apache NlpCraft расширение списка поддерживаемых языков. Использование NlpCraft IDL позволит сохранить один общий язык определения интентов для разных типов моделей в рамках одного проекта.
Заключение
Надеюсь вы смогли получить первое представление о возможностях языка определения интентов NlpCraft IDL и типах задач, которые можно решить с его помощью. Здесь вы найдете детальное описание языка и его возможностей. Дополнительные примеры моделей созданных на java, kotlin, groovy и scala, использующих для определения интентов NlpCraft IDL, доступны в гитхабе проекта.
aradzinski
Do you plan to have an English version of this article?