| --- |
| active_crumb: Alarm Clock <code><sub>ex</sub></code> |
| layout: documentation |
| id: alarm_clock |
| --- |
| |
| <!-- |
| 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="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]"> |
| <dependencies> |
| <dependency> |
| <groupId>org.apache.nlpcraft</groupId> |
| <artifactId>nlpcraft</artifactId> |
| <version>{{site.latest_version}}</version> |
| </dependency> |
| </dependencies> |
| </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]"> |
| <properties> |
| <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> |
| <maven.compiler.source>1.8</maven.compiler.source> |
| <maven.compiler.target>1.8</maven.compiler.target> |
| </properties> |
| </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 the static part of our semantic model using JSON which we will later load using |
| <code>NCModelFileAdapter</code> in our Java-based model implementation. 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> |
| <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> |
| <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.). |
| </li> |
| </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<NCToken> 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.getContext().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 line 9 our class extends <code>NCModelFileAdapter</code> that allows us to load most |
| of the model declaration from the external JSON or YAML file (line 18) and only provide functionality that we |
| couldn't express in declarative portion in JSON. |
| </li> |
| <li> |
| Lines 21-24 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> |
| <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 (line 68) |
| 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> |
| <li> |
| On the line 81 the intent callback simply returns a confirmation message telling |
| for what actual time the alarm clock was set. |
| </li> |
| </ul> |
| </section> |
| <section id="start_probe"> |
| <h3 class="section-title">Start Data Probe <sub>optional</sub></h3> |
| <div class="bq warn"> |
| <p><b>Embedded Probe</b></p> |
| <p> |
| If you are using the <a href="#testing">unit test</a> that comes with this example you <b>do not</b> |
| need to start the data probe standalone as this unit test uses embedded probe mode. In this mode, the unit |
| test will be automatically start and stop the data probe from within the test itself. |
| </p> |
| <p> |
| <b>If using <a href="#testing">unit test</a> below - skip this step, you only need to start the server.</b> |
| </p> |
| </div> |
| <p> |
| NLPCraft data models get deployed into data probe. Let's start a standalone 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> |
| <li> |
| <b>VM arguments:</b> <code>-Dconfig.override_with_env_vars=true</code> |
| </li> |
| <li> |
| <b>Environment variable:</b> <code>CONFIG_FORCE_nlpcraft_probe_models.0=org.apache.nlpcraft.examples.alarm.AlarmModel</code> |
| </li> |
| <li> |
| <b>Program arguments: </b> <code>-probe</code> |
| </li> |
| </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 id="start_server"> |
| <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> |
| <li> |
| <b>Program arguments: </b> <code>-server</code> |
| </li> |
| </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="/tools/test_framework.html">test framework</a> |
| that allows you to write convenient unit tests against your data model. |
| </p> |
| </section> |
| <section id="testing"> |
| <h3 class="section-title">Testing</h3> |
| <p> |
| NLPCraft comes with easy to use <a href="/tools/test_framework.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> |
| <li><code>"Buzz me in an hour and 15mins"</code></li> |
| <li><code>"Set my alarm for 30s"</code></li> |
| </ul> |
| <p> |
| Let's create new Java class <code>AlarmTest.java</code> with the following code: |
| </p> |
| <pre class="brush: java, highlight: [20, 24, 32, 37, 38, 39]"> |
| package org.apache.nlpcraft.examples.alarm; |
| |
| import org.apache.nlpcraft.common.NCException; |
| import org.apache.nlpcraft.model.tools.test.NCTestClient; |
| import org.apache.nlpcraft.model.tools.test.NCTestClientBuilder; |
| import org.apache.nlpcraft.probe.embedded.NCEmbeddedProbe; |
| import org.junit.jupiter.api.AfterAll; |
| import org.junit.jupiter.api.BeforeAll; |
| import org.junit.jupiter.api.Test; |
| |
| import java.io.IOException; |
| |
| import static org.junit.jupiter.api.Assertions.assertTrue; |
| |
| class AlarmTest { |
| static private NCTestClient cli; |
| |
| @BeforeAll |
| static void setUp() throws NCException, IOException { |
| NCEmbeddedProbe.start(AlarmModel.class); |
| |
| cli = new NCTestClientBuilder().newBuilder().build(); |
| |
| cli.open("nlpcraft.alarm.ex"); // See alarm_model.json |
| } |
| |
| @AfterAll |
| static void tearDown() throws NCException, IOException { |
| if (cli != null) |
| cli.close(); |
| |
| NCEmbeddedProbe.stop(); |
| } |
| |
| @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 line 24 we open the test client with the model ID (see <code>alarm_model.json</code> |
| file for where we declared it). |
| </li> |
| <li> |
| Line 37, 38, and 39 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. |
| </li> |
| </ul> |
| <div class="bq info"> |
| <p><b>Embedded Prove</b></p> |
| <p> |
| This test uses <a href="/tools/embedded_probe.html">embedded probe</a> which automatically |
| start and stops the data probe from within the tests itself. See lines 20 and 32 for details. |
| </p> |
| <p> |
| <b>NOTE:</b> when using test you don't need to start data probe standalone in a previous step. |
| </p> |
| </div> |
| <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> |
| <li><a href="#overview">Overview</a></li> |
| <li><a href="#new_project">New Project</a></li> |
| <li><a href="#add_nlpcraft">Add NLPCraft</a></li> |
| <li><a href="#model">Data Model</a></li> |
| <li><a href="#code">Model Class</a></li> |
| <li><a href="#start_probe">Start Probe <sub>opt</sub></a></li> |
| <li><a href="#start_server">Start Server</a></li> |
| {% include quick-links.html %} |
| </ul> |
| </div> |
| |
| |
| |
| |
| |
| |