Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up

Step Into the AI Era: Chatbots that know if you are angry - a workshop to build a chatbot using Rasa

License

NotificationsYou must be signed in to change notification settings

Cheukting/rasa_workshop

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

59 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

In this workshop, we will be usingRasa, an open source machine learning framework, to build a chatbot that will ask for an individual's contact details (compliant to GDPR) and for feedback for an event that they may have attended. Feedback will then be analyse for sentiment and reported in a basic web app.


Table of Contents


Install Rasa and set up the environment

Install python

This workshop uses Python >= 3.6. Make sure you have Python >= 3.6 available on your machine. You are recommended to use environment controls (conda or virtualenv) described below.

If you are using Mac or Linux, the best way to manage multiple Python environments is to usepyenv. If you don't have Python installed, you can skip installing Python directly on your machine. Instead,install pyenv andpyenv-virtualenv for creating different virtualenv environments with different versions of Pythons.

Another way to have Python on your machine (either Windows, Mac or Linux) is to downloadAnaconda orMiniconda, you will then create conda enviroments in the following step.

Create new environment

Open a terminal.

Clone this repo from Github:

git clone https://github.com/Cheukting/rasa_workshop.git

Enter the directory:

cd rasa_workshop

Create a newconda orvirtualenv environment.

conda create --name rasa_workshop python=x.xwherex.x is the python version (Rasa require python>=3.5)

or

pyenv virtualenv <version> rasa_workshopwhere<version> is the python version (Rasa require python>=3.5)

Notes:If virtualenv is too difficult to set up (e.g. using Windows) and already have Python >= 3.6 installed, you can usevenv instead

Activate the environment byconda activate rasa_workshop orpyenv activate rasa_workshop

While you are in the new environment, install the requirements:

pip install -r requirements.txt

Notes:If you are using conda and have problems with pip install, you may try installing individual packages usingconda-forge

Create a new project

Creat a new directory (you could replacemy_chatbot to any name you like):

mkdir my_chatbot

Go to the directory:

cd my_chatbot

Initiate a project:

rasa init --no-prompt

Rasa will create a list of files for you, but we mostly care about the following:

  • actions.py : code for your custom actions
  • config.yml : configuration of your NLU and Core models
  • data/nlu.md : your NLU training data
  • data/stories.md : your stories
  • domain.yml : your assistant’s domain

We will explain what they are and how to set them up in this workshop.

NLU and Pipeline setup

First we will need to train the NLU, which is a natural language processing tool for intent classification and entity extraction.

Opendata/nlu.md with the text editor or IDE of your choice.

In the file, we see that some examples for different intents are already supplied. The intents are defined by lines starting with##. Intents are a way to group messages with the same meaning, and example messages are provided below each intent. NLU's job will be to predict the correct intent for each new message your user sends your chatbot.

For our use case, since we will be doing sentiment analysis usingNatural Language Toolkit (NLTK), we can delete the sections formood_great andmood_unhappy.

Feel free to add more examples for the other intents: the more examples, the better the understanding of NLU and your chatbot.

Collecting user's data is one of the goals of our bot. To enable this, we need to add more intents for data capturing, such as:self_intro,give_email,give_tel.

Here are some examples for the additional intents, please feel free to add more:

## intent:self_intro- I am [Mary](PERSON)- My name is [Anna](PERSON)## intent:give_email- my email is [joe@aol.com](email)- [123@123.co.uk](email)## intent:give_tel- my number is [01234567890](tel)- contact me at [07896234653](tel)

We can see that below the intents the examples have a slightly different structure than before. That is because each example contains an entity, which is a specific part of the text that needs to be identified. An entity has two terms and these function as a key-value pair:[entity](entity name). Often we would like the named entity to map to more than one entity and thus we can give multiple examples for each entity name, as we do above. We now have three entities:PERSON,email andtel.

PERSON is a entity provided by SpaCy. To help captureemail andtel, we will also useregex. To do so, put this innlu.md as well:

## regex:email- [\w-]+@([\w-]+\.)+[\w-]+## regex:tel- (0)([0-9][\s]*){10}

If you are a regex expert, you can change it to a better expression. 😉

After that, we have to setup theNLP pipeline, which can be done by editingconfig.yml. This configuration file defines the NLU and Core components that your model will use.

Inconfig.yml change thesupervised_embeddings topretrained_embeddings_spacy so that we use the pretrained SpaCy embedding pipeline. You can find out more about NLU pipelineshere.

Train and test NLU

The following 2 commands download and set up the Spacy model that we will be using. In the terminal:

python -m spacy download en_core_web_md-2.0.0 --directpython -m spacy link en_core_web_md en

Then we tell rasa to train the NLU.

rasa train nlu

The trained model should be saved undermodels/.

Notes:You can ignore all the future warnings for now as we will only use the current version in the workshop.

Now we can test the NLU model that we trained:

rasa shell nlu

After loading (may take a moment) you can type in messages and see the prediction that the NLU returns. If you are not happy with the result, you can go back and add more examples to thenlu.md and then train the NLU again (rasa train nlu). Repeat the training and testing until you are happy.

Congratulations, you have complete 1/3 of the workshop, feel free to take a 3 mins break

Now we will train our chatbots how to respond to messages. This is called dialogue management, and is handled by Rasa Core.

Planning the conversation

In this part, we will write the plan for the flow of the conversation. It will be written indata/stories.md. The flow of the conversation will be broken into 3 parts:

  1. greeting -> ask if user has attended event:

    yes -> (go to part 2.a)

    no -> (go to part 2.b)

  2. a) ask for feedback -> ask if we can contact them

    b) encourage them to go next year -> ask if we can contact them

    yes -> (go to part 3.a)

    no -> (go to part 3.b)

  3. a) contact form and see you next year

    b) see you next year

If you open and editdata/stories.md, you can see that there are example stories already written. The story## say goodbye enables the user to end the conversation at anytime. Keep## say goodbye and delete the rest of the file. We will write our own stories for the conversation flow outlined above.

The skeleton for the 3 parts of our conversation flow looks like this:

## greetings* greet  <something>> check ask experience## I have been to the event> check ask experience* affirm  <something>> check ask contact## Not been to the event> check ask experience* deny  <something>> check ask contact## get contact info> check ask contact* affirm  <something>## do not contact me> check ask contact* deny  <something>

We will fill in<something> later. The lines with>, e.g.> check ask experience, are a checkpoints which link the different parts of the stories together. So instead of creating multiple dialogue stories where users answer the questions differently, we can use checkpoints to map different paths.

Line starting with* are for when our chatbot recognises an intent. For example,* affirm will trigger when the NLU predicts anaffirm intent.

Domain and templates

We also need to tell the chatbot what action to take and what to answer when it reaches certain points in the conversation. To do this, we define aDomain for our chatbot, which is our chatbot's 'universe'. Your chatbot's domain specifies theintents,entities,slots, andactions your bot needs to know about. We will define these further below. The domain is recorded indomain.yml. If you open updomain.yml, you can see theDefaultDomain. You can delete the contents of that file and then we'll create a domain for our chatbot.

The NLU model has defines theintents, andentities which need to be in the bots domain.

Adding intents

Remember the intents we defined innlu.md? Let's put them indomain.yml:

intents:- greet- goodbye- affirm- deny- self_intro- give_email- give_tel

Adding entities

Similarly, add the entities that we defined innlu.md:

entities:- PERSON- email- tel

We will also be addingslots andactions to our bot's domain.Slots store information that we want to keep track of in a conversation.Actions are things your bot can do, including:respond to a user,make an external API call,query a database, orjust about anything!

We'll also be adding optional additional information to our bot's domain, includingformsandtemplates.

Adding slots

Slots enable us to store information about a conversation and the user having that conversation. For our chatbot, we would like to gather each user'sname,email andtel number and userfeedback:

slots:  name:    type: unfeaturized  email:    type: unfeaturized  tel:    type: unfeaturized  feedback:    type: unfeaturized

unfeaturized means that this information does not affect the flow of the conversation.

Adding forms

To capture a user's contact information and feedback, we will use form actions. Let's define them like this for now:

forms:  - experience_form  - contact_form

Adding actions

There are two main kinds of actions for our chatbot. The first are Utterance Actions, which send a message to a user and start withutter_. The second are Custom Actions, which run code you have written to enable your chatbot to perform specific custom actions. Our form actions are a type of custom action, and we will write the code for those actions further in the workshop.

For now, we will define ourutter actions.

actions:- utter_greet- utter_happy- utter_goodbye- utter_thanks- utter_ask_contact- utter_ask_experience- utter_ask_name- utter_ask_email- utter_ask_tel- utter_ask_feedback- utter_submit- utter_wrong_email- utter_wrong_tel- utter_encourage

These are the different utterances of dialog for our chatbot. You will see them come into place as we complete our chatbot. You may come back and change theutter actions later if you want.

Adding templates

