blob: ad223d92c517bfc9db6940238b3893cc39cde67f [file] [log] [blame]
---
active_crumb: Introduction To NLPCraft
layout: blog
blog_title: Quick Introduction to Apache NLPCraft
author_name: Nikita Ivanov
author_avatar: images/nivanov.png
author_twitter_id: c64hacker
publish_date: November 16, 2020
---
<!--
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<section>
<div class="bq info">
This blog is an English adaptation of Sergey Kamov's <a target=habr href="https://habr.com/ru/post/526950/">blog</a> written in Russian.
</div>
<p>
In this short article I would like to introduce Apache NLPCraft - an open source library for adding Natural
Language Interface to any application. It enables people to interact with your products using voice
or text. The goal of this project from its inception in 2017 was and still is
unambiguously straightforward - provide an efficient & highly productive API to develop advanced NLP-based
interfaces for modern applications.
</p>
{% include latest-ver-blog-warn.html %}
</section>
<section>
<h2 class="section-title">Overview <a href="#"><i class="top-link fas fa-fw fa-angle-double-up"></i></a></h2>
<p>
These are some of the key features that should give you a flavor of what NLPCraft is and what it can do:
</p>
<div class="container-fluid" style="padding: 0">
<div class="row">
<div class="col-6">
<b>Intent Definition Language</b><br/>
<p>
Advanced <a href="/intent-matching.html">Intent Definition Language</a> (IDL) coupled with deterministic intent matching
provide ease of use and unprecedented expressiveness for designing real-life, non-trivial intents.
</p>
</div>
<div class="col-6">
<b>Composable Named Entities</b><br/>
<p>
Easily compose, mix and match new named entities out of built-in or external ones, creating new
reusable named entity recognizers on the fly.
</p>
</div>
</div>
<div class="row">
<div class="col-6">
<b>Model-As-A-Code</b><br/>
<p>
Everything you do with NLPCraft is part of your source code. No more awkward web UIs
splitting your logic across different incompatible places. Model-as-a-code is built by
engineers, and it reflects how engineers work.
</p>
</div>
<div class="col-6">
<b>Java First</b><br/>
<p>
REST API and Java-based implementation natively supports the world's largest ecosystem of development tools,
multiple programming languages, frameworks and services.
</p>
</div>
</div>
<div class="row">
<div class="col-6">
<b>Any Data Source</b><br/>
<p>
NLPCraft can work with any data source, device, or service - public or private. From databases and SaaS systems, to smart home devices, voice assistants and chatbots.
</p>
</div>
<div class="col-6">
<b>Short-Term-Memory</b><br/>
<p>
Advanced out-of-the-box support for maintaining and managing conversational context that is fully integrated with intent matching.
</p>
</div>
</div>
<div class="row">
<div class="col-6">
<b>Out-Of-The-Box Integration</b><br/>
<p>
NLPCraft natively integrates with 3rd party libraries for basic NLP processing and named entity recognition:
</p>
<div style="display: inline-block; margin-bottom: 20px">
<a style="margin-right: 10px" target=_ href="https://opennlp.apache.org"><img src="/images/opennlp-logo-h32.png" alt=""></a>
<a style="margin-right: 10px" target=_ href="https://cloud.google.com/natural-language/"><img src="/images/google-cloud-logo-small-h32.png" alt=""></a>
<a style="margin-right: 10px" target=_ href="https://stanfordnlp.github.io/CoreNLP"><img src="/images/corenlp-logo-h48.png" alt=""></a>
<a style="margin-right: 10px" target=_ href="https://spacy.io"><img src="/images/spacy-logo-h32.png" alt=""></a>
</div>
</div>
<div class="col-6">
</div>
</div>
</div>
</section>
<section>
<h2 class="section-title">Terminology <a href="#"><i class="top-link fas fa-fw fa-angle-double-up"></i></a></h2>
<p>
There are two terms that are important to define before we proceed:
</p>
<ul>
<li>
<b>Named Entity (NE)</b> - typically a real world object or entity that can be recognized in the input
text (see <a target=wikipedia href="https://www.wikiwand.com/en/Named_entity">https://www.wikiwand.com/en/Named_entity</a>
for details). Note that NEs can be universal, like country to city name, or domain specific for each model.
</li>
<li>
<b>Intent</b> - a template made out of NEs and a callback function to call when such template is found in the input text.
</li>
</ul>
</section>
<section>
<h2 class="section-title">Runtime Components <a href="#"><i class="top-link fas fa-fw fa-angle-double-up"></i></a></h2>
<p>
When working with NLPCraft you will be dealing with these three main components:
</p>
<ul>
<li>
<b>Data Model</b><br/>
Data Model encapsulates the NLPCraft’s model-as-a-code approach where the data model is simply an
implementation of NCDataModel interface. Typically, static model configuration and NE definitions are
abstracted out to an external YAML or JSON file.
</li>
<li>
<b>Data Probe</b><br/>
Data probe is a light-weight container that securely deploys and manages data models. You can have
multiple data probes and each data probe can host multiple models.
</li>
<li>
<b>REST Server</b><br/>
REST server provides URL endpoint for user applications to securely query data sources using natural
language. REST server routes such requests to a data probe that hosts the requested data model.
</li>
</ul>
<figure>
<img class="img-fluid" src="/images/homepage-fig1.1.png" alt="">
<figcaption><b>Fig 1.</b> NLPCraft Runtime Components</figcaption>
</figure>
</section>
<section>
<h2 class="section-title">Smart Home Example <a href="#"><i class="top-link fas fa-fw fa-angle-double-up"></i></a></h2>
<p>
Let’s see how NLPCraft works using a prototypical smart home NLP-based light switch as an example. We would
like to be able to say things like <code>"Turn off all the light in the house"</code> or
<code>"Light up the garage, please!"</code> to control our lights. Note that NLPCraft does not deal with
voice-to-text conversion - so this example deals only with the text regardless of its origin.
</p>
<p>
To accomplish that we need to first develop a data model that involves the following:
</p>
<ul>
<li>Determining which NEs we need and how to detect them in the text.</li>
<li>Use these NEs to define intents as well as code their callbacks for when such an intent is found in the input text.</li>
</ul>
</section>
<section>
<h2 class="section-title">Required Named Entities (NEs) <a href="#"><i class="top-link fas fa-fw fa-angle-double-up"></i></a></h2>
<p>
For our example we’ll need to define three NEs in our model:
</p>
<ul>
<li>A place where we need to turn on or off the lights like “kitchen” or “garage”.</li>
<li>Two actions: “on” and “off”.</li>
</ul>
<p>
If we can detect these NE in the input text we can perform the necessary operation.
</p>
</section>
<section>
<h2 class="section-title">Data Model <a href="#"><i class="top-link fas fa-fw fa-angle-double-up"></i></a></h2>
<p>
Once we have an idea of what NEs we’ll need - we can start building the data model that would define how
these NEs can be detected and ultimately what intents we are going to detecting using these NEs.
</p>
<p>
In NLPCraft the default built-in approach for detecting NEs is a synonym matching. For each NE you provide
a list of synonyms by which it will be found in the input text. To make this task really simple NLPCraft
comes with a set of tools including <a href="/data-model.html#macros">Macro DSL</a> and <a href="/intent-matching.html">Intent Definition Language</a> (IDL).
Here’s the static model configuration for our
example as <code>lightswitch_model.yaml</code> file that includes NE definitions and one intent:
</p>
<pre class="brush: js, highlight: [14, 21, 29, 38]">
id: "nlpcraft.lightswitch.ex"
name: "Light Switch Example Model"
version: "1.0"
description: "NLI-powered light switch example model."
macros:
- name: "&lt;ACTION&gt;"
macro: "{turn|switch|dial|control|let|set|get|put}"
- name: "&lt;ENTIRE_OPT&gt;"
macro: "{entire|full|whole|total|_}"
- name: "&lt;LIGHT&gt;"
macro: "{all|_} {it|them|light|illumination|lamp|lamplight}"
enabledBuiltInTokens: [] # This example doesn't use any built-in tokens.
elements:
- id: "ls:loc"
description: "Location of lights."
synonyms:
- "&lt;ENTIRE_OPT&gt; {upstairs|downstairs|_} {kitchen|library|closet|garage|office|playroom|{dinning|laundry|play} room}"
- "&lt;ENTIRE_OPT&gt; {upstairs|downstairs|_} {master|kid|children|child|guest|_} {bedroom|bathroom|washroom|storage} {closet|_}"
- "&lt;ENTIRE_OPT&gt; {house|home|building|{1st|first} floor|{2nd|second} floor}"
- id: "ls:on"
groups:
- "act"
description: "Light switch ON action."
synonyms:
- "&lt;ACTION&gt; {on|up|_} &lt;LIGHT&gt; {on|up|_}"
- "&lt;LIGHT&gt; {on|up}"
- id: "ls:off"
groups:
- "act"
description: "Light switch OFF action."
synonyms:
- "&lt;ACTION&gt; &lt;LIGHT&gt; {off|out}"
- "{&lt;ACTION&gt;|shut|kill|stop|eliminate} {off|out} &lt;LIGHT&gt;"
- "no &lt;LIGHT&gt;"
intents:
- "intent=ls term(act)={has(tok_groups, 'act')} term(loc)={# == 'ls:loc'}*"
</pre>
<p><b>NOTES:</b></p>
<ul>
<li>
We define 3 model elements (corresponding to the NEs we’ve come up with earlier):
<ul>
<li><code>"ls:loc"</code> to define the location.</li>
<li><code>"ls:on"</code> to define ON action.</li>
<li><code>"ls:off"</code> to define OFF action.</li>
</ul>
</li>
<li>
We grouped <code>"ls:on"</code> and <code>"ls:off"</code> into group <code>"act"</code> for easier use in the intent.
</li>
<li>
Each model element is defined through synonyms using <a href="/data-model.html#macros">Macro DSL</a>.
</li>
<li>
Intent <code>"ls"</code> uses <a href="/intent-matching.html">Intent Definition Language</a>. In short - this intent
will match if we can detect one element
from the group <code>"act"</code> and zero or more <code>"ls:loc"</code> elements in any order.
</li>
</ul>
<p>
Notice that <a href="/data-model.html#macros">Macro DSL</a> allow you to define hundreds and often thousands of synonyms for each model
element with only a few lines of YAML (or JSON). In the above model, for example, the three elements have <b>over
7,700 unique synonyms</b> after all <a href="/data-model.html#macros">Macro DSL</a> processing.
</p>
<p>
Now, dealing with synonyms may sound limiting at first. It is, however, a surprisingly powerful and flexible mechanism
for the <em>domain-specific</em> applications:
</p>
<ul>
<li>
Synonyms can be developed, extended and tested using many tools that NLPCraft provides like
<a href="/data-model.html#macros">Macro DSL</a>, <a href="/intent-matching.html">Intent Definition Language</a> and
<a href="/tools/syn_tool.html">Synonym Suggester</a> (that uses Google’ BERT and Facebook fastText models).
</li>
<li>
Classic NLP problems like word-sense disambiguation (“bass” the fish, and “bass” the sound) are
not a real concern with semantic modelling for domain specific models.
</li>
<li>
Unlike neural networks-based approaches, synonym based NEs and intents don’t require expensive
and time consuming model training and creation of specialized large text corpora.
</li>
<li>
Synonym-based NE detection and Intent matching is fully deterministic (i.e. predictable) which
is a stark contrast to deep learning approaches that allow only for probabilistic results.
</li>
</ul>
<p>
It’s also important to note that if synonym based approach is not enough NLPCraft, of course, supports custom
NE resolvers that can use any desired approach and technique.
</p>
</section>
<section>
<h2 class="section-title">Intent Matching <a href="#"><i class="top-link fas fa-fw fa-angle-double-up"></i></a></h2>
<p>
Although full explanation of the <a href="/intent-matching.html">intent matching</a> algorithm is outside the scope of this article, the basic workflow looks like this:
</p>
<ul>
<li>
User text is tokenized into individual words and tokens.
</li>
<li>
Each token gets lemmatized and stemmatized, and basic syntactic and grammatical information (such as POS tagging) being obtained.
</li>
<li>
Using these tokens the system tries to detect all NEs defined in the model.
</li>
<li>
Found NEs are matched against all model’s intents and the callback for the winning match, if any, is called.
</li>
</ul>
</section>
<section>
<h2 class="section-title">Model Implementation <a href="#"><i class="top-link fas fa-fw fa-angle-double-up"></i></a></h2>
<p>
Below is a full implementation of the data model in Scala (the same implementation in Java or Kotlin is practically identical):
</p>
<pre class="brush: scala">
package org.apache.nlpcraft.examples.lightswitch
import org.apache.nlpcraft.model.{NCIntentTerm, _}
class LightSwitchModel extends NCModelFileAdapter("org/apache/nlpcraft/examples/lightswitch/lightswitch_model.yaml") {
@NCIntentRef("ls")
def onMatch(
@NCIntentTerm("act") actTok: NCToken,
@NCIntentTerm("loc") locToks: List[NCToken]
): NCResult = {
val status = if (actTok.getId == "ls:on") "on" else "off"
val locations =
if (locToks.isEmpty)
"entire house"
else
locToks.map(_.meta[String]("nlpcraft:nlp:origtext")).mkString(", ")
// Add HomeKit, Arduino or other integration here.
// By default - return a descriptive action string.
NCResult.text(s"Lights are [$status] in [${locations.toLowerCase}].")
}
}
</pre>
<p><b>NOTES:</b></p>
<ul>
<li>
We use an <a target="javadoc" href="/apis/latest/org/apache/nlpcraft/model/NCModelFileAdapter.html">NCModelFileAdapter</a>
adapter that allows us to load our static model configuration from a YAML file.
</li>
<li>
Method <code>onMatch(...)</code> is a callback function for our intent <code>"ls"</code>
(define above in the <code>lightswitch_model.yaml</code> file).
</li>
<li>
Method <code>onMatch(...)</code> has two input parameters:
<ul>
<li>A single token from the <code>"act"</code> term.</li>
<li>A list of tokens (zero or more) from the <code>"loc"</code> term.</li>
</ul>
</li>
<li>
The body of the <code>onMatch(...)</code> method is pretty straightforward: it detects the
desired action from <code>"actTok"</code> and the places to operate the lights on
from <code>"locToks"</code> parameters.
</li>
</ul>
<p>
We’ll leave outside of this article the details of the particular integration with HomeKit or Arduino devices. We’ll also defer
to the NLPCraft <a href="/docs.html">documentation</a> to learn about other topics such as
<a href="/short-term-memory.html">conversation management</a>,
details of <a href="/data-model.html#macros">Macro DSL</a> and <a href="/intent-matching.html">Intent Definition Language</a>,
built-in <a href="/tools/test_framework.html">testing tools</a>,
3rd party NER <a href="/integrations.html">integrations</a>, etc.
</p>
</section>
<section>
<h2 class="section-title">Conclusion <a href="#"><i class="top-link fas fa-fw fa-angle-double-up"></i></a></h2>
<p>
In a couple dozen lines of code we’ve created a non-trivial application that understands free-speech
natural language interface to operate a simple lightswitch. You can ask it many things like:
</p>
<ul>
<li><code>"Turn the lights off in the entire house."</code></li>
<li><code>"Switch on the illumination in the master bedroom closet."</code></li>
<li><code>"Get the lights on."</code></li>
<li><code>"Lights up in the kitchen."</code></li>
<li><code>"Please, put the light out in the upstairs bedroom."</code></li>
<li><code>"Set the lights on in the entire house."</code></li>
<li><code>"Turn the lights off in the guest bedroom."</code></li>
<li><code>"Could you please switch off all the lights?"</code></li>
<li><code>"Dial off illumination on the 2nd floor."</code></li>
<li><code>"Please, no lights!"</code></li>
<li><code>"Kill off all the lights now!"</code></li>
<li><code>"No lights in the bedroom, please."</code></li>
</ul>
<p>
You can also extend this model and support different locations or actions like dimming or brightening
the lights, etc. Model-as-a-code approach enables you to iterate on this in the matter of minutes without
heavy re-training cycles.
</p>
<p>
Hopefully, this short introduction to Apache NLPCraft gave you a hint of what it is capable of and
what you can do with it.
</p>
</section>