• Docs
  • Community
  • Use Cases
  • Downloads
  • v.1.0.0
  • GitHub
  1. Home
  2. Time ex

Time ex

  • Introduction
  • Overview
  • Installation
  • First Example
  • Developer Guide
  • Key Concepts
  • Intent Matching
  • Short-Term Memory
  • Examples
  • Calculator
  • Time
  • Light Switch
  • Light Switch FR
  • Light Switch RU
  • Pizzeria

Overview

This example provides a very simple implementation for world time bot. You can say something like "What time is it now in New York City" or "What's the local time?".

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

Create New Project

You can create new Scala projects in many ways - we'll use SBT to accomplish this task. Make sure that build.sbt file has the following content:

            ThisBuild / version := "0.1.0-SNAPSHOT"
            ThisBuild / scalaVersion := "3.2.2"
            lazy val root = (project in file("."))
              .settings(
                name := "NLPCraft Time Example",
                version := "1.0.0",
                libraryDependencies += "org.apache.nlpcraft" % "nlpcraft" % "1.0.0",
                libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.15" % "test"
              )
        

NOTE: use the latest versions of Scala and ScalaTest.

Create the following files so that resulting project structure would look like the following:

  • time_model.yaml - YAML configuration file which contains model description.
  • cities_timezones.txt - Cities timezones database.
  • TimeModel.scala - Model implementation.
  • CitiesDataProvider.scala - Helper service which loads timezones database.
  • GeoManager.scala - Helper service which provides cities timezones information for user request.
  • TimeModelSpec.scala - Test that allows to test your model.
            |  build.sbt
            +--project
            |    build.properties
            \--src
               +--main
               |  +--resources
               |  |    time_model.yaml
               |  |    cities_timezones.txt
               |  \--scala
               |     \--demo
               |          CitiesDataProvider.scala
               |          GeoManager.scala
               |          TimeModel.scala
               \--test
                  \--scala
                     \--demo
                          TimeModelSpec.scala
        

Data Model

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 src/main/resources/time_model.yaml file and replace its content with the following YAML:

            macros:
              "<OF>": "{of|for|per}"
              "<CUR>": "{current|present|now|local}"
              "<TIME>": "{time <OF> day|day time|date|time|moment|datetime|hour|o'clock|clock|date time|date and time|time and date}"
            elements:
              - type: "x:time"
                description: "Date and/or time token indicator."
                synonyms:
                  - "{<CUR>|_} <TIME>"
                  - "what <TIME> {is it now|now|is it|_}"
        

There are number of important points here:

  • Line 1 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.
  • Line 6 defines x:time model element which is used in our intent defined in TimeModel class. Note that this model element is defined mostly through macros we have defined above.

YAML vs. API

As usual, this YAML-based static model definition is convenient but totally optional. All elements definitions can be provided programmatically inside Scala model TimeModel class as well.

Model Class

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

            package demo

            import com.fasterxml.jackson.core.JsonProcessingException
            import com.fasterxml.jackson.databind.ObjectMapper
            import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
            import org.apache.nlpcraft.*
            import org.apache.nlpcraft.annotations.*
            import org.apache.nlpcraft.internal.util.NCResourceReader
            import org.apache.nlpcraft.nlp.parsers.NCOpenNLPEntityParser
            import java.time.format.DateTimeFormatter
            import java.time.format.FormatStyle.MEDIUM
            import java.time.*

            @NCIntent("fragment=city term(city)~{# == 'opennlp:location'}")
            @NCIntent("intent=intent2 term~{# == 'x:time'} fragment(city)")
            @NCIntent("intent=intent1 term={# == 'x:time'}")
            class TimeModel extends NCModel(
                NCModelConfig("nlpcraft.time.ex", "Time Example Model", "1.0"),
                new NCPipelineBuilder().
                    withSemantic("en", "time_model.yaml").
                    withEntityParser(NCOpenNLPEntityParser(
                        NCResourceReader.getPath("opennlp/en-ner-location.bin"))
                    ).
                    build
            ):
                private val FMT: DateTimeFormatter = DateTimeFormatter.ofLocalizedDateTime(MEDIUM)

                private val citiesData: Map[City, CityData] = CitiesDataProvider.get

                private def mkResult(
                    city: String, cntry: String, tmz: String, lat: Double, lon: Double
                ): NCResult =
                    val m =
                        Map(
                            "city" -> capitalize(city),
                            "country" -> capitalize(cntry),
                            "timezone" -> tmz,
                            "lat" -> lat,
                            "lon" -> lon,
                            "localTime" -> ZonedDateTime.now(ZoneId.of(tmz)).format(FMT)
                        )

                    try
                        NCResult(new ObjectMapper(new YAMLFactory).writeValueAsString(m))
                    catch
                        case e: JsonProcessingException =>
                            throw new RuntimeException("YAML conversion error.", e)

                private def capitalize(s: String): String =
                    if s == null || s.isEmpty
                        then s
                        else s"${s.substring(0, 1).toUpperCase}${s.substring(1, s.length)}"

                @NCIntentRef("intent2")
                private def onRemoteMatch(
                    ctx: NCContext, im: NCIntentMatch, @NCIntentTerm("city") cityEnt: NCEntity
                ): NCResult =
                    val cityName: String = cityEnt.mkText

                    val (city, data) =
                        citiesData.find(_._1.name.equalsIgnoreCase(cityName)).
                        getOrElse(
                            throw new NCRejection(String.format("No timezone mapping for %s.", cityName))
                        )

                    mkResult(city.name, city.country, data.timezone, data.latitude, data.longitude)

                @NCIntentRef("intent1")
                private def onLocalMatch(ctx: NCContext, im: NCIntentMatch): NCResult =
                    val geo = GeoManager.get(ctx.getRequest).getOrElse(GeoManager.getSiliconValley)

                    mkResult(geo.city, geo.country_name, geo.timezone, geo.latitude, geo.longitude)
        

