Rasa course, rasa training and rasa actual financial banking robot micro service call word slot Explain function
Rasa 2.x
Financial services example robot: This is an example chat robot that shows how to build AI assistants for financial services and banking. This starter kit can be used as a basis for your own development or as a reference guide for implementing common banking functions using Rasa. It includes pre built intentions, actions, and stories for handling the flow of conversations, such as checking spending history and transferring funds to another account. With Rasa open source version 2 X compatible
Document overview
-
data/nlu/nlu.yml - contains NLU training data
-
data/nlu/rules.yml - contains rule training data
-
data/stories/stories*.yml - contains story training data
-
actions.py - contains custom action / api code
-
domain.yml domain file, including robot response template
-
config. Yml- training configuration of NLU pipeline and Strategy Integration
-
tests / - end to end testing
Introduction to Gavin
Founder of star sky intelligent dialogue robot and author of AI general two-line thinking method. Now he works in the top AI laboratory in Silicon Valley. Specialized in Conversational AI In the United States, he worked in the top machine learning and artificial intelligence laboratories in Silicon Valley
Gavin big coffee wechat: NLP_Matrix_Space
Tel: + 1 650-603-1290
Contact email: hiheartfirst@gmail.com
Teaching assistant wechat: Spark_AI_NLP
The financial banking robot has five skills:
- Transfer the money to another person
- Check your revenue or expense history (with specific suppliers or as a whole)
- Answer questions about transfer fees
- Pay credit card bills
- Tell you your account balance
The robot recognizes the following fictional credit card accounts:
- emblem
- justice bank
- credit all
- iron bank
Include the following payment amounts (excluding actual monetary amounts):
- minimum balance
- current balance
Include the following suppliers (for expenses):
- Starbucks
- Amazon
- Target
Bot explain
class CustomFormValidationAction(FormValidationAction, metaclass=abc.ABCMeta): """Validates if slot values are valid and handles repeated validation failures. To use, you add the following to your bot: (-) Include these two intents: - affirm - deny (-) In domain.yml define these slots: slots: repeated_validation_failures: type: any AA_CONTINUE_FORM: type: any (-) In domain.yml, for each form, declare 'AA_CONTINUE_FORM' as first required slot. For example: forms: cc_payment_form: AA_CONTINUE_FORM: - type: from_intent intent: affirm value: yes - type: from_intent intent: deny value: no - type: from_text intent: - inform - cc_payment_form (-) In domain.yml, for each form, define the 'utter_{form}_AA_CONTINUE_FORM' response, using /affirm & /deny buttons. For example: utter_ask_cc_payment_form_AA_CONTINUE_FORM: - buttons: - payload: /affirm title: Yes - payload: /deny title: No, cancel the transaction text: Would you like to continue scheduling the credit card payment? (-) In the custom action Class that validates slots, subclass from 'CustomFormValidationAction' instead of from 'FormValidationAction'. Optionally, add an 'explain_{slot}' method for every slot that the bot should explain in some detail if the user is repeatedly provides answer that cannot be validated. For example: class ValidatePayCCForm(CustomFormValidationAction): async def explain_credit_card( self, value: Text, dispatcher: CollectingDispatcher, tracker: Tracker, domain: Dict[Text, Any], ) -> Dict[Text, Any]: # Bot utters a message that explains the slot dispatcher.utter_message( ) # optionally, you can set slots by returning a dict return {} """ # Avoids registering this class as a custom action @abc.abstractmethod def name(self) -> Text: """Unique identifier of the CustomFormValidationAction""" raise NotImplementedError("A CustomFormValidationAction must implement a name") async def validate( self, dispatcher: CollectingDispatcher, tracker: Tracker, domain: Dict, ) -> List[EventType]: """Validates slots by calling a validation function for each slot. Calls an explain function for the requested slot when validation fails MAX_VALIDATION_FAILURES in a row, and sets 'AA_CONTINUE_FORM' slot to None, which triggers the bot to utter the 'utter_ask_{form}_AA_CONTINUE_FORM' template. Args: dispatcher: the dispatcher which is used to send messages back to the user. tracker: the conversation tracker for the current user. domain: the bot's domain. Returns: `SlotSet` events for every validated slot. """ events = [] if not tracker.get_slot(CF_SLOT): events.append(SlotSet(CF_SLOT, "yes")) events.extend(await super().validate(dispatcher, tracker, domain)) events.extend( await self.repeated_validation_failures(dispatcher, tracker, domain, events) ) return events async def repeated_validation_failures( self, dispatcher: CollectingDispatcher, tracker: Tracker, domain: Dict, events: List[EventType], ) -> List[EventType]: """Updates the slot repeated_validation_failures, and sets required form slot `AA_CONTINUE_FORM` to None when the threshold is reached. This will trigger utter_ask_{form}_AA_CONTINUE_FORM, asking the user if they want to continue with this form or not. """ rvf_events: List[EventType] = [] requested_slot = tracker.get_slot("requested_slot") # Only do this while form is asking for a certain slot if not requested_slot: return rvf_events # if the requested slot was not extracted, interupt the form interrupt_form = False if not events: interrupt_form = True else: for event in events: if event["event"] == "slot" and event["name"] == "requested_slot": interrupt_form = True if interrupt_form: # Sending LoopInterrupted will prevent rasa.core from asking for the slot rvf_events.append(LoopInterrupted(is_interrupted=True)) # Sending ActionExecutionRejected will allow rasa.core to predict # something else before continuing with the form rvf_events.append(ActionExecutionRejected(action_name=self.form_name())) return rvf_events # Skip if validate_{slot} turned off the form by setting requested_slot to None for event in events: if ( event["event"] == "slot" and event["name"] == "requested_slot" and not event["value"] ): rvf_events.append(SlotSet(RVF_SLOT, 0)) return rvf_events rvf = tracker.get_slot(RVF_SLOT) if rvf: rvf = int(rvf) else: # initialize counter to 0 rvf = 0 # check if validation of the requested_slot failed validation_failed = True for event in events: if ( event["event"] == "slot" and event["name"] == requested_slot and event["value"] ): validation_failed = False break # keep track of repeated validation failures if validation_failed: rvf += 1 else: rvf = 0 if rvf >= MAX_VALIDATION_FAILURES: rvf_events.extend( await self.explain_requested_slot(dispatcher, tracker, domain) ) # reset counter rvf = 0 # Triggers 'utter_ask_{form}_AA_CONTINUE_FORM' rvf_events.append(SlotSet(CF_SLOT, None)) rvf_events.append(SlotSet(RVF_SLOT, rvf)) return rvf_events async def explain_requested_slot( self, dispatcher: CollectingDispatcher, tracker: Tracker, domain: Dict[Text, Any], ) -> List[EventType]: """Explains requested slot by calling an explain function for the slot. Args: dispatcher: the dispatcher which is used to send messages back to the user. tracker: the conversation tracker for the current user. domain: the bot's domain. Returns: `SlotSet` events for the explained slot (Optional). """ slot_name = tracker.get_slot("requested_slot") if not slot_name: return [] slot_value = tracker.get_slot(slot_name) method_name = f"explain_{slot_name.replace('-','_')}" explain_method = getattr(self, method_name, None) if not explain_method: logger.debug( f"Skipping explanation for `{slot_name}`: there is no explanation " "method specified." ) return [] slots = {} explanation_output = await utils.call_potential_coroutine( explain_method(slot_value, dispatcher, tracker, domain) ) if explanation_output: slots.update(explanation_output) return [SlotSet(slot, value) for slot, value in slots.items()] async def validate_AA_CONTINUE_FORM( self, value: Text, dispatcher: CollectingDispatcher, tracker: Tracker, domain: Dict[Text, Any], ) -> Dict[Text, Any]: """Validates value of 'AA_CONTINUE_FORM' slot""" if value == "yes": return {CF_SLOT: value} if value == "no": # This will activate rule 'Submit ---_form' to cancel the operation return { "requested_slot": None, "zz_confirm_form": "no", CF_SLOT: value, } # The user's answer was not valid. Just re-set it to None. return {CF_SLOT: None}
actions.py
class ValidateTransferMoneyForm(CustomFormValidationAction): """Validates Slots of the transfer_money_form""" def name(self) -> Text: """Unique identifier of the action""" return "validate_transfer_money_form" async def validate_PERSON( self, value: Text, dispatcher: CollectingDispatcher, tracker: Tracker, domain: Dict[Text, Any], ) -> Dict[Text, Any]: """Validates value of 'PERSON' slot""" # It is possible that both Spacy & DIET extracted the PERSON # Just pick the first one if isinstance(value, list): value = value[0] name = value.lower() if value else None known_recipients = profile_db.list_known_recipients(tracker.sender_id) first_names = [name.split()[0] for name in known_recipients] if name is not None and name in known_recipients: return {"PERSON": name.title()} if name in first_names: index = first_names.index(name) fullname = known_recipients[index] return {"PERSON": fullname.title()} dispatcher.utter_message(response="utter_unknown_recipient", PERSON=value) return {"PERSON": None} async def explain_PERSON( self, value: Text, dispatcher: CollectingDispatcher, tracker: Tracker, domain: Dict[Text, Any], ) -> Dict[Text, Any]: """Explains 'PERSON' slot""" recipients = profile_db.list_known_recipients(tracker.sender_id) formatted_recipients = "\n" + "\n".join( [f"- {recipient.title()}" for recipient in recipients] ) dispatcher.utter_message( response="utter_recipients", formatted_recipients=formatted_recipients, ) return {} async def validate_amount_of_money( self, value: Text, dispatcher: CollectingDispatcher, tracker: Tracker, domain: Dict[Text, Any], ) -> Dict[Text, Any]: """Validates value of 'amount-of-money' slot""" account_balance = profile_db.get_account_balance(tracker.sender_id) try: entity = get_entity_details( tracker, "amount-of-money" ) or get_entity_details(tracker, "number") amount_currency = parse_duckling_currency(entity) if not amount_currency: raise TypeError if account_balance < float(amount_currency.get("amount-of-money")): dispatcher.utter_message(response="utter_insufficient_funds") return {"amount-of-money": None} return amount_currency except (TypeError, AttributeError): dispatcher.utter_message(response="utter_no_payment_amount") return {"amount-of-money": None} async def validate_zz_confirm_form( self, value: Text, dispatcher: CollectingDispatcher, tracker: Tracker, domain: Dict[Text, Any], ) -> Dict[Text, Any]: """Validates value of 'zz_confirm_form' slot""" if value in ["yes", "no"]: return {"zz_confirm_form": value} return {"zz_confirm_form": None}
Schematic diagram of test dialogue
Link Github: https://github.com/RasaHQ/financial-demo/
Rasa 3.X
At rasa 3 The above codes can be modified in the X project. In validate_ Explain is called directly in the word slot function_ requested_ Slot function.
Test configuration
custom_forms: # When validation of required slots fails this many times: # (-) Bot will explain the slot if user explain_{slot} method exists # (-) Bot will ask to continue with the form or not max_validation_failures: 0
Schematic diagram of test dialogue: