| --- |
| active_crumb: Calculator <code><sub>ex</sub></code> |
| layout: documentation |
| id: calculator |
| 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 example provides a very simple calculator implementation. |
| It supports restricted set of arithmetic operations for numeric values. |
| </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/calculator">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 Scala projects in many ways - we'll use SBT |
| to accomplish this task. Make sure that <code>build.sbt</code> file has the following content: |
| </p> |
| <pre class="brush: js, highlight: [7]"> |
| ThisBuild / version := "0.1.0-SNAPSHOT" |
| ThisBuild / scalaVersion := "3.1.3" |
| lazy val root = (project in file(".")) |
| .settings( |
| name := "NLPCraft Calculator Example", |
| version := "{{site.latest_version}}", |
| libraryDependencies += "org.apache.nlpcraft" % "nlpcraft" % "{{site.latest_version}}", |
| libraryDependencies += "org.apache.nlpcraft" % "nlpcraft-stanford" % "1.0.0", |
| libraryDependencies += "edu.stanford.nlp" % "stanford-corenlp" % "4.5.1", |
| libraryDependencies += "org.scalatest" %% "scalatest" % "3.2.14" % "test" |
| ) |
| </pre> |
| <p><b>NOTE: </b>use the latest versions of Scala, ScalaTest and StanfordNLP library.</p> |
| <p>Create the following files so that resulting project structure would look like the following:</p> |
| <ul> |
| <li><code>CalculatorModel.scala</code> - Model implementation</li> |
| <li><code>CalculatorModelSpec.scala</code> - Test that allows to test your model.</li> |
| </ul> |
| <pre class="brush: plain, highlight: [8, 12]"> |
| | build.sbt |
| +--project |
| | build.properties |
| \--src |
| +--main |
| | \--scala |
| | \--demo |
| | CalculatorModel.scala |
| \--test |
| \--scala |
| \--demo |
| CalculatorModelSpec.scala |
| </pre> |
| </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> |
| All element definitions are provided programmatically inside Scala model <code>CalculatorModel</code> class. |
| Open <code>src/main/scala/demo/<b>CalculatorModel.scala</b></code> file and replace its content with the following code: |
| </p> |
| <pre class="brush: scala, highlight: [11, 12, 14, 21, 22, 23, 31, 35, 41, 47, 56, 61]"> |
| package demo |
| |
| import edu.stanford.nlp.pipeline.StanfordCoreNLP |
| import org.apache.nlpcraft.* |
| import org.apache.nlpcraft.annotations.* |
| import org.apache.nlpcraft.nlp.entity.parser.stanford.* |
| import org.apache.nlpcraft.nlp.parsers.* |
| import org.apache.nlpcraft.nlp.token.parser.stanford.NCStanfordNLPTokenParser |
| 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 NCModelAdapter( |
| 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:token: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:token: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)) |
| </pre> |
| <p> |
| 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 |
| (<code>+</code>, <code>-</code>, <code>*</code>, <code>/</code>) are used as is for without any additional synonyms. |
| Let's review this implementation step by step: |
| </p> |
| <ul> |
| <li> |
| <code>Line 11</code> declares <code>CalculatorModel</code> model companion object which contains |
| static content and helper methods. |
| </li> |
| <li> |
| <code>Line 12</code> defines arithmetic operations map with notations as keys and functions definitions as values. |
| </li> |
| <li> |
| <code>Line 14</code> defines model pipeline based on three built-in components. |
| <code>Line 21</code> defines Stanford token parser |
| <code>NCStanfordNLPTokenParser</code> |
| (we use Stanford NLP components in this example). |
| </li> |
| <li> |
| <code>Line 22</code> declares entity parser |
| {% scaladoc nlp/parsers/NCNLPEntityParser NCNLPEntityParser %} |
| which allows to find arithmetic operations notations. |
| </li> |
| <li> |
| <code>Line 23</code> defines entity parser |
| <code>NCStanfordNLPEntityParser</code> |
| which allows to find numerics in the text input. |
| </li> |
| <li> |
| <code>Line 31</code> declares <code>CalculatorModel</code> model class. |
| </li> |
| <li> |
| <code>line 35</code> declares variable named <code>mem</code> which act as a holder for the last operation result. |
| </li> |
| <li> |
| <code>Lines 41 and 47</code> annotate intents <code>calc</code> and its callback method <code>onMatch()</code>. |
| Intent <code>calc</code> requires one arithmetic operation notation and two numerics as its arguments. |
| </li> |
| <li> |
| <code>Lines 56 and 61</code> annotate intents <code>calcMem</code> and its callback method <code>onMatchMem()</code>. |
| Intent <code>calcMem</code> 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. |
| </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> |
| The test provided in <code>CalculatorModelSpec</code> allows to check that all input test sentences are |
| processed correctly and trigger the expected intents <code>calc</code> or <code>calcMem</code>: |
| </p> |
| <pre class="brush: scala, highlight: [9, 11, 15, 16]"> |
| 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) |
| } |
| } |
| </pre> |
| <ul> |
| <li> |
| <code>Line 9</code> creates the client for our model. |
| </li> |
| <li> |
| <code>Line 11</code> calls the method <code>ask()</code>. Its result is checked with expected value. |
| </li> |
| <li> |
| Note that test sentences on <code>lines 15, 16</code> trigger <code>calcMem</code> intent while other sentences |
| trigger <code>calc</code> intent. |
| </li> |
| </ul> |
| <p> |
| You can run this test via SBT task <code>executeTests</code> or using IDE. |
| </p> |
| <pre class="brush: scala, highlight: []"> |
| $ sbt executeTests |
| </pre> |
| </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 calculator model and tested it. |
| </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="#testing">Testing</a></li> |
| {% include quick-links.html %} |
| </ul> |
| </div> |
| |
| |
| |
| |
| |
| |