How to get actions from dynamically created button in RASA - rasa-nlu

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

Related

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

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).

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()}

Selecting Ajax Dropdown suggestion list using Selenium for Firefox

How can i select Ajax Dropdown suggestion list item using selenium code for firefox??
My problem is :the Ajax dropdown list is visible but it is not selected and next steps gets stuck.
May be selenium is waiting for something.
the list that page populates is dynamic and in bla bla tags.
Please help with a example code.
How can i use waitfor* here.
Remember i am not using firefox ide but i am writing a code.
Please help.
I had a similar problem whereby, selenium was able to find the dropdown menu but was unable to click on the visible text. I later found out that there was an Ajax call that was populating the dropdown menu data and as a result selenium seemed to not be able to select the intended visible text because the list items had not been fully populated. That is, by the time the script was selecting my option value, Ajax had not completely loaded the menu options. Here's my solution:
public void nameOfCollegeList(String optionItem) {
// declare the dropdownMenu web element
WebElement dropDownMenu = driver.findElement(By.cssSelector("#CollegeNames"));
// click on the dropdownMenu element to initiate Ajax call
dropDownMenu.click();
// keep checking the drop down menu item list until you find the desired text that indicates that the menu has
// been fully loaded. In this example I always expect "Other (please specify)" to be the last item in the drop down menu.
// If I don't find the expected last item in the list in my if condition, execute the else condition by calling the
// same method(recursively). Please note that if the "if" statement is never satisfied then you'll end up with an
// infinite loop.
if (dropDownMenu.getText().contains("Other (please specify)")) {
new Select(dropDownMenu).selectByVisibleText(optionItem);
}
else {
nameOfCollegeList(optionItem);
}
}
i am little confused with your question at " :the Ajax dropdown list is visible but it is not selected "
this sounds like that the element is disabled. (Java coding)
if so selenium.isElementDisabled()
if not then,
1) programming laguage solution using while loop and isElementPresent() OR isElementDisabled()
//trigger the Ajax request and then
long initialTime = System.currentTimeMillis();
do{
thread.sleep(1000);
}while((!selenium.isElementPresent("AjaxElement")) && (System.getCurrentTimeMillis() - initialTime <= 5000)) ;
//some thing like above for client programming solution...but for,
2) selenium's inbuilt solution
we have a method called waitForCondition("java script to be executed", "time out value");
this method loops the javascript statement until it returns true or the supplied time out occurs
here the important thing is analyzing the application/Ajax element to find out which particular condition of the element changes.
from your explation my guess is this, display=none will be changed to display=block OR
disabled=true will be changed to disabled=false OR isReadOnly will be changed to no such attribute ect.....(you need to figure out this)
and then, use this attribute = value to build a javascript function as ,
selenium.waitForCondition("window.document.getElementById('AJAX ELEMENT').disabled == 'false'", "3000");
you can work out the above statement however you want in your programming language.
try {
//do the action which triggers the Ajax call
selenium.waitForCondition("window.document.getElementById('AJAX ELEMENT[drop down element]').disabled == 'false'", "3000");
//OR
selenium.waitForCondition("window.document.getElementById('AJAX ELEMENT').disabled == 'false'", "3000");
}
catch(SeleniumException se)
{
if((se.getMessage()).toLowerCase().contains("timed out")
throw //..some a custom exception however your organisation requires
}
selenium.select("drop down element id", "option id");
and so on.....

Resources