Using Business Objects In Conversation Context

Tony Hickman
7 min readJan 23, 2025

--

In my previous articles I’ve explained the journey my team and I have been on to build truly natural conversations. As this work progressed we began to realise that we needed to revisit the management of data within the conversation context.

We had taken the approach of a “flat” context with all collected data being held at the same level. Although this was a simple way to start it became clear that more structure was needed, in particular a way to “collect” related information together. Hence the requirement to implement the concept of “business objects” was born.

[Now as someone who spent a lot of his early development career working with Object Oriented Languages I need to make it clear that in this case “business objects” only represent the control of attributes and not function so really more like “Business Data Structures”.]

When I thought about this requirement it dawned on me that our framework already had the core concept we needed in the shape of the A2 “journeys”. These journeys focus on the collection of information related to a specific customer objective. So basically an A2 journey repesents the “data structure” needed for the journey, which is what I needed. The main difference being that the initial A2 journeys were flat but if we supported “nesting” of an A2 journey within another A2 journey we could create the “Business Objects” we were looking for.

This is shown in the following configuration JSON where the “Get Balance” journey specifies the need for an account “Business Object” which in turn requires an address “Business Object”.

    "A2-getBalance": {
"action": {
"type": "dialog",
"name": "A1-inquiry-balance"
},
"captureType": "static",
"default": {
"confirm": "I can help you with that.",
"clarify": "Do you want me to get your balance for you?",
"response": "I can get your balance for you.",
"repeat": "I can get your balance for you.",
"example": null,
"paraphrase": "Sure I can do that.",
"verify": true,
"completionResponse": "Thank you that's all I need.",
"dataCapture": {
"numberOfItems": 1,
"dataToCapture": [
{
"variableName": "account",
"description": "account",
"justification": "I need to know which account to look at.",
"confirm": "tester",
"response": "What is your account?",
"repeat": "What is your account?",
"example": null,
"paraphrase": "The asccount you would like the balance for?",
"type": "A2-account",
"options": [],
"collected": false,
"always_ask": false,
"required": "yes"
}
]
}
}
},
"A2-account": {
"action": {
"type": null,
"name": null
},
"captureType": "static",
"default": {
"confirm": "I can certainly help you with that.",
"clarify": null,
"response": null,
"repeat": null,
"example": null,
"paraphrase": null,
"verify": false,
"completionResponse": null,
"type": null,
"owner": null,
"dataCapture": {
"numberOfItems": 5,
"dataToCapture": [
{
"variableName": "account_number",
"description": "account number",
"justification": "I need to know which account to look at.",
"confirm": null,
"response": "What is the account number?",
"repeat": "What is the account number?",
"example": "It's a ten digit number, for example 1234567890.",
"paraphrase": "What is the 10-digit number associated with the account?",
"type": "A2-account-number",
"options": [],
"collected": false,
"always_ask": true,
"required": "yes"
},
{
"variableName": "sort_code",
"description": "account sort code",
"justification": "I need the sort code to locate the account.",
"confirm": null,
"response": "What is the account sort code?",
"repeat": "What is the account sort code?",
"example": "It's three two digit numbers with hyphens between, for example 50-29-44.",
"paraphrase": "What is the 6-digit sort code?",
"type": "A2-account-sortcode",
"options": [],
"collected": false,
"always_ask": true,
"required": "yes"
},
{
"variableName": "account_name",
"description": "account name",
"justification": "I need it.",
"confirm": null,
"response": "What is the account name?",
"repeat": "What is the account name?",
"example": "It's the name",
"paraphrase": "What is the name?",
"type": "A2-string",
"options": [],
"collected": false,
"always_ask": true,
"required": "yes"
},
{
"variableName": "account_address",
"description": "account address",
"justification": "I need the post code of the account branch.",
"confirm": null,
"response": "What is the account address?",
"repeat": "What is the account address?",
"example": null,
"paraphrase": "What address is associated to the account?",
"type": "A2-address",
"options": [],
"collected": false,
"always_ask": true,
"required": "yes"
}
]
}
}
},
"A2-address": {
"action": {
"type": null,
"name": null
},
"captureType": "static",
"default": {
"confirm": "I can help you with that.",
"clarify": null,
"response": null,
"repeat": null,
"example": null,
"paraphrase": "Sure I can do that.",
"verify": false,
"completionResponse": null,
"dataCapture": {
"numberOfItems": 1,
"dataToCapture": [
{
"variableName": "postcode",
"description": "post code",
"justification": "I need to know the post code.",
"confirm": null,
"response": "What is the post code?",
"repeat": "What is the post code?",
"example": "It's a post code.",
"paraphrase": "What is post code?",
"type": "A2-postcode",
"options": [],
"collected": false,
"always_ask": false,
"required": "yes"
}
]
}
}

With this revelation in mind we started to look at how to enhance our existing framework

To support an A2 journey being nested within another A2 journey it was necessary to allow the a journey to declare a data attribute of type “Object”. This proved to be simply a matter of adding an “Object” data type to the “data collection” Action within our wastonx assistant workspace.

Object Collection

We added a step to call our backend extension to check if the “type” of the variable to collect matches a defined A2 journey in the Natural Conversation Framework (NCF) ’s configuration. If it does then we invoke the journey to collect its as a “Business Object” [remember a “Business Object” is just an A2 journey]. Once the collection has completed the collected “Business Object“” is added to the short term memory of the journey that needed to collect it. So that gave us the mechanism to declare and initial the collection of the “Business Object” but opened up another question…. How to manage journey state when “recursing”.

Anyone familar with recursion knows that it depends on a “stack” which when unwound delivers the outcome that is needed. Now I wouldn't say that we definitely implemented recursion here but we did implement a “stack” to support management of state. To do this we created an Action to handle the “pushing” and “popping” of state data into and out of an array object.

Stack Management Action

The Action is pretty self explanatory, we save our current journey state, define the journey we need to call, call it, handle the returned “business object”, revert to the original state and finally return to the parent journey.

This approach allows us to nest “Business Objects” multiple levels deep [In theory as deep as the watsonx Assistants context size can handle]. We tested to a level of three as we were struggling to see situations where nesting would realistically go beyond that.

So we now had a clean way in which we can define and collect “Business Object” but what if we have a collection of the necessary “Business Object” in out longer term memory.

The NCF has two core memory areas, journey memory which manages the “state” for an active journey, and conversation memory which holds pre-populated information along with and information collected previously during the conversation. These two “memory” areas have different structures. The “Conversation” memory is a standard JSON structure. The “journey” memory uses a data describing approach to support loose coupling between declared variables and the data collection implementation [this allows variables to be added without the need to explicitly declare/implement them in watsonx Assistant].

Back to the earlier problem statement let me use an example to help explain the requirement. If we assume we are implementing a conversational assistant for a bank we may pre-populate the “Conversation” memory with a list of “Accounts” that the customers has. Now let's assume the customer asks for their balance. This would trigger a “Balance Enquiry” A2 journey which requires an “Account Business Object”. We have a list of the accounts owned by the customer so we need to be able to resolve the required account from this list.

As we started to look at how to resolve a “Business Object” instance from a collection we realised that we would need the concept of a “key” to facilitat the location the required object. We chose to implement the definition of the “key” as part of the collection with in the “Conversation” memory. The following shows an example for a collection of account “Business Objects”.

{
"A2-account_list": {
"key": "account_name",
"values": [
{
"account_number": "1234567890",
"account_name": "Pocket money account",
"sort_code": "12-12-12",
"account_address": {
"postcode": "EH2 7ZX"
},
"owner": true,
"type": "savings"
},
{
"account_number": "99999999",
"account_name": "main account",
"sort_code": "20-12-12",
"account_address": {
"postcode": "EH12 5FG"
},
"owner": true,
"type": "current"
},
{
"account_number": "1111111111",
"account_name": "Tony's Current Account",
"sort_code": "11-11-11",
"account_address": {
"postcode": "E1 7TT"
},
"owner": false,
"type": "current"
}
]
}
}

Two things to note from the above…

  1. The collection array has an attribute “key” which defines the attribute of the contained “Business Objects” to use as the “key”
  2. The collection array uses a strict naming convention of <Object Type Name>_list

This gives us a way to detect if we have a collection of the required “Business Object” and if we do how to locate / identify the required instance. To implement this we created another Action (the sharp eye’d among you may have spotted this already in an earlier screenshot :-) ).

Handler for object lists

This handler performs the tasks

  1. Checking if a collection exists for the required “Business Object”
  2. Identifying the necessary “Business Object” if its provided in the customers initial question e.g. “I’d like the balance for my savings account”
  3. Presenting the potential “Business Objects” to the customer (using the “key”)
  4. Calling the “object collector” Action if no suitable object found

In addition we implemented the ability to “filter” a collection but thats getting into a level of detail beyond the scope of this post.

In conclusion it was a relatively simple task for us to enhance the NCF to support the concept of “Business Objects”. There are some more areas we want to work on (e.g. limiting object selection to only items in a collection) but the core capability is fully working.

--

--

Tony Hickman
Tony Hickman

Written by Tony Hickman

I‘ve worked for IBM all of my career and am an avid technologist who is keen to get his hands dirty. My role affords me this opportunity and I share what I can

No responses yet