| --- |
| active_crumb: Alarm Clock <code><sub>ex</sub></code> |
| layout: documentation |
| id: alarm_clock |
| fa_icon: fa-cube |
| --- |
| |
| <!-- |
| 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 example"> |
| <section id="overview"> |
| <h2 class="section-title">Overview <a href="#"><i class="top-link fas fa-fw fa-angle-double-up"></i></a></h2> |
| <p> |
| This simple example provides an "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 timer is up it will |
| simply print out "BEEP BEEP BEEP" in the data probe console. |
| </p> |
| <p> |
| <b>Complexity:</b> <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/> |
| <span class="ex-src">Source code: <a target="github" href="https://github.com/apache/incubator-nlpcraft/tree/master/nlpcraft-examples/alarm">GitHub <i class="fab fa-fw fa-github"></i></a><br/></span> |
| <span class="ex-review-all">Review: <a target="github" href="https://github.com/apache/incubator-nlpcraft/tree/master/nlpcraft-examples">All Examples at GitHub <i class="fab fa-fw fa-github"></i></a></span> |
| </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 Java project in many ways - we'll use <a href="/tools/script.html">NLPCraft CLI</a> |
| to accomplish this task: |
| </p> |
| <nav> |
| <div class="nav nav-tabs" role="tablist"> |
| <a class="nav-item nav-link active" data-toggle="tab" href="#nav-prj-cmd" role="tab">Command</a> |
| <a class="nav-item nav-link" data-toggle="tab" href="#nav-prj-out" role="tab">Output <i class="fa fa-desktop output"></i></a> |
| </div> |
| </nav> |
| <div class="tab-content"> |
| <div class="tab-pane fade show active" id="nav-prj-cmd" role="tabpanel"> |
| <pre class="brush: bash"> |
| $ bin/nlpcraft.sh gen-project --baseName=AlarmClock --outputDir=~ --pkgName=demo --mdlType=json |
| </pre> |
| <p> |
| <b>NOTES:</b> |
| </p> |
| <ul> |
| <li> |
| New project created in <code>/home/AlarmClock</code> directory. |
| </li> |
| <li> |
| <code>gen-project</code> command defaults to Java and Maven as its built tool. |
| </li> |
| <li> |
| Run <code class="script">bin/nlpcraft.sh help --cmd=gen-project</code> to get a full help on <code>gen-project</code> command. |
| </li> |
| <li> |
| <a href="/tools/script.html">NLPCraft CLI</a> is available as <code>nlpcraft.sh</code> for |
| <i class="fab fa-fw fa-linux"></i> and <code>nlpcraft.cmd</code> |
| for <i class="fab fa-fw fa-windows"></i>. |
| </li> |
| </ul> |
| </div> |
| <div class="tab-pane fade show" id="nav-prj-out" role="tabpanel"> |
| <p></p> |
| <img alt="" class="img-fluid" src="/images/alarm_clock_fig1.png"> |
| </div> |
| </div> |
| </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 JSON which we will later load using |
| <code>NCModelFileAdapter</code> in our Java-based model implementation. Open <code>src/main/resources/<b>alarm_clock.json</b></code> |
| file and replace its content with this JSON: |
| </p> |
| <pre class="brush: js, highlight: [7, 11, 20]"> |
| { |
| "id": "nlpcraft.alarm.ex", |
| "name": "Alarm Example Model", |
| "version": "1.0", |
| "description": "Alarm example model.", |
| "enabledBuiltInTokens": [ |
| "nlpcraft:num" |
| ], |
| "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~{# == 'x:alarm'} term(nums)~{# == 'nlpcraft:num' && meta_token('nlpcraft:num:unittype') == 'datetime' && meta_token('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 11</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 20</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"> |
| <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/java/demo/<b>AlarmClock.java</b></code> file and replace its content with the following code: |
| </p> |
| <pre class="brush: java, highlight: [10, 18, 22, 27], 82"> |
| package demo; |
| |
| import org.apache.nlpcraft.model.*; |
| import java.time.*; |
| import java.time.format.*; |
| import java.util.*; |
| |
| import static java.time.temporal.ChronoUnit.*; |
| |
| public class AlarmClock 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 AlarmClock() { |
| // Loading the model from the file in the classpath. |
| super("alarm_model.json"); |
| } |
| |
| @NCIntentRef("alarm") |
| @NCIntentSample({ |
| "Ping me in 3 minutes", |
| "Buzz me in an hour and 15mins", |
| "Set my alarm for 30s" |
| }) |
| 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 assert, because 'datetime' unit can be extended. |
| 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)); |
| } |
| |
| @Override |
| public void onDiscard() { |
| // Clean up when model gets discarded (e.g. during testing). |
| timer.cancel(); |
| } |
| } |
| </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 10</code> 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> |
| <code>Line 27</code> defines 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> |
| Note the <code>line 22</code> where we use <a target="javadoc" href="/apis/latest/org/apache/nlpcraft/model/NCIntentSample.html">@NCIntentSample</a> |
| annotation to provide samples of the user input that this intent should match. Apart from documentation |
| purpose these samples will be used when we will be <a href="#testing">testing out model below.</a> |
| </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 <code>line 82</code> the intent callback simply returns a confirmation message telling |
| for what actual time the alarm clock was set. |
| </li> |
| </ul> |
| </section> |
| <section id="build_project"> |
| <h2 class="section-title">Build Project <a href="#"><i class="top-link fas fa-fw fa-angle-double-up"></i></a></h2> |
| <p> |
| Once we have our model ready let's go to <code>~/AlarmClock</code> directory and run the Maven build: |
| </p> |
| <pre class="brush: bash"> |
| $ cd ~/AlarmClock |
| $ mvn clean package |
| </pre> |
| <p> |
| At this stage we have our project built and we are ready to start testing. |
| </p> |
| </section> |
| <section id="start_server"> |
| <h2 class="section-title">Start Server <a href="#"><i class="top-link fas fa-fw fa-angle-double-up"></i></a></h2> |
| <p> |
| Run the following command to start local REST server, if it hasn't been started already, from the NLPCraft installation directory: |
| </p> |
| <nav> |
| <div class="nav nav-tabs" role="tablist"> |
| <a class="nav-item nav-link active" data-toggle="tab" href="#nav-srv-cmd" role="tab">Command</a> |
| <a class="nav-item nav-link" data-toggle="tab" href="#nav-srv-out" role="tab">Output <i class="fa fa-desktop output"></i></a> |
| </div> |
| </nav> |
| <div class="tab-content"> |
| <div class="tab-pane fade show active" id="nav-srv-cmd" role="tabpanel"> |
| <pre class="brush: bash"> |
| $ bin/nlpcraft.sh start-server |
| </pre> |
| </div> |
| <div class="tab-pane fade show" id="nav-srv-out" role="tabpanel"> |
| <p></p> |
| <p> |
| <img class="img-fluid" alt="" src="/images/server-fig1.png"> |
| </p> |
| </div> |
| </div> |
| <p> |
| <b>NOTES:</b> |
| </p> |
| <ul> |
| <li> |
| <i style="color: #F39C12" class="fa fa-exclamation-triangle"></i> REST server is a "fire-and-forget" component that you generally needs to start only once for this and any other |
| examples. |
| </li> |
| <li> |
| Run <code class="script">bin/nlpcraft.sh help --cmd=start-server</code> to get a full help on this command. |
| </li> |
| <li> |
| <a href="/tools/script.html">NLPCraft CLI</a> is available as <code>nlpcraft.sh</code> for |
| <i class="fab fa-fw fa-linux"></i> and <code>nlpcraft.cmd</code> |
| for <i class="fab fa-fw fa-windows"></i>. |
| </li> |
| </ul> |
| </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> |
| Remember the <a target="javadoc" href="/apis/latest/org/apache/nlpcraft/model/NCIntentSample.html">@NCIntentSample</a> |
| annotation we have used in our model code next to intent definition? |
| </p> |
| <p> |
| Part of the <a href="/tools/test_framework.html">test framework</a>, the auto-validator class <a |
| target="javadoc" |
| href="/apis/latest/org/apache/nlpcraft/model/tools/test/NCTestAutoModelValidator.html">NCTestAutoModelValidator</a> takes one or more model IDs |
| (or class names) and performs validation. Validation consists of starting an <a href="/tools/embedded_probe.html">embedded probe</a> with a given model, |
| scanning for <a target="javadoc" href="/apis/latest/org/apache/nlpcraft/model/NCIntentSample.html">@NCIntentSample</a> and |
| <a target="javadoc" href="/apis/latest/org/apache/nlpcraft/model/NCIntentSampleRef.html">@NCIntentSampleRef</a> annotations |
| and their corresponding callback methods, submitting each sample input |
| sentences from these annotations and checking that resulting intent matches the intent the sample was attached to. |
| Note that auto-testing does not require any additional code to be written - the class gathers all required information from the model |
| itself. |
| </p> |
| <p> |
| As always, you can launch model auto-validator as any other Java class but we'll use NLPCraft CLI |
| to do it more conveniently: |
| </p> |
| <pre class="brush: bash"> |
| $ bin/nlpcraft.sh test-model --cp=~/AlarmClock/target/classes --mdls=demo.AlarmClock |
| </pre> |
| <p> |
| <b>NOTES:</b> |
| </p> |
| <ul> |
| <li> |
| Run <code class="script">bin/nlpcraft.sh help --cmd=test-model</code> to get a full help on this command. |
| </li> |
| <li> |
| Note that you can use <code>retest-model</code> command in REPL mode to re-run the last model test |
| avoiding the retyping of all required parameters. |
| </li> |
| <li> |
| <a href="/tools/script.html">NLPCraft CLI</a> is available as <code>nlpcraft.sh</code> for |
| <i class="fab fa-fw fa-linux"></i> and <code>nlpcraft.cmd</code> |
| for <i class="fab fa-fw fa-windows"></i>. |
| </li> |
| </ul> |
| <p> |
| Look at the output of this command and you will see the test results for all our sample utterances: |
| </p> |
| <p> |
| <img style="max-width: 667px !important;" class="img-fluid" alt="" src="/images/alarm-clock-test.png"> |
| </p> |
| </section> |
| <section id="rinse"> |
| <h2 class="section-title">Rinse <span class="amp">&</span> Repeat <a href="#"><i class="top-link fas fa-fw fa-angle-double-up"></i></a></h2> |
| <p> |
| Typical development cycle consists of: |
| </p> |
| <ul> |
| <li> |
| <a href="#model">Modifying the model</a> |
| </li> |
| <li> |
| <a href="#build_project">Re-building the project</a> |
| </li> |
| <li> |
| <a href="#testing">Re-running the test</a> |
| </li> |
| </ul> |
| <p> |
| All of these operations can be performed from <a href="/tools/script.html">NLPCraft CLI</a> in REPL mode or from any IDE. |
| </p> |
| <p> |
| NOTE: you don't need to restart REST server every time - it only needs to be started once. |
| </p> |
| </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 alarm clock data model, started the REST server and tested this model using 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="#model">Data Model</a></li> |
| <li><a href="#code">Model Class</a></li> |
| <li><a href="#build_project">Build Project</li> |
| <li><a href="#start_server">Start Server</a></li> |
| <li><a href="#testing">Testing</a></li> |
| <li><a href="#rinse">Rinse <span class="amp">&</span> Repeat</a></li> |
| {% include quick-links.html %} |
| </ul> |
| </div> |
| |
| |
| |
| |
| |
| |