RASA FormAction ActionExecutionRejection doesn’t re-prompt for missing slot - rasa-nlu

I am trying to implement a FormAction here, and I’ve overridden validate method.
Here is the code for the same:
def validate(self, dispatcher, tracker, domain):
logger.info("Validate of single entity called")
document_number = tracker.get_slot("document_number")
# Run regex on latest_message
extracted = re.findall(regexp, tracker.latest_message['text'])
document_array = []
for e in extracted:
document_array.append(e[0])
# generate set for needed things and
document_set = set(document_array)
document_array = list(document_set)
logger.info(document_set)
if len(document_set) > 0:
if document_number and len(document_number):
document_array = list(set(document_array + document_number))
return [SlotSet("document_number", document_array)]
else:
if document_number and len(document_number):
document_array = list(set(document_array + document_number))
return [SlotSet("document_number", document_array)]
else:
# Here it doesn't have previously set slot
# So Raise an error
raise ActionExecutionRejection(self.name(),
"Please provide document number")
So, ideally as per the docs, when ActionExecutionRejection occurs, it should utter a template with name utter_ask_{slotname} but it doesn’t trigger that action.
Here is my domain.yml templates
templates:
utter_greet:
- text: "Hi, hope you are having a good day! How can I help?"
utter_ask_document_number:
- text: "Please provide document number"
utter_help:
- text: "To find the document, please say the ID of a single document or multiple documents"
utter_goodbye:
- text: "Talk to you later!"
utter_thanks:
- text: "My pleasure."

The ActionExecutionRejection doesn't by default utter a template with the name utter_ask_{slotname}, but rather leaves the form logic to allow other policies (e.g. FallbackPolicy) to take action. The utter_ask_{slotname} is the default for the happy path in which it's trying to get a required slot for the first time. This default implementation of the action rejection is there in order to handle certain unhappy paths such as if a user decides they want to exit the flow by denying, or take a detour by chatting, etc.
If you want to implement the template to re-ask for the required slot using the utterance, you could replace the ActionExecutionRejection with dispatcher.utter_template(<desired template name>, tracker). However, this will leave you with no way to exit the form action without validation -- I don't know what your intents are, but perhaps you want to also incorporate some logic based on the intent (i.e. if it's something like "deny", let the ActionExecutionRejection happen so it can exit, it it's an "enter data" type of intent make sure it asks again).

Related

How to get actions from dynamically created button in RASA

I have create multiple button response with following , now how can get the action from clicking on this button click
class ActionSearchCat(Action):
def name(self):
return "action_search_cat"
def run(self, dispatcher, tracker, domain):
buttons = []
resp = requests.get('http://localhost:3001/api/categoryList/0')
if resp.status_code != 200:
# This means something went wrong.
raise ApiError('GET /tasks/ {}'.format(resp.status_code))
msg = resp.json().get('category_list').get('text')
for list in resp.json().get('category_list').get('choices').items():
for title, value in list.items():
buttons.append({"title": title, "payload": "/"+value})
dispatcher.utter_button_template(msg,buttons)
return []
So first of all, use dispatcher.utter_button_message(msg, buttons) instead of utter_button_template.
Now the buttons will be displayed on the channel to the end-user. After they click one, the next message your AI assistant receives will have the intent value (the payload from the selected button).
You'll have to write a story to handle this intent. If it is a unique intent to this button (i.e. you're not using something generic like Yes or No for your buttons) then the best approach would be to use the MappingPolicy. Either way, you'll also have to add the following story to your data as well:
## button_specific_action
* value
- corresponding_action_for_value

Get the user's value of an intent in RASA Core/NLU

I have the same question as in: Get Intent Value in RASA Core/NLU
but I want the value that the user gives for a given intent.
For example:
User: I want to take it (this sentence is an intent called: 'use_it')
Bot: ....
User: .... (Later in the chat I decide to answer with the same phrase of intent 'use it')
Bot: you said previously "I want to take it"
How can I do something like: tracker.get_slot but for intent?
I don't want the name of the last intent I want the text of a user-given intent.
Execute a custom action after the intent in which you store the intent text in a slot:
from rasa_core_sdk import Action
from rasa_core_sdk.events import SlotSet
class ActionStoreIntentMessage(Action):
"""Stores the bot use case in a slot"""
def name(self):
return "action_store_intent_message"
def run(self, dispatcher, tracker, domain):
# we grab the whole user utterance here as there are no real entities
# in the use case
message = tracker.latest_message.get('text')
return [SlotSet('intent_message', message)]
You can then use the value of the set slot within an utter template:
slots:
intent_message:
type: text
templates:
utter_last_intent:
- "you said previously: {intent_message}"
You can use tracker for the task.
text=tracker.latest_message['text']

