Advanced turns - DynamicBotTurn and BridgeTurn

As you build your conversational app, you will inevitably come to a point where hardcoding responses, variables and entities simply will not be possible, or make sense. You will have to call one, or several, APIs to fetch user information, to make an order reservation. This is typically where other (especially GUI-based) bot creation tools fail and force you to "eject" your app and hand it over to developers in some way.

This is not the case for Narratory, dynamic data and special handling of this is expected and built into the platform and thus jack in perfectly with the conversational design ultimately allowing developers and dialog designers to work side by side.

DynamicBotTurns - Calling APIs in BotTurns

To call an API in a BotTurn, you define your BotTurn as a DynamicBotTurn instead of a normal BotTurn. This will give the turn access to additional parameters:

Notably, for the DynamicBotTurn, the say parameter is optional, it has a mandatory url parameter and an optional params parameter. When the turn is executed, Narratory will call the URL and pass along all parameters that match the names given in the params array. Narratory expects a response with an optional say parameter with a string value or string array, and an optional set parameter with an object of parameters. In other words, the webhook can return a subset of BotTurn parameters. Narratory will wait for the return of the webhook unless you use Async webhooks - Fire and forget.

Important: the say parameter that comes from your API endpoint has priority over the one in the dialog script, allowing you to dynamically override the behavior in your script depending on some backend/business logic.

The following example illustrates a narrative using a DynamicBotTurn to change a variable and set a new one.

import { BotTurn, DynamicBotTurn, ANYTHING } from "narratory"
const setVariable: BotTurn = {
say: "I am now setting a variable in the script",
set: {
myVariable: "Raspberry"
}
}
const beforeCall = "The variable is currently set to _myVariable"
const setVariableDynamically: DynamicBotTurn = {
say: "This text will not be said since the API endpoint provides a say text",
url: "https://address-to-your-server",
params: ["myVariable"], // Since params accept an array (unlike for example the set parameter above which accepts an Object), we have to surround myVariable with quotes, i.e "myVariable"
}
const narrative = [setVariable, beforeCall, setVariableDynamically]

In our cloud endpoint, we could do DB lookups, call other APIs or do whatever we want. As noted above, Narratory expects the response to be a JSON object with optional say and set parameters. You can create new variables as you see fit in this fashion.

The below is an example of how an endpoint could look, in NodeJS pseudocode. You could really build this in any language or framework of your (or your developer's) preference. The parameters will be passed as a JSON. Aside from receiving parameters from the script as shown above, the sessionId of the current dialog session is always passed with the call, and can be accessed as below.

const handleRequest = (req, res) => {
console.log(`the variable previously was set to ${req.body.myVariable}`)
console.log(`the session id was ${req.body.sessionId}`)
// Here you could do things like save data to a database, fetch data from an API
// or similar. But, here we just return the following object with a set parameter:
res.json({
say: "I called an API that set myVariable to _myVariable and also added a second variable myOtherVariable set to _myOtherVariable"
set: {
myVariable: "Cloudberry",
myOtherVariable: "Blueberry"
}
})
}

The above will result in the following script:

<Chat initiated>
Bot: I am now setting a variable in the script
Bot: The variable is currently set to Raspberry
Bot: I called an API that set myVariable to Cloudberry and also added a second variable myOtherVariable set to Blueberry
<Chat ended>

Note, currently you have to provide answers statically in the script, i.e your endpoint can't return an answer-array. This might be implemented at a later point if it is requested from creators. Let us know! :-)

Async webhook calls - fire and forget

Sometimes you might want to do an API-call but the dialog is not dependent on the answer. This could for example be a call do log something or a request that takes longer time to process than you want users to wait in the dialog.

To set a webhook call to asynchronous, you add the flag asyncWebhook: true (default is false) to the DynamicBotTurn. An example follows:

const fireAndForget: DynamicBotTurn = {
say: "Thanks for your feedback!",
url: "https://address-to-your-server-accepting-the-feedback",
params: ["feedback"],
asyncWebhook: true
}

BridgeTurns, allowing sequencial BotTurns

In some cases, you want to have a BotTurn directly follow another BotTurn in nested dialogs. One example could be if you call an API in a BotTurn (as shown above) and want to handle it differently depending on the reply from the API directly in the BotTurn (as opposed to in the next bot initiative - the top-level BotTurn in Narrative). The reason why you want this is, for example, to be able to use repair to let the user try again if the API response comes back with a failed reply. An example of this follows:

const orderNumberCheck: BotTurn = {
say: "Do you have an order number?,
user: [
{ intent: intents.orderNumber, bot: {
url: "https://SOME-URL-THAT-VERIFIES-ORDERNUMBERS-AND-RETURNS-AN-ORDER-IF-VALID",
params: ["orderNumber"],
bot: [{
cond: { order: true },
say: "Excellent, I found your order"
},{
say: "It seems I can't find that order. Try saying the order number again",
repair: true
}]
}},
]
}

Above, if the user presents an order number that the API finds valid, the API returns an order variable and the dialog continues. If the API doesn't reply an order however, the bot gives the user another chance. BridgeTurns really shine in this context, but can also be used in the top-level, e.g. to do conditional greetings in one turn instead of several.

For example, instead of using several turns for serving returning and new users as shown here:

const greeting = "Hi"
const welcomeReturning : BotTurn = {
cond: { user_returning: true },
say: "Welcome back!"
}
const welcomeNew : BotTurn = {
cond: { user_returning: false },
say: "Welcome here!"
}

... you can instead write it more condensed:

const greeting: BridgeTurn = {
say: "Hi",
bot: [{
cond: { user_returning: true },
say: "Welcome back!"
}, {
say: "Welcome here!"
}]
}