├── .gitignore ├── LICENSE ├── Pipfile ├── README.md ├── chatbot_example ├── __init__.py ├── actions.py ├── config.yml ├── credentials.yml ├── data │ ├── nlu.md │ └── stories.md ├── domain.yml ├── endpoints.yml ├── models │ └── 20190619-220022.tar.gz └── report │ ├── result.css │ ├── result.html │ └── result.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | 58 | # Flask stuff: 59 | instance/ 60 | .webassets-cache 61 | 62 | # Scrapy stuff: 63 | .scrapy 64 | 65 | # Sphinx documentation 66 | docs/_build/ 67 | 68 | # PyBuilder 69 | target/ 70 | 71 | # Jupyter Notebook 72 | .ipynb_checkpoints 73 | 74 | # pyenv 75 | .python-version 76 | 77 | # celery beat schedule file 78 | celerybeat-schedule 79 | 80 | # SageMath parsed files 81 | *.sage.py 82 | 83 | # dotenv 84 | .env 85 | 86 | # virtualenv 87 | .venv 88 | venv/ 89 | ENV/ 90 | 91 | # Spyder project settings 92 | .spyderproject 93 | .spyproject 94 | 95 | # Rope project settings 96 | .ropeproject 97 | 98 | # mkdocs documentation 99 | /site 100 | 101 | # mypy 102 | .mypy_cache/ 103 | 104 | # training output 105 | models/ 106 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019, Cheuk Ting Ho 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | 8 | [packages] 9 | rasa = ">=1.1.3,<2" 10 | spacy = ">=2.1.4,<3" 11 | nltk = ">=3.4.3,<4" 12 | CherryPy = ">=18.1.2,<19" 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Step Into the AI Era: Chatbots that know if you are angry 2 | 3 | In this workshop, we will be using [Rasa](https://rasa.com/), 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. 4 | 5 | ---- 6 | 7 | 8 | ## Table of Contents 9 | 10 | * **Part 1** 11 | * [Install Rasa and set up the environment](#install-rasa-and-set-up-the-environment) 12 | * [Create a new project](#create-a-new-project) 13 | * [NLU and Pipeline setup](#nlu-and-pipeline-setup) 14 | * [Train and test NLU](#train-and-test-nlu) 15 | * **Part 2** 16 | * [Planning the conversation](#planning-the-conversation) 17 | * [Domain and templates](#domain-and-templates) 18 | * [Finishing the stories](#finishing-the-stories) 19 | * [Form actions](#form-actions) 20 | * [Train and test your Chatbots](#train-and-test-your-chatbots) 21 | * **Part 3** 22 | * [Using NLTK to analyse the sentiment](#using-nltk-to-analyse-the-sentiment) 23 | * [Generate user report](#generate-user-report) 24 | * [Fallback dialog](#fallback-dialog) 25 | * [What's beyond](#whats-beyond) 26 | 27 | 28 | ---- 29 | 30 | ## Install Rasa and set up the environment 31 | 32 | ### Install python 33 | 34 | 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. 35 | 36 | If you are using Mac or Linux, the best way to manage multiple Python environments is to use [pyenv](https://github.com/pyenv/pyenv). If you don't have Python installed, you can skip installing Python directly on your machine. Instead, [install pyenv](https://github.com/pyenv/pyenv#installation) and [pyenv-virtualenv](https://github.com/pyenv/pyenv-virtualenv) for creating different virtualenv environments with different versions of Pythons. 37 | 38 | Another way to have Python on your machine (either Windows, Mac or Linux) is to download [Anaconda](https://www.anaconda.com/distribution/) or [Miniconda](https://docs.conda.io/en/latest/miniconda.html), you will then create conda enviroments in the following step. 39 | 40 | ### Create new environment 41 | 42 | Open a terminal. 43 | 44 | Clone this repo from Github: 45 | 46 | `git clone https://github.com/Cheukting/rasa_workshop.git` 47 | 48 | Enter the directory: 49 | 50 | `cd rasa_workshop` 51 | 52 | Create a new [conda](https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html) or [virtualenv](https://github.com/pyenv/pyenv-virtualenv) environment. 53 | 54 | `conda create --name rasa_workshop python=x.x` 55 | where `x.x` is the python version (Rasa require python>=3.5) 56 | 57 | or 58 | 59 | `pyenv virtualenv rasa_workshop` 60 | where `` is the python version (Rasa require python>=3.5) 61 | 62 | > Notes: 63 | > If virtualenv is too difficult to set up (e.g. using Windows) and already have Python >= 3.6 installed, you can use [venv](https://docs.python.org/3/library/venv.html) instead 64 | 65 | Activate the environment by `conda activate rasa_workshop` or `pyenv activate rasa_workshop` 66 | 67 | While you are in the new environment, install the requirements: 68 | 69 | `pip install -r requirements.txt` 70 | 71 | > Notes: 72 | > If you are using conda and have problems with pip install, you may try installing individual packages using [conda-forge](https://conda-forge.org/docs/user/introduction.html) 73 | 74 | ## Create a new project 75 | 76 | Creat a new directory (you could replace `my_chatbot` to any name you like): 77 | 78 | `mkdir my_chatbot` 79 | 80 | Go to the directory: 81 | 82 | `cd my_chatbot` 83 | 84 | Initiate a project: 85 | 86 | `rasa init --no-prompt` 87 | 88 | Rasa will create a list of files for you, but we mostly care about the following: 89 | 90 | * `actions.py` : code for your custom actions 91 | * `config.yml` : configuration of your NLU and Core models 92 | * `data/nlu.md` : your NLU training data 93 | * `data/stories.md` : your stories 94 | * `domain.yml` : your assistant’s domain 95 | 96 | We will explain what they are and how to set them up in this workshop. 97 | 98 | ## NLU and Pipeline setup 99 | 100 | First we will need to train the NLU, which is a natural language processing tool for intent classification and entity extraction. 101 | 102 | Open `data/nlu.md` with the text editor or IDE of your choice. 103 | 104 | 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. 105 | 106 | For our use case, since we will be doing sentiment analysis using [Natural Language Toolkit (NLTK)](https://www.nltk.org/), we can delete the sections for `mood_great` and `mood_unhappy`. 107 | 108 | Feel free to add more examples for the other intents: the more examples, the better the understanding of NLU and your chatbot. 109 | 110 | 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`. 111 | 112 | Here are some examples for the additional intents, please feel free to add more: 113 | 114 | ``` 115 | ## intent:self_intro 116 | - I am [Mary](PERSON) 117 | - My name is [Anna](PERSON) 118 | 119 | ## intent:give_email 120 | - my email is [joe@aol.com](email) 121 | - [123@123.co.uk](email) 122 | 123 | ## intent:give_tel 124 | - my number is [01234567890](tel) 125 | - contact me at [07896234653](tel) 126 | ``` 127 | 128 | 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` and `tel`. 129 | 130 | `PERSON` is a entity provided by SpaCy. To help capture `email` and `tel`, we will also use [regex](https://www.rexegg.com/). To do so, put this in `nlu.md` as well: 131 | 132 | ``` 133 | ## regex:email 134 | - [\w-]+@([\w-]+\.)+[\w-]+ 135 | 136 | ## regex:tel 137 | - (0)([0-9][\s]*){10} 138 | ``` 139 | 140 | If you are a regex expert, you can change it to a better expression. 😉 141 | 142 | After that, we have to setup the [NLP pipeline](http://rasa.com/docs/rasa/nlu/choosing-a-pipeline/), which can be done by editing `config.yml`. This configuration file defines the NLU and Core components that your model will use. 143 | 144 | In `config.yml` change the `supervised_embeddings` to `pretrained_embeddings_spacy` so that we use the pretrained SpaCy embedding pipeline. You can find out more about NLU pipelines [here](https://rasa.com/docs/rasa/nlu/choosing-a-pipeline/#choosing-a-pipeline). 145 | 146 | ## Train and test NLU 147 | 148 | The following 2 commands download and set up the Spacy model that we will be using. In the terminal: 149 | 150 | ``` 151 | python -m spacy download en_core_web_md-2.0.0 --direct 152 | python -m spacy link en_core_web_md en 153 | ``` 154 | Then we tell rasa to train the NLU. 155 | 156 | `rasa train nlu` 157 | 158 | The trained model should be saved under `models/`. 159 | 160 | > Notes: 161 | > You can ignore all the future warnings for now as we will only use the current version in the workshop. 162 | 163 | Now we can test the NLU model that we trained: 164 | 165 | `rasa shell nlu` 166 | 167 | 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 the `nlu.md` and then train the NLU again (`rasa train nlu`). Repeat the training and testing until you are happy. 168 | 169 | *Congratulations, you have complete 1/3 of the workshop, feel free to take a 3 mins break* 170 | 171 | Now we will train our chatbots how to respond to messages. This is called dialogue management, and is handled by Rasa Core. 172 | 173 | ## Planning the conversation 174 | 175 | In this part, we will write the plan for the flow of the conversation. It will be written in `data/stories.md`. The flow of the conversation will be broken into 3 parts: 176 | 177 | 1. greeting -> ask if user has attended event: 178 | 179 | yes -> (go to part 2.a) 180 | 181 | no -> (go to part 2.b) 182 | 183 | 2. a) ask for feedback -> ask if we can contact them 184 | 185 | b) encourage them to go next year -> ask if we can contact them 186 | 187 | yes -> (go to part 3.a) 188 | 189 | no -> (go to part 3.b) 190 | 191 | 3. a) contact form and see you next year 192 | 193 | b) see you next year 194 | 195 | If you open and edit `data/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. 196 | 197 | The skeleton for the 3 parts of our conversation flow looks like this: 198 | 199 | ``` 200 | ## greetings 201 | * greet 202 | 203 | > check ask experience 204 | 205 | ## I have been to the event 206 | > check ask experience 207 | * affirm 208 | 209 | > check ask contact 210 | 211 | ## Not been to the event 212 | > check ask experience 213 | * deny 214 | 215 | > check ask contact 216 | 217 | ## get contact info 218 | > check ask contact 219 | * affirm 220 | 221 | 222 | ## do not contact me 223 | > check ask contact 224 | * deny 225 | 226 | ``` 227 | 228 | We will fill in `` 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. 229 | 230 | Line starting with `*` are for when our chatbot recognises an intent. For example, `* affirm` will trigger when the NLU predicts an `affirm` intent. 231 | 232 | ## Domain and templates 233 | 234 | 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 a `Domain` for our chatbot, which is our chatbot's 'universe'. Your chatbot's domain specifies the `intents`, `entities`, `slots`, and `actions` your bot needs to know about. We will define these further below. The domain is recorded in `domain.yml`. If you open up `domain.yml`, you can see the `DefaultDomain`. You can delete the contents of that file and then we'll create a domain for our chatbot. 235 | 236 | The NLU model has defines the `intents`, and `entities` which need to be in the bots domain. 237 | 238 | #### Adding intents 239 | 240 | Remember the intents we defined in `nlu.md`? Let's put them in `domain.yml`: 241 | 242 | ``` 243 | intents: 244 | - greet 245 | - goodbye 246 | - affirm 247 | - deny 248 | - self_intro 249 | - give_email 250 | - give_tel 251 | ``` 252 | 253 | #### Adding entities 254 | 255 | Similarly, add the entities that we defined in `nlu.md`: 256 | 257 | ``` 258 | entities: 259 | - PERSON 260 | - email 261 | - tel 262 | ``` 263 | 264 | We will also be adding `slots` and `actions` 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: 265 | respond to a user, 266 | make an external API call, 267 | query a database, or 268 | just about anything! 269 | 270 | We'll also be adding optional additional information to our bot's domain, including `forms`and `templates`. 271 | 272 | #### Adding slots 273 | 274 | 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's `name`, `email` and `tel` number and user `feedback`: 275 | 276 | ``` 277 | slots: 278 | name: 279 | type: unfeaturized 280 | email: 281 | type: unfeaturized 282 | tel: 283 | type: unfeaturized 284 | feedback: 285 | type: unfeaturized 286 | ``` 287 | 288 | `unfeaturized` means that this information does not affect the flow of the conversation. 289 | 290 | 291 | #### Adding forms 292 | 293 | To capture a user's contact information and feedback, we will use form actions. Let's define them like this for now: 294 | 295 | ``` 296 | forms: 297 | - experience_form 298 | - contact_form 299 | ``` 300 | 301 | #### Adding actions 302 | 303 | There are two main kinds of actions for our chatbot. The first are Utterance Actions, which send a message to a user and start with `utter_`. 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. 304 | 305 | For now, we will define our `utter` actions. 306 | 307 | ``` 308 | actions: 309 | - utter_greet 310 | - utter_happy 311 | - utter_goodbye 312 | - utter_thanks 313 | - utter_ask_contact 314 | - utter_ask_experience 315 | - utter_ask_name 316 | - utter_ask_email 317 | - utter_ask_tel 318 | - utter_ask_feedback 319 | - utter_submit 320 | - utter_wrong_email 321 | - utter_wrong_tel 322 | - utter_encourage 323 | ``` 324 | 325 | 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 the `utter` actions later if you want. 326 | 327 | #### Adding templates 328 | 329 | 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 each `utter` action listed in our domain. If we have more than one response text for an `utter` 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. 330 | 331 | For the utterance templates, the utterance can be used directly as an action. 332 | 333 | ``` 334 | templates: 335 | utter_greet: 336 | - text: "Hello! My name is Alex." 337 | 338 | utter_happy: 339 | - text: "Great!" 340 | - text: "Awesome!" 341 | 342 | utter_goodbye: 343 | - text: "Bye!" 344 | - text: "Have a nice day!" 345 | 346 | utter_thanks: 347 | - text: "Thank you for chatting, please feel free to talk to me again." 348 | 349 | utter_ask_contact: 350 | - text: "Do you want to be contacted regarding EuroPython next year?" 351 | 352 | utter_ask_experience: 353 | - text: "Have you been to EuroPython this year?" 354 | 355 | utter_ask_name: 356 | - text: "What's your name?" 357 | 358 | utter_ask_email: 359 | - text: "What's your email address?" 360 | 361 | utter_ask_tel: 362 | - text: "What's your contact number?" 363 | 364 | utter_ask_feedback: 365 | - text: "So, how was your experience in EuroPython?" 366 | 367 | utter_submit: 368 | - text: "You information collected will not be shared to 3rd party." 369 | 370 | utter_wrong_email: 371 | - text: "This doesn't look like an email..." 372 | 373 | utter_wrong_tel: 374 | - text: "This doesn't look like a phone number..." 375 | 376 | utter_encourage: 377 | - text: "It's a shame, we would like to meet you there next year." 378 | 379 | ``` 380 | 381 | Please feel free to change the response texts and add more response texts for each `utter` action. 382 | 383 | For now, we are done with `domain.yml`; let's go back to `stories.md` 384 | 385 | ## Finishing the stories 386 | 387 | Now we know what's available in the `domain`, let's fill in the `` in the skeleton we had before: 388 | 389 | ``` 390 | ## greetings 391 | * greet 392 | - utter_greet 393 | - utter_ask_experience 394 | > check ask experience 395 | 396 | ## I have been to the event 397 | > check ask experience 398 | * affirm 399 | - utter_happy 400 | - experience_form 401 | - form{"name": "experience_form"} 402 | - form{"name": null} 403 | - utter_ask_contact 404 | > check ask contact 405 | 406 | ## Not been to the event 407 | > check ask experience 408 | * deny 409 | - utter_encourage 410 | - utter_ask_contact 411 | > check ask contact 412 | 413 | ## get contact info 414 | > check ask contact 415 | * affirm 416 | - utter_happy 417 | - contact_form 418 | - form{"name": "contact_form"} 419 | - form{"name": null} 420 | - utter_thanks 421 | 422 | ## do not contact me 423 | > check ask contact 424 | * deny 425 | - utter_thanks 426 | ``` 427 | 428 | Notice that some of our actions start with `form`, these are the form actions that we defined in our domain. For example, `- form{"name": "experience_form"}` states to use the action form `experience_form`. After we are done, it will be reset to `null` to continue the conversation. 429 | 430 | Let's set up our form actions now. 431 | 432 | ## Form actions 433 | 434 | 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 the `FormPolicy` to the configuration. Go to `config.yml` and under `policies` add: 435 | 436 | ```YAML 437 | - name: FormPolicy 438 | ``` 439 | 440 | When a custom action is predicted in our dialogue, Core will call an endpoint we specify in `endpoint.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. 441 | 442 | 443 | To enable the action endpoint, go to `endpoint.yml` and uncomment the following: 444 | 445 | ```YAML 446 | action_endpoint: 447 | url: "http://localhost:5055/webhook" 448 | ``` 449 | 450 | The custom action scripts we write will be hosted on a server setup by Rasa at port 5055. 451 | 452 | Open `actions.py`. From the default file, uncomment the following lines: 453 | 454 | ```python 455 | from typing import Any, Text, Dict, List 456 | 457 | from rasa_sdk import Action, Tracker 458 | from rasa_sdk.executor import CollectingDispatcher 459 | ``` 460 | 461 | These import an object that is used to communicate with the Rasa framework. On top of that, we also need: 462 | 463 | ```python 464 | from rasa_sdk.forms import FormAction 465 | ``` 466 | 467 | This additional import allows us to write custom form action classes which inherit from `FormAction`. 468 | 469 | #### ExperienceForm 470 | 471 | Let's define the `experience_form`, adding it below our imports in `actions.py`: 472 | 473 | ```python 474 | class ExperienceForm(FormAction): 475 | """Form action to capture user experience""" 476 | 477 | def name(self): 478 | # type: () -> Text 479 | """Unique identifier of the form""" 480 | return "experience_form" 481 | 482 | @staticmethod 483 | def required_slots(tracker): 484 | # type: () -> List[Text] 485 | """A list of required slots that the form has to fill 486 | this form collect the feedback of the user experience""" 487 | return ["feedback"] 488 | 489 | def submit(self, dispatcher, tracker, domain): 490 | # type: (CollectingDispatcher, Tracker, Dict[Text, Any]) -> List[Dict] 491 | """Define what the form has to do 492 | after all required slots are filled. 493 | Generates sentiment analysis 494 | using the user's feedback""" 495 | return [] 496 | 497 | def slot_mappings(self): 498 | # type: () -> Dict[Text: Union[Dict, List[Dict]]] 499 | """A dictionary to map required slots to 500 | - an extracted entity 501 | - intent: value pairs 502 | - a whole message 503 | or a list of them, where a first match will be picked""" 504 | return {"feedback": [self.from_text()]} 505 | 506 | ``` 507 | 508 | This form will collect the text the user inputs in the `feedback` slot. When the form is triggered, the action `utter_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 complicated `contact_form`. 509 | 510 | #### ContactForm 511 | 512 | Similarly, we define `contact_form`: 513 | 514 | ```python 515 | class ContactForm(FormAction): 516 | """Form action to capture contact details""" 517 | 518 | def name(self): 519 | # type: () -> Text 520 | """Unique identifier of the form""" 521 | return "contact_form" 522 | 523 | @staticmethod 524 | def required_slots(tracker): 525 | # type: () -> List[Text] 526 | """A list of required slots that the form has to fill""" 527 | return ["name", "email", "tel"] 528 | 529 | def submit(self, dispatcher, tracker, domain): 530 | # type: (CollectingDispatcher, Tracker, Dict[Text, Any]) -> List[Dict] 531 | """Define what the form has to do 532 | after all required slots are filled""" 533 | 534 | dispatcher.utter_template('utter_submit', tracker) 535 | return [] 536 | 537 | def slot_mappings(self): 538 | # type: () -> Dict[Text: Union[Dict, List[Dict]]] 539 | """A dictionary to map required slots to 540 | - an extracted entity 541 | - intent: value pairs 542 | - a whole message 543 | or a list of them, where a first match will be picked""" 544 | 545 | return {"name": [self.from_entity(entity="PERSON", 546 | intent="self_intro"), 547 | self.from_text()], 548 | "email": [self.from_entity(entity="email"), 549 | self.from_text()], 550 | "tel": [self.from_entity(entity="tel"), 551 | self.from_text()]} 552 | 553 | ``` 554 | 555 | This time the slot mapping is more complicated, using `from_entity` we can specify the slot to be fill with a certain recognised entity / intent instead of free text. However, we put `from_text` in the list after `from_entity` as a fail safe catching the information if the user's input is not recognisable. 556 | 557 | #### Validating slots 558 | 559 | For the `email` and `tel` the user input, we want to validate them. To do so, we add more methods to our `ContactForm` class: 560 | 561 | ```python 562 | @staticmethod 563 | def is_email(string: Text) -> bool: 564 | """Check if a string is valid email""" 565 | pattern = re.compile("[\w-]+@([\w-]+\.)+[\w-]+") 566 | return pattern.match(string) 567 | 568 | @staticmethod 569 | def is_tel(string: Text) -> bool: 570 | """Check if a string is valid email""" 571 | pattern_uk = re.compile("(0)([0-9][\s]*){10}") 572 | pattern_world = re.compile("^(00|\+)[\s]*[1-9]{1}([0-9][\s]*){9,16}$") 573 | return pattern_uk.match(string) or pattern_world.match(string) 574 | 575 | def validate_email( 576 | self, 577 | value: Text, 578 | dispatcher: CollectingDispatcher, 579 | tracker: Tracker, 580 | domain: Dict[Text, Any], 581 | ) -> Optional[Text]: 582 | if self.is_email(value): 583 | return {"email": value} 584 | else: 585 | dispatcher.utter_template('utter_wrong_email', tracker) 586 | # validation failed, set this slot to None, meaning the 587 | # user will be asked for the slot again 588 | return {"email": None} 589 | 590 | def validate_tel( 591 | self, 592 | value: Text, 593 | dispatcher: CollectingDispatcher, 594 | tracker: Tracker, 595 | domain: Dict[Text, Any], 596 | ) -> Optional[Text]: 597 | if self.is_tel(value): 598 | return {"tel": value} 599 | else: 600 | dispatcher.utter_template('utter_wrong_tel', tracker) 601 | # validation failed, set this slot to None, meaning the 602 | # user will be asked for the slot again 603 | return {"tel": None} 604 | ``` 605 | 606 | Notice we have used `re` module, so we have to import it: 607 | 608 | ```python 609 | import re 610 | ``` 611 | 612 | Also, we have use one more `typing`: `Optional`. We have to import it as well: 613 | 614 | ```python 615 | from typing import Any, Text, Dict, List, Optional 616 | ``` 617 | 618 | Here we have defined 2 helper methods: `is_email` and `is_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 to `None` and use an `utter` action to ask again. 619 | 620 | ## Train and test your Chatbots 621 | 622 | Now it's time to train and test our chatbot!! 🎉 623 | 624 | To train the bot using the settings that we have set up, in the terminal run: 625 | 626 | ``` 627 | rasa train 628 | ``` 629 | 630 | 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: 631 | 632 | ``` 633 | rasa run actions 634 | ``` 635 | 636 | Now the server is running, let's open an other terminal and then type: 637 | 638 | ``` 639 | rasa shell --endpoint endpoint.yml 640 | ``` 641 | (Note: you may need to activate the environment you created for the workshop.) 642 | 643 | The command above will call Rasa to run the chatbot with the endpoint. Now you can talk to it! 644 | 645 | #### Restart the action server 646 | 647 | In you have made changes to your `actions.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: 648 | 649 | 1. find the `PID` of the process: 650 | ``` 651 | sudo lsof -i tcp:5055 652 | ``` 653 | 2. kill the process: 654 | ``` 655 | kill -9 656 | ``` 657 | fill in the `` with the `PID` you found in step 1. 658 | 659 | *You have complete 2/3 of the workshop! Yes, there's more. Feel free to take a 3 mins break* 660 | 661 | ## Using NLTK to analyse the sentiment 662 | 663 | Here comes the fun part!! We will use [Natural Language Toolkit (NLTK)](https://www.nltk.org/), a suite of libraries for natural language processing, to analyse the sentiment of the `feedback` so we know if the feedback is positive or negative. 664 | 665 | Before we add code in the action script, let's add 2 more slots in our `domain.yml`: 666 | 667 | ```YAML 668 | feedback_class: 669 | type: unfeaturized 670 | feedback_score: 671 | type: unfeaturized 672 | ``` 673 | 674 | This 2 slots will store the result of the analysis. Then head to `actions.py`. First we have to import and download the resources in NLTK: 675 | 676 | ```python 677 | import nltk 678 | nltk.download('vader_lexicon') 679 | from nltk.sentiment.vader import SentimentIntensityAnalyzer 680 | ``` 681 | 682 | This is a built-in sentiment analyzer in NLTK and it's super easy to use. Then we add the following to the `submit` method of `ExperienceForm`: 683 | 684 | ```python 685 | sid = SentimentIntensityAnalyzer() 686 | 687 | all_slots = tracker.slots 688 | for slot, value in all_slots.items(): 689 | if slot in self.required_slots(tracker): 690 | res = sid.polarity_scores(value) 691 | score = res.pop('compound', None) 692 | classi, confidence = max(res.items(), key=lambda x: x[1]) 693 | # classification of the feedback, could be pos, neg, or neu 694 | all_slots[slot+'_class'] = classi 695 | # sentiment score of the feedback, range form -1 to 1 696 | all_slots[slot+'_score'] = score 697 | ``` 698 | 699 | and return the new values of the slots: 700 | 701 | ```python 702 | return [SlotSet(slot, value) for slot, value in all_slots.items()] 703 | ``` 704 | 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 called `SlotSet`; let's import it at the beginning: 705 | 706 | ```python 707 | from rasa_sdk.events import SlotSet 708 | ``` 709 | 710 | Now you can restart the action server and test the chatbot again (remember to retrain it as we have changed the `domain.yml`). Make sure the chatbot works as before. 711 | 712 | 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. 713 | 714 | ## Generate user report 715 | 716 | 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 called [CherryPy](https://docs.cherrypy.org/en/latest/index.html). 717 | 718 | 719 | #### Set up CherryPy server 🍒 720 | 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: 721 | 722 | ``` 723 | mkdir report 724 | cd report 725 | ``` 726 | create 3 files as follow: 727 | 728 | 1. result.css 729 | ```css 730 | body { 731 | padding-left: 15px; 732 | } 733 | ``` 734 | 735 | 2. result.html 736 | ```html 737 | 738 | 739 | 740 | 741 | 742 |

