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

Calculator 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 calculator implementation. It supports restricted set of arithmetic operations for numeric values.

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 Calculator Example",
                version := "1.0.0",
                libraryDependencies += "org.apache.nlpcraft" % "nlpcraft" % "1.0.0",
                libraryDependencies += "org.apache.nlpcraft" % "nlpcraft-stanford" % "1.0.0",
                libraryDependencies += "edu.stanford.nlp" % "stanford-corenlp" % "4.5.1",
                libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.15" % "test"
              )
        

NOTE: use the latest versions of Scala, ScalaTest and StanfordNLP library.

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

  • CalculatorModel.scala - Model implementation
  • CalculatorModelSpec.scala - Test that allows to test your model.
            |  build.sbt
            +--project
            |    build.properties
            \--src
               +--main
               |  \--scala
               |     \--demo
               |          CalculatorModel.scala
               \--test
                  \--scala
                     \--demo
                          CalculatorModelSpec.scala
        

Data Model

All element definitions are provided programmatically inside Scala model CalculatorModel class. Open src/main/scala/demo/CalculatorModel.scala file and replace its content with the following code:

            package demo

            import edu.stanford.nlp.pipeline.StanfordCoreNLP
            import org.apache.nlpcraft.*
            import org.apache.nlpcraft.annotations.*
            import org.apache.nlpcraft.nlp.parsers.*
            import org.apache.nlpcraft.nlp.stanford.*

            import java.util.Properties

            object CalculatorModel:
                private val OPS: Map[String, (Int, Int) => Int] =
                    Map("+" -> (_ + _), "-" -> (_ - _), "*" -> (_ * _), "/" -> (_ / _))
                private val PIPELINE: NCPipeline =
                    val props = new Properties()
                    props.setProperty("annotators", "tokenize, ssplit, pos, lemma, ner")

                    val stanford = new StanfordCoreNLP(props)

                    new NCPipelineBuilder().
                        withTokenParser(new NCStanfordNLPTokenParser(stanford)).
                        withEntityParser(new NCNLPEntityParser(t => OPS.contains(t.getText))).
                        withEntityParser(new NCStanfordNLPEntityParser(stanford, Set("number"))).
                        build

                private def nne(e: NCEntity): Int =
                    java.lang.Double.parseDouble(e[String]("stanford:number:nne")).intValue

            import CalculatorModel.*

            class CalculatorModel extends NCModel(
                NCModelConfig("nlpcraft.calculator.ex", "Calculator Example Model", "1.0"),
                PIPELINE
            ) :
                private var mem: Option[Int] = None

                private def calc(x: Int, op: String, y: Int): NCResult =
                    mem = Some(OPS.getOrElse(op, throw new IllegalStateException()).apply(x, y))
                    NCResult(mem.get)

                @NCIntent(
                    "intent=calc options={ 'ordered': true }" +
                    "   term(x)={# == 'stanford:number'}" +
                    "   term(op)={has(list('+', '-', '*', '/'), meta_ent('nlp:entity:text')) == true}" +
                    "   term(y)={# == 'stanford:number'}"
                )
                def onMatch(
                    ctx: NCContext,
                    im: NCIntentMatch,
                    @NCIntentTerm("x") x: NCEntity,
                    @NCIntentTerm("op") op: NCEntity,
                    @NCIntentTerm("y") y: NCEntity
                ): NCResult =
                    calc(nne(x), op.mkText, nne(y))

                @NCIntent(
                    "intent=calcMem options={ 'ordered': true }" +
                    "   term(op)={has(list('+', '-', '*', '/'), meta_ent('nlp:entity:text')) == true}" +
                    "   term(y)={# == 'stanford:number'}"
                )
                def onMatchMem(
                    ctx: NCContext,
                    im: NCIntentMatch,
                    @NCIntentTerm("op") op: NCEntity,
                    @NCIntentTerm("y") y: NCEntity
                ): NCResult =
                    calc(mem.getOrElse(throw new NCRejection("Memory is empty.")), op.mkText, nne(y))
        

There are two intents with simple logic. First returns arithmetic operation result with two input parameters, second uses last operation result instead of the first argument. Note also that the arithmetic notations (+, -, *, /) are used as is for without any additional synonyms. Let's review this implementation step by step:

  • Line 11 declares CalculatorModel model companion object which contains static content and helper methods.
  • Line 12 defines arithmetic operations map with notations as keys and functions definitions as values.
  • Line 14 defines model pipeline based on three built-in components. Line 21 defines Stanford token parser NCStanfordNLPTokenParser (we use Stanford NLP components in this example).
  • Line 22 declares entity parser NCNLPEntityParser which allows to find arithmetic operations notations.
  • Line 23 defines entity parser NCStanfordNLPEntityParser which allows to find numerics in the text input.
  • Line 31 declares CalculatorModel model class.
  • line 35 declares variable named mem which act as a holder for the last operation result.
  • Lines 41 and 47 annotate intents calc and its callback method onMatch(). Intent calc requires one arithmetic operation notation and two numerics as its arguments.
  • Lines 56 and 61 annotate intents calcMem and its callback method onMatchMem(). Intent calcMem requires one arithmetic operation notation and one numeric as its second arguments. Note that it attempts to use last operation result as its first argument, if one exists.

Testing

The test provided in CalculatorModelSpec allows to check that all input test sentences are processed correctly and trigger the expected intents calc or calcMem:

            package demo

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

            class CalculatorModelSpec extends AnyFunSuite:
                test("test") {
                    Using.resource(new NCModelClient(new CalculatorModel())) { client =>
                        def check(txt: String, v: Int): Unit =
                            require(v == client.ask(txt, "userId").getBody)

                        check("2 + 2", 4)
                        check("3 * 4", 12)
                        check("/ two", 6)
                        check("+ twenty two", 28)
                        check("7 + 2", 9)
                    }
                }
        
  • Line 9 creates the client for our model.
  • Line 11 calls the method ask(). Its result is checked with expected value.
  • Note that test sentences on lines 15, 16 trigger calcMem intent while other sentences trigger calc intent.

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

            $ sbt executeTests
        

Done! 👌

You've created calculator model and tested it.

  • On This Page
  • Overview
  • New Project
  • Data Model
  • 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: