Data from Dialogue History
In general, it is best not to store information about the dialogue as you go along, for use later in case it is needed; rather, it is better to extract it from the dialogue history if and when it actually is needed.
There are a few reasons for this:
- Clarity of purpose: At the point that you extract the information, you are doing it for a reason and that reason is contextually clear, e.g.:
- Extract: before handing over to an agent extract the dialogue transcript to pass as metadata along with the call - the transcript is being extracted to pass to an agent
- Store: at the beginning and end of every transaction write the input and output to a variable - the transcript is being recorded as we go along because... hmm? (search the code, find variable usages, eventually probably work out it is for handover)
- Data footprint: All information in the dialogue history will be recorded in the session logs (a session log is an xml serialised version of the dialogue history).
- Any information extracted from the dialogue history and then written to a global variable (or flow variable, integration, etc.) will become part of both the dialogue history and the session log. Therefore the storing of information extracted from the dialogue history is duplicating that information - and therefore doubling the data footprint for that information.
- This increase in data size also has an impact on memory - since the data must also be stored in memory for the duration of the session.
- This data increase in the session logs will impact query performance as there is more data to query - as well as pushing session log size upwards which risks hitting the max session size ceiling and preventing the sessions from being written to Inquire.
- In SaaS, session data is a paid for resource and therefore reduced data footprint implies reduced cost.
- Any information extracted from the dialogue history and then written to a global variable (or flow variable, integration, etc.) will become part of both the dialogue history and the session log. Therefore the storing of information extracted from the dialogue history is duplicating that information - and therefore doubling the data footprint for that information.
- Encapsulation: With the logic contained in a single place - with a single goal - it is clear what the intention of the code is and so maintaining it is a simpler, self contained task
- e.g. if the format of the transcript needed to be tweaked, or additional information needed for each transaction, this is done in a single place)
- Works well with segregation of duties and versioning having the function encapsulated in a single document without needing to modify multiple parts of the solution
- Easier to test a single function than a few disparate sections spread across the solution
- Less "moving parts" so harder to accidentally break the overall function by writing/overwriting the stored variable incorrectly.
Use case: Safetynet count
Use case: When hitting the Safetynet flow - offer to hand over if this is the third (or more) occurrence.
Extract
Here we will use a generic script in a Scripted Context which will be true if the flow it is used in has been triggered more than twice.
groovy
1// Find all "raise flow" events across all previous transactions
2def raises = _.dialogHistory.collectMany {
3 transaction -> transaction.path.findAll {
4 event -> event.type == PathElementType.RAISE_FLOW
5 }
6}
7
8// Count how many were this flow (the flow calling the scripted context)
9return raises.count {
10 it.flowId == _.thisFlow.id
11} >= 2
12// If it has previously been raised twice or more - then this is the third time
13
Use this Scripted Context in a Safetynet trigger match - or further into the flow in a transition match - to offer a handover when the user hits the Safetynet if they have previously done so 3 or more times.
Store
Add a Global variable safetynetCount
, it must be global to be persisted across safetynet flow instances. Give it a default value of 0.
In the Safetynet On Drop script, increment this variable.
groovy
1// Safetynet flow - On Drop
2safetynetCount++
3
Add a Scripted Context to test the value of this variable and use it the same way as the Extract case above:
groovy
1// Scripted Context
2safetynetCount >= 2
3
This "extra" variable:
- adds data to the session log
- is another variable to ignore when scrolling through variables to pass to integrations etc
- is a functional risk as it is possible to modify this variable incorrectly elsewhere in the solution - thus breaking the logic
- only works for the Safetynet flow - to have the same functionality in another flow, the variable and logic needs duplicating, the extract approach can be applied to any flow
Use case: What did you say?
Use case: Implement a flow to repeat the bot response if the user asks.
Extract
Element | Content |
---|---|
Intent | Can you repeat? What did you say? I beg your pardon? |
Output | I said: ${_.dialogHistory.last().answerText} |
This is over simplistic as the flow would need to respond differently if this was the first interaction - as there is no previous response to repeat.
Store
previousAnswer
Global variable - in Post-processing store the output:
previousAnswer = _.outputText
.
This "extra" variable:
- adds data to the session log - even if the user does not ever use the functionality
- is another variable to ignore when scrolling through variables to pass to integrations etc
- is a functional risk as it is possible to modify this variable incorrectly elsewhere in the solution - thus breaking the logic
Use case: Previous Variable Values
Use case: When the weather flow is launched - attempt to get previous mentioned locations from the dialogue history.
Extract
groovy
1class VariableHistory {
2 static getPreviousFlowVariableValues(_, variableName) {
3 return _.dialogHistory.collectMany {
4 transaction -> transaction.path.findAll {
5 event -> event.changedFlowVariables?[variableName]
6 }.collect {
7 event -> event.changedFlowVariables[variableName]
8 }.unique()
9 }
10 }
11}
12
Wherever this information is needed, call the above method to extract all previous values of any flow variable called location. What is then done with these values depends on the use case. For example, in an output in the weather flow, if no location is mentioned, prompt the previously used locations with:
groovy
1VariableHistory.getPreviousFlowVariableValues(_, 'location')
2
Store
Add a Global variable previousLocations
and in every flow that can receive a location, push the handled location to the Global variable.
This "extra" variable:
- adds data to the session log
- is another variable to ignore when scrolling through variables to pass to integrations etc
- is a functional risk as it is possible to modify this variable incorrectly elsewhere in the solution - thus breaking the logic
- only works for the one variable defined - to have the same for a different variable (say - the chosen date), a new variable would be needed to store the previous dates, and all flows which can retrieve a date would need to be changed to write to this global variable. With this script it would be:
VariableHistory.getPreviousFlowVariableValues(_, 'date')
.
Use case: Previous variable value - with lifetime
Use case: Get the previous value of a flow variable on flow raise. Assume (unless otherwise stated) that the location of interest is the same as the previous transaction:
weather in london
weather in london today will be grey
weather tomorrow
weather in london tomorrow will be even greyer
Extract
This will be similar to the "all previous values" approach but here we walk backwards - and only for a limited number of transactions.
groovy
1class VariableHistory {
2 static getPreviousFlowVariableValue(_, variableName, lifespan) {
3 def previousValue
4 _.dialogHistory.reverse().eachWithIndex { transaction, index ->
5 if(!previousValue && index < lifespan) {
6 def previousValueEvent = transaction.path.find {
7 event -> event.changedFlowVariables?[variableName]
8 }
9 if(previousValueEvent) {
10 previousValue = previousValueEvent.changedFlowVariables[variableName]
11 }
12 }
13 }
14 return previousValue
15 }
16}
17
Then in the variable default value:
groovy
1// location variable, default value:
2VariableHistory.getPreviousFlowVariableValue(_, 'location', 1)
3
This will mean that when the flow is raised, the value of location will be retrieved from the previous transaction (or however many transactions are defined: lifetime).
If the trigger extracts a value (for example in an After Match), then the extracted value "wins" over the default from the previous transaction - so extracting information specifically from the input still functions as expected.
Store
The only solution here is a Global variable (for each Flow variable to persist) to store the value On Flow Drop (or use the Global variable instead of the Flow variable). To also support lifetime would require using setSessVariableLifespan
).
Limitations of this include:
- As for other cases, this extra variable is more data, more to ignore, more risk from accidental change, requires defining for each variable to share
- Using
setSessVariableLifespan
will only set the lifespan in advance at write time - meaning that the lifespan is specific to the variable. Using a lifespan at interrogation (get) time means that the lifespan can be different for different use cases:- For repeated weather requests, only look back 1 transaction
- For future travel requests look back further
- Project flexibility: The lookup could be modified to define lifespan in a more project specific way e.g: transactions involving non-Safetynet/"support" flows (e.g. hello, please repeat, etc. do not get counted in the lifespan)
Use case: Hand Over: Dialogue Transcript
Use Case: At handover, provide to the agent the transcript of the session. The system allows plain text metadata to be included in the handover request, so a simple user readable form will be used.
Extract
Before handing over, walk the dialogue history and extract all inputs and outputs into a String.
groovy
1// Collect user input and bot output for all previous transactions
2String transcript = _.dialogHistory.collectMany {
3 ["user: ${it.userInputText}", "bot: ${it.answerText}"]
4 }.join(System.lineSeparator())
5
6// When used during a transaction - you can also include the "current" user input
7transcript += """
8user: ${_.userInputText}"""
9
This will produce a transcript like the following:
User: will I win the lottery?
Bot: It is certain.
User: is it going to rain?
Bot: It is decidedly so.
User: should I take a coat?
Bot: Without a doubt.
User: hand over
Store
Create a Global variable to store the transcript. Give it an empty default value of ""
.
Before and after each transaction store the user input and given output in a global variable.
groovy
1// Pre-matching
2transcript += "user: ${_.userInputText}\n"
3
groovy
1// Post-processing
2transcript += "bot: ${_.outputText}\n"
3
This will also create a variable in the system with a final value:
User: will I win the lottery?
Bot: It is certain.
User: is it going to rain?
Bot: It is decidedly so.
User: should I take a coat?
Bot: Without a doubt.
User: hand over
BUT it will also create this value in the session logs, AND for every write to the transcript variable it will do the same, so the session logs would contain:
Transaction 1
- Pre-matching
gv:s:transcript
- "User: will I win the lottery?"
- Post-processing
gv:s:transcript
- "User: will I win the lottery?"
- "Bot: It is certain." Transaction 2
- Pre-matching
gv:s:transcript
- "User: will I win the lottery?"
- "Bot: It is certain."
- "User: is it going to rain?"
- Post-processing
gv:s:transcript
- "User: will I win the lottery?"
- "Bot: It is certain."
- "User: is it going to rain?"
- "Bot: It is decidedly so."
... and so on. This is a lot more data without adding any new information. Of course, this can be stripped out at Pre-logging, but it is simpler to not add it in the first place! (And even if the value is removed during Pre-logging, it will still have an impact on the memory footprint at runtime.)