{name} survey result

743 | {result} 744 | 745 | 746 | ``` 747 | 748 | 3. result.py 749 | ```python 750 | import cherrypy 751 | import os 752 | class SurveyResult(object): 753 | @cherrypy.expose 754 | def index(self, name=None, result=None): 755 | return open("result.html").read().format(name=name, result=result) 756 | conf={'/result.css': 757 | { 'tools.staticfile.on':True, 758 | 'tools.staticfile.filename': os.path.abspath("./result.css"), 759 | } 760 | } 761 | if __name__ == '__main__': 762 | cherrypy.quickstart(SurveyResult(), config=conf) 763 | ``` 764 | 765 | You may need to install cherrypy in your environment. 766 | 767 | Then in the terminal: 768 | 769 | ``` 770 | python result.py 771 | ``` 772 | 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. 773 | 774 | #### Action for showing report 775 | 776 | After setting up the report server, we have to add the `Action` in the action script to send the request when the conversation is ended, but before that, we will need to add `- action_show_result` under `actions` in `domain.yml` and at the end of the `## get contact info` and `## do not contact me` stories in `data/stories.md`. 777 | 778 | In `actions.py` add the following: 779 | 780 | ```python 781 | class ActionShowResult(Action): 782 | """open the html showing the result of the user survey""" 783 | def name(self): 784 | # type: () -> Text 785 | return "action_show_result" 786 | 787 | def run(self, dispatcher, tracker, domain): 788 | # type: (CollectingDispatcher, Tracker, Dict[Text, Any]) -> List[Dict[Text, Any]] 789 | 790 | result = tracker.slots 791 | name = result['name'] 792 | if name is None: 793 | name = 'Anonymous' 794 | else: 795 | name = name + "'s" 796 | http_result ="""""" 797 | for key, value in result.items(): 798 | if key != 'requested_slot': 799 | http_result += """

{}: {}

""".format(key, value) 800 | 801 | # url of the server set up by result.py 802 | url = 'http://localhost:8080/?name={}&result={}'.format(name, http_result) 803 | webbrowser.open(url) 804 | 805 | return [] 806 | ``` 807 | 808 | We'll need to import `webbrowser`: 809 | 810 | ```python 811 | import webbrowser 812 | ``` 813 | 814 | This will gather the slots and send them with the request to the report server. 815 | 816 | Now restart the action server and re-train rasa and test the chatbot. 817 | 818 | ## Fallback dialog 819 | 820 | 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 enable `FallbackPolicy`, in `config.yml` under `policies`, add: 821 | 822 | ```YAML 823 | - name: "FallbackPolicy" 824 | nlu_threshold: 0.4 825 | core_threshold: 0.3 826 | fallback_action_name: "action_default_fallback" 827 | ``` 828 | 829 | `action_default_fallback` is a default action in Rasa Core which sends the `utter_default` template message to the user. So in `domain.yml`, add `- utter_default` under `actions` and `templates`: 830 | 831 | ``` 832 | utter_default: 833 | - text: "Sorry, I don't understand." 834 | - text: "I am not sure what you mean." 835 | ``` 836 | 837 | Now you can re-train and test the chatbot. Make sure you try to be a naughty user. 838 | 839 | *Congratulations! You have complicated the Rasa workshop... for now. Please feel free to integrate more functions to it, experiment and have fun.* 840 | 841 | ## What's beyond 842 | 843 | For more things you can do with Rasa, please refer to the [Rasa documentation](http://rasa.com/docs/rasa/). 844 | 845 | 846 | We are always looking for more content, so if you have a good idea, please feel free to contribute. 847 | -------------------------------------------------------------------------------- /chatbot_example/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheukting/rasa_workshop/10848569708ba37d03d7ff9a1ed8d35d2ee6010a/chatbot_example/__init__.py -------------------------------------------------------------------------------- /chatbot_example/actions.py: -------------------------------------------------------------------------------- 1 | # This files contains your custom actions which can be used to run 2 | # custom Python code. 3 | # 4 | # See this guide on how to implement these action: 5 | # https://rasa.com/docs/rasa/core/actions/#custom-actions/ 6 | 7 | 8 | # This is a simple example for a custom action which utters "Hello World!" 9 | 10 | # from typing import Any, Text, Dict, List 11 | # 12 | # from rasa_sdk import Action, Tracker 13 | # from rasa_sdk.executor import CollectingDispatcher 14 | # 15 | # 16 | # class ActionHelloWorld(Action): 17 | # 18 | # def name(self) -> Text: 19 | # return "action_hello_world" 20 | # 21 | # def run(self, dispatcher: CollectingDispatcher, 22 | # tracker: Tracker, 23 | # domain: Dict[Text, Any]) -> List[Dict[Text, Any]]: 24 | # 25 | # dispatcher.utter_message("Hello World!") 26 | # 27 | # return [] 28 | 29 | 30 | from typing import Any, Text, Dict, List, Optional 31 | 32 | from rasa_sdk import Action, Tracker 33 | from rasa_sdk.executor import CollectingDispatcher 34 | from rasa_sdk.forms import FormAction 35 | 36 | import re 37 | 38 | import nltk 39 | nltk.download('vader_lexicon') 40 | from nltk.sentiment.vader import SentimentIntensityAnalyzer 41 | 42 | from rasa_sdk.events import SlotSet 43 | import webbrowser 44 | 45 | 46 | class ExperienceForm(FormAction): 47 | """Form action to capture user experience""" 48 | 49 | def name(self): 50 | # type: () -> Text 51 | """Unique identifier of the form""" 52 | 53 | return "experience_form" 54 | 55 | @staticmethod 56 | def required_slots(tracker): 57 | # type: () -> List[Text] 58 | """A list of required slots that the form has to fill 59 | this form collect the feedback of the user experience""" 60 | 61 | return ["feedback"] 62 | 63 | def submit(self, dispatcher, tracker, domain): 64 | # type: (CollectingDispatcher, Tracker, Dict[Text, Any]) -> List[Dict] 65 | """Define what the form has to do 66 | after all required slots are filled 67 | basically it generate sentiment analysis 68 | using the user's feedback""" 69 | 70 | sid = SentimentIntensityAnalyzer() 71 | 72 | all_slots = tracker.slots 73 | for slot, value in all_slots.copy().items(): 74 | if slot in self.required_slots(tracker): 75 | res = sid.polarity_scores(value) 76 | score = res.pop('compound', None) 77 | classi, confidence = max(res.items(), key=lambda x: x[1]) 78 | # classification of the feedback, could be pos, neg, or neu 79 | all_slots[slot+'_class'] = classi 80 | # sentiment score of the feedback, range form -1 to 1 81 | all_slots[slot+'_score'] = score 82 | 83 | return [SlotSet(slot, value) for slot, value in all_slots.items()] 84 | 85 | def slot_mappings(self): 86 | # type: () -> Dict[Text: Union[Dict, List[Dict]]] 87 | """A dictionary to map required slots to 88 | - an extracted entity 89 | - intent: value pairs 90 | - a whole message 91 | or a list of them, where a first match will be picked""" 92 | 93 | return {"feedback": [self.from_text()]} 94 | 95 | class ContactForm(FormAction): 96 | """Form action to capture contact details""" 97 | 98 | def name(self): 99 | # type: () -> Text 100 | """Unique identifier of the form""" 101 | 102 | return "contact_form" 103 | 104 | @staticmethod 105 | def required_slots(tracker): 106 | # type: () -> List[Text] 107 | """A list of required slots that the form has to fill""" 108 | 109 | return ["name", "email", "tel"] 110 | 111 | def submit(self, dispatcher, tracker, domain): 112 | # type: (CollectingDispatcher, Tracker, Dict[Text, Any]) -> List[Dict] 113 | """Define what the form has to do 114 | after all required slots are filled""" 115 | 116 | dispatcher.utter_template('utter_submit', tracker) 117 | 118 | return [] 119 | 120 | def slot_mappings(self): 121 | # type: () -> Dict[Text: Union[Dict, List[Dict]]] 122 | """A dictionary to map required slots to 123 | - an extracted entity 124 | - intent: value pairs 125 | - a whole message 126 | or a list of them, where a first match will be picked""" 127 | 128 | return {"name": [self.from_entity(entity="PERSON", 129 | intent="self_intro"), 130 | self.from_text()], 131 | "email": [self.from_entity(entity="email"), 132 | self.from_text()], 133 | "tel": [self.from_entity(entity="tel"), 134 | self.from_text()]} 135 | 136 | @staticmethod 137 | def is_email(string: Text) -> bool: 138 | """Check if a string is valid email""" 139 | pattern = re.compile("[\w-]+@([\w-]+\.)+[\w-]+") 140 | return pattern.match(string) 141 | 142 | @staticmethod 143 | def is_tel(string: Text) -> bool: 144 | """Check if a string is valid email""" 145 | pattern_uk = re.compile("(0)([0-9][\s]*){10}") 146 | pattern_world = re.compile("^(00|\+)[\s]*[1-9]{1}([0-9][\s]*){9,16}$") 147 | return pattern_uk.match(string) or pattern_world.match(string) 148 | 149 | def validate_email( 150 | self, 151 | value: Text, 152 | dispatcher: CollectingDispatcher, 153 | tracker: Tracker, 154 | domain: Dict[Text, Any], 155 | ) -> Optional[Text]: 156 | if self.is_email(value): 157 | return {"email": value} 158 | else: 159 | dispatcher.utter_template('utter_wrong_email', tracker) 160 | # validation failed, set this slot to None, meaning the 161 | # user will be asked for the slot again 162 | return {"email": None} 163 | 164 | def validate_tel( 165 | self, 166 | value: Text, 167 | dispatcher: CollectingDispatcher, 168 | tracker: Tracker, 169 | domain: Dict[Text, Any], 170 | ) -> Optional[Text]: 171 | if self.is_tel(value): 172 | return {"tel": value} 173 | else: 174 | dispatcher.utter_template('utter_wrong_tel', tracker) 175 | # validation failed, set this slot to None, meaning the 176 | # user will be asked for the slot again 177 | return {"tel": None} 178 | 179 | 180 | class ActionShowResult(Action): 181 | """open the html showing the result of the user survey""" 182 | def name(self): 183 | # type: () -> Text 184 | return "action_show_result" 185 | 186 | def run(self, dispatcher, tracker, domain): 187 | # type: (CollectingDispatcher, Tracker, Dict[Text, Any]) -> List[Dict[Text, Any]] 188 | 189 | result = tracker.slots 190 | name = result['name'] 191 | if name is None: 192 | name = 'Anonymous' 193 | else: 194 | name = name + "'s" 195 | http_result ="""""" 196 | for key, value in result.items(): 197 | if key != 'requested_slot': 198 | http_result += """