Using JobQueue to continuously refresh a message

I'm building a Telegram bot that uses ConversationHandler to prompt the user for a few parameters and settings about how the bot should behave. This information is stored in some global variables since it needs to be available and editable by different functions inside the program. Every global variable is a dictionary in which each user is associated with its own value. Here's an example:
language = {123456: 'English', 789012: 'Italian'}
where 123456 and 789012 are user ids obtained from update.message.from_user.id inside each function.
After all the required information has been received and stored, the bot should send a message containing a text fetched from a web page; the text on the web page is constantly refreshed, so I want the message to be edited every 60 seconds and updated with the new text, until the user sends the command /stop.
The first solution that came to my mind in order to achieve this was something like
info_message = bot.sendMessage(update.message.chat_id, text = "This message will be updated...")
...
def update_message(bot, update):
while True:
url = "http://example.com/etc/" + language[update.message.from_user.id]
result = requests.get(url).content
bot.editMessageText(result, chat_id = update.message.chat_id, message_id = info_message.message_id)
time.sleep(60)
Of course that wouldn't work at all, and it is a really bad idea. I found out that the JobQueue extension would be what I need. However, there is something I can't figure out.
With JobQueue I would have to set up a callback function for my job. In my case, the function would be
def update_message(bot, job):
url = "http://example.com/etc/" + language[update.message.from_user.id]
result = requests.get(url).content
bot.editMessageText(result, chat_id = update.message.chat_id, message_id = info_message.message_id)
and it would be called every 60 seconds. However this wouldn't work either. Indeed, the update parameter is needed inside the function in order to fetch the page according to the user settings and to send the message to the correct chat_id. I'd need to pass that parameter to the function along with bot, job, but that doesn't seem to be possible.
Otherwise I would have to make update a global variable, but I thought there must be a better solution. Any thoughts? Thanks.
I had the same issue. A little digging into the docs revealed that you can pass job objects a context parameter which can then be accessed by the callback function as job.context.
context (Optional[object]) – Additional data needed for the callback function. Can be accessed through job.context in the callback. Defaults to None
global language
language = {123456: 'English', 789012: 'Italian'}
j=updater.job_queue
context={"chat_id":456754, "from_user_id":123456, "message_id":111213}
update_job = job(update_message, 60, repeat=True, context=context)
j.put(update_job, next_t=0.0)
def update_message(bot, job):
global language
context=job.context
url = "http://example.com/etc/" + language[context["from_user_id"]]
result = requests.get(url).content
bot.editMessageText(result,
chat_id = context["chat_id"],
message_id = context["message_id"])

Adding a deform form in an existing page (mako template) validator not called?

I have an existing (WIP) pyramid project, with the simplistic forms all being done by hand. As the user requirements have been steadily increasing in complexity, I wanted to integrate deform forms to simplify my own maintenance/programming tasks.
My initial test was to try for an interfield form[1], the purpose being to ensure that a certain date predates another date in the form. Here's the simplified definition for the schema and validator:-
class Schema(colander.MappingSchema):
startdate = colander.SchemaNode(colander.Date())
enddate = colander.SchemaNode(colander.Date())
def validator(form, value):
if value['enddate'] - value['startdate'] < 0:
exc = colander.Invalid(form, 'Start date must precede End date')
exc['enddate'] = 'Must be after %s' % value['startdate']
raise exc
schema = Schema(validator=validator)
form = deform.Form(schema, buttons=('submit',))
I then pass the form to my mako template and call:-
${form.render() | n}
This renders the form properly, and my date selectors work (of course, after I had to mess around with loading the correct CSS and javascripts). However clicking submit doesn't do any validation (not even the basic 'you didn't enter a value'), instead it goes right back to my view_config.
What could I be missing?
[1] - https://deformdemo.pylonsproject.org/interfield/
It turns out deform doesn't handle the validation automatically, and I have to actually call validate, something like below:-
try:
appstruct = form.validate(request.POST.items())
except deform.ValidationFailure as e:
return {'form': e.render()}

