blob: 22774231f3c890b17e91237a77fea86f6491a99e [file] [log] [blame]
<!--
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.
-->
<!doctype html><html lang="en"><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"><meta name="description" content="An open source API to convert natural language into actions."><meta name="author" content="NLPCraft."><title>Apache NLPCraft - Natural Language Interface</title><link href="//netdna.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" rel="stylesheet"><link href="//use.fontawesome.com/releases/v5.7.1/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" rel="stylesheet" crossorigin="anonymous"><link href="/ext/syntaxhighlighter/styles/shCoreNLPCraft.css" rel="stylesheet" type="text/css"><link href="/ext/syntaxhighlighter/styles/shThemeNLPCraft.css" rel="stylesheet" type="text/css"><link href="//fonts.googleapis.com/css?family=Amatic+SC|Roboto+Mono" rel="stylesheet"> <script src="/ext/syntaxhighlighter/scripts/XRegExp.js" type="text/javascript"></script> <script src="/ext/syntaxhighlighter/scripts/shCore.js" type="text/javascript"></script> <script src="/ext/syntaxhighlighter/scripts/shBrushXml.js" type="text/javascript"></script> <script src="/ext/syntaxhighlighter/scripts/shBrushPlain.js" type="text/javascript"></script> <script src="/ext/syntaxhighlighter/scripts/shBrushJava.js" type="text/javascript"></script> <script src="/ext/syntaxhighlighter/scripts/shBrushScala.js" type="text/javascript"></script> <script src="/ext/syntaxhighlighter/scripts/shBrushPython.js" type="text/javascript"></script> <script src="/ext/syntaxhighlighter/scripts/shBrushJScript.js" type="text/javascript"></script> <script async defer src="https://buttons.github.io/buttons.js"></script><link rel="stylesheet" type="text/css" href="/assets/css/style.css"/> <script type="text/javascript" src="//use.typekit.net/pso2adz.js"></script> <script type="text/javascript"> try { Typekit.load(); } catch(e) { // Ignore. } </script><nav class="navbar navbar-expand-lg navbar-light bg-light" id="top-header"> <a class="navbar-brand mr-4" href="/index.html"> <img src="/images/nlpcraft_logo_white.gif" height="24px"> </a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button><div class="collapse navbar-collapse" id="navbarSupportedContent"><ul class="navbar-nav mr-auto"><li class="nav-item"> <a class="nav-link" href="/index.html#features">Features</a><li class="nav-item"> <a class="nav-link" href="/docs.html">Docs</a><li class="nav-item"> <a class="nav-link" href="/download.html">Downloads <i class="fas fa-download"></i></a><li class="nav-item"> <a class="nav-link" href="/community.html">Community</a><li class="nav-item"> <a class="nav-link" href="/use-cases.html">Use Cases</a></ul><ul class="navbar-nav ml-auto"><li class="nav-item mr-2"> <a class="nav-link" href="/download.html">v.0.5.0</a><li class="nav-item"> <a class="nav-link fork-link" target="github" href="https://github.com/apache/incubator-nlpcraft">GitHub <img height="20px" src="/images/github_logo_white.png"></a></ul></div></nav><div class="container-fluid"><div class="navbar-aligned"><ol class="breadcrumb"><li class="mr-1"><a href="/index.html">Home</a><li class="mr-1 active">Alarm Clock <code><sub>ex</sub></code></ol><h1 class="page-title"> <span><i class="fas fa-fw fa-book"></i> Alarm Clock <code><sub>ex</sub></code></span></h1><div class="three-cols-container"><div class="col-md-2 first-column"><ul class="side-nav"><li class="side-nav-title">Introduction<li> <a href="/docs.html">Overview</a><li> <a href="/installation.html">Installation</a><li> <a href="/getting-started.html">Getting Started</a><li class="side-nav-title">Developer Guide<li> <a href="/basic-concepts.html">Basic Concepts</a><li> <a href="/first-example.html">First Example</a><li> <a href="/data-model.html">Data Model</a><li> <a href="/intent-matching.html">Intent Matching</a><li> <a href="/using-rest.html">REST API</a><li> <a href="/server-and-probe.html">Server <span class="amp">&amp;</span> Probe</a><li> <a href="/metrics-and-tracing.html">Metrics <span class="amp">&amp;</span> Tracing</a><li> <a href="/integrations.html">Integrations</a><li class="side-nav-title">Examples<li> <a class="active" href="/examples/alarm_clock.html">Alarm Clock</a><li> <a href="/examples/light_switch.html">Light Switch</a></ul></div><div class="col-md-8 second-column"><section id="overview"><h2 class="section-title">Overview</h2><p> This simple example provides a "alarm clock" implementation where you can ask to set the timer for a specific duration from now expressed in hours, minutes and/or seconds. You can say "ping me in 3 minutes", "buzz me in an hour and 15 minutes", or "set my alarm for 30 secs". When the timers is up it will simply print out "BEEP BEEP BEEP" in the data probe console.</p><p> Complexity: <span class="complexity-one-star"><i class="fas fa-star"></i><i class="far fa-star"></i><i class="far fa-star"></i></span><br/> Source code: <a target="github" href="https://github.com/apache/incubator-nlpcraft/tree/master/src/main/scala/org/apache/nlpcraft/examples/alarm">GitHub</a></p></section><section id="new_project"><h3 class="section-title">Create New Project</h3><p> You can create new Java project in many different ways - we'll use Maven archetype generation for that. In your home folder run the following command:</p><pre class="brush: text">
mvn archetype:generate -DgroupId=examples -DartifactId=my-app -DarchetypeVersion=1.4 -DinteractiveMode=false
</pre><p> This will create <code>my-app</code> folder with the following default maven project structure:</p><pre class="console">
├── <b>pom.xml</b>
└── src
├── main
│   └── java
│   └── examples
│   └── App.java
└── test
└── java
└── examples
└── AppTest.java
</pre><div class="bq info"><p> Note that this setup is same for all examples. Note also that you can use any other tools for creating and managing Java project with or without Maven.</p></div><p> For our example we'll use JetBrain's <a target=_new href="https://www.jetbrains.com/idea/">IntelliJ IDEA</a>. Create new IDEA project from this source folder (make sure to pick JDK 8 or later JDK and language support). Let's also delete auto-generated files <code>App.java</code> and <code>AppTest.java</code> from our project as we won't be using them.</p></section><section id="add_nlpcraft"><h3 class="section-title">Add NLPCraft</h3><p> Next we need to add NLPCraft dependency to our new project. Open <code>pom.xml</code> file and replace <code>dependencies</code> section with the following code:</p><pre class="brush: xml, highlight: [3, 4, 5]">
&lt;dependencies&gt;
&lt;dependency&gt;
&lt;groupId&gt;org.apache.nlpcraft&lt;/groupId&gt;
&lt;artifactId&gt;nlpcraft&lt;/artifactId&gt;
&lt;version&gt;0.5.0&lt;/version&gt;
&lt;/dependency&gt;
&lt;/dependencies&gt;
</pre><p> Also make sure that you have correct JDK version (1.8 or above) for the maven compiler plugin:</p><pre class="brush: xml, highlight: [3, 4]">
&lt;properties&gt;
&lt;project.build.sourceEncoding&gt;UTF-8&lt;/project.build.sourceEncoding&gt;
&lt;maven.compiler.source&gt;1.8&lt;/maven.compiler.source&gt;
&lt;maven.compiler.target&gt;1.8&lt;/maven.compiler.target&gt;
&lt;/properties&gt;
</pre><p> IDEA should automatically reload the project with newly updated <code>pom.xml</code> file and we should be ready now to develop our data model.</p></section><section id="model"><h3 class="section-title">Data Model</h3><p> We are going to start with declaring our semantic model using JSON. Create new <code>alarm_model.json</code> file and add the following model declaration into it:</p><pre class="brush: js, highlight: [7, 16, 25]">
{
"id": "nlpcraft.alarm.ex",
"name": "Alarm Example Model",
"version": "1.0",
"description": "Alarm example model.",
"enabledBuiltInTokens": [
"nlpcraft:num"
],
"examples": [
"Ping me in 3 minutes",
"Buzz me in an hour and 15mins",
"Set my alarm for 30s"
],
"elements": [
{
"id": "x:alarm",
"description": "Alarm token indicator.",
"synonyms": [
"{ping|buzz|wake|call|hit} {me|up|me up|*}",
"{set|*} {my|*} {wake|wake up|*} {alarm|timer|clock|buzzer|call} {up|*}"
]
}
],
"intents": [
"intent=alarm term={id=='x:alarm'} term(nums)={id=='nlpcraft:num' && ~nlpcraft:num:unittype=='datetime' && ~nlpcraft:num:isequalcondition==true}[0,7]"
]
}
</pre><p>There are number of important points here:</p><ul><li> <code>Line 7</code> indicates that we'll be using built-in token <code>nlpcraft:num</code> (and therefore it needs to be explicitly enabled).<li> <code>Line 16</code> defines the only custom model element we'll need. It's ID is <code>x:alarm</code> and it is defined through synonyms. It basically means a verb to set up an alarm clock.<li> On <code>line 25</code> we define an intent <code>alarm</code> that we are going to be looking for in the user input that consists of two terms: one for <code>x:alarm</code> element (defined above) and another one for up to 7 numeric values of unit type <code>datetime</code> (minutes, seconds, hours, etc.).</ul><p> Now that our model is ready let's create a Java class that would load this model and provide the actual callback for when the intent <code>alarm</code> is detected in the user input.</p></section><section id="code"><h3 class="section-title">Model Class</h3><p> Let's create new Java class in <code>AlarmModel.java</code> with the following code:</p><pre class="brush: java, highlight: [9, 18, 21, 22, 23, 24, 68, 81]">
import org.apache.nlpcraft.model.*;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.*;
import static java.time.temporal.ChronoUnit.MILLIS;
public class AlarmModel extends NCModelFileAdapter {
private static final DateTimeFormatter FMT =
DateTimeFormatter.ofPattern("HH'h' mm'm' ss's'")
.withZone(ZoneId.systemDefault());
private final Timer timer = new Timer();
public AlarmModel() {
// Loading the model from the file in the classpath.
super("org/apache/nlpcraft/examples/alarm/alarm_model.json");
}
@NCIntentRef("alarm")
private NCResult onMatch(
NCIntentMatch ctx,
@NCIntentTerm("nums") List&lt;NCToken&gt; numToks
) {
if (ctx.isAmbiguous())
throw new NCRejection("Not exact match.");
long unitsCnt = numToks.stream().map(
tok -> (String)tok.meta("num:unit")
).distinct().count();
if (unitsCnt != numToks.size())
throw new NCRejection("Ambiguous time units.");
LocalDateTime now = LocalDateTime.now();
LocalDateTime dt = now;
for (NCToken num : numToks) {
String unit = num.meta("nlpcraft:num:unit");
// Skip possible fractional to simplify.
long v = ((Double)num.meta("nlpcraft:num:from")).longValue();
if (v <= 0)
throw new NCRejection("Value must be positive: " + unit);
switch (unit) {
case "second": { dt = dt.plusSeconds(v); break; }
case "minute": { dt = dt.plusMinutes(v); break; }
case "hour": { dt = dt.plusHours(v); break; }
case "day": { dt = dt.plusDays(v); break; }
case "week": { dt = dt.plusWeeks(v); break; }
case "month": { dt = dt.plusMonths(v); break; }
case "year": { dt = dt.plusYears(v); break; }
default:
// It shouldn't be an assertion, because 'datetime'
// unit type can be extended in the future.
throw new NCRejection("Unsupported time unit: " + unit);
}
}
long ms = now.until(dt, MILLIS);
assert ms >= 0;
timer.schedule(
new TimerTask() {
@Override
public void run() {
System.out.println(
"BEEP BEEP BEEP for: " +
ctx.getQueryContext().getRequest().getNormalizedText() + ""
);
}
},
ms
);
return NCResult.text("Timer set for: " + FMT.format(dt));
}
}
</pre><p> There's a bit of a logic here that deals mostly with taking multiple numeric values and converting them into a single number of milliseconds that the alarm clock needs to be set up for. Let's review it step by step:</p><ul><li> On <code>line 1</code> our class extends <code>NCModelFileAdapter</code> that allows us to load most of the model declaration from the external JSON or YAML file (<code>line 10</code>) and only provide functionality that we couldn't express in declarative portion in JSON.<li> <code>Lines 13-16</code> define method <code>onMatch</code> as a callback for intent <code>alarm</code> when it is detected in the user input. Method parameter <code>numToks</code> will get up to 7 tokens of type <code>nlpcraft:num</code> (see intent definition above).<li> The rest of the method <code>onMatch</code> implementation is a relatively straight forward Java code that calculates timer delay from multiple numeric units and their types. In the end (<code>line 60</code>) it schedules a timer to print "BEEP BEEP BEEP" at calculated time. For simplicity, this message will be printed right in the data probe console.<li> On the <code>line 73</code> the intent callback simply returns a confirmation message telling for what actual time the alarm clock was set.</ul></section><section><h3 class="section-title">Start Data Probe</h3><p> NLPCraft data models get deployed into data probe. Let's start data probe with our newly created data model. To start data probe we need to configure Run Configuration in IDEA with the following parameters:</p><ul><li> <b>Main class:</b> <code>org.apache.nlpcraft.NCStart</code><li> <b>VM arguments:</b> <code>-Dconfig.override_with_env_vars=true</code><li> <b>Environment variable:</b> <code>CONFIG_FORCE_nlpcraft_probe_models.0=path.to.AlarmModel</code><li> <b>Program arguments: </b> <code>-probe</code></ul><div class="bq info"><p> <b>NOTE:</b> instead of supplying a <a href="/server-and-probe.html">full configuration file</a> we just set one configuration property using configuration override via environment variables.</p></div><p> Start this run configuration and make sure you have positive console output indicating that our model has been successfully loaded and probe started.</p></section><section><h3 class="section-title">Start REST Server</h3><p> REST server listens for requests from client applications and routes them to the requested data models via connected data probes. REST server starts the same way as the data probe. Configure new Run Configuration in IDEA with the following parameters:</p><ul><li> <b>Main class:</b> <code>org.apache.nlpcraft.NCStart</code><li> <b>Program arguments: </b> <code>-server</code></ul><p> Once started ensure that your REST server console output shows that data probe is connected and the REST server is listening on the default <code>localhost:8081</code> endpoint.</p><p> At this point we've developed our data model, deployed it into the data probe, and started the REST server. To test it, we'll use the built-in <a href="/testing-data-model.html">test framework</a> that allows you to write convenient unit tests against your data model.</p></section><section><h3 class="section-title">Testing</h3><p> NLPCraft comes with easy to use <a target="javadoc" href="/apis/latest/org/apache/nlpcraft/model/test/package-summary.html">test framework</a> for data models that can be used with any unit testing framework like JUnit or ScalaTest. It is essentially a simplified version of Java REST client that is custom designed for data model testing.</p><p> We would like to test with following user requests:</p><ul><li><code>"Ping me in 3 minutes"</code><li><code>"Buzz me in an hour and 15mins"</code><li><code>"Set my alarm for 30s"</code></ul><p> Let's create new Java class <code>AlarmTest.java</code> with the following code:</p><pre class="brush: java, highlight: [16, 26, 27, 28]">
import org.junit.jupiter.api.*;
import org.apache.nlpcraft.common.NCException;
import org.apache.nlpcraft.model.tools.test.*;
import java.io.IOException;
import static org.junit.jupiter.api.Assertions.assertTrue;
class AlarmTest {
private NCTestClient cli;
@BeforeEach
void setUp() throws NCException, IOException {
cli = new NCTestClientBuilder().newBuilder().build();
cli.open("nlpcraft.alarm.ex"); // See alarm_model.json
}
@AfterEach
void tearDown() throws NCException, IOException {
cli.close();
}
@Test
void test() throws NCException, IOException {
assertTrue(cli.ask("Ping me in 3 minutes").isOk());
assertTrue(cli.ask("Buzz me in an hour and 15mins!").isOk());
assertTrue(cli.ask("Set my alarm for 30s").isOk());
}
} </pre><p> This test is pretty straight forward:</p><ul><li> On <code>line 16</code> we open the test client with the model ID (see <code>alarm_model.json</code> file for where we declared it).<li> <code>Line 26, 27, and 28</code> is where we issue our test sentences and we should see the confirmation messages and eventually "BEEP BEEP BEEP" print outs in the data probe console.</ul><p> Right click on this class in the project view and run it. You should be getting standard output in JUnit panel as well as the output in the data probe console.</p></section><section><h2 class="section-title">Done! 👌</h2><p> You've created alarm clock data model, deployed it into the data probe, started the REST server and tested this model using JUnit 5 and the built-in test framework.</p></section></div><div class="col-md-2 third-column"><ul class="side-nav"><li class="side-nav-title">On This Page<li><a href="#overview">Overview</a><li class="side-nav-title">Quick Links<li> <img alt=github" class="quick-link-img mr-2" src="/images/github_logo.png"><a target="github" href="https://github.com/apache/incubator-nlpcraft/tree/master/src/main/scala/org/apache/nlpcraft/examples">Examples</a><li> <i class="fab fa-java mr-2"></i><a target="_" href="/apis/latest/index.html">Javadoc</a><li> <img alt="Apiary" class="quick-link-img mr-2" src="/images/apiary.svg"><a href="https://nlpcraft.docs.apiary.io" target="apiary">REST API</a><li> <i class="fas fa-download mr-2"></i><a href="/download.html">Download</a><li> <i class="fas fa-blog mr-2"></i><a href="/blogs.html">Blogs</a><li class="side-nav-title">Support<li> <nobr> <i class="far fa-fw fa-envelope mr-2"></i><a href="mailto:dev@nlpcraft.apache.org">Dev List</a> </nobr><li> <nobr> <i class="fab fa-fw fa-stack-overflow mr-2"></i><a target="so" href="https://stackoverflow.com/questions/ask">Stack Overflow</a> </nobr><li> <nobr> <i class="fab fa-fw fa-github mr-2"></i><a target="github" href="https://github.com/apache/incubator-nlpcraft">GitHub</a> </nobr></ul></div></div></div></div><div id="footer"><div class="container"><div class="text-muted text-center"> <span>Copyright &copy; 2020 Apache Software Foundation</span> <span> <a target=_new href="https://apache.org"><img alt="asf" src="/images/asf_logo.png" height="24px"></a> </span> <a target="asf" href="http://apache.org/foundation/policies/privacy.html" class="btn btn-link ml-4">Privacy</a> <span class="sep"></span> <a href="/index.html#news" class="btn btn-link">News</a> <span class="sep"></span> <a href="/docs.html" class="btn btn-link">Docs</a> <span class="ml-4">release: <a href="/download.html"><code>0.5.0</code></a></span> <span class="ml-2"> <a target="jenkins" href="https://builds.apache.org/view/Incubator%20Projects/job/incubator-nlpcraft/"><img src="https://img.shields.io/jenkins/build?jobUrl=https%3A%2F%2Fbuilds.apache.org%2Fview%2FIncubator%2520Projects%2Fjob%2Fincubator-nlpcraft%2F"></a> <a target=_ href="https://gitter.im/apache-nlpcraft/community"><img alt="Gitter" src="https://badges.gitter.im/apache-nlpcraft/community.svg"></a> </span></div></div></div><script src="//code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.0/umd/popper.min.js" integrity="sha384-cs/chFZiN24E4KMATLdqdvsezGxaGsi4hLGOzlXwp5UZB1LY//20VyM2taTB4QvJ" crossorigin="anonymous"></script> <script src="//stackpath.bootstrapcdn.com/bootstrap/4.1.0/js/bootstrap.min.js" integrity="sha384-uefMccjFJAIv6A+rW+L4AHf99KvxDjWSu1z9VI8SKNVmz4sk7buKt/6v9KI65qnm" crossorigin="anonymous"></script> <script src="//cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.min.js" type="text/javascript" ></script> <script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.12.0/moment.min.js" type="text/javascript" ></script> <script src="//cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.5/moment-timezone-with-data.min.js" type="text/javascript" ></script> <script type="text/javascript"> SyntaxHighlighter.defaults["auto-links"] = false; SyntaxHighlighter.defaults["tab-size"] = 2; SyntaxHighlighter.all(); </script>