{}: {}

""".format(key, value) 199 | 200 | # url of the server set up by result.py 201 | url = 'http://localhost:8080/?name={}&result={}'.format(name, http_result) 202 | webbrowser.open(url) 203 | 204 | return [] 205 | -------------------------------------------------------------------------------- /chatbot_example/config.yml: -------------------------------------------------------------------------------- 1 | # Configuration for Rasa NLU. 2 | # https://rasa.com/docs/rasa/nlu/components/ 3 | language: en 4 | pipeline: pretrained_embeddings_spacy 5 | 6 | # Configuration for Rasa Core. 7 | # https://rasa.com/docs/rasa/core/policies/ 8 | policies: 9 | - name: MemoizationPolicy 10 | - name: KerasPolicy 11 | - name: MappingPolicy 12 | - name: FormPolicy 13 | - name: "FallbackPolicy" 14 | nlu_threshold: 0.4 15 | core_threshold: 0.3 16 | fallback_action_name: "action_default_fallback" 17 | -------------------------------------------------------------------------------- /chatbot_example/credentials.yml: -------------------------------------------------------------------------------- 1 | # This file contains the credentials for the voice & chat platforms 2 | # which your bot is using. 3 | # https://rasa.com/docs/rasa/user-guide/messaging-and-voice-channels/ 4 | 5 | rest: 6 | # # you don't need to provide anything here - this channel doesn't 7 | # # require any credentials 8 | 9 | 10 | #facebook: 11 | # verify: "" 12 | # secret: "" 13 | # page-access-token: "" 14 | 15 | #slack: 16 | # slack_token: "" 17 | # slack_channel: "" 18 | 19 | #socketio: 20 | # user_message_evt: 21 | # bot_message_evt: 22 | # session_persistence: 23 | 24 | rasa: 25 | url: "http://localhost:5002/api" 26 | -------------------------------------------------------------------------------- /chatbot_example/data/nlu.md: -------------------------------------------------------------------------------- 1 | ## intent:greet 2 | - hey 3 | - hello 4 | - hi 5 | - good morning 6 | - good evening 7 | - hey there 8 | 9 | ## intent:goodbye 10 | - bye 11 | - goodbye 12 | - see you around 13 | - see you later 14 | 15 | ## intent:affirm 16 | - yes 17 | - indeed 18 | - of course 19 | - that sounds good 20 | - correct 21 | 22 | ## intent:deny 23 | - no 24 | - never 25 | - I don't think so 26 | - don't like that 27 | - no way 28 | - not really 29 | 30 | ## intent:self_intro 31 | - I am [Mary](PERSON) 32 | - My name is [Anna](PERSON) 33 | - Hi! I am [John](PERSON) 34 | - [Jason](PERSON) 35 | - Hey, I am [Nick](PERSON) 36 | - Hello, I am [Peter](PERSON) 37 | - Hey ya'll my name is [Ann](PERSON) 38 | 39 | ## intent:give_email 40 | - my email is [joe@aol.com](email) 41 | - [123@123.co.uk](email) 42 | - Email: [asdasd@asdasd.info](email) 43 | - this is my email - [asd123@asd1.co.uk](email) 44 | - here it is [123@123.co.uk](email) 45 | 46 | ## intent:give_tel 47 | - my number is [01234567890](tel) 48 | - contact me at [07896234653](tel) 49 | - call me at [+853 9876 2345](tel) 50 | - contact is [+44 7893096125](tel) 51 | - tel number: [+33 9876 1234 083](tel) 52 | - my contact is [+44 7893096125](tel) 53 | - sure, my number is [+853 98762345](tel) 54 | 55 | ## regex:email 56 | - [\w-]+@([\w-]+\.)+[\w-]+ 57 | 58 | ## regex:tel 59 | - (0)([0-9][\s]*){10} 60 | -------------------------------------------------------------------------------- /chatbot_example/data/stories.md: -------------------------------------------------------------------------------- 1 | ## greetings 2 | * greet 3 | - utter_greet 4 | - utter_ask_experience 5 | > check ask experience 6 | 7 | ## I have been to the event 8 | > check ask experience 9 | * affirm 10 | - utter_happy 11 | - experience_form 12 | - form{"name": "experience_form"} 13 | - form{"name": null} 14 | - utter_ask_contact 15 | > check ask contact 16 | 17 | ## Not been to the event 18 | > check ask experience 19 | * deny 20 | - utter_encourage 21 | - utter_ask_contact 22 | > check ask contact 23 | 24 | ## get contact info 25 | > check ask contact 26 | * affirm 27 | - utter_happy 28 | - contact_form 29 | - form{"name": "contact_form"} 30 | - form{"name": null} 31 | - utter_thanks 32 | - action_show_result 33 | 34 | ## do not contact me 35 | > check ask contact 36 | * deny 37 | - utter_thanks 38 | - action_show_result 39 | 40 | ## say goodbye 41 | * goodbye 42 | - utter_goodbye 43 | -------------------------------------------------------------------------------- /chatbot_example/domain.yml: -------------------------------------------------------------------------------- 1 | intents: 2 | - greet 3 | - goodbye 4 | - affirm 5 | - deny 6 | - self_intro 7 | - give_email 8 | - give_tel 9 | 10 | slots: 11 | name: 12 | type: unfeaturized 13 | email: 14 | type: unfeaturized 15 | tel: 16 | type: unfeaturized 17 | feedback: 18 | type: unfeaturized 19 | feedback_class: 20 | type: unfeaturized 21 | feedback_score: 22 | type: unfeaturized 23 | 24 | entities: 25 | - PERSON 26 | - email 27 | - tel 28 | 29 | forms: 30 | - experience_form 31 | - contact_form 32 | 33 | actions: 34 | - utter_greet 35 | - utter_happy 36 | - utter_goodbye 37 | - utter_thanks 38 | - utter_ask_contact 39 | - utter_ask_experience 40 | - utter_ask_name 41 | - utter_ask_email 42 | - utter_ask_tel 43 | - utter_ask_feedback 44 | - utter_default 45 | - utter_submit 46 | - utter_wrong_email 47 | - utter_wrong_tel 48 | - utter_encourage 49 | - action_show_result 50 | 51 | templates: 52 | utter_greet: 53 | - text: "Hello! My name is Alex." 54 | 55 | utter_happy: 56 | - text: "Great!" 57 | - text: "Awesome!" 58 | 59 | utter_default: 60 | - text: "Sorry, I don't understand." 61 | - text: "I am not sure what you mean." 62 | 63 | utter_goodbye: 64 | - text: "Bye!" 65 | - text: "Have a nice day!" 66 | 67 | utter_thanks: 68 | - text: "Thank you for chatting, please feel free to talk to me again." 69 | 70 | utter_ask_contact: 71 | - text: "Do you want to be contacted regarding EuroPython next year?" 72 | 73 | utter_ask_experience: 74 | - text: "Have you been to EuroPython this year?" 75 | 76 | utter_ask_name: 77 | - text: "What's your name?" 78 | 79 | utter_ask_email: 80 | - text: "What's your email address?" 81 | 82 | utter_ask_tel: 83 | - text: "What's your contact number?" 84 | 85 | utter_ask_feedback: 86 | - text: "So, how was your experience in EuroPython?" 87 | 88 | utter_submit: 89 | - text: "You information collected will not be shared to 3rd party." 90 | 91 | utter_wrong_email: 92 | - text: "This doesn't look like an email..." 93 | 94 | utter_wrong_tel: 95 | - text: "This doesn't look like a phone number..." 96 | 97 | utter_encourage: 98 | - text: "It's a shame, we would like to meet you there next year." 99 | -------------------------------------------------------------------------------- /chatbot_example/endpoints.yml: -------------------------------------------------------------------------------- 1 | # This file contains the different endpoints your bot can use. 2 | 3 | # Server where the models are pulled from. 4 | # https://rasa.com/docs/rasa/user-guide/running-the-server/#fetching-models-from-a-server/ 5 | 6 | #models: 7 | # url: http://my-server.com/models/default_core@latest 8 | # wait_time_between_pulls: 10 # [optional](default: 100) 9 | 10 | # Server which runs your custom actions. 11 | # https://rasa.com/docs/rasa/core/actions/#custom-actions/ 12 | 13 | action_endpoint: 14 | url: "http://localhost:5055/webhook" 15 | 16 | # Tracker store which is used to store the conversations. 17 | # By default the conversations are stored in memory. 18 | # https://rasa.com/docs/rasa/api/tracker-stores/ 19 | 20 | #tracker_store: 21 | # type: redis 22 | # url: 23 | # port: 24 | # db: 25 | # password: 26 | 27 | #tracker_store: 28 | # type: mongod 29 | # url: 30 | # db: 31 | # username: 32 | # password: 33 | 34 | # Event broker which all conversation events should be streamed to. 35 | # https://rasa.com/docs/rasa/api/event-brokers/ 36 | 37 | #event_broker: 38 | # url: localhost 39 | # username: username 40 | # password: password 41 | # queue: queue 42 | -------------------------------------------------------------------------------- /chatbot_example/models/20190619-220022.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cheukting/rasa_workshop/10848569708ba37d03d7ff9a1ed8d35d2ee6010a/chatbot_example/models/20190619-220022.tar.gz -------------------------------------------------------------------------------- /chatbot_example/report/result.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding-left: 15px; 3 | } 4 | -------------------------------------------------------------------------------- /chatbot_example/report/result.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

{name} survey result

7 | {result} 8 | 9 | 10 | -------------------------------------------------------------------------------- /chatbot_example/report/result.py: -------------------------------------------------------------------------------- 1 | """This set up a sever to deploy the result html""" 2 | 3 | import cherrypy 4 | import os 5 | 6 | class SurveyResult(object): 7 | @cherrypy.expose 8 | def index(self, name=None, result=None): 9 | return open("result.html").read().format(name=name, result=result) 10 | 11 | conf={'/result.css': 12 | { 'tools.staticfile.on':True, 13 | 'tools.staticfile.filename': os.path.abspath("./result.css"), 14 | } 15 | } 16 | 17 | if __name__ == '__main__': 18 | cherrypy.quickstart(SurveyResult(), config=conf) 19 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | nltk==3.4.3 2 | rasa==1.1.3 3 | spacy==2.1.4 4 | cherrypy==18.1.2 5 | --------------------------------------------------------------------------------