get value from text_field without submitting?

What I need- some kind of way to grab the number entered into the form in order to check it against previous records PRIOR to updating, so that if a validation error occurs, the user can be prompted to confirm before the form is submitted. Params would work, but are only returned after the form is posted- so no help. Is there an ajax call that I can pass into a ruby variable? Or perhaps some kind of ruby code that will read the input in the text box without submitting or linking?
What I'm doing- I'm trying to set up a 'manual validation' because I don't want the validation to 'prevent' from saving. Instead, it should be more like a confirmation.
If you care for context, Here's the run-down- I have a client that pays monthly deposits. We confirm these deposits over the phone through a third party. Naturally, in order to get the most accurate data as possible, we have to account for human error and other factors. A deposit this month should never be less than a deposit last month- but deposits can be "moved" from one account to another, which would make it seem like it was less. I have a form that new data is input on, and I want it to check against previous records to see if the deposit is more or less than reported previously. If less, it should ask for confirmation- an "are you sure?" kind of thing.
The code is old & outdated, should be changed from the ground up, but would take months when I have days to do this. I'm just looking for a patch.
What I have so far- note that cur_deposit is this months and rec_deposit is last months.
<%
arr1 = []
arr2 = []
is_less = false
r = #recent_inquiries.last
r.inquiry_deposits.order(:id).each do |t|
arr1 << t.cur_deposit.to_f
arr1 << t.rec_deposit.to_f
end
#inquiry.inquiry_deposits.order(:id).each do |td|
#============THIS is the part that needs help
arr2 << params["cur_deposit_text_box"]
arr2 << params["rec_deposit_text_box"]
end
i = 0
while i < (arr1.size - 1)
comp_arr1 = []
comp_arr2 = []
comp_arr1 << arr1[i]
comp_arr1 << arr1[i + 1]
comp_arr2 << arr2[i]
comp_arr2 << arr2[i + 1]
if Inquiry.compare_deposits(comp_arr1, comp_arr2) != nil then is_less = true end
i = i + 2
end
if is_less
strConf = "A deposit from last month is greater than the same deposit this month, which should not happen. Are you sure?"
end
%>
<%= submit_tag "Save Inquiry", :onclick=>"$('#submit_form').val('Save Inquiry summary');", :class => 'tgButton3', :id => 'save_inquiry_button_bottom', :confirm => strConf %>
When I get this code working, I will stash all the functioning code into a model- I just have it in the view for testing. It is safe to assume that all the 'custom methods' this script calls to are functioning. If you need code from them, I'll happily share it.
Rails version 3.0.20
Can you use jQuery on your website? (if not it is doable in plain javascript)
$('#id-of-your-field').change(function(e){
//do here your client side logic if any needed
var yourfirstvalue = $(e.target).val();
//now take the value and send it to server (your ruby stuff)
$.ajax({
url: yourURL + "/" + yourfirstvalue,
success: function(data){
//this data can be sent as JSON in structure which suits the best to you
//so you can use it to populate your second dropdown
var values = JSON.parse(data);
//use your values
}
});
});
Google "combo box example" it might help you.
In order to close this question out, and in case anybody else is wondering, I will answer my own question. This is what I've found out.
Because of the nature of the relationship between client & server, there is really no way to get the value of the text input, store it in a ruby variable, and check it against another ruby variable. Ruby script only runs once and then is rendered, so while ajax may be able to continually run in the background and gather inputs, etc, the integration with ruby falls short when talking client-side only interaction. (Correct me if I'm wrong- after all, I posted the question to get everybody's input!)
The fix: I created a switch using hidden tags. When the form loads, the hidden tag is blank. After submitting the form, the update action checks the params of the newly entered data against the numbers from last month. If the conditions check out, it saves. If not, it re-loads the page with a message. If the message is confirmed, an ajax command changes the hidden tag to "true" which bypasses the comparison once it hits the update action again. Otherwise, the data is not saved. Problem solved!
I'm making this a community wiki answer in case anybody would like to add their two cents.

Resources