Now we will add Utterance Templates, which are the messages our chatbot will send to the user. We need to define the response text for eachutter action listed in our domain. If we have more than one response text for anutter action, then one of them will be chosen at random for the chatbot's response. It's good design to have multiple responses so as to generate variety in your bot's dialogue.

For the utterance templates, the utterance can be used directly as an action.

templates:  utter_greet:  - text: "Hello! My name is Alex."  utter_happy:  - text: "Great!"  - text: "Awesome!"  utter_goodbye:  - text: "Bye!"  - text: "Have a nice day!"  utter_thanks:  - text: "Thank you for chatting, please feel free to talk to me again."  utter_ask_contact:  - text: "Do you want to be contacted regarding EuroPython next year?"  utter_ask_experience:  - text: "Have you been to EuroPython this year?"  utter_ask_name:  - text: "What's your name?"  utter_ask_email:  - text: "What's your email address?"  utter_ask_tel:  - text: "What's your contact number?"  utter_ask_feedback:  - text: "So, how was your experience in EuroPython?"  utter_submit:  - text: "You information collected will not be shared to 3rd party."  utter_wrong_email:  - text: "This doesn't look like an email..."  utter_wrong_tel:  - text: "This doesn't look like a phone number..."  utter_encourage:  - text: "It's a shame, we would like to meet you there next year."

Please feel free to change the response texts and add more response texts for eachutter action.

For now, we are done withdomain.yml; let's go back tostories.md

Finishing the stories

Now we know what's available in thedomain, let's fill in the<something> in the skeleton we had before:

## greetings* greet- utter_greet- utter_ask_experience> check ask experience## I have been to the event> check ask experience* affirm- utter_happy- experience_form- form{"name": "experience_form"}- form{"name": null}- utter_ask_contact> check ask contact## Not been to the event> check ask experience* deny- utter_encourage- utter_ask_contact> check ask contact## get contact info> check ask contact* affirm- utter_happy- contact_form- form{"name": "contact_form"}- form{"name": null}- utter_thanks## do not contact me> check ask contact* deny- utter_thanks

Notice that some of our actions start withform, these are the form actions that we defined in our domain. For example,- form{"name": "experience_form"} states to use the action formexperience_form. After we are done, it will be reset tonull to continue the conversation.

Let's set up our form actions now.

Form actions

Now we come to the fun part! Our form actions are custom actions that we are using to collect the user's information. Before we do anything, first we need to add theFormPolicy to the configuration. Go toconfig.yml and underpolicies add:

  -name:FormPolicy

When a custom action is predicted in our dialogue, Core will call an endpoint we specify inendpoint.yml. This endpoint should be a webserver that reacts to this call, runs the code for the custom action, and optionally returns information to modify the dialogue state.

To enable the action endpoint, go toendpoint.yml and uncomment the following:

action_endpoint:url:"http://localhost:5055/webhook"

The custom action scripts we write will be hosted on a server setup by Rasa at port 5055.

Openactions.py. From the default file, uncomment the following lines:

fromtypingimportAny,Text,Dict,Listfromrasa_sdkimportAction,Trackerfromrasa_sdk.executorimportCollectingDispatcher

These import an object that is used to communicate with the Rasa framework. On top of that, we also need:

fromrasa_sdk.formsimportFormAction

This additional import allows us to write custom form action classes which inherit fromFormAction.

ExperienceForm

Let's define theexperience_form, adding it below our imports inactions.py:

classExperienceForm(FormAction):"""Form action to capture user experience"""defname(self):# type: () -> Text"""Unique identifier of the form"""return"experience_form"@staticmethoddefrequired_slots(tracker):# type: () -> List[Text]"""A list of required slots that the form has to fill           this form collect the feedback of the user experience"""return ["feedback"]defsubmit(self,dispatcher,tracker,domain):# type: (CollectingDispatcher, Tracker, Dict[Text, Any]) -> List[Dict]"""Define what the form has to do           after all required slots are filled.           Generates sentiment analysis           using the user's feedback"""return []defslot_mappings(self):# type: () -> Dict[Text: Union[Dict, List[Dict]]]"""A dictionary to map required slots to            - an extracted entity            - intent: value pairs            - a whole message            or a list of them, where a first match will be picked"""return {"feedback": [self.from_text()]}

This form will collect the text the user inputs in thefeedback slot. When the form is triggered, the actionutter_ask_feedback is activated and the user input after that will be captured. Have a look at the doc string of each methods and make sure you understand what each function does, we will use them again in the more complicatedcontact_form.

ContactForm

Similarly, we definecontact_form:

