Dialogflow
As a first step towards recipe recommendation, we want a user to be able to ask for a random recipe recommendation. The idea is that the agent would randomly select one of the recipes from its database and recommend that to its user. This may not seem particularly useful at this stage, when you only have implemented Capability 1: Greet, and Self-Identify. A user cannot yet have made any requests for specific features of a recipe. However, at a later stage, when a user can also already have made such specific feature requests, adding this capability makes more sense. In these cases, a user simply may not want to add any other feature requests and ask the agent to recommend an arbitrary recipe from those that satisfy the requests they have made thus far. We want to prepare the agent for doing just that already now. The first thing to do to enable this is to create another intent in your Dialogflow agent for requesting a (random) recipe recommendation. To create such an intent, you should follow the same steps as before:
Create a new intent with a name that is indicative of its function, such as requestRecommendation.
Add Training Phrases: Add several different expressions (at least 10) as examples of how a user might ask for a recommendation for any recipe from the agent’s database. Good examples would be: “Give me a random recipe,” “Can you recommend a recipe?” and “Show me a random recipe.” Be thorough and try to cover as many phrases as you can come up with. For inspiration, you might again ask ChatGPT to generate example phrases for requesting a random recipe recommendation. But do not stop there. Also, think about what a user might say if they no longer want to add any recipe features but simply want the agent to recommend a recipe based on the conversation thus far.
Refrain from including any specific recipe features in your phrases as this may later confuse your Dialogflow agent when we add intents to allow a user to ask for such features! ChatGPT will most likely come up with phrases that include such features. Although you should not add these now, they may come in handy later when you are asked to think about specific recipe features!
Under Action and parameters make sure the box above the table is filled in with the name of your intent. You do not need to define any parameters since a recipe recommendation does not require the specification of anything specific. Keep the warning above in mind, however!
Don’t forget to press SAVE!
Test that your intent is correctly recognizing user requests by using the microphone button in the Dialogflow test console. Try various phrases and check whether what you say is classified as your recipe recommendation intent.
Prolog and Patterns
This section is about implementing the dialog management capabilities of the conversational agent but also about implementing the domain logic that the agent needs to be able to perform its task. As our conversational agent is a task-based agent for recipe recommendation, the conversational competence of our agent focuses on patterns and responses for recipe recommendation. The logic that we need to implement concerns the capabilities our agent needs to reason about user requests about recipes that are available in the agent’s database. We will first focus on adding some conversational capabilities for our agent to respond to a user request to provide a (random) recipe. As before, the approach to dialog management that we use here will require the definition of a pattern and the specification of a (textual) response to the user request. When we have completed that part, we will continue with the part focusing on implementing the reasoning capabilities for extracting such a recipe from the database in Prolog. This will require the definition of several rules for extracting a suitable recipe from the agent’s recipe database.
Patterns and responses
Conversational patterns represent natural patterns of interaction in a conversation (cf. Moore and Arar, 2019). A conversation essentially consists of a sequence of dialog moves that accomplish certain conversational and domain-specific aims of the conversational partners or actors. A pattern is a part of a conversation that is repeated and more often seen also in other conversations. The parts that are repeated are often short. Patterns therefore are typically also short sequences of dialog moves that can be reused in various dialog contexts. The part of a dialog between the user and agent that we focus on for this capability provides an example:
A: What recipe would you like to cook?
U: Please, just recommend me something.
A: What about ___*?
* insert a recipe name here (from the agent’s database)
This conversation fragment above consists of three consecutive dialog moves: the first move is performed by the agent and asks for what a user would like to cook; the second is performed by the user and should remind you of the intent you added for this capability to your Dialogflow agent above; the third move is performed by the agent again and suggests a particular recipe in response to the user’s request. This fragment also nicely illustrates that patterns can also be viewed as specifying expectations: After asking for a recommendation (request), the user will expect the agent to respond by making a suggestion (grant). Pairs of request/grant moves and similar pairs are so common that they have a name: adjacency pairs (Moore and Arar, 2019).
Our generic approach to implementing dialog management based on conversational patterns provides us with a patterned design approach that repeats several very similar steps every time. Let’s break it down:
Identify fragments of a conversation as a type of pattern and classify it using the taxonomy of Moore and Arar; we will suggest which pattern types to implement for each capability, identify the taxonomy code, and suggest slightly more concrete pattern IDs for instantiating a pattern for our recipe recommendation agent.
Identify the actors of each consecutive move in a pattern and specify an intent label for each move; when these intents are performed by the user, a Dialogflow intent needs to be defined; otherwise, if the agent is the actor, a new label needs to be provided to refer to the agent’s move (whereas we saw for the greeting pattern we can also choose to reuse the same label greeting twice, for both user and agent).
Specify the agent’s textual responses for each agent intent label that is introduced; this step concerns specifying what the agent will say when it performs its move.
The first two steps, specifying a pattern by providing an ID and its dialog moves and agent intent labels, are related to core dialog management capabilities. The third step is about providing the agent with the Natural Language Generation (NLG) capabilities it needs. Let’s go through these steps for the dialog fragment above:
Specify a Pattern Identifier:
We will classify conversation fragments where the agent starts asking what they would like to cook within Moore and Arar’s taxonomy as an A5.0 Inquiry (Agent) pattern. As all of these aim at asking the user for input on how to select a recipe from the database, we will call them
a50recipeSelect
patterns. We will encounter other variants than the example above of this pattern later and reuse the pattern ID for specifying these variants of a user asking the agent to narrow down the recipes they like too.
Specify Intent Labels and Identify the Actors of a Dialog Move:
First move: The
agent
is the actor; we suggest usingspecifyGoal
as the intent label for the move of asking a user to tell the agent more about what kind of recipe they are looking for.Second move: The
user
is the actor; we already suggested usingrequestRecommendation
as an intent label and adding that to your Dialogflow agent above.Third move: The
agent
is the actor; we suggest usingrecommend
as the intent label for the move of recommending a recipe to a user.
Note that the third and last move in our example move can be viewed as raising expectations again about the user letting the agent know that it appreciates the recommendation made. Because there is always the possibility that a user does not like the recommendation, we would like the user to confirm that they like the recipe that was recommended by the agent. We do not want to include this confirmation as part of our current a50recipeSelect
pattern but introduce another pattern ID a50recipeConfirm
for a confirmation pattern. The idea is that the agent should follow up the recommendation with this pattern and ask the user whether they like the recommendation. As we do not want to add these moves to the current pattern but do want to put this as a follow-up in the agenda of the agent after completing the a50recipeSelect
pattern, we add a special action insert(a50recipeConfirm)
that inserts the pattern a50recipeConfirm
in the agenda at the end of the a50recipeSelect
pattern. This will tell the agent how to follow up and continue the conversation after completing the pattern where a user asks for a random recipe recommendation.
Now that we have introduced all the ingredients that we need for specifying the a50recipeSelect
pattern, let's implement it in Prolog and add a pattern/1
fact to the patterns.pl
file. We’ll once do a very detailed step-by-step coding walkthrough:
Start creating a pattern fact:
Begin by typing
pattern([
for adding a new pattern fact; note that the[
is the start of a list!
Add the pattern ID:
Add as the first item on the list the pattern ID:
a50recipeSelect
.
Add the list of actor-intent pairs:
For each move in the pattern, add an actor-intent pair in the order they are supposed to occur.
Ensure these pairs are written between square brackets and separated by commas.
For example, type
[agent, specifyGoal],
to represent the first move by the agent.
Add the special agenda insert action at the end of the list:
Add
[agent, insert(a50recipeConfirm)]
to the list of actor-intent pairs.
Close the pattern fact:
End the list and close the pattern fact with a closing bracket
])
.Conclude the statement with a period
.
as it signifies the end of a Prolog statement.
Note that the intent labels we introduced above are type labels for referring to the types of moves an actor makes. They do not specify what the actor actually says. What is still missing is the output or the responses that the agent will generate. You also will need to specify these agent responses, so the agent will be able to perform the pattern. You should specify these responses in the responses.pl
, file for each of the agent intent labels that you introduce in a pattern in the patterns.pl
file.
Let’s start with the first intent label specifyGoal
which asks the user to tell more about the recipes they like. You should specify phrases that the agent can use for asking a user to tell the agent more about what kind of recipe they are looking for. Do so by adding facts of the form text(specifyGoal, "...")
where you add these phrases instead of the ...
. You can begin with adding the phrase “What recipe would you like to cook?” that we used in the example above. (If you copy-paste this text from this page, make sure the right kind of square quote symbols are used in your code: "
but not "!) But also make sure the agent can vary a bit by adding other phrases for asking the same in slightly different ways. You can also use ChatGPT again to help you come up with such phrases. As we would like our agent to be conversational, and not start talking in long paragraphs, you could ask, for example:
Generate short phrases for asking someone to tell more about the kind of recipe they are looking for.
Not all of the phrases ChatGPT will generate will be that natural to include in the responses for your conversational recipe recommendation agent. But they may inspire you to create some more natural responses. In any case, make sure that the responses you add are designed to engage a user in a conversation about their recipe preferences, guiding them to provide more specific information that the agent can use to tailor its recommendations!
The second intent label that we need to specify a response for is the recommend
intent. The example phrase above “What about ___?”, however, is incomplete. The idea is that the agent could insert a random recipe name here from the agent’s database. That, of course, would require the agent to extract one of the recipes from its database. We will look at that next. For now, to add at least something, let’s add the fact text(recommend, "What about a pasta dish?").
to the responses.pl
file. After implementing the logic for extracting recipes from the database below, you should change this into something that uses the implemented logic.
To test the pattern you just added, as before, you still need to do one more thing: In the dialog_init.mod2g
file, this time add the a50recipeSelect
pattern also to the agenda of the agent after c10
that is already in it.
You can now Run your Conversational Agent again to hear your agent ask you for your recipe preferences.
Note that your team must have also added the recipe recommendation page (but not the recipe confirmation page; see Visuals section below) to enable you to respond to the agent’s inquiry. This page is needed to display a microphone icon that you will need to respond to the agent.
Extracting a recipe from the database
We will now proceed with implementing the logic for retrieving recipes from the agent’s recipe database. All of the recipes the agent knows about can be found in the recipe_database.pl
file. This file that is provided to you at the start of the project stores about 900 recipes. To get an understanding of how this file is organized, each team member should add one of their favorite recipes to the database (your team thus should add a total of six recipes).
Each team member: add one of your favorite recipes to the end of the recipe_database.pl
file. Add a comment to the file using %
at the start of a line in the file to let us know that it was you who added the recipe. Make sure to add all the needed predicates! One way of doing that is by copy-pasting an already existing recipe in the database and modifying it.
There are two details that you should think about:
Coordinate with your team members which recipe ID (a number of the form
'<PositiveInteger>'
) your recipe will have (they should be different from any other recipe ID in the database to ensure consistency!).Check the ingredients that you add to the database for your recipe. There already are a very large number of ingredients present in the database and you should not introduce a new name for an ingredient that is already in the database to ensure consistency!
After adding your recipe to the database, commit the file to your team's git repository. You will need to merge your changes. For more on how to do that, check out the https://socialrobotics.atlassian.net/wiki/spaces/PM2/pages/2215706657/Project+Background+Knowledge#Git-Commands section on how to merge and resolve conflicts. (You should not have conflicts but just in case, that section should help you resolve those.)
Updating the memory of the MARBEL agent when a recommendation request is made
Before we add Prolog code for retrieving recipes from the database, we will first add an action rule to the MARBEL agent for updating its conversational memory. The agent uses a memory(KeyValuePairList)
fact for recording user parameter input (the predicate is declared in the dialog.pl
file. It is used to store a list of entity key-value pairs that are updated during the conversation. We have already seen one example of such a key-value pair before the button='start'
. We now want to add another key-value pair to keep track of the recipe that has been selected of the form recipe= RecipeName
. Here, the idea is to add such a key-value pair whenever a user requests a recommendation for a recipe (when they do not want to provide more features that can be used to select a recipe, for example). When a user asks for just a recommendation, we want the agent to randomly select a recipe from all the recipes that remain after filtering all recipes using the features that have been provided already (in our case, for now, that is no feature).
The action rule that we will add should be added to the dialog_update.mod2g
file. So please open this file in your text editor and look for a comment about a random recommendation rule in this file. The rule should be added to the section labeled Event processing in the file. We provide the rule that you should add below and you can simply copy-paste it at the right location in the file. Add the following MARBEL action rule:
if intent(requestRecommendation, _, _, _, _), recipesFiltered(RecipeIDs), random_member(RecipeID, RecipeIDs), recipeName(RecipeID, RecipeName), Params = ['recipe'= RecipeName], updateMemory(Params, OldMemory, NewMemory), log_info("memory: ", NewMemory, Info) then delete(memory(OldMemory)) + insert(memory(NewMemory)) + log(Info).
We briefly explain the idea behind this action rule. The first query intent(requestRecommendation, _, _, _, _)
makes sure that the rule is only applicable when a requestRecommendation
intent has been recognized, indicating that the user wants the agent to recommend a recipe now. The second query recipesFiltered(RecipeIDs)
then retrieves a list with all IDs of recipes that satisfy the requests made thus far (if any) and the third query random_member(RecipeID, RecipeIDs)
then selects a random recipe from that list. The recipeName(RecipeID, RecipeName)
query then retrieves the name of the selected recipe, and the remaining code computes how the memory should be updated with the key-value pair 'recipe'= RecipeName
. The action part of the rule then updates the conversational memory by removing the old one and inserting the new one. Most rules in the dialog_update.mod2g
file, like this one, log changes to a log file. Finally, make sure to save your changes when you have added the action rule.
The action rule that we just introduced sets a clear agenda for what we still need to do. If you check the Problems console in Eclipse, you should see that recipesFiltered(RecipeIDs)
has not been defined yet. We will also still need to define a predicate for retrieving all the recipe IDs from the database, and introduce a predicate for retrieving the selected recipe from the agent’s conversational memory. We will now do so, starting with the predicate for retrieving the selected recipe from the conversational memory.
Retrieving recipes
The logic for retrieving recipes from the agent’s database should be implemented in the recipe_selection.pl
file. For this capability, we will add some basic rules to retrieve the recipe that has been selected and make a start with the rules the agent needs for retrieving only those recipes that satisfy the requests a user made (their preferences, constraints).
Retrieving a recipe from conversational memory
The first rule you will be working on is a rule for defining currentRecipe(RecipeID)
. This rule will first retrieve a recipe name from the agent’s conversational memory. The idea is to look up the the recipe name in memory (assuming it is there, otherwise the rule will simply fail) using the predefined memoryKeyValue("recipe", RecipeName)
predicate (it is defined in the dialog.pl
file, check it out). We then use recipeName(RecipeID, RecipeName)
to retrieve the RecipeID
matching the recipe’s name from the database. This matching or mapping is essential as it translates a user's choice, i.e. a selected recipe stored with its recipe name in conversational memory, into an identifier of a recipe that can subsequently be used to retrieve other recipe features from the recipe database too (these features have been indexed using recipe identifiers, not the recipe’s name!).
The (implicit) assumption made here is that recipe names are just as unique as recipe IDs. You should therefore make sure that this assumption remains valid and check this for the recipes you added to the database above.
If this assumption is not valid, what would be the consequence? To answer this question, you should take into account that Prolog uses the order of the rules in a database. If there are two recipes with the same name but different recipe IDs, which recipe would be retrieved from the database when the agent would use the recipe ID of a recipe?
Now add a rule for currentRecipe/1
to the recipe_selection.pl
file (find where to do so by locating the comment about this capability). We walk you through all of the components of the rule:
The head of the rule should be:
currentRecipe(RecipeID)
;RecipeID
is a variable that will hold the ID of the recipe stored in the conversational memory.The body of the rule consists of two consecutive queries:
memoryKeyValue('recipe', RecipeName)
; this query fetches the name of the recipe stored in memory under the key'recipe'
.recipeName(RecipeID, RecipeName)
; this query matches the recipe name fetched from memory to a recipe ID.
Fixing the response for the recommend
intent
Now that we have a way to get the recipe the agent thinks the user would like by retrieving the recipe stored in conversational memory, you can also fix the issue with our previous ad hoc solution for defining a textual response for the recommend
intent. Using the currentRecipe(ID), recipeName(ID, Name)
, look again at the rule for the textual response for the selfIdentification
intent, and introduce a similar rule that makes the agent say “What about ___” where the “___” is replaced with the recipe name.
Retrieving recipes from the recipe database
The second rule will define recipeIDs(RecipeIDs)
for collecting all available recipe IDs from the recipe database. We can implement this rule using the Prolog built-in predicate setof/3
. The idea is to use a setof(+Template, +Goal, -Set)
query with recipeID(RecipeID)
as the goal, to fetch every unique instance of RecipeID
by using that as the template. The result will be collected in RecipeIDs
by instantiating the set argument with that variable. In other words, this query will go through the database, look up every distinct RecipeID
, and then add these IDs to a list that is returned in RecipeIDs
. Now add a rule for recipeIDs(RecipeIDs)
:
The head of the rule should be:
recipeIDs(RecipeIDs)
.The body of the rule consists of a single query: use the
setof/3
predicate to specify the query for collecting all recipe identifiers from the database using the hints above.
The third rule defines recipesFiltered(RecipeIDs)
for filtering recipes. This rule will be essential for developing a conversational recipe recommendation agent. The rule will be designed to filter the entire list of recipes in the database, using requests, preferences, or constraints (which we will simply call filters) specified by the user. As the rule combines several complex queries using predefined predicates, we will walk you through the definition of this rule step-by-step:
The head of the rule: use
recipesFiltered(RecipeIDs)
as the head of the rule.The body of the rule:
First query: Retrieve all recipe IDs: Use the
recipeIDs(RecipeIDsAll)
query that we just defined above to get a list of all recipe identifiers from the recipe database.Second query: Fetch all filters from conversational memory: For fetching the set of filters that have been stored in the agent’s conversational memory, we use the predefined predicate
filters_from_memory(Filters)
(you can find the definition of this predicate in thedialogflow.pl
file). At this stage of the project, there is nothing to do for this predicate yet, and an empty list (of filters) will be returned, i.e.Filters=[]
. It will be useful, however, to already add it to complete the implementation of the rule that we are defining.Third query: Filter the recipes: Assuming that a (right now still empty) list of filters has been retrieved, in hand, the idea is to apply the filters to the list of recipes to retrieve only those recipes that meet the criteria specified by each filter. We assume that we have a predicate
recipesFiltered(RecipeIDsAll, Filters, RecipeIDsFiltered)
that does exactly that. You should use this as the third query of the rule to produce a list of recipesRecipeIDsFiltered
that only consists of identifiers of recipes that match all filters.Fourth query: Remove duplicates: We still need to add a final query for eliminating any duplicate entries in our filtered list of recipes. As we will be making use of the builtin
findall
predicate later to define the filtering process and the use of that predicate may result in duplicates in our list, you should add thelist_to_set(RecipeIDsFiltered, RecipeIDs)
query to convert the list of filtered recipe identifiers into a set, effectively removing any duplicates, to complete the definition.
In the definition of our last rule, we assumed that recipesFiltered(RecipeIDsAll, Filters, RecipeIDsFiltered)
is defined. We therefore need to make sure that this query will succeed to make our code do something. As discussed above, the list of filters will still be empty. For now, it will thus be sufficient to simply add a fact to our Prolog code that covers the base case (the empty list) of a definition that filters all recipes by recursively going through the list of filters.
You should add
recipesFiltered(RecipeIDs, [], RecipeIDs)
to therecipe_selection.pl
file.
This clause specifies that if there are no filters to apply (i.e., the filter list is empty), the output of this filtering of a list of RecipeIDs
thus remains unchanged and is that same list of recipe identifiers RecipeIDs
.
Visuals
Recipe recommendation page
The objective of this capability is to create a page that facilitates users in having a conversation about recipes with the agent. We will walk you through the detailed steps to illustrate how to implement the code for such a page below. You should add the code for a page to the html.pl
file.
At this stage, for this capability, we will only provide code for a very basic skeleton page that provides the minimal functionality needed to implement the main requirement for this page: enable a user to talk to the agent using a microphone button. The design is kept minimal below and in the step-by-step walkthrough below we will only use a single and simple alert element to style the page. As the recipe recommendation page will be the first interaction point for users who are actually interested in finding a recipe, we leave it up to you to try and make this page more inviting and add clear and useful content that the agent will show. Note that we will also ask you to extend this page at a later stage when you are implementing Capability 5: Filter Recipes by Ingredients.
Step 1: Adding the head of a Prolog rule for a recipe recommendation page.
The Prolog predicate that we use for displaying a webpage is the
page/3
predicate. We usepage(+Name, +Txt, -Html)
as the head of a rule for generating the HTML code returned in the output argumentHtml
for a page calledName
. For the name of the page that we need to specify in the first argument of the predicate, as a convention, we choose to use the pattern ID of a conversational pattern in the agenda. The idea is that the page is shown when the conversational pattern is ongoing. We want to show our recipe recommendation page when thea50recipeSelect
pattern is active. As we do not use the second argument here, you should add the following code to thehtml.pl
file (as indicated also in that file itself):page(a50recipeSelect, _, Html) :-
Step 2: Adding a condition for when to display the page.
The condition that we always will add to a rule for generating pages is a condition to only show a page when the associated pattern, in our case
a50recipeSelect
, is the currently active top-level pattern. We can use the pre-defined predicatecurrentTopLevel/1
for this (check out its definition in thedialog.pl
file). Add the following condition to the rule that we are implementing:currentTopLevel(a50recipeSelect),
Step 3: Generating the HTML code for the page.
Implement the code for generating the HTML code for the page after the condition we just added. For now, we will just add a simple https://www.w3schools.com/bootstrap4/bootstrap_alerts.asp to the page to inform the user about the recipe recommendation task the agent and the user should engage in. We use the
div/4
predicate (see the Visual Support Guide) to add an alert class with a centered heading text. Add the following code to the rule that we are implementing:div('<center><h1>What recipe are you looking for?</h1></center>', 'alert alert-success', '', MainElementContent),
Step 4: Combining the different HTML elements (rows).
We have kept things here very simple and just added one HTML element to our page above. When you add more elements, typically you will have to combine them using a built-in Prolog predicate such as
atomic_list_concat/2
to piece everything together. This time we have nothing to do here.
Step 5: Create the main element for the HTML page.
Finally, use the
html/4
predicate to create the main element for your HTML page that will be added to the body of the HTML page. You should feed the HTML code generated above (and pieced together, if needed), into the first argument of this predicate. In our case, we should use the output argument of thediv/4
predicateMainElementContent
that we introduced above. The second is used to indicate whether you want your page to have a header (usetrue
when you do,false
if not) and the third argument is used to indicate whether you want your page to have a footer. The header is predefined in thehtml.pl
file and displays a microphone button. Finally, the generated code for the main element is returned in the fourth output argumentHtml
. Add the following code to the rule to finish implementing it:html(MainElementContent, true, true, Html).
If your team has implemented the Dialogflow intents and patterns for this capability, you can already test the page you just implemented for the a50recipeSelect
pattern. You don’t need the extraction logic for retrieving a recipe from the database for this.
So try and Run your Conversational Agent again to see the page you just created. Also, check out the MARBEL agent’s state using the introspector. You should see that the top-level pattern that is at the front of the session is a50recipeConfirm
after you tell your agent to just show a recipe.
Recipe confirmation page
The next page that we want you to add is a recipe confirmation page. At the end of the pattern enabling a user to request a (random) recipe, the agent inserts the a50recipeConfirm
pattern ID in the agenda. No pattern has been added for this pattern ID yet, but because it will appear at the top level in the agent’s agenda, we can already create a confirmation page for the recipe that is selected. The idea is that this page allows the user to preview the recipe and indicate whether they are satisfied with the recipe (confirm) or not (disconfirm). To enable a user to make this decision, the main requirement for this page is that it shows the recipe’s name, and what the result of cooking the recipe will look like (a picture of the recipe). A second requirement is that the page shows a microphone button to enable the user to inform the agent about whether it wants to (dis)confirm the recipe. Note that at a later stage, we will ask you to extend the recipe confirmation page when you implement Capability 6: Filter by Number of Ingredients.
For the design, a https://www.w3schools.com/bootstrap4/bootstrap_cards.asp element seems a suitable element for styling the page. We will use it below to create a first (but still simple) recipe confirmation page with a basic recipe card. We will want to generate the following HTML code for this card:
<div class="card" style="width:400px"> <div class="card-body"> <h4 class="card-title">A RECIPE TITLE HERE</h4> </div> <img class="card-img-bottom" src="..." alt="Card image"> </div>
We will also use various predicates to collect the information about the recipe we want to display from the agent’s database and memory. We will briefly introduce those predicates here first:
currentRecipe(-RecipeID)
: predicate for retrieving the currently selected recipe ID from the agent’s memory; you are asked to implement this predicate above in the Prolog and patterns section.recipeName(ID, Name)
: Retrieves theName
of a recipe uniquely identified byID
from the agent’s recipe databaserecipe_database.pl
.picture(ID, URL)
: Retrieves theURL
to a(n online) recipe picture for the recipe uniquely identified byID
from the agent’s databaserecipe_database.pl
.
Step 1: Adding the head of a Prolog rule for a recipe confirmation page.
We use the
page/3
predicate again to define the head of a rule for generating the recipe confirmation page HTML code. Add the following code to thehtml.pl
file (as indicated also in that file itself):page(a50recipeConfirm, _, Html) :-
Step 2: Adding a condition for when to display the page.
You should add a condition such that the recipe confirmation page is only shown when the associated pattern
a50recipeConfirm
is the currently active top-level pattern. Add the following condition to the rule that we are implementing:currentTopLevel(a50recipeConfirm),
Step 3: Generating the HTML code for the page.
Before we can even begin to create the recipe card that we have in mind, we need to retrieve the ID for that recipe. So we begin by doing that. Add the following code to the rule:
currentRecipe(ID),
Now we can begin to generate the HTML code for this recipe. We first generate the code for the recipe picture and use the
picture(ID, URL)
to retrieve the URL to the recipe’s picture to fill in the...
for the src attribute in the HTML card template above and theimg/4
predicate defined inhtml.pl
for creating an image element. Add the following code to the rule:picture(ID, URL),
img(URL, 'card-img-bottom', '', ImageElement),
Next, we generate some HTML code for a card title heading. We want the recipe name to appear as the card’s title and will retrieve that using the
recipeName(ID, Title)
predicate and use the predefined to_upper_case/2 predicate (seeutils.pl
file) to make sure the recipe title starts with a capital. To fill in the recipe title in the right place in the card title heading template, we useapplyTemplate/3
to replace the placeholder~a
in a template for a card title. Add the following code to the rule:recipeName(ID, Title), to_upper_case(Title, UpperTitle),
applyTemplate('<h4 class="card-title">~a</h4>', UpperTitle, TitleHeading),
The next part that we will generate is the card body element using the
div/4
predicate. We want our card title to be the content of that element and use that as the first argument of our div/4 predicate (the first arguments of our predefined predicates by convention are the content of an element, see also the Visual Support Guide). Add the following code to the rule:div(TitleHeading, 'card-body', '', CardBodyElement),
Finally, we need to combine the card body and image elements (check for yourself to see that they are combined within the card element HTML code above) and create the card element. We use the
atomic_list_concat/2
predicate for combining the elements together and thediv/4
predicate again to create the card element. Add the following code to the rule:atomic_list_concat([CardBodyElement, ImageElement], CardContent),
div(CardContent, 'card', 'width:400px', MainElementContent),
Step 4: Combining the different HTML elements (rows).
We have kept things again simple here and just created one HTML element for our recipe confirmation page. We therefore have nothing to do here either.
Step 5: Create the main element for the HTML page.
We use the
html/4
predicate again to create the main element for your HTML page that will be added to the body of the HTML page. You should feed the HTML code generated above (and pieced together, if needed), into the first argument of this predicate. In our case, we should use the output argument of thediv/4
predicateMainElementContent
that we introduced above. As before, we include a header and footer. Add the following code to the rule to finish implementing it:html(MainElementContent, true, true, Html).
We have introduced the bare essentials for these pages but you should feel free to modify it in any way you like to make it look more appealing. However, please note that the a50recipeConfirm
page is extended later to include recipe information. More information about these extensions can be found here: https://socialrobotics.atlassian.net/wiki/spaces/PM2/pages/2229600267/Capability+6+Filter+by+Number+of+Ingredients#Visuals.
It is important that you make sure that the requirements marked in bold in the text are met.
Test it Out
If you have not yet done this, add the a50recipeSelect
pattern ID to the agent's agenda in the dialog_init.mod2g
file. When you finished implementing this capability and you Run your Conversational Agent again, you should be able to conduct the following conversational interaction and go through the following dialog moves:
After the Start page, when you click the Start button,
You should see the welcoming page.
The agent starts by greeting you by self-identifying it (moves
greeting
,selfIdentification
).You should be able to greet the agent (
greeting
).You should see the recipe recommendation page.
The agent asks you what kind of recipe you would like (
specifyGoal
).You should be able to say that you just want some recipe (
requestRecommendation
).The agent then suggests a random recipe (
recommend
).You should see the recipe confirmation page.
You can also check whether the intent recognition works. In your Dialogflow agent in the Training Tool, you should be able to see a conversation listed with a greeting
and requestRecommendation
intent. You also should be able to see the SIC server received those intents with transcripts from Dialogflow in the terminal where you started the server. You should see something like this:
The conversational agent at this stage is not able to handle misunderstandings or speech recognition errors. Dialogflow will return a default fallback intent (check the intents in your Dialogflow agent) if it is not able to classify speech into one of the intents created. We have not yet introduced a pattern, however, needed for handling such a fallback intent and the agent will therefore get stuck if speech recognition fails. Dialogflow should not have too much trouble with recognizing a simple greeting phrase, however.
To better understand how the MARBEL agent displays the pages, check out the dialog_generation.mod2g
file. Each of the action rules has code for generating the HTML code using the page(Name, Txt, Html)
query and for performing the renderPage(Html)
action.
All done?
Then proceed with Capability 3: Select Recipes by Name.