blob: ab2a0a69d71f8208842caf76cf10ebe25c86befc [file] [log] [blame]
---
active_crumb: Docs
layout: documentation
id: first-example
---
<!--
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.
-->
<div class="col-md-8 second-column">
<section id="setup">
<h2 class="section-title">Overview <a href="#"><i class="top-link fas fa-fw fa-angle-double-up"></i></a></h2>
<p>
It is highly recommended to familiarize yourself with the <a href="/key-concepts.html">key concepts</a> first.
</p>
<p>
Let's develop the first NLPCraft example to familiarize ourselves with the main workflow of NLPCraft.
We'll put together a NLI-powered home light switch prototype that can be controlled through the natural language.
We'll keep <a target=_ href="https://cloud.google.com/speech-to-text/">speech-to-text conversion</a> and integration
with <a target=_ href="https://developer.apple.com/homekit/">HomeKit</a> or
<a href="https://www.arduino.cc/" target=_>Ardunio</a> outside of this example - and concentrate just on understanding
the natural language commands.
</p>
</section>
<section id="new_project">
<h2 class="section-title">Create New Project <a href="#"><i class="top-link fas fa-fw fa-angle-double-up"></i></a></h2>
<p>
You can create new Scala projects in many ways - we'll use SBT
to accomplish this task. Make sure that <code>build.sbt</code> file has the following content:
</p>
<pre class="brush: js, highlight: [7]">
ThisBuild / version := "0.1.0-SNAPSHOT"
ThisBuild / scalaVersion := "3.2.2"
lazy val root = (project in file("."))
.settings(
name := "NLPCraft LightSwitch Example",
version := "{{site.latest_version}}",
libraryDependencies += "org.apache.nlpcraft" % "nlpcraft" % "{{site.latest_version}}",
libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.15" % "test"
)
</pre>
<p><b>NOTE: </b>use the latest versions of Scala and ScalaTest.</p>
<p>Create the following files so that resulting project structure would look like the following:</p>
<ul>
<li><code>lightswitch_model.yaml</code> - YAML configuration file which contains model description.</li>
<li><code>LightSwitchModel.scala</code> - Model implementation.</li>
<li><code>LightSwitchModelSpec.scala</code> - Test that allows to test your model.</li>
</ul>
<pre class="brush: plain, highlight: [7, 10, 14]">
| build.sbt
+--project
| build.properties
\--src
+--main
| +--resources
| | lightswitch_model.yaml
| \--scala
| \--demo
| LightSwitchModel.scala
\--test
\--scala
\--demo
LightSwitchModelSpec.scala
</pre>
</section>
<section id="model">
<h2 class="section-title">Data Model<a href="#"><i class="top-link fas fa-fw fa-angle-double-up"></i></a></h2>
<p>
We are going to start with declaring the static part of our model using YAML which we will later load
in our Scala-based model implementation.
Open <code>src/main/resources/<b>lightswitch_model.yaml</b></code>
file and replace its content with the following YAML:
</p>
<pre class="brush: js, highlight: [1, 10, 17, 25]">
macros:
"&lt;ACTION&gt;" : "{turn|switch|dial|let|set|get|put}"
"&lt;KILL&gt;" : "{shut|kill|stop|eliminate}"
"&lt;ENTIRE_OPT&gt;" : "{entire|full|whole|total|_}"
"&lt;FLOOR_OPT&gt;" : "{upstairs|downstairs|{1st|first|2nd|second|3rd|third|4th|fourth|5th|fifth|top|ground} floor|_}"
"&lt;TYPE&gt;" : "{room|closet|attic|loft|{store|storage} {room|_}}"
"&lt;LIGHT&gt;" : "{all|_} {it|them|light|illumination|lamp|lamplight}"
elements:
- type: "ls:loc"
description: "Location of lights."
synonyms:
- "&lt;ENTIRE_OPT&gt; &lt;FLOOR_OPT&gt; {kitchen|library|closet|garage|office|playroom|{dinning|laundry|play} &lt;TYPE&gt;}"
- "&lt;ENTIRE_OPT&gt; &lt;FLOOR_OPT&gt; {master|kid|children|child|guest|_} {bedroom|bathroom|washroom|storage} {&lt;TYPE&gt;|_}"
- "&lt;ENTIRE_OPT&gt; {house|home|building|{1st|first} floor|{2nd|second} floor}"
- type: "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}"
- type: "ls:off"
groups:
- "act"
description: "Light switch OFF action."
synonyms:
- "&lt;ACTION&gt; &lt;LIGHT&gt; {off|out|down}"
- "{&lt;ACTION&gt;|&lt;KILL&gt;} {off|out|down} &lt;LIGHT&gt;"
- "&lt;KILL&gt; &lt;LIGHT&gt;"
- "&lt;LIGHT&gt; &lt;KILL&gt;"
- "{out|no|off|down} &lt;LIGHT&gt;"
- "&lt;LIGHT&gt; {out|off|down}"
</pre>
<ul>
<li>
<code>Line 1</code> defines several macros that are used later on throughout the model's elements
to shorten the synonym declarations. Note how macros coupled with option groups
shorten overall synonym declarations 1000:1 vs. manually listing all possible word permutations.
</li>
<li>
<code>Lines 10, 17, 25</code> define three model elements: the location of the light, and actions to turn
the light on and off. Action elements belong to the same group <code>act</code> which
will be used in our intent, defined in <code>LightSwitchModel</code> class. Note that these model
elements are defined mostly through macros we have defined above.
</li>
</ul>
<div class="bq info">
<p><b>YAML vs. API</b></p>
<p>
As usual, this YAML-based static model definition is convenient but totally optional. All elements definitions
can be provided programmatically inside Scala model <code>LightSwitchModel</code> class as well.
</p>
</div>
</section>
<section id="code">
<h2 class="section-title">Model Class <a href="#"><i class="top-link fas fa-fw fa-angle-double-up"></i></a></h2>
<p>
Open <code>src/main/scala/demo/<b>LightSwitchModel.scala</b></code> file and replace its content with the following code:
</p>
<pre class="brush: scala, highlight: [6, 7, 8, 10, 11, 14, 15, 22]">
package demo
import org.apache.nlpcraft.*
import org.apache.nlpcraft.annotations.*
class LightSwitchModel extends NCModel(
NCModelConfig("nlpcraft.lightswitch.java.ex", "LightSwitch Example Model", "1.0"),
new NCPipelineBuilder().withSemantic("en", "lightswitch_model.yaml").build
):
@NCIntent("intent=ls term(act)={has(ent_groups, 'act')} term(loc)={# == 'ls:loc'}*")
def onMatch(
ctx: NCContext,
im: NCIntentMatch,
@NCIntentTerm("act") actEnt: NCEntity,
@NCIntentTerm("loc") locEnts: List[NCEntity]
): NCResult =
val status = if actEnt.getType == "ls:on" then "on" else "off"
val locations = if locEnts.isEmpty then "entire house" else locEnts.map(_.mkText).mkString(", ")
// Add HomeKit, Arduino or other integration here.
// By default - just return a descriptive action string.
NCResult(s"Lights are [$status] in [${locations.toLowerCase}].")
</pre>
<p>
The intent callback logic is very simple - we return a descriptive confirmation message
back (explaining what lights were changed). With action and location detected, you can add
the actual light switching using HomeKit or Arduino devices. Let's review this implementation step by step:
</p>
<ul>
<li>
On <code>line 6</code> our class extends {% scaladoc NCModel NCModel %} with two mandatory parameters.
</li>
<li>
<code>Line 7</code> creates model configuration with most default parameters.
</li>
<li>
<code>Line 8</code> creates pipeline, based on semantic model definition,
described in <code>lightswitch_model.yaml</code> file.
</li>
<li>
<code>Lines 10 and 11</code> annotate intents <code>ls</code> and its callback method <code>onMatch()</code>.
Intent <code>ls</code> requires one action (a token belonging to the group <code>act</code>) and optional list of light locations
(zero or more tokens with ID <code>ls:loc</code>) - by default we assume the entire house as a default location.
</li>
<li>
<code>Lines 14 and 15</code> map terms from detected intent to the formal method parameters of the
<code>onMatch()</code> method.
</li>
<li>
On the <code>line 22</code> the intent callback simply returns a confirmation message.
</li>
</ul>
</section>
<section id="build">
<h2 class="section-title">Build Project<a href="#"><i class="top-link fas fa-fw fa-angle-double-up"></i></a></h2>
<p>
Once the model ready, run the SBT build from the project folder:
</p>
<pre class="brush: scala, highlight: []">
$ sbt clean compile
</pre>
</section>
<section id="testing">
<h2 class="section-title">Testing <a href="#"><i class="top-link fas fa-fw fa-angle-double-up"></i></a></h2>
<p>
The test defined in <code>LightSwitchModelSpec</code> allows to check that all input test sentences are
processed correctly and trigger the expected intent <code>ls</code>:
</p>
<pre class="brush: scala, highlight: [9, 11]">
package demo
import org.apache.nlpcraft.*
import org.scalatest.funsuite.AnyFunSuite
import scala.util.Using
class LightSwitchModelSpec extends AnyFunSuite:
test("test") {
Using.resource(new NCModelClient(new LightSwitchModel())) { client =>
def check(txt: String): Unit =
require(client.debugAsk(txt, "userId", true).getIntentId == "ls")
check("Turn the lights off in the entire house.")
check("Turn off all lights now")
check("Switch on the illumination in the master bedroom closet.")
check("Get the lights on.")
check("Off the lights on the 1st floor")
check("Lights up in the kitchen.")
check("Please, put the light out in the upstairs bedroom.")
check("Set the lights on in the entire house.")
check("Turn the lights off in the guest bedroom.")
check("Could you please switch off all the lights?")
check("Dial off illumination on the 2nd floor.")
check("Turn down lights in 1st floor bedroom")
check("Lights on at second floor kitchen")
check("Please, no lights!")
check("Kill off all the lights now!")
check("Down the lights in the garage")
check("Lights down in the kitchen!")
check("Turn up the illumination in garage and master bedroom")
check("Turn down all the light now!")
check("No lights in the bedroom, please.")
check("Light up the garage, please!")
check("Kill the illumination now!")
}
}
</pre>
<ul>
<li>
<code>Line 9</code> creates the client for our model.
</li>
<li>
<code>Line 11</code> calls a special method
{% scaladoc NCModelClient#debugAsk-fffff96c debugAsk() %}.
It allows to check the winning intent and its callback parameters without actually
calling the intent.
</li>
<li>
<code>Lines 13-34</code> define all the test input sentences that should all
trigger <code>ls</code> intent.
</li>
</ul>
<p>
You can run this test via SBT task <code>executeTests</code> or using IDE.
</p>
<pre class="brush: scala, highlight: []">
$ sbt executeTests
</pre>
</section>
<section>
<h2 class="section-title">Done! 👌 <a href="#"><i class="top-link fas fa-fw fa-angle-double-up"></i></a></h2>
<p>
You've created light switch data model and tested it.
</p>
</section>
</div>
<div class="col-md-2 third-column">
<ul class="side-nav">
<li class="side-nav-title">On This Page</li>
<li><a href="#new_project">New Project</a></li>
<li><a href="#model">Data Model</a></li>
<li><a href="#code">Model Class</a></li>
<li><a href="#build">Build Project</a></li>
<li><a href="#testing">Testing</a></li>
{% include quick-links.html %}
</ul>
</div>