There are two intents, for local and remote locations. Result is represented as JSON value. Let's review this implementation step by step:

  • On line 17 our class extends NCModel with two mandatory parameters.
  • Line 14 creates IDL fragment which is used in intent2 definition below.
  • Lines 15 and 16 annotate two intents definitions intent1 and intent2. Their callbacks below have references on them by their identifiers.
  • Line 18 creates model configuration with most default parameters.
  • Line 19 creates pipeline based on built-in components:
    • Built-in English language NCSemanticEntityParser configured with time_model.yaml.
    • Entity parser NCOpenNLPTokenParser configured by opennlp/en-ner-location.bin for GEO locations detection.
  • Lines 54 and 55 annotate intent intent2 and its callback method onRemoteMatch(). This intent requires one mandatory entity - city which is used for getting time for its timezone.
  • Lines 68 and 69 annotate intent intent1 and its callback method onLocalMatch(). This intent is triggered by default, tries to detect timezone by request data and return time for this timezone. Otherwise, it returns Silicon Valley current time.

Implementations of helper classes GeoManager and CitiesDataProvider are not related to given example logic. Just copy these classes and cities_timezones.txt file from project source code into your demo project and don't forget to correct package names to demo for copied classes. Links are below:

  • GeoManager.scala
  • CitiesDataProvider.scala
  • cities_timezones.txt

Testing

The test defined in TimeModelSpec allows to check that all input test sentences are processed correctly and trigger the expected intents intent2 and intent1:

            package demo

            import org.apache.nlpcraft.*
            import org.scalatest.funsuite.AnyFunSuite
            import scala.util.Using

            class TimeModelSpec extends AnyFunSuite:
                test("test") {
                    Using.resource(new NCModelClient(new TimeModel())) { client =>
                        def check(txt: String, intentId: String): Unit =
                            require(client.debugAsk(txt, "userId", true).getIntentId == intentId)

                        check("What time is it now in New York City?", "intent2")
                        check("What's the current time in Moscow?", "intent2")
                        check("Show me time of the day in London.", "intent2")
                        check("Can you please give me the Tokyo's current date and time.", "intent2")

                        check("What's the local time?", "intent1")
                    }
                }
        
  • Line 9 creates the client for our model.
  • Line 11 calls a special method debugAsk(). It allows to check the winning intent and its callback parameters without actually calling the intent.
  • Lines 13-18 define all the test input sentences that should trigger requited intent.

You can run this test via SBT task executeTests or using IDE.

            sbt executeTests
        

Done! 👌

You've created time data model and tested it.

  • On This Page
  • Overview
  • New Project
  • Data Model
  • Model Class
  • Testing
  • Quick Links
  • Examples
  • Scaladoc
  • Download
  • Installation
  • Support
  • JIRA
  • Dev List
  • Stack Overflow
  • GitHub
  • Gitter
  • Twitter
Copyright © 2023 Apache Software Foundation asf Events • Privacy • News • Docs release: 1.0.0 Gitter Built in: