• Docs
  • Resources
  • Community
  • Use Cases
  • Downloads
  • v.0.9.0
  • GitHub
  1. Home
  2. Alarm Clock ex

Alarm Clock ex

  • Developer Guide
  • Overview
  • Installation
  • First Example
  • Data Model
  • Intent Matching
  • Short-Term Memory
  • Server & Probe
  • Metrics & Tracing
  • Integrations
  • REST API
  • Tools
  • nlpcraft.{sh|cmd}
  • Test Framework
  • Embedded Probe
  • SQL Model Generator
  • Synonyms Tool
  • Examples
  • Alarm Clock
  • Light Switch
  • Weather Bot
  • SQL Model

Overview

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.

Complexity:
Source code: GitHub
Review: All Examples at GitHub

Create New Project

You can create new Java project in many ways - we'll use NLPCraft CLI to accomplish this task:

Command Output
                    $ bin/nlpcraft.sh gen-project --baseName=AlarmClock --outputDir=~ --pkgName=demo --mdlType=json
                

NOTES:

  • New project created in /home/AlarmClock directory.
  • gen-project command defaults to Java and Maven as its built tool.
  • Run bin/nlpcraft.sh help --cmd=gen-project to get a full help on gen-project command.
  • NLPCraft CLI is available as nlpcraft.sh for and nlpcraft.cmd for .

Data Model

We are going to start with declaring the static part of our model using JSON which we will later load using NCModelFileAdapter in our Java-based model implementation. Open src/main/resources/alarm_clock.json file and replace its content with this JSON:

{
    "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]"
    ]
}
        

There are number of important points here:

  • Line 7 indicates that we'll be using built-in token nlpcraft:num (and therefore it needs to be explicitly enabled).
  • Line 11 defines the only custom model element we'll need. It's ID is x:alarm and it is defined through synonyms. It basically means a verb to set up an alarm clock.
  • On line 20 we define an intent alarm that we are going to be looking for in the user input that consists of two terms: one for x:alarm element (defined above) and another one for up to 7 numeric values of unit type datetime (minutes, seconds, hours, etc.).

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 alarm is detected in the user input.

Model Class

Open src/main/java/demo/AlarmClock.java file and replace its content with the following code:

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();
    }
}
        

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:

  • On line 10 our class extends NCModelFileAdapter 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.
  • Line 27 defines method onMatch as a callback for intent alarm when it is detected in the user input. Method parameter numToks will get up to 7 tokens of type nlpcraft:num (see intent definition above).
  • Note the line 22 where we use @NCIntentSample 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 testing out model below.
  • The rest of the method onMatch 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.
  • On the line 82 the intent callback simply returns a confirmation message telling for what actual time the alarm clock was set.

Build Project

Once we have our model ready let's go to ~/AlarmClock directory and run the Maven build:

            $ cd ~/AlarmClock
            $ mvn clean package
        

At this stage we have our project built and we are ready to start testing.

Start Server

Run the following command to start local REST server, if it hasn't been started already, from the NLPCraft installation directory:

Command Output
                    $ bin/nlpcraft.sh start-server
                

NOTES:

  • REST server is a "fire-and-forget" component that you generally needs to start only once for this and any other examples.
  • Run bin/nlpcraft.sh help --cmd=start-server to get a full help on this command.
  • NLPCraft CLI is available as nlpcraft.sh for and nlpcraft.cmd for .

Testing

Remember the @NCIntentSample annotation we have used in our model code next to intent definition?

Part of the test framework, the auto-validator class NCTestAutoModelValidator takes one or more model IDs (or class names) and performs validation. Validation consists of starting an embedded probe with a given model, scanning for @NCIntentSample and @NCIntentSampleRef 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.

As always, you can launch model auto-validator as any other Java class but we'll use NLPCraft CLI to do it more conveniently:

            $ bin/nlpcraft.sh test-model --cp=~/AlarmClock/target/classes --mdls=demo.AlarmClock
        

NOTES:

  • Run bin/nlpcraft.sh help --cmd=test-model to get a full help on this command.
  • Note that you can use retest-model command in REPL mode to re-run the last model test avoiding the retyping of all required parameters.
  • NLPCraft CLI is available as nlpcraft.sh for and nlpcraft.cmd for .

Look at the output of this command and you will see the test results for all our sample utterances:

Rinse & Repeat

Typical development cycle consists of:

  • Modifying the model
  • Re-building the project
  • Re-running the test

All of these operations can be performed from NLPCraft CLI in REPL mode or from any IDE.

NOTE: you don't need to restart REST server every time - it only needs to be started once.

Done! 👌

You've created alarm clock data model, started the REST server and tested this model using the built-in test framework.

  • On This Page
  • Overview
  • New Project
  • Data Model
  • Model Class
  • Build Project
  • Start Server
  • Testing
  • Rinse & Repeat
  • Quick Links
  • Examples
  • Javadoc
  • REST API
  • Download
  • Cheat Sheet
  • News & Events
  • Support
  • JIRA
  • Dev List
  • Stack Overflow
  • GitHub
  • Gitter
  • Twitter
  • YouTube
Copyright © 2021 Apache Software Foundation asf Events • Privacy • News • Docs release: 0.9.0 Gitter Built in: