This is an automated email from the ASF dual-hosted git repository.
sergeykamov pushed a commit to branch NLPCRAFT-491
in repository https://gitbox.apache.org/repos/asf/incubator-nlpcraft.git
The following commit(s) were added to refs/heads/NLPCRAFT-491 by this push:
new 81eab344 Minor fixes.
81eab344 is described below
commit 81eab344df26f44b0432b20e529d53e158187a4c
Author: Sergey Kamov <[email protected]>
AuthorDate: Tue Apr 5 18:52:08 2022 +0300
Minor fixes.
---
.../nlpcraft/examples/order/OrderModel.scala | 329 ++++++++++-----------
.../nlpcraft/examples/order/OrderState.scala | 105 ++-----
.../order/components/DrinkQtyExtender.scala | 32 ++
.../examples/order/components/OrderValidator.scala | 34 +++
...SimpleCombiner.scala => PizzaQtyExtender.scala} | 39 ++-
...impleCombiner.scala => PizzaSizeExtender.scala} | 39 ++-
.../order/src/main/resources/order_model.yaml | 38 +--
7 files changed, 303 insertions(+), 313 deletions(-)
diff --git
a/nlpcraft-examples/order/src/main/java/org/apache/nlpcraft/examples/order/OrderModel.scala
b/nlpcraft-examples/order/src/main/java/org/apache/nlpcraft/examples/order/OrderModel.scala
index 6962b5a3..ec706567 100644
---
a/nlpcraft-examples/order/src/main/java/org/apache/nlpcraft/examples/order/OrderModel.scala
+++
b/nlpcraft-examples/order/src/main/java/org/apache/nlpcraft/examples/order/OrderModel.scala
@@ -28,195 +28,172 @@ import org.apache.nlpcraft.nlp.entity.parser.*
import scala.collection.mutable
import org.apache.nlpcraft.NCResultType.*
-import org.apache.nlpcraft.examples.order.components.SimpleCombiner
-import org.apache.nlpcraft.nlp.entity.parser.semantic.{NCSemanticEntityParser,
NCSemanticStemmer}
-import org.apache.nlpcraft.nlp.entity.parser.stanford.NCStanfordNLPEntityParser
-import org.apache.nlpcraft.nlp.token.parser.stanford.NCStanfordNLPTokenParser
+import org.apache.nlpcraft.examples.order.components.*
+import org.apache.nlpcraft.nlp.entity.parser.semantic.*
+import org.apache.nlpcraft.nlp.entity.parser.stanford.*
+import org.apache.nlpcraft.nlp.token.parser.stanford.*
-import java.util.Properties
import scala.jdk.CollectionConverters.*
+import java.util.Properties
+import scala.jdk.OptionConverters._
+
+object StanfordEn:
+ val PIPELINE: NCPipeline =
+ val stanford =
+ val props = new Properties()
+ props.setProperty("annotators", "tokenize, ssplit, pos, lemma,
ner")
+ new StanfordCoreNLP(props)
+ val tokParser = new NCStanfordNLPTokenParser(stanford)
+ val stemmer = new NCSemanticStemmer():
+ private val ps = new PorterStemmer
+ override def stem(txt: String): String = ps.synchronized {
ps.stem(txt) }
+
+ new NCPipelineBuilder().
+ withTokenParser(tokParser).
+ withEntityParser(new NCStanfordNLPEntityParser(stanford,
"number")).
+ withEntityParser(new NCSemanticEntityParser(stemmer, tokParser,
"order_model.yaml")).
+ withEntityMappers(Seq(new PizzaSizeExtender, new PizzaQtyExtender,
new DrinkQtyExtender).asJava).
+ withEntityValidator(new OrderValidator).
+ build()
object OrderModel extends LazyLogging:
- private val STANFORD =
- val props = new Properties()
- props.setProperty("annotators", "tokenize, ssplit, pos, lemma, ner")
- new StanfordCoreNLP(props)
- private val TOK_PARSER = new NCStanfordNLPTokenParser(STANFORD)
-
- private def extractPizzaKind(e: NCEntity): String =
e.get[String]("ord:pizza:kind:value")
- private def extractPizzaSize(e: NCEntity): PizzaSize =
PizzaSize.valueOf(e.get[String]("ord:pizza:size:value").toUpperCase)
- private def extractDrink(e: NCEntity): String =
e.get[String]("ord:drink:value")
- private def isStopWord(t: NCToken): Boolean = t.get[Boolean]("stopword")
- private def confirmOrSpecify(ord: OrderState): NCResult =
- NCResult(if ord.isValid() then ord.ask2Confirm() else
ord.ask2Specify(), ASK_DIALOG)
- private def continue(ord: OrderState): NCResult =
- NCResult(if ord.isValid() then s"OK, please continue your order: $ord"
else ord.ask2Specify(), ASK_DIALOG)
-
- private def log(o: OrderState): Unit = logger.info(o.getState())
- private def onStart(im: NCIntentMatch, o: OrderState): Unit =
- logger.info(s"Initial state before request:
${im.getContext.getRequest.getText}")
- private def onFinish(o: OrderState): Unit =
- logger.info(s"Result state")
- logger.info(o.getState())
-
- private def withRelations(
- ents: Seq[NCEntity], relations: Seq[NCEntity], skip: Seq[NCEntity],
allToks: Seq[NCToken]
- ): Map[NCEntity, NCEntity] =
- case class IdxHolder(from: Int, to: Int)
-
- def getIndexes(e: NCEntity): IdxHolder =
- val toks = e.getTokens.asScala
- IdxHolder(toks.head.getIndex, toks.last.getIndex)
-
- val skipIdxs = skip.flatMap(_.getTokens.asScala.map(_.getIndex)).toSet
- val used = mutable.ArrayBuffer.empty[NCEntity]
-
- def areNeighbours(i1: Int, i2: Int): Boolean =
- if i2 == i1 + 1 then true else Range(i1 + 1, i2).forall(i =>
isStopWord(allToks(i)) || skipIdxs.contains(i))
-
- ents.map(e => {
- val eIdxs = getIndexes(e)
- val r = relations.filter(r => !used.contains(r)).find(r => {
- val rIdxs = getIndexes(r)
- areNeighbours(rIdxs.to, eIdxs.from) || areNeighbours(eIdxs.to,
rIdxs.from)
- }).orNull
-
- if r != null then used += r
- e -> r
- }).toMap
+ private def norm(s: String) = s.trim.replaceAll("(?m)^[ \t]*\r?\n", "")
+ private def withComma[T](iter: Iterable[T]): String = iter.mkString(", ")
+ private def seq2Str[T](name: String, seq: Iterable[T]): String = if
seq.nonEmpty then s"$name: ${withComma(seq)}." else ""
+
+ private def extractPizzaSize(e: NCEntity): String =
e.get[String]("ord:pizza:size:value")
+ private def extractQty(e: NCEntity, qty: String): Option[Int] =
Option.when(e.contains(qty))(e.get[String](qty).toInt)
+ private def extractPizza(e: NCEntity): Pizza =
+ Pizza(e.get[String]("ord:pizza:value"),
e.getOpt[String]("ord:pizza:size").toScala, extractQty(e, "ord:pizza:qty"))
+ private def extractDrink(e: NCEntity): Drink =
Drink(e.get[String]("ord:drink:value"), extractQty(e, "ord:drink:qty"))
+
+ private def getContent(o: OrderState): String =
+ s"""
+ |${seq2Str("Pizza", o.getPizzas.values.map(p => s"${p.name}
${p.size.getOrElse("undefined")} ${p.qty.getOrElse(1)}"))}
+ |${seq2Str("Drinks", o.getDrinks.values.map(p => s"${p.name}
${p.qty.getOrElse(1)}"))}
+ """.stripMargin
+
+
+ private def toString(o: OrderState): String =
+ norm(
+ s"""
+ |Order
+ |${getContent(o)}
+ """.stripMargin
+ )
import org.apache.nlpcraft.examples.order.OrderModel.*
/**
*
*/
class OrderModel extends NCModelAdapter (
- new NCModelConfig("nlpcraft.order.ex", "Order Example Model", "1.0"),
- new NCPipelineBuilder().
- withTokenParser(TOK_PARSER).
- withEntityParser(new NCStanfordNLPEntityParser(STANFORD, "number")).
- withEntityParser(new NCSemanticEntityParser(
- new NCSemanticStemmer():
- final private val ps = new PorterStemmer
- override def stem(txt: String): String = ps.synchronized {
ps.stem(txt) }
- ,
- TOK_PARSER,
- "order_model.yaml"
- )).
-// withEntityMappers(
-// Seq(
-// SimpleCombiner("ord:pizza:size", "ord:pizza:kind", "x"),
-// SimpleCombiner("x", "stanford:number", "")
-// ).asJava
-// ).
- build()
+ new NCModelConfig("nlpcraft.order.ex", "Order Example Model", "1.0"),
StanfordEn.PIPELINE
) with LazyLogging:
private val ords = mutable.HashMap.empty[String, OrderState]
private def getOrder(im: NCIntentMatch): OrderState =
ords.getOrElseUpdate(im.getContext.getRequest.getUserId, new OrderState)
-
- @NCIntent("intent=confirm term(confirm)={has(ent_groups, 'confirm')}")
- def onConfirm(im: NCIntentMatch, @NCIntentTerm("confirm") confirm:
NCEntity): NCResult =
- val ord = getOrder(im)
- onStart(im, ord)
- val dlg = im.getContext.getConversation.getDialogFlow
-
- def cancelAll(): NCResult =
- ord.clear()
- im.getContext.getConversation.clearStm(_ => true)
- im.getContext.getConversation.clearDialog(_ => true)
- NCResult("Order canceled. We are ready for new orders.",
ASK_RESULT)
-
- val res =
- // 'stop' command.
- if !dlg.isEmpty && dlg.get(dlg.size() -
1).getIntentMatch.getIntentId == "stop" then
- confirm.getId match
- case "ord:confirm:yes" => cancelAll()
- case "ord:confirm:no" => confirmOrSpecify(ord)
- case "ord:confirm:continue" => continue(ord)
- case _ => throw new AssertionError()
- // 'confirm' command.
- else
- if !ord.inProgress() then throw new NCRejection("No orders in
progress.")
-
- confirm.getId match
- case "ord:confirm:yes" =>
- if ord.isValid() then
- logger.info(s"ORDER EXECUTED: $ord")
- ord.clear()
- NCResult("Congratulations. Your order executed.
You can start make new orders.", ASK_RESULT)
- else
- NCResult(ord.ask2Specify(), ASK_DIALOG)
- case "ord:confirm:no" => cancelAll()
- case "ord:confirm:continue" => continue(ord)
- case _ => throw new AssertionError()
-
- onFinish(ord)
- res
+ private def getLastIntentId(im: NCIntentMatch): Option[String] =
+ im.getContext.getConversation.getDialogFlow.asScala.lastOption match
+ case Some(e) => Some(e.getIntentMatch.getIntentId)
+ case None => None
+
+ private def mkOrderFinishDialog(o: OrderState): NCResult =
+ if o.isValid then new NCResult("Is order ready?", ASK_DIALOG)
+ else NCResult(s"What is size size (large, medium or small) for:
${o.getPizzaNoSize.name}", ASK_DIALOG)
+
+ private def mkOrderContinueDialog(o: OrderState): NCResult =
+ require(o.inProgress)
+ NCResult("OK. Please continue", ASK_DIALOG)
+
+ private def mkOrderReadyDialog(o: OrderState): NCResult =
+ require(o.isValid)
+ NCResult(
+ norm(
+ s"""
+ |Let me specify your order.
+ |${getContent(o)}
+ |Is it correct?
+ """.stripMargin
+ ),
+ ASK_DIALOG
+ )
+
+ private def mkClearResult(im: NCIntentMatch, o: OrderState): NCResult =
+ o.clear()
+ val conv = im.getContext.getConversation
+ conv.clearStm(_ => true)
+ conv.clearDialog(_ => true)
+ NCResult("Order canceled. We are ready for new orders.", ASK_RESULT)
+
+ private def mkExecuteResult(o: OrderState): NCResult =
+ println(s"EXECUTED:")
+ println(OrderModel.toString(o))
+ o.clear()
+ NCResult("Congratulations. Your order executed. You can start make new
orders.", ASK_RESULT)
+
+ @NCIntent("intent=yes term(yes)={# == 'ord:yes'}")
+ def onYes(im: NCIntentMatch, @NCIntentTerm("yes") yes: NCEntity): NCResult
=
+ val o = getOrder(im)
+ val lastIntentId = getLastIntentId(im).orNull
+
+ if lastIntentId == "stop" then mkOrderContinueDialog(o)
+ else if o.isWait4Approve then mkClearResult(im, o)
+ else mkOrderFinishDialog(o)
+
+ @NCIntent("intent=no term(no)={# == 'ord:no'}")
+ def onNo(im: NCIntentMatch, @NCIntentTerm("no") no: NCEntity): NCResult =
+ val o = getOrder(im)
+ val lastIntentId = getLastIntentId(im).orNull
+
+ if lastIntentId == "stop" then mkOrderContinueDialog(o)
+ else if o.isWait4Approve then mkClearResult(im, o)
+ else mkOrderFinishDialog(o)
@NCIntent("intent=stop term(stop)={# == 'ord:stop'}")
- def onStop(im: NCIntentMatch, @NCIntentTerm("stop") stop: NCEntity):
NCResult =
- val ord = getOrder(im)
- onStart(im, ord)
- val res =
- if ord.inProgress() then NCResult("Are you sure that you want
to cancel current order?", ASK_DIALOG)
- else NCResult("Nothing to cancel", ASK_RESULT)
- onFinish(ord)
- res
-
- @NCIntent(
- "intent=order " +
- " term(common)={# == 'ord:common'}* " +
- " term(pizzaList)={# == 'ord:pizza:kind'}*" +
- " term(pizzaSizesList)={# == 'ord:pizza:size'}* " +
- " term(drinkList)={# == 'ord:drink'}*"
- )
- def onCommonOrder(
- im: NCIntentMatch,
- @NCIntentTerm("common") common: List[NCEntity],
- @NCIntentTerm("pizzaList") pizzaKinds: List[NCEntity],
- @NCIntentTerm("pizzaSizesList") pizzaSizes: List[NCEntity],
- @NCIntentTerm("drinkList") drinks: List[NCEntity]
- ): NCResult =
- if pizzaKinds.isEmpty && drinks.isEmpty then throw new
NCRejection("Please order some pizza or drinks")
- if pizzaSizes.size > pizzaKinds.size then throw new NCRejection("Pizza
and their sizes cannot be recognized")
-
- val ord = getOrder(im)
- onStart(im, ord)
- val m = withRelations(pizzaKinds, pizzaSizes, Seq.empty,
im.getContext.getTokens.asScala.toSeq)
-
- for ((p, sz) <- m)
- val pz = extractPizzaKind(p)
- if sz != null then ord.addPizza(pz, extractPizzaSize(sz)) else
ord.addPizza(pz)
-
- for (p <- drinks.map(extractDrink)) ord.addDrink(p)
-
- val res = confirmOrSpecify(ord)
- onFinish(ord)
- res
-
- @NCIntent(
- "intent=specifyPizzaSize " +
- " term(common)={# == 'ord:common'}* " +
- " term(size)={# == 'ord:pizza:size'} " +
- " term(pizza)={# == 'ord:pizza:kind'}?"
- )
- def onSpecifyPizzaSize(
- im: NCIntentMatch,
- @NCIntentTerm("common") common: List[NCEntity],
- @NCIntentTerm("size") size: NCEntity,
- @NCIntentTerm("pizza") pizzaOpt: Option[NCEntity]
- ): NCResult =
- val ord = getOrder(im)
- require(!ord.isValid())
-
- onStart(im, ord)
-
- val sz = extractPizzaSize(size)
-
- pizzaOpt match
- case Some(pizza) => ord.addPizza(extractPizzaKind(pizza), sz)
- case None => if !ord.specifyPizzaSize(sz) then throw new
NCRejection("What specified?")
-
- val res = confirmOrSpecify(ord)
- onFinish(ord)
- res
\ No newline at end of file
+ def onStop(im: NCIntentMatch, @NCIntentTerm("stop") stop: NCEntity):
NCResult =
+ val o = getOrder(im)
+
+ if o.inProgress then NCResult("Are you sure that you want to cancel
current order?", ASK_DIALOG)
+ else NCResult("Nothing to cancel.", ASK_RESULT)
+
+ @NCIntent("intent=order term(ps)={# == 'ord:pizza'}* term(ds)={# ==
'ord:drink'}*")
+ def onOrder(im: NCIntentMatch, @NCIntentTerm("ps") ps: List[NCEntity],
@NCIntentTerm("ds") ds: List[NCEntity]): NCResult =
+ if ps.isEmpty && ds.isEmpty then throw new NCRejection("Please order
some pizza or drinks")
+
+ val o = getOrder(im)
+
+ for (p <- ps) o.addPizza(extractPizza(p))
+ for (d <- ds) o.addDrink(extractDrink(d))
+
+ mkOrderFinishDialog(o)
+
+ @NCIntent("intent=orderPizzaSize term(size)={# == 'ord:pizza:size'}")
+ def onOrderPizzaSize(im: NCIntentMatch, @NCIntentTerm("size") size:
NCEntity): NCResult =
+ val o = getOrder(im)
+
+ if !o.inProgress then throw NCRejection("") // TODO
+ o.setPizzaNoSize(extractPizzaSize(size))
+
+ mkOrderFinishDialog(o)
+
+ @NCIntent("intent=status term(status)={# == 'ord:status'}")
+ def onStatus(im: NCIntentMatch, @NCIntentTerm("status") s: NCEntity):
NCResult =
+ val o = getOrder(im)
+
+ if o.inProgress then NCResult(OrderModel.toString(o), ASK_RESULT)
+ else NCResult("Nothing ordered.", ASK_RESULT)
+
+ @NCIntent("intent=finish term(finish)={# == 'ord:finish'}")
+ def onFinish(im: NCIntentMatch, @NCIntentTerm("finish") f: NCEntity):
NCResult =
+ val o = getOrder(im)
+
+ if o.inProgress then mkOrderReadyDialog(o)
+ else NCResult("Nothing to finish.", ASK_RESULT)
+
+ @NCIntent("intent=menu term(menu)={# == 'ord:menu'}")
+ def onMenu(im: NCIntentMatch, @NCIntentTerm("menu") m: NCEntity): NCResult
=
+ NCResult(
+ "There are margherita, marbonara and marinara. Sizes: large,
medium or small. " +
+ "Also there are tea, gren tea, coffee and cola.",
+ ASK_RESULT
+ )
\ No newline at end of file
diff --git
a/nlpcraft-examples/order/src/main/java/org/apache/nlpcraft/examples/order/OrderState.scala
b/nlpcraft-examples/order/src/main/java/org/apache/nlpcraft/examples/order/OrderState.scala
index b1c128e2..ba99ced2 100644
---
a/nlpcraft-examples/order/src/main/java/org/apache/nlpcraft/examples/order/OrderState.scala
+++
b/nlpcraft-examples/order/src/main/java/org/apache/nlpcraft/examples/order/OrderState.scala
@@ -19,83 +19,36 @@ package org.apache.nlpcraft.examples.order
import scala.collection.mutable
-private enum PizzaSize:
- case SMALL, MEDIUM, LARGE
+case class Pizza(name: String, var size: Option[String], qty: Option[Int])
+case class Drink(name: String, qty: Option[Int])
-object OrderState:
- private val allPizzaSizeKinds: String =
withComma(PizzaSize.values.map(_.toString.toLowerCase))
-
- private def withComma(iter: Iterable[String]): String = iter.mkString(", ")
- private def pizza2Str(name: String, size: PizzaSize): String =
- if size != null then s"$name ${size.toString.toLowerCase} size" else
name
- private def seq2Str[T](name: String, seq: Seq[T], toStr: T => String = (t:
T) => t.toString): String =
- if seq.nonEmpty then s"$name: ${withComma(seq.map(toStr))}." else ""
- private def norm(s: String) = s.trim.replaceAll("(?m)^[ \t]*\r?\n", "")
-
-import OrderState.*
-
-/**
- * Contract.
- * 1. 'mkSpecifyRequest' scans ordered data, finds first invalid element and
asks to specify it.
- * 2. 'specify' methods (specifyPizzaSize) should specify elements in the
same order.
- * So, we don't need to save which concrete element we treying to specify.
- */
class OrderState:
- private val pizza = mutable.LinkedHashMap.empty[String, PizzaSize]
- private val drinks = mutable.LinkedHashSet.empty[String]
-
- def addDrink(name: String): Unit = drinks += name
- def addPizza(name: String): Unit = pizza += name -> null
- def addPizza(name: String, size: PizzaSize): Unit = pizza += name -> size
-
- def inProgress(): Boolean = pizza.nonEmpty || drinks.nonEmpty
-
- def isValid(): Boolean =
- (pizza.nonEmpty || drinks.nonEmpty) &&
- (pizza.isEmpty || pizza.forall{ (_, size) => size != null } )
-
- def specifyPizzaSize(sz: PizzaSize): Boolean = pizza.find { (_, size) =>
size == null } match
- case Some((name, _)) => pizza += name -> sz; true
- case None => false
-
- def ask2Specify(): String =
- require(!isValid())
-
- if pizza.isEmpty && drinks.isEmpty then
- "Order is empty. Please order some pizza or drinks."
- else
- pizza.find { (_, size) => size == null } match
- case Some((name, _)) => s"Please specify $name size? It can be
$allPizzaSizeKinds"
- case None => throw new AssertionError("Invalid state")
-
- def ask2Confirm(): String =
- require(isValid())
- norm(
- s"""
- |Let me specify your order.
- |${seq2Str("Pizza", pizza.toSeq, pizza2Str)}
- |${seq2Str("Drinks", drinks.toSeq)}
- |Is it correct?
- """.stripMargin
- )
-
- def getState(): String =
- norm(
- s"""
- |Current order state: '${if inProgress() then "in progress"
else "empty"}'.
- |${seq2Str("Pizza", pizza.toSeq, pizza2Str)}
- |${seq2Str("Drinks", drinks.toSeq)}
- """.stripMargin
- )
-
- override def toString(): String =
- norm(
- s"""
- |${seq2Str("Pizza", pizza.toSeq, pizza2Str)}
- |${seq2Str("Drinks", drinks.toSeq)}
- """.stripMargin
- ).replaceAll("\n", " ")
-
+ private val pizzas = mutable.LinkedHashMap.empty[String, Pizza]
+ private val drinks = mutable.LinkedHashMap.empty[String, Drink]
+
+ private var wait4Appr = false
+
+ private def findPizzaNoSize: Option[Pizza] =
pizzas.values.find(_.size.isEmpty)
+
+ def addPizza(p: Pizza): Unit = pizzas += p.name -> p
+ def addDrink(d: Drink): Unit = drinks += d.name -> d
+
+ def getPizzas: Map[String, Pizza] = pizzas.toMap
+ def getDrinks: Map[String, Drink] = drinks.toMap
+
+ def inProgress: Boolean = pizzas.nonEmpty || drinks.nonEmpty
+ def isValid: Boolean = (pizzas.nonEmpty || drinks.nonEmpty) &&
pizzas.forall(_._2.size.nonEmpty)
+ def isWait4Approve: Boolean = wait4Appr
+ def wait4Approve(): Unit = wait4Appr = true
+ def getPizzaNoSize: Pizza =
+ require(!isValid)
+ findPizzaNoSize.get
+ def setPizzaNoSize(size: String): Unit =
+ require(!isValid)
+ require(size != null)
+ findPizzaNoSize.get.size = Option(size)
def clear(): Unit =
- pizza.clear()
+ pizzas.clear()
drinks.clear()
+
+
diff --git
a/nlpcraft-examples/order/src/main/java/org/apache/nlpcraft/examples/order/components/DrinkQtyExtender.scala
b/nlpcraft-examples/order/src/main/java/org/apache/nlpcraft/examples/order/components/DrinkQtyExtender.scala
new file mode 100644
index 00000000..e3158153
--- /dev/null
+++
b/nlpcraft-examples/order/src/main/java/org/apache/nlpcraft/examples/order/components/DrinkQtyExtender.scala
@@ -0,0 +1,32 @@
+/*
+ * 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.
+ */
+
+package org.apache.nlpcraft.examples.order.components
+
+import org.apache.nlpcraft.*
+
+import java.util
+import java.util.List as JList
+import scala.collection.mutable
+import scala.jdk.CollectionConverters.*
+
+/**
+ *
+ */
+class DrinkQtyExtender extends NCEntityMapper:
+ private def extract(e: NCEntity): mutable.Seq[NCToken] =
e.getTokens.asScala
+ override def map(req: NCRequest, cfg: NCModelConfig, entities:
util.List[NCEntity]): util.List[NCEntity] = entities
diff --git
a/nlpcraft-examples/order/src/main/java/org/apache/nlpcraft/examples/order/components/OrderValidator.scala
b/nlpcraft-examples/order/src/main/java/org/apache/nlpcraft/examples/order/components/OrderValidator.scala
new file mode 100644
index 00000000..dc51c804
--- /dev/null
+++
b/nlpcraft-examples/order/src/main/java/org/apache/nlpcraft/examples/order/components/OrderValidator.scala
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+package org.apache.nlpcraft.examples.order.components
+
+import org.apache.nlpcraft.*
+
+import java.util
+import scala.jdk.CollectionConverters.*
+
+/**
+ *
+ */
+class OrderValidator extends NCEntityValidator:
+ override def validate(req: NCRequest, cfg: NCModelConfig, ents:
util.List[NCEntity]): Unit =
+ val es = ents.asScala
+
+ if !es.exists(_.getId == "ord:pizza") then
+ if es.count(_.getId == "stanford:number") > 1 then throw new
NCRejection("Error1") // TODO:
+ if es.count(_.getId == "ord:pizza:size") > 1 then throw new
NCRejection("Error2") // TODO:
\ No newline at end of file
diff --git
a/nlpcraft-examples/order/src/main/java/org/apache/nlpcraft/examples/order/components/SimpleCombiner.scala
b/nlpcraft-examples/order/src/main/java/org/apache/nlpcraft/examples/order/components/PizzaQtyExtender.scala
similarity index 60%
copy from
nlpcraft-examples/order/src/main/java/org/apache/nlpcraft/examples/order/components/SimpleCombiner.scala
copy to
nlpcraft-examples/order/src/main/java/org/apache/nlpcraft/examples/order/components/PizzaQtyExtender.scala
index 28f49626..5499f6d6 100644
---
a/nlpcraft-examples/order/src/main/java/org/apache/nlpcraft/examples/order/components/SimpleCombiner.scala
+++
b/nlpcraft-examples/order/src/main/java/org/apache/nlpcraft/examples/order/components/PizzaQtyExtender.scala
@@ -26,38 +26,35 @@ import scala.jdk.CollectionConverters.*
/**
*
- * @param id1
- * @param id2
- * @param newId
*/
-case class SimpleCombiner(id1: String, id2: String, newId: String) extends
NCEntityMapper:
+class PizzaQtyExtender extends NCEntityMapper:
private def extract(e: NCEntity): mutable.Seq[NCToken] =
e.getTokens.asScala
override def map(req: NCRequest, cfg: NCModelConfig, entities:
util.List[NCEntity]): util.List[NCEntity] =
var es = entities.asScala
- val es1 = es.filter(_.getId == id1)
- val es2 = es.filter(_.getId == id2)
+ val pizzas = es.filter(_.getId == "ord:pizza")
+ val nums = es.filter(_.getId == "stanford:number")
- if es1.nonEmpty && es2.size == es1.size then
+ if pizzas.nonEmpty && nums.nonEmpty then
+ if pizzas.size != nums.size then throw new NCRejection("Pizza and
their nums should be defined together1")
var ok = true
-
val mapped =
- for ((e1, e2) <- es1.zip(es2) if ok) yield
+ for ((e1, e2) <- pizzas.zip(nums) if ok) yield
if e1.getId == e2.getId then
ok = false
null
else
+ val (pizza, num) = if e1.getId == "ord:pizza" then
(e1, e2) else (e2, e1)
new NCPropertyMapAdapter with NCEntity:
- override val getTokens: JList[NCToken] =
(extract(e1) ++ extract(e2)).sortBy(_.getIndex).asJava
- override val getRequestId: String =
req.getRequestId
- override val getId: String = newId
-
- if ok then
- es = es --= es1
- es = es --= es2
- (es ++ mapped).sortBy(extract(_).head.getIndex).asJava
- else
- entities
- else
- entities
+ // Copy from pizza.
+ pizza.keysSet().forEach(k => put(k, pizza.get(k)))
+ // New value from size.
+ put[String]("ord:pizza:qty",
num.get[String]("stanford:number:nne").toLowerCase)
+ override val getTokens: JList[NCToken] =
(extract(pizza) ++ extract(num)).sortBy(_.getIndex).asJava
+ override val getRequestId: String =
req.getRequestId
+ override val getId: String = pizza.getId
+ es = es --= pizzas
+ es = es --= nums
+ (es ++ mapped).sortBy(extract(_).head.getIndex).asJava
+ else entities
\ No newline at end of file
diff --git
a/nlpcraft-examples/order/src/main/java/org/apache/nlpcraft/examples/order/components/SimpleCombiner.scala
b/nlpcraft-examples/order/src/main/java/org/apache/nlpcraft/examples/order/components/PizzaSizeExtender.scala
similarity index 60%
rename from
nlpcraft-examples/order/src/main/java/org/apache/nlpcraft/examples/order/components/SimpleCombiner.scala
rename to
nlpcraft-examples/order/src/main/java/org/apache/nlpcraft/examples/order/components/PizzaSizeExtender.scala
index 28f49626..52106f25 100644
---
a/nlpcraft-examples/order/src/main/java/org/apache/nlpcraft/examples/order/components/SimpleCombiner.scala
+++
b/nlpcraft-examples/order/src/main/java/org/apache/nlpcraft/examples/order/components/PizzaSizeExtender.scala
@@ -26,38 +26,35 @@ import scala.jdk.CollectionConverters.*
/**
*
- * @param id1
- * @param id2
- * @param newId
*/
-case class SimpleCombiner(id1: String, id2: String, newId: String) extends
NCEntityMapper:
+class PizzaSizeExtender extends NCEntityMapper:
private def extract(e: NCEntity): mutable.Seq[NCToken] =
e.getTokens.asScala
override def map(req: NCRequest, cfg: NCModelConfig, entities:
util.List[NCEntity]): util.List[NCEntity] =
var es = entities.asScala
- val es1 = es.filter(_.getId == id1)
- val es2 = es.filter(_.getId == id2)
+ val pizzas = es.filter(_.getId == "ord:pizza")
+ val sizes = es.filter(_.getId == "ord:pizza:size")
- if es1.nonEmpty && es2.size == es1.size then
+ if pizzas.nonEmpty && sizes.nonEmpty then
+ if pizzas.size != sizes.size then throw new NCRejection("Pizza and
their sizes should be defined together1")
var ok = true
-
val mapped =
- for ((e1, e2) <- es1.zip(es2) if ok) yield
+ for ((e1, e2) <- pizzas.zip(sizes) if ok) yield
if e1.getId == e2.getId then
ok = false
null
else
+ val (pizza, size) = if e1.getId == "ord:pizza" then
(e1, e2) else (e2, e1)
new NCPropertyMapAdapter with NCEntity:
- override val getTokens: JList[NCToken] =
(extract(e1) ++ extract(e2)).sortBy(_.getIndex).asJava
- override val getRequestId: String =
req.getRequestId
- override val getId: String = newId
-
- if ok then
- es = es --= es1
- es = es --= es2
- (es ++ mapped).sortBy(extract(_).head.getIndex).asJava
- else
- entities
- else
- entities
+ // Copy from pizza.
+ size.keysSet().forEach(k => put(k, size.get(k)))
+ // New value from size.
+ put[String]("ord:pizza:size",
size.get[String]("ord:pizza:size:value").toLowerCase)
+ override val getTokens: JList[NCToken] =
(extract(pizza) ++ extract(size)).sortBy(_.getIndex).asJava
+ override val getRequestId: String =
req.getRequestId
+ override val getId: String = pizza.getId
+ es = es --= pizzas
+ es = es --= sizes
+ (es ++ mapped).sortBy(extract(_).head.getIndex).asJava
+ else entities
\ No newline at end of file
diff --git a/nlpcraft-examples/order/src/main/resources/order_model.yaml
b/nlpcraft-examples/order/src/main/resources/order_model.yaml
index 798b1f29..400b8e5c 100644
--- a/nlpcraft-examples/order/src/main/resources/order_model.yaml
+++ b/nlpcraft-examples/order/src/main/resources/order_model.yaml
@@ -16,14 +16,7 @@
#
elements:
- - id: "ord:common"
- description: "Common words for order."
- synonyms:
- - "{pizza|food}"
- - "{drink|lemonade}"
- - "{buy|order|bring|delivery}"
-
- - id: "ord:pizza:kind"
+ - id: "ord:pizza"
description: "Kinds of pizza."
values:
"margherita": [ ]
@@ -45,27 +38,34 @@ elements:
"coffee": [ ]
"cola": ["{coca|cola|coca cola|cocacola|coca-cola}"]
- - id: "ord:confirm:yes"
+ - id: "ord:yes"
description: "Conformation (yes)."
- groups: ["confirm"]
synonyms:
- "{yes|yeah|right|fine|nice|excellent|good}"
- "{you are|_} {correct|right}"
- - id: "ord:confirm:no"
+ - id: "ord:no"
description: "Conformation (no)."
- groups: ["confirm"]
synonyms:
- "{no|nope|incorrect|wrong}"
- "{you are|_} {not|are not|aren't} {correct|right}"
- - id: "ord:confirm:continue"
- description: "Confirmation (continue)."
- groups: ["confirm"]
- synonyms:
- - "continue"
-
- id: "ord:stop"
description: "Stop and cancel all."
synonyms:
- - "{stop|cancel} {it|all|everything|_}"
\ No newline at end of file
+ - "{stop|cancel} {it|all|everything|_}"
+
+ - id: "ord:status"
+ description: "Order status information."
+ synonyms:
+ - "{order|_} {status|state}"
+
+ - id: "ord:finish"
+ description: "Order finish."
+ synonyms:
+ - "{order|I|_} {is|are|_} {ready|done|finish}"
+
+ - id: "ord:menu"
+ description: "Order menu."
+ synonyms:
+ - "menu"