classContactForm(FormAction):"""Form action to capture contact details"""defname(self):# type: () -> Text"""Unique identifier of the form"""return"contact_form"@staticmethoddefrequired_slots(tracker):# type: () -> List[Text]"""A list of required slots that the form has to fill"""return ["name","email","tel"]defsubmit(self,dispatcher,tracker,domain):# type: (CollectingDispatcher, Tracker, Dict[Text, Any]) -> List[Dict]"""Define what the form has to do           after all required slots are filled"""dispatcher.utter_template('utter_submit',tracker)return []defslot_mappings(self):# type: () -> Dict[Text: Union[Dict, List[Dict]]]"""A dictionary to map required slots to            - an extracted entity            - intent: value pairs            - a whole message            or a list of them, where a first match will be picked"""return {"name": [self.from_entity(entity="PERSON",intent="self_intro"),self.from_text()],"email": [self.from_entity(entity="email"),self.from_text()],"tel": [self.from_entity(entity="tel"),self.from_text()]}

This time the slot mapping is more complicated, usingfrom_entity we can specify the slot to be fill with a certain recognised entity / intent instead of free text. However, we putfrom_text in the list afterfrom_entity as a fail safe catching the information if the user's input is not recognisable.

Validating slots

For theemail andtel the user input, we want to validate them. To do so, we add more methods to ourContactForm class:

@staticmethoddefis_email(string:Text)->bool:"""Check if a string is valid email"""pattern=re.compile("[\w-]+@([\w-]+\.)+[\w-]+")returnpattern.match(string)@staticmethoddefis_tel(string:Text)->bool:"""Check if a string is valid email"""pattern_uk=re.compile("(0)([0-9][\s]*){10}")pattern_world=re.compile("^(00|\+)[\s]*[1-9]{1}([0-9][\s]*){9,16}$")returnpattern_uk.match(string)orpattern_world.match(string)defvalidate_email(self,value:Text,dispatcher:CollectingDispatcher,tracker:Tracker,domain:Dict[Text,Any],    )->Optional[Text]:ifself.is_email(value):return {"email":value}else:dispatcher.utter_template('utter_wrong_email',tracker)# validation failed, set this slot to None, meaning the# user will be asked for the slot againreturn {"email":None}defvalidate_tel(self,value:Text,dispatcher:CollectingDispatcher,tracker:Tracker,domain:Dict[Text,Any],    )->Optional[Text]:ifself.is_tel(value):return {"tel":value}else:dispatcher.utter_template('utter_wrong_tel',tracker)# validation failed, set this slot to None, meaning the# user will be asked for the slot againreturn {"tel":None}

Notice we have usedre module, so we have to import it:

importre

Also, we have use one moretyping:Optional. We have to import it as well:

fromtypingimportAny,Text,Dict,List,Optional

Here we have defined 2 helper methods:is_email andis_tel which will use Regex to check if the input matches an email format and phone number format. We also have validate methods for each of them. If the format does not match what we expected, we will reset the slot toNone and use anutter action to ask again.

Train and test your Chatbots

Now it's time to train and test our chatbot!! 🎉

To train the bot using the settings that we have set up, in the terminal run:

rasa train

When it is done, you can see that a new model is saved. Now let's try it out. First, make sure the server hosting the action script is up and running:

rasa run actions

Now the server is running, let's open an other terminal and then type:

rasa shell --endpoint endpoint.yml

(Note: you may need to activate the environment you created for the workshop.)

The command above will call Rasa to run the chatbot with the endpoint. Now you can talk to it!

Restart the action server

In you have made changes to youractions.py and want to start the server with the new script, you have to kill the server that is already running. Follow the following steps to kill the server:

  1. find thePID of the process:
sudo lsof -i tcp:5055
  1. kill the process:
kill -9 <PID>

fill in the<PID> with thePID you found in step 1.

You have complete 2/3 of the workshop! Yes, there's more. Feel free to take a 3 mins break

Using NLTK to analyse the sentiment

Here comes the fun part!! We will useNatural Language Toolkit (NLTK), a suite of libraries for natural language processing, to analyse the sentiment of thefeedback so we know if the feedback is positive or negative.

Before we add code in the action script, let's add 2 more slots in ourdomain.yml:

feedback_class:type:unfeaturizedfeedback_score:type:unfeaturized

This 2 slots will store the result of the analysis. Then head toactions.py. First we have to import and download the resources in NLTK:

importnltknltk.download('vader_lexicon')fromnltk.sentiment.vaderimportSentimentIntensityAnalyzer

