Search code examples
bixbybixbystudio

How to prevent duplicate action execution in Bixby?


I want to implement a capsule that does a calculation if the user provides the full input necessary for the calculation or asks the user for the necessary input if the user doesn't provide the full input with the very first request. Everything works if the user provides the full request. If the user doesn't provide the full request but Bixby needs more information, I run into some strange behavior where the Calculation is being called more than once and Bixby takes the necessary information for the Calculation from a result of another Calculation, it looks like in the debug graph.

To easier demonstrate my problem I've extended the dice sample capsule capsule-sample-dice and added numSides and numDice to the RollResultConcept, so that I can access the number of dice and sides in the result. RollResult.model.bxb now looks like this:

structure (RollResultConcept) {
  description (The result object produced by the RollDice action.)
  property (sum) {
    type (SumConcept)
    min (Required)
    max (One)
  }
  property (roll) {
    description (The list of results for each dice roll.)
    type (RollConcept)
    min (Required)
    max (Many)
  }
  // The two properties below have been added
  property (numSides) {
    description (The number of sides that the dice of this roll have.)
    type (NumSidesConcept)
    min (Required)
    max (One)
  }
  property (numDice) {
    description (The number of dice in this roll.)
    type (NumDiceConcept)
    min (Required)
    max (One)
  }
}

I've also added single-lines in RollResult.view.bxb so that the number of sides and dice are being shown to the user after a roll. RollResult.view.bxb:

 result-view {
   match {
     RollResultConcept (rollResult)
   }

   render {
     layout {
       section {
         content {
           single-line {
             text {
               style (Detail_M)
               value ("Sum: #{value(rollResult.sum)}")
             }
           }
           single-line {
             text {
               style (Detail_M)
               value ("Rolls: #{value(rollResult.roll)}")
             }
           }
           // The two single-line below have been added
           single-line {
             text {
               style (Detail_M)
               value ("Dice: #{value(rollResult.numDice)}")
             }
           }
           single-line {
             text {
               style (Detail_M)
               value ("Sides: #{value(rollResult.numSides)}")
             }
           }
         }
       }
     }
   }
 }

Edit: I forgot to add the code that I changed in RollDice.js, see below: RollDice.js

// RollDice
// Rolls a dice given a number of sides and a number of dice

// Main entry point
module.exports.function = function rollDice(numDice, numSides) {

  var sum = 0;
  var result = [];

  for (var i = 0; i < numDice; i++) {
    var roll = Math.ceil(Math.random() * numSides);
    result.push(roll);
    sum += roll;
  }

  // RollResult
  return {
    sum: sum,           // required Sum
    roll: result,       // required list Roll
    numSides: numSides, // required for numSides
    numDice: numDice    // required for numDice
  }
}

End Edit


In the Simulator I now run the following query

intent {
  goal: RollDice
  value: NumDiceConcept(2)
}

which is missing the required NumSidesConcept.

Debug view shows the following graph, with NumSidesConcept missing (as expected). Debug Graph 1

I now run the following query in the simulator

intent {
  goal: RollDice
  value: NumDiceConcept(2)
  value: NumSidesConcept(6)
}

which results in the following Graph in Debug view:

Debug Graph 2

and it looks like to me that the Calculation is being done twice in order to get to the Result. I've already tried giving the feature { transient } to the models, but that didn't change anything. Can anybody tell me what's happening here? Am I not allowed to use the same primitive models in an output because they will be used by Bixby when trying to execute an action?


Solution

  • I tried modifying the code as you have but was unable to run the intent (successfully).

    enter image description here

    BEGIN EDIT

    I added the additional lines in RollDice.js and was able to see the plan that you are seeing.

    The reason for the double execution is that you ran the intents consecutively and Bixby derived the value of the NumSidesConcept that you did NOT specify in the first intent, from the second intent, and executed the first intent.

    You can verify the above by providing a different set of values to NumSidesConcept and NumDiceConcept in each of the intents.

    If you had given enough time between these two intents, then the result would be different. In your scenario, the first intent was waiting on a NumSidesConcept to be available, and as soon as the Planner found it (from the result of the second intent), the execution went through.

    How can you avoid this? Make sure that you have an input-view for each of the inputs so Bixby can prompt the user for any values that did not come through the NL (or Aligned NL).

    END EDIT

    Here is another approach that will NOT require changing the RollResultConcept AND will work according to your expectations (of accessing the number of dice and sides in the result-view)

     result-view {
      match: RollResultConcept (rollResult) {
        from-output: RollDice(action)
      }
    
    
       render {
         layout {
           section {
             content {
               single-line {
                 text {
                   style (Detail_M)
                   value ("Sum: #{value(rollResult.sum)}")
                 }
               }
               single-line {
                 text {
                   style (Detail_M)
                   value ("Rolls: #{value(rollResult.roll)}")
                 }
               }
               // The two single-line below have been added
               single-line {
                 text {
                   style (Detail_M)
                   value ("Dice: #{value(action.numDice)}")
                 }
               }
               single-line {
                 text {
                   style (Detail_M)
                   value ("Sides: #{value(action.numSides)}")
                 }
               }
             }
           }
         }
       }
     }
    

    Give it a shot and let us know if it works!