This is a built-in sentiment analyzer in NLTK and it's super easy to use. Then we add the following to thesubmit method ofExperienceForm:

sid=SentimentIntensityAnalyzer()all_slots=tracker.slotsforslot,valueinall_slots.items():ifslotinself.required_slots(tracker):res=sid.polarity_scores(value)score=res.pop('compound',None)classi,confidence=max(res.items(),key=lambdax:x[1])# classification of the feedback, could be pos, neg, or neuall_slots[slot+'_class']=classi# sentiment score of the feedback, range form -1 to 1all_slots[slot+'_score']=score

and return the new values of the slots:

return [SlotSet(slot,value)forslot,valueinall_slots.items()]

Here we use the analyzer to get the classification for the feedback, and its score, and store them in the new slots. To do so, we have to use a event in Rasa calledSlotSet; let's import it at the beginning:

fromrasa_sdk.eventsimportSlotSet

Now you can restart the action server and test the chatbot again (remember to retrain it as we have changed thedomain.yml). Make sure the chatbot works as before.

We cannot see the difference in the Rasa shell as the slots are not shown anywhere in the conversation. In the next part, we will generate a report using a web framework.

Generate user report

To display the information that we collected from the user, we have to generate a report. You can use any web framework of your choice but we'll use a lightweight framework calledCherryPy.

Set up CherryPy server 🍒

Since we are not teaching web development here, we will just tell you how to set it up with CherryPy. First open a new directory and go there. In the terminal:

mkdir reportcd report

create 3 files as follow:

  1. result.css
body {padding-left:15px;}
  1. result.html
<html><head><linkrel="stylesheet"href="result.css"></head><body><h1>{name} survey result</h1>        {result}</body></html>
  1. result.py
importcherrypyimportosclassSurveyResult(object):@cherrypy.exposedefindex(self,name=None,result=None):returnopen("result.html").read().format(name=name,result=result)conf={'/result.css':                    {'tools.staticfile.on':True,'tools.staticfile.filename':os.path.abspath("./result.css"),                    }      }if__name__=='__main__':cherrypy.quickstart(SurveyResult(),config=conf)

You may need to install cherrypy in your environment.

Then in the terminal:

python result.py

It will set up a web app running at port 8080. Just like with the action script server, we will leave it running and open a new terminal.

Action for showing report

After setting up the report server, we have to add theAction in the action script to send the request when the conversation is ended, but before that, we will need to add- action_show_result underactions indomain.yml and at the end of the## get contact info and## do not contact me stories indata/stories.md.

Inactions.py add the following:

classActionShowResult(Action):"""open the html showing the result of the user survey"""defname(self):# type: () -> Textreturn"action_show_result"defrun(self,dispatcher,tracker,domain):# type: (CollectingDispatcher, Tracker, Dict[Text, Any]) -> List[Dict[Text, Any]]result=tracker.slotsname=result['name']ifnameisNone:name='Anonymous'else:name=name+"'s"http_result=""""""forkey,valueinresult.items():ifkey!='requested_slot':http_result+="""<p>{}: {}</p>""".format(key,value)# url of the server set up by result.pyurl='http://localhost:8080/?name={}&result={}'.format(name,http_result)webbrowser.open(url)return []

We'll need to importwebbrowser:

importwebbrowser

This will gather the slots and send them with the request to the report server.

Now restart the action server and re-train rasa and test the chatbot.

Fallback dialog

So far everything should work fine if the user has been good. However, what if the user gives an unexpected answer and the NLU fails to determine what to do. Here we use a fallback action to prompt the user to try again. First we have to enableFallbackPolicy, inconfig.yml underpolicies, add:

-name:"FallbackPolicy"nlu_threshold:0.4core_threshold:0.3fallback_action_name:"action_default_fallback"

action_default_fallback is a default action in Rasa Core which sends theutter_default template message to the user. So indomain.yml, add- utter_default underactions andtemplates:

utter_default:- text: "Sorry, I don't understand."- text: "I am not sure what you mean."

Now you can re-train and test the chatbot. Make sure you try to be a naughty user.

Congratulations! You have complicated the Rasa workshop... for now. Please feel free to integrate more functions to it, experiment and have fun.

What's beyond

For more things you can do with Rasa, please refer to theRasa documentation.

We are always looking for more content, so if you have a good idea, please feel free to contribute.

About

Step Into the AI Era: Chatbots that know if you are angry - a workshop to build a chatbot using Rasa

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

[8]ページ先頭

©2009-2025 Movatter.jp