Repository: RasaHQ/helpdesk-assistant Branch: main Commit: 2e9f2258c664 Files: 29 Total size: 76.1 KB Directory structure: gitextract_o7a7u4a9/ ├── .github/ │ └── workflows/ │ ├── continuous-integration.yml │ ├── lint_and_test.yml │ └── lint_and_test_pr.yml ├── .gitignore ├── .pre-commit-config.yaml ├── Dockerfile.chatroom ├── LICENSE ├── Makefile ├── README.md ├── actions/ │ ├── __init__.py │ ├── actions.py │ ├── handoff.py │ ├── handoff_config.yml │ ├── requirements-actions.txt │ ├── snow.py │ └── snow_credentials.yml ├── chatroom_handoff.html ├── config.yml ├── data/ │ ├── handoff.yml │ ├── nlu.yml │ ├── rules.yml │ └── stories.yml ├── domain.yml ├── endpoints.yml ├── format_results.py ├── pyproject.toml ├── requirements-dev.txt ├── requirements.txt └── tests/ └── test_conversations.yml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/continuous-integration.yml ================================================ name: Continuous Integration on: push: branches: - main paths: - 'actions/**' - '.github/workflows/continuous-integration.yml' jobs: docker: name: Build Action Server Docker Image runs-on: ubuntu-latest env: DOCKERHUB_USERNAME: oakela steps: - name: Checkout git repository 🕝 uses: actions/checkout@v2 - name: Build Actions Server Image uses: RasaHQ/rasa-action-server-gha@main with: actions_directory: 'actions' requirements_file: 'actions/requirements-actions.txt' docker_registry_login: ${{ env.DOCKERHUB_USERNAME }} docker_registry_password: ${{ secrets.DOCKERHUB_PASSWORD }} docker_image_name: 'rasa/helpdesk-assistant' docker_image_tag: 'latest' rasa_sdk_version: '3.0.0' ================================================ FILE: .github/workflows/lint_and_test.yml ================================================ name: Lint and Test on: push: branches: - main paths-ignore: - "README.md" - "Makefile" - "Dockerfile" jobs: lint-testing: name: Code Formatting Tests runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - name: Set up Python 3.7 uses: actions/setup-python@v1 with: python-version: 3.7 - name: Install dependencies run: | python -m pip install -U pip pip install -r requirements-dev.txt - name: Code Formatting Tests working-directory: ${{ github.workspace }} run: | make lint type-testing: name: Type Tests runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - name: Set up Python 3.6 uses: actions/setup-python@v1 with: python-version: 3.6 - name: Install dependencies run: | python -m pip install -U pip pip install -r requirements-dev.txt - name: Type Checking working-directory: ${{ github.workspace }} run: | make types training-testing: name: Training and Testing runs-on: ubuntu-latest needs: [lint-testing, type-testing] steps: - uses: actions/checkout@v1 - id: files uses: jitterbit/get-changed-files@v1 - name: set_training if: | contains( steps.files.outputs.all, 'data/' ) || contains( steps.files.outputs.all, 'config.yml' ) || contains( steps.files.outputs.all, 'domain.yml' ) run: echo "RUN_TRAINING=true" >> $GITHUB_ENV - name: Rasa Train and Test GitHub Action if: env.RUN_TRAINING == 'true' uses: RasaHQ/rasa-train-test-gha@main with: rasa_version: '3.0.0' test_type: all data_validate: true cross_validation: true publish_summary: true github_token: ${{ secrets.GITHUB_TOKEN }} - name: Upload model if: github.ref == 'refs/heads/main' uses: actions/upload-artifact@main with: name: model path: models ================================================ FILE: .github/workflows/lint_and_test_pr.yml ================================================ name: Lint and Test PR on: pull_request: paths-ignore: - "README.md" - "Makefile" - "Dockerfile" jobs: lint-testing: name: Code Formatting Tests runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - name: Set up Python 3.7 uses: actions/setup-python@v1 with: python-version: 3.7 - name: Install dependencies run: | python -m pip install -U pip pip install -r requirements-dev.txt - name: Code Formatting Tests working-directory: ${{ github.workspace }} run: | make lint type-testing: name: Type Tests runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - name: Set up Python 3.7 uses: actions/setup-python@v1 with: python-version: 3.7 - name: Install dependencies run: | python -m pip install -U pip pip install -r requirements-dev.txt - name: Type Checking working-directory: ${{ github.workspace }} run: | make types training-testing: name: Training and Testing runs-on: ubuntu-latest needs: [lint-testing, type-testing] steps: - uses: actions/checkout@v1 - id: files uses: jitterbit/get-changed-files@v1 - name: set_training if: | contains( steps.files.outputs.all, 'data/' ) || contains( steps.files.outputs.all, 'config.yml' ) || contains( steps.files.outputs.all, 'domain.yml' ) run: echo "RUN_TRAINING=true" >> $GITHUB_ENV - name: Rasa Train and Test GitHub Action if: env.RUN_TRAINING == 'true' uses: RasaHQ/rasa-train-test-gha@main with: rasa_version: '3.0.0' test_type: all data_validate: true cross_validation: true publish_summary: true github_token: ${{ secrets.GITHUB_TOKEN }} - name: Upload model if: github.ref == 'refs/heads/main' uses: actions/upload-artifact@main with: name: model path: models ================================================ FILE: .gitignore ================================================ # Standard Ignores venv __pycache__/ .DS_Store .vscode/ .history/ .pytype/ # Rasa Ignores credentials.yml *.db-* rasa.db* *.db results/ models/ snow_connector.py chatroom/ .idea/ ================================================ FILE: .pre-commit-config.yaml ================================================ repos: - repo: https://github.com/ambv/black rev: stable hooks: - id: black ================================================ FILE: Dockerfile.chatroom ================================================ # docker build -t chatroom -f Dockerfile.chatroom # docker run --name chatroom -p 8080:3000 -d chatroom FROM node:14 RUN node --version RUN git clone https://github.com/RasaHQ/chatroom.git WORKDIR /chatroom RUN curl -o- -L https://yarnpkg.com/install.sh | bash RUN yarn # replace default chatroom index.html COPY chatroom_handoff.html index.html RUN yarn build EXPOSE 8080 CMD [ "yarn", "serve" ] ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2020 Rasa Technologies GmbH Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: Makefile ================================================ help: @echo "make" @echo " clean" @echo " Remove Python/build artifacts." @echo " formatter" @echo " Apply black formatting to code." @echo " lint" @echo " Lint code with flake8, and check if black formatter should be applied." @echo " types" @echo " Check for type errors using pytype." @echo " validate" @echo " Runs the rasa data validate to verify data." @echo " test" @echo " Runs the rasa test suite checking for issues." @echo " crossval" @echo " Runs the rasa cross validation tests and creates results.md" @echo " shell" @echo " Runs the rasa train and rasa shell for testing" clean: find . -name '*.pyc' -exec rm -f {} + find . -name '*.pyo' -exec rm -f {} + find . -name '*~' -exec rm -f {} + rm -rf build/ rm -rf .pytype/ rm -rf dist/ rm -rf docs/_build formatter: black actions --line-length 79 lint: flake8 actions black --check actions types: pytype --keep-going actions validate: rasa train rasa data validate --debug test: rasa train rasa test --fail-on-prediction-errors crossval: rasa test nlu -f 5 --cross-validation python format_results.py shell: rasa train --debug rasa shell --debug ================================================ FILE: README.md ================================================ # Rasa Helpdesk Assistant Example This is a Rasa chatbot example demonstrating how to build an AI assistant for an IT Helpdesk. It includes an integration with the Service Now API to open incident reports and check on incident report statuses. Below is an example conversation, showing the bot helping a user open a support ticket and query its status. You can use this chatbot as a starting point for building customer service assistants or as a template for collecting required pieces of information from a user before making an API call. Here is an example of a conversation you can have with this bot: ![Screenshot](./screenshots/demo_ss.png?raw=true) **Table of Contents** - [Rasa Helpdesk Assistant Example](#rasa-helpdesk-assistant-example) - [Setup](#setup) - [Install the dependencies](#install-the-dependencies) - [Optional: Connect to a ServiceNow instance](#optional-connect-to-a-servicenow-instance) - [Running the bot](#running-the-bot) - [Things you can ask the bot](#things-you-can-ask-the-bot) - [Example conversations](#example-conversations) - [Handoff](#handoff) - [Try it out](#try-it-out) - [How it works](#how-it-works) - [Bot-side configuration](#bot-side-configuration) - [Testing the bot](#testing-the-bot) - [Rasa X Deployment](#rasa-x-deployment) - [Action Server Image](#action-server-image) - [Notes on Chatroom](#notes-on-chatroom) ## Setup ### Install the dependencies In a Python3 virtual environment run: ```bash pip install -r requirements.txt ``` To install development dependencies, run: ```bash pip install -r requirements-dev.txt pre-commit install ``` > With pre-commit installed, the `black` and `doctoc` hooks will run on every `git commit`. > If any changes are made by the hooks, you will need to re-add changed files and re-commit your changes. ### Optional: Connect to a ServiceNow instance You can run this bot without connecting to a ServiceNow instance, in which case it will send responses without creating an incident or checking the actual status of one. To run the bot without connecting ServiceNow, you don't need to change anything in `actions/snow_credentials.yml`; `localmode` should already be set to `true` If you do want to connect to ServiceNow, you can get your own free Developer instance to test this with [here](https://developer.servicenow.com/app.do#!/home) To connect to your ServiceNow developer instance, configure the following in `actions/snow_credentials.yml`: - `snow_instance` - This is the address of the ServiceNow developer instance, you don't need the leading https. - `snow_user` - The username of the service account for the ServiceNow developer instance - `snow_pw` - The password of the service account for the ServiceNow developer instance - `localmode` - Whether the action server should **not** try to reach out to a `snow_instance` based on the credentials in `actions/snow_credentials.yml`. When set to `True` (default in the code), it will just take all the data in and message out the information that would normally be sent. ## Running the bot Use `rasa train` to train a model. Then, to run, first set up your action server in one terminal window: ```bash rasa run actions ``` In another window, run the duckling server (for entity extraction): ```bash docker run -p 8000:8000 rasa/duckling ``` Then to talk to the bot, run: ```bash rasa shell --debug ``` Note that `--debug` mode will produce a lot of output meant to help you understand how the bot is working under the hood. You can also add this flag to the action server command. To simply talk to the bot, you can remove this flag. You can also try out the bot locally using Rasa X by running ```bash rasa x ``` Refer to our guided workflow in the [Wiki page](https://github.com/RasaHQ/helpdesk-assistant/wiki/Using-Rasa-X-with-the-Helpdesk-Assistant) for how to get started with Rasa X in local mode. ## Things you can ask the bot The bot has two main skills: 1. Opening an incident in ServiceNow. 2. Checking the status of incidents in ServiceNow by email address of the caller. It will prompt the user to re-use previously provided (during the chat session) email addresses, if any exist. For the purposes of illustration, the bot recognizes the following as requests to open an incident: 1. Asking to open an incident directly e.g. "I want to open an incident" 2. Asking about a problem resetting their password e.g. "I can't reset my password" 3. Asking about a problem with outlook/email e.g. "I can't log in to my outlook" Take a look at `data/nlu.md` to see what the bot is currently trained to recognize. It can also respond to requests for help (e.g. "help me"). If configured, the bot can also hand off to another bot in response to the user asking for handoff. More [details on handoff](#handoff) below. ## Example conversations With `localmode=true`: ``` Bot loaded. Type a message and press enter (use '/stop' to exit): Your input -> hi Hello! I can help you open a service request ticket, or check the status of your open incidents. You can ask me things like: - Open an incident - Help me reset my password - I'm having a issue with my email - What's the status of the ticket I opened? Your input -> i have a problem with my email What is your email address? Your input -> anything@example.com ? What is the priority of this issue? Your input -> 1: low (/inform{"priority":"low"}) What is the problem description for the issue? Your input -> Can't log in to my email ? Should I open an incident with the following details? email: anything@example.com problem description: Can't log in to my email title: Problem with email Your input -> priority: low 1: Yes (/affirm) An incident with the following details would be opened if ServiceNow was connected: email: anything@example.com problem description: Can't log in to my email title: Problem with email priority: low Your input -> thanks. Can i check the status of my other tickets? Would you like to use the last email address you used, anything@example.com? Your input -> Yes please Since ServiceNow isn't connected, I'm making this up! The most recent incident for anything@example.com is currently awaiting triage ``` With `localmode=false`: With a Service Now instance connected, it will check if the email address is in the instance database and provide an incident number for the final response: ``` Your input -> help me reset my password What is your email address? Your input -> idontexist@example.com Sorry, "idontexist@example.com" isn't in our incident management system. Please try again. What is your email address? Your input -> abraham.lincoln@example.com ? What is the priority of this issue? Your input -> 3: high (/inform{"priority":"high"}) What is the problem description for the issue? Your input -> Password stuck in a loop ? Should I open an incident with the following details? email: abraham.lincoln@example.com problem description: Password stuck in a loop title: Problem resetting password priority: high Your input -> 1: Yes (/affirm) Successfully opened up incident INC0010008 for you. Someone will reach out soon. Your input -> Can I check the status of my tickets? Would you like to use the last email address you used, abraham.lincoln@example.com? Your input -> Yes please Incident INC0010002: "Email Log in problem", opened on 2020-05-21 09:57:06 is currently in progress Incident INC0010008: "Problem resetting password", opened on 2020-05-21 12:12:49 is currently awaiting triage Your input -> thanks You're welcome! ``` ## Handoff This bot includes a simple skill for handing off the conversation to another bot or a human. This demo relies on [this fork of chatroom](https://github.com/RasaHQ/chatroom) to work, however you could implement similar behaviour in another channel and then use that instead. See the chatroom README for more details on channel-side configuration. Using the default set up, the handoff skill enables this kind of conversation with two bots: ### Try it out The simplest way to use the handoff feature is to do the following: 1. Clone [chatroom](https://github.com/RasaHQ/chatroom) and [Financial-Demo](https://github.com/RasaHQ/Financial-Demo) alongside this repo 2. In the chatroom repo, install the dependencies: ```bash yarn install ``` 3. In the chatroom repo, build and serve chatroom: ```bash yarn build yarn serve ``` 4. In the Financial-Demo repo, install the dependencies and train a model (see the Financial-Demo README) 5. In the Helpdesk-Assistant repo (i.e. this repo), run the rasa server and action server at the default ports (shown here for clarity) In one terminal window: ```bash rasa run --enable-api --cors "*" --port 5005 --debug ``` In another terminal window: ```bash rasa run actions --port 5055 --debug ``` 6. In the Financial-Demo repo, run the rasa server and action server at **the non-default ports shown below** In one terminal window: ```bash rasa run --enable-api --cors "*" --port 5006 --debug ``` In another terminal window: ```bash rasa run actions --port 5056 --debug ``` 7. Open `chatroom_handoff.html` in a browser to see handoff in action ### How it works Using chatroom, the general approach is as follows: 1. User asks original bot for a handoff. 2. The original bot handles the request and eventually sends a message with the following custom json payload: ``` { "handoff_host": "", "title": "" } ``` This message is not displayed in the Chatroom window. 3. Chatroom switches the host to the specified `handoff_host` 4. The original bot no longer receives any messages. 5. The handoff host receives the message `/handoff{"from_host":"<original bot url">}` 6. The handoff host should be configured to respond to this message with something like, "Hi, I'm <so and so>, how can I help you??" 7. The handoff host can send a message in the same format as specified above to hand back to the original bot. In this case the same pattern repeats, but with the roles reversed. It could also hand off to yet another bot/human. ### Bot-side configuration The "try it out" section doesn't require any further configuration; this section is for those who want to change or further understand the set up. For this demo, the user can ask for a human, but they'll be offered a bot (or bots) instead, so that the conversation looks like this: For handoff to work, you need at least one "handoff_host". You can specify any number of handoff hosts in the file `actions/hanodff_config.yml`. ``` handoff_hosts: financial_demo: title: "Financial Demo" url: "http://localhost:5006" ## you can add more handoff hosts to this list e.g. # moodbot: # title: "MoodBot" # url: "http://localhost:5007" ``` Handoff hosts can be other locally running rasa bots, or anything that serves responses in the format that chatroom accepts. If a handoff host is not a rasa bot, you will of course want to update the response text to tell the user who/what they are being handed off to. The [Financial-Demo](https://github.com/RasaHQ/Financial-Demo) bot has been set up to handle handoff in exactly the same way as Helpdesk-Assistant, so the simplest way to see handoff in action is to clone Financial-Demo alongside this repo. If you list other locally running bots as handoff hosts, make sure the ports on which the various rasa servers & action servers are running do not conflict with each other. ## Testing the bot You can test the bot on the test conversations by running `rasa test`. This will run [end-to-end testing](https://rasa.com/docs/rasa/user-guide/testing-your-assistant/#end-to-end-testing) on the conversations in `tests/conversation_tests.md`. ## Rasa X Deployment To [deploy helpdesk-assistant](https://rasa.com/docs/rasa/user-guide/how-to-deploy/), it is highly recommended to make use of the [one line deploy script](https://rasa.com/docs/rasa-x/installation-and-setup/one-line-deploy-script/) for Rasa X. As part of the deployment, you'll need to set up [git integration](https://rasa.com/docs/rasa-x/installation-and-setup/integrated-version-control/#connect-your-rasa-x-server-to-a-git-repository) to pull in your data and configurations, and build or pull an action server image. ### Action Server Image You will need to have docker installed in order to build the action server image. If you haven't made any changes to the action code, you can also use the [public image on Dockerhub](https://hub.docker.com/r/rasa/helpdesk-assistant) instead of building it yourself. See the Dockerfile for what is included in the action server image, To build the image: ```bash docker build . -t <name of your custom image>:<tag of your custom image> ``` To test the container locally, you can then run the action server container with: ```bash docker run -p 5055:5055 <name of your custom image>:<tag of your custom image> ``` Once you have confirmed that the container works as it should, you can push the container image to a registry with `docker push` It is recommended to use an[automated CI/CD process](https://rasa.com/docs/rasa/user-guide/setting-up-ci-cd) to keep your action server up to date in a production environment. ## Notes on Chatroom If you want to try the transfer to another bot feature, you'll need to use Chatroom. As of this writing, the main Scalable Minds chatroom [project](https://github.com/scalableminds/chatroom) has not included this feature so you will need to build from a fork. The following docker commands will build an image from the adapted Chatroom and run it. ``` docker build -t chatroom -f Dockerfile.chatroom . docker run --name chatroom -p 8080:8080 -d chatroom ``` From the `docker-compose.yml` below, you can start chatroom with `docker-compose up -d` Here's an example docker-compose.yml for this image. Note that the initial Rasa endpoint URL is hard coded in `chatroom_handoff.html`; to use your locally running bot, point it to `http://localhost:5005`. ``` version: "3.4" services: chatroom: image: chatroom build: context: ./ dockerfile: Dockerfile.chatroom ports: - "8080:8080" command: [ "yarn", "serve" ] ``` ================================================ FILE: actions/__init__.py ================================================ ================================================ FILE: actions/actions.py ================================================ import logging from typing import Dict, Text, Any, List from rasa_sdk import Tracker from rasa_sdk.executor import CollectingDispatcher, Action from rasa_sdk.forms import FormValidationAction from rasa_sdk.events import AllSlotsReset, SlotSet from actions.snow import SnowAPI import random logger = logging.getLogger(__name__) vers = "vers: 0.1.0, date: Apr 2, 2020" logger.debug(vers) snow = SnowAPI() localmode = snow.localmode logger.debug(f"Local mode: {snow.localmode}") class ActionAskEmail(Action): def name(self) -> Text: return "action_ask_email" def run( self, dispatcher: CollectingDispatcher, tracker: Tracker, domain: Dict[Text, Any], ) -> List[Dict]: if tracker.get_slot("previous_email"): dispatcher.utter_message(template=f"utter_ask_use_previous_email",) else: dispatcher.utter_message(template=f"utter_ask_email") return [] def _validate_email( value: Text, dispatcher: CollectingDispatcher, tracker: Tracker, domain: Dict[Text, Any], ) -> Dict[Text, Any]: """Validate email is in ticket system.""" if not value: return {"email": None, "previous_email": None} elif isinstance(value, bool): value = tracker.get_slot("previous_email") if localmode: return {"email": value} results = snow.email_to_sysid(value) caller_id = results.get("caller_id") if caller_id: return {"email": value, "caller_id": caller_id} elif isinstance(caller_id, list): dispatcher.utter_message(template="utter_no_email") return {"email": None} else: dispatcher.utter_message(results.get("error")) return {"email": None} class ValidateOpenIncidentForm(FormValidationAction): def name(self) -> Text: return "validate_open_incident_form" def validate_email( self, value: Text, dispatcher: CollectingDispatcher, tracker: Tracker, domain: Dict[Text, Any], ) -> Dict[Text, Any]: """Validate email is in ticket system.""" return _validate_email(value, dispatcher, tracker, domain) def validate_priority( self, value: Text, dispatcher: CollectingDispatcher, tracker: Tracker, domain: Dict[Text, Any], ) -> Dict[Text, Any]: """Validate priority is a valid value.""" if value.lower() in snow.priority_db(): return {"priority": value} else: dispatcher.utter_message(template="utter_no_priority") return {"priority": None} class ActionOpenIncident(Action): def name(self) -> Text: return "action_open_incident" def run( self, dispatcher: CollectingDispatcher, tracker: Tracker, domain: Dict[Text, Any], ) -> List[Dict]: """Create an incident and return details or if localmode return incident details as if incident was created """ priority = tracker.get_slot("priority") email = tracker.get_slot("email") problem_description = tracker.get_slot("problem_description") incident_title = tracker.get_slot("incident_title") confirm = tracker.get_slot("confirm") if not confirm: dispatcher.utter_message( template="utter_incident_creation_canceled" ) return [AllSlotsReset(), SlotSet("previous_email", email)] if localmode: message = ( f"An incident with the following details would be opened " f"if ServiceNow was connected:\n" f"email: {email}\n" f"problem description: {problem_description}\n" f"title: {incident_title}\npriority: {priority}" ) else: snow_priority = snow.priority_db().get(priority) response = snow.create_incident( description=problem_description, short_description=incident_title, priority=snow_priority, email=email, ) incident_number = ( response.get("content", {}).get("result", {}).get("number") ) if incident_number: message = ( f"Successfully opened up incident {incident_number} " f"for you. Someone will reach out soon." ) else: message = ( f"Something went wrong while opening an incident for you. " f"{response.get('error')}" ) dispatcher.utter_message(message) return [AllSlotsReset(), SlotSet("previous_email", email)] class IncidentStatusForm(FormValidationAction): def name(self) -> Text: return "validate_incident_status_form" def validate_email( self, value: Text, dispatcher: CollectingDispatcher, tracker: Tracker, domain: Dict[Text, Any], ) -> Dict[Text, Any]: """Validate email is in ticket system.""" return _validate_email(value, dispatcher, tracker, domain) class ActionCheckIncidentStatus(Action): def name(self) -> Text: return "action_check_incident_status" def run( self, dispatcher: CollectingDispatcher, tracker: Tracker, domain: Dict[Text, Any], ) -> List[Dict]: """Look up all incidents associated with email address and return status of each""" email = tracker.get_slot("email") incident_states = { "New": "is currently awaiting triage", "In Progress": "is currently in progress", "On Hold": "has been put on hold", "Closed": "has been closed", } if localmode: status = random.choice(list(incident_states.values())) message = ( f"Since ServiceNow isn't connected, I'm making this up!\n" f"The most recent incident for {email} {status}" ) else: incidents_result = snow.retrieve_incidents(email) incidents = incidents_result.get("incidents") if incidents: message = "\n".join( [ f'Incident {i.get("number")}: ' f'"{i.get("short_description")}", ' f'opened on {i.get("opened_at")} ' f'{incident_states.get(i.get("incident_state"))}' for i in incidents ] ) else: message = f"{incidents_result.get('error')}" dispatcher.utter_message(message) return [AllSlotsReset(), SlotSet("previous_email", email)] ================================================ FILE: actions/handoff.py ================================================ from rasa_sdk import Tracker, Action from rasa_sdk.executor import CollectingDispatcher import ruamel.yaml import pathlib from typing import Dict, Text, Any, List from rasa_sdk.events import EventType here = pathlib.Path(__file__).parent.absolute() handoff_config = ( ruamel.yaml.safe_load(open(f"{here}/handoff_config.yml", "r")) or {} ).get("handoff_hosts", {}) class ActionHandoffOptions(Action): def name(self) -> Text: return "action_handoff_options" async def run( self, dispatcher: CollectingDispatcher, tracker: Tracker, domain: Dict[Text, Any], ) -> List[EventType]: if not any( [config.get("url") for bot, config in handoff_config.items()] ): dispatcher.utter_message(template="utter_no_handoff") else: buttons = [ { "title": config.get("title"), "payload": f'/trigger_handoff{{"handoff_to":"{bot}"}}', } for bot, config in handoff_config.items() ] dispatcher.utter_message( text="I can't transfer you to a human, \ but I can transfer you to one of these bots", buttons=buttons, ) return [] class ActionHandoff(Action): def name(self) -> Text: return "action_handoff" async def run( self, dispatcher: CollectingDispatcher, tracker: Tracker, domain: Dict[Text, Any], ) -> List[EventType]: dispatcher.utter_message(template="utter_handoff") handoff_to = tracker.get_slot("handoff_to") handoff_bot = handoff_config.get(handoff_to, {}) url = handoff_bot.get("url") if url: if tracker.get_latest_input_channel() == "rest": dispatcher.utter_message( json_message={ "handoff_host": url, "title": handoff_bot.get("title"), } ) else: dispatcher.utter_message( template="utter_wouldve_handed_off", handoffhost=url ) else: dispatcher.utter_message(template="utter_no_handoff") return [] ================================================ FILE: actions/handoff_config.yml ================================================ handoff_hosts: financial_demo: title: "Financial Assistant" url: "http://localhost:5006" ## you can add more handoff hosts to this list e.g. # moodbot: # title: "MoodBot" # url: "http://localhost:5007" ================================================ FILE: actions/requirements-actions.txt ================================================ pytablewriter requests ruamel.yaml ================================================ FILE: actions/snow.py ================================================ import logging import requests import json import pathlib import ruamel.yaml from typing import Dict, Text, Any logger = logging.getLogger(__name__) here = pathlib.Path(__file__).parent.absolute() json_headers = { "Content-Type": "application/json", "Accept": "application/json", } class SnowAPI(object): """class to connect to the ServiceNow API""" def __init__(self): snow_config = ( ruamel.yaml.safe_load(open(f"{here}/snow_credentials.yml", "r")) or {} ) self.snow_user = snow_config.get("snow_user") self.snow_pw = snow_config.get("snow_pw") self.snow_instance = snow_config.get("snow_instance") self.localmode = snow_config.get("localmode", True) self.base_api_url = "https://{}/api/now".format(self.snow_instance) def handle_request( self, request_method=requests.get, request_args={} ) -> Dict[Text, Any]: result = dict() try: response = request_method(**request_args) result["status_code"] = response.status_code if response.status_code >= 200 < 300: result["content"] = response.json() else: error = ( f"ServiceNow error: {response.status_code}: " f'{response.json().get("error",{}).get("message")}' ) logger.debug(error) result["error"] = error except requests.exceptions.Timeout: error = "Could not connect to ServiceNow (Timeout)" logger.debug(error) result["error"] = error return result def email_to_sysid(self, email) -> Dict[Text, Any]: lookup_url = ( f"{self.base_api_url}/table/sys_user?" f"sysparm_query=email={email}&sysparm_display_value=true" ) request_args = { "url": lookup_url, "auth": (self.snow_user, self.snow_pw), "headers": json_headers, } result = self.handle_request(requests.get, request_args) records = result.get("content", {}).get("result") if len(records) == 1: caller_id = records[0].get("sys_id") result["caller_id"] = caller_id elif isinstance(records, list): result["caller_id"] = [] result["error"] = ( f"Could not retrieve caller id; " f"{records} records found for email {email}" ) return result def retrieve_incidents(self, email) -> Dict[Text, Any]: result = self.email_to_sysid(email) caller_id = result.get("caller_id") if caller_id: incident_url = ( f"{self.base_api_url}/table/incident?" f"sysparm_query=caller_id={caller_id}" f"&sysparm_display_value=true" ) request_args = { "url": incident_url, "auth": (self.snow_user, self.snow_pw), "headers": json_headers, } result = self.handle_request(requests.get, request_args) incidents = result.get( "content", {} # pytype: disable=attribute-error ).get("result") if incidents: result["incidents"] = incidents elif isinstance(incidents, list): result["error"] = f"No incidents on record for {email}" return result def create_incident( self, description, short_description, priority, email ) -> Dict[Text, Any]: result = self.email_to_sysid(email) caller_id = result.get("caller_id") if caller_id: incident_url = f"{self.base_api_url}/table/incident" data = { "opened_by": caller_id, "short_description": short_description, "description": description, "urgency": priority, "caller_id": caller_id, "comments": description, } request_args = { "url": incident_url, "auth": (self.snow_user, self.snow_pw), "headers": json_headers, "data": json.dumps(data), } result = self.handle_request(requests.post, request_args) return result @staticmethod def priority_db() -> Dict[str, int]: """Database of supported priorities""" priorities = {"low": 3, "medium": 2, "high": 1} return priorities ================================================ FILE: actions/snow_credentials.yml ================================================ # snow_instance: "dev97377.service-now.com" # snow_user: "admin" # snow_pw: "mySnowinstance1" # localmode: false ================================================ FILE: chatroom_handoff.html ================================================ <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <link rel="stylesheet" href="./dist/Chatroom.css" /> <link rel="stylesheet" type="text/css" href="./index.css"> </head> <body> <div class="chat-container"></div> <script src="./dist/Chatroom.js"></script> <script type="text/javascript"> var chatroom = new window.Chatroom({ host: "http://helpdesk-assistant.rasa.com", title: "Helpdesk Assistant", container: document.querySelector(".chat-container"), welcomeMessage: "Hi, how may I help you?" // speechRecognition: "en-US", // voiceLang: "en-US" }); chatroom.openChat(); </script> </body> </html> ================================================ FILE: config.yml ================================================ language: en pipeline: - name: WhitespaceTokenizer - name: RegexFeaturizer - name: LexicalSyntacticFeaturizer - name: CountVectorsFeaturizer - name: CountVectorsFeaturizer analyzer: "char_wb" min_ngram: 1 max_ngram: 4 - name: DIETClassifier epochs: 150 random_seed: 1 - name: FallbackClassifier threshold: 0.7 - name: DucklingEntityExtractor url: http://localhost:8000 dimensions: - email - name: EntitySynonymMapper policies: - name: AugmentedMemoizationPolicy max_history: 4 - name: TEDPolicy max_history: 4 epochs: 100 - name: RulePolicy core_fallback_threshold: 0.4 core_fallback_action_name: "action_default_fallback" enable_fallback_prediction: True ================================================ FILE: data/handoff.yml ================================================ version: "3.0" stories: - story: handoff steps: - intent: human_handoff - action: utter_ask_handoff - action: action_handoff_options - intent: trigger_handoff - action: action_handoff - story: handoff steps: - intent: human_handoff - action: utter_ask_handoff - action: action_handoff_options - intent: trigger_handoff entities: - handoff_to: financial_demo - action: action_handoff - story: handoff deny steps: - intent: human_handoff - action: utter_ask_handoff - action: action_handoff_options - intent: deny - action: utter_ask_whatelse ================================================ FILE: data/nlu.yml ================================================ version: "3.0" nlu: - intent: greet examples: | - hey - hello - hi - good morning - good evening - hey there - hi there - hello there - greetings - Hi - helo - Hi! - Hi' - Hi, - Hi, bot - Hieee - Hieeeeeeeeeeeee - Hola - What is up? - Whats up - Whats up my bot - Whats up? - ayyyy whaddup - bonjour - ey boss - I said, helllllloooooO!!!! - Well hello there ;) - intent: goodbye examples: | - bye - goodbye - see you around - see you later - see ya - buh bye - Bye - Bye bye - adios - adios? - bye :P - bye bot - bye bye - bye bye bot - bye for now - bye udo - bye was nice talking to you - bye! - byee - catch you later - ciao - cya - farewell - good bye - good bye rasa bot! - good night - goodbye - goodbye. - goodnight - gotta go - ok Bye - ok bye - ok, bye - ok.bye - see u later - see ya - see you - see you . bye - take care - then bye - tlak to you later - toodle-oo - byr - intent: thank examples: | - thanks! - thank you - thanks a lot - great thanks - appreciate it - cool thanks - Cool. Thanks - Great, thanks - Thank you so much - Thank's! - Thanks bot - Thanks for that - Thanks! - amazing, thanks - cool thank you - cool, thanks - danke - great thanks - ok thanks - ok thanks - ok thanks! - perfect thank you - thank u - thank you - thank you anyways - thanks - thanks a bunch for everything - thanks a lot - thanks for the help - thanks you - thanks! - thankyou - thnks - thx - yes thanks - thanks for your information - thanks f - intent: bot_challenge examples: | - are you a bot? - are you a human? - am I talking to a bot? - am I talking to a human? - you are a bot right - aren't you a bot - Are you a bot? - are you a BOT - are you a bot? - are you a chatbot - are you a rasa bot? - are you a real bot? - are you a robot - are you ai - are you artificial - are you artificial intelligence - are you bot - are you bot? - are you rasa bot? - are you real - are you real lol - are you really a bot - are you robot - are you sure that you're a bot? - bot? - cuz you are a bot - i guess you are a chatbot - oh are you chatbot? - tell me, are you a bot? - what are you, a bot? - you are a robot - you are ai - you are chatbot - you're a bot - you robo - real bot then? - are you really a bbot? - intent: password_reset examples: | - need to reset my password - need help resetting my password - I can't reset my password - need help with my password - having issues resetting my password - freaking password won't work - reset my password - password reset - forgot my password and can't reset it - uh password reset help please - I'm having so many problems resetting my password - Can you reset my password reset - My pass word isn't working for me what do I do - How can I reconfigure my pass word - pass word reset? - forgot my pass word again what now - password help fix - show me how to set password again - can you tell me how to change my password - change pass word - I forgot my password what can I do - forgot my password help me reset it pls - intent: open_incident examples: | - I need to open an incident - Can you open a case for me - I have an issue I need to open a incident for - open incident - can you help me open a incident please - I want to open a new incident - I want to open a incident - I'm having a weird issue. - I'm having a problem - I'm having a issue I need to open a incident for. - I have a issue I need to open a case for. - open an incident for aileen.mottern@example.com - open an [urgent]{"entity": "priority", "value": "high"} issue - I want to open an incident, but it's [low](priority) priority - I need to open a helpdesk ticket. it's [really important]{"entity": "priority", "value": "high"} - open an incident - open an incedent - i want to open an incident - i wanna open a incident - I want to open a ticket - can I open a ticket - pls help me open tix support - ticket support incident open now can you do that for me - intent: help examples: | - I need help - what can you help me with - can you help me - what can you do - I need some help - help me - can you please help me - problem - big problem - How can you help me - How you help me? - I need some help - What are my options - What are you able to do? - What can I ask you? - What can I do? - What can you demo - What can you do for me? - What can you do? - What can you tell me? - What do you do - anything els - are there any other options? - can I ask you anything else? - can you do anything else? - can you help me? - come back - cool! can I do something else here? - hello what can you do for me - help - help me - help please - help pls - help? - hep me - intent: problem_email examples: | - my outlook application won't open - I'm having issues with outlook. - issues with outlook - outlook application will not open - there's a problem with my email - something is broken with outlook - I can't log in to my email - can't log in to outlook - my email won't let me log in - i have a problem with my email - can't get to slack - i cant open my email - i can't able to login - I'm having trouble logging into my email - can you help me with me with my email please - what can you do about my email it's not working - can you fix outlook for me I don't know what to do - something isn't right with my e-mail - can you help me with my e-mail account - my internet mail isn't working - outlook is showing me a weird message - email isn't loading can you help - intent: inform examples: | - my email is test@example.com - my email is abraham.lincoln@example.com - name.name@example.com - it is admin@example.com - aileen.mottern@example.com - [low](priority) - [medium](priority) - [high](priority) - it should be a [low](priority) priority - it should be [medium](priority) - [escalated]{"entity": "priority", "value": "high"} - [escalate]{"entity": "priority", "value": "high"} - [super high](priority) - john - the priority is [high](priority) - test@gmail.com - xyz@example.com - my email is ella@rasa.com - abraham.lincoln@example.com - steve - sara@rasa.com - [urgent]{"entity": "priority", "value": "high"} - [critical]{"entity": "priority", "value": "high"} - intent: out_of_scope examples: | - what is the square root of 5 - I want to know the weather - what is the meaning of life. - Fridge Isn't Running - my tv isn't working - I want a pizza - my washing machine isn't working - what year is it - order a pizza - I want to order a pizza - what is the weather today - what is the weather - how old are you? - add two plus two - Hdjejene - asd - title - how about the forecast - foolish - joke - Can I get a hamburger? - Can YouTube talk? - Can you call me back ? - Can you give me your datacenter's password - Can you give me your datacenter's password? - Can you make sandwiches? - Can you please send me an uber - intent: incident_status examples: | - I want to check if my ticket has been closed - what's the status of my incident? - Has the incident been closed yet? - Is my ticket in progress? - What has happened with my open ticket? - what's going on with the incident I opened? - Do I have any open incidents at the moment? - Can I check the status of my open tickets? - Can I see the status of the ticket I opened? - Check ticket status - check incident status - ok, try looking it up for abraham.lincoln@example.com - look up ticket status for abel.tuter@example.com - my ticket status - What's the status of the ticket I opened? - what's my ticket status - how is my ticket going - an updates on my incident ticket? - I opened an issue last week how is it doing now - can you check the status my email is sara@rasa.com - has the ticket been closed yet? - is my issue still open - intent: affirm examples: | - yes - yes please - yes I would - please do - yup - yep - that's right - indeed - Yes, please - yeah - sure - yes thanks - That would be great - YES - YUP - Yea - Yeah - Yeah sure - Yep - Yep that's fine - Yep! - Yepp - Yes - Yes I do - Yes please - Yes please! - Yes, I accept - intent: deny examples: | - no - nope - no thanks - not that one - don't use that - please no - no don't do that - no, use abraham.lincoln@example.com - Neither - Never - Nevermind - No thank you - No, not really. - No, thank you - No. - Nopes - Not really - absolutely not - decline - definitely not - deny - na - nah - nah I'm good - intent: human_handoff examples: | - I want a human - can I speak to an agent - real agent please - real human - chat with a live agent - give me a person please - i want to talk to a human - can you transfer me to a human? - handoff - talk to a human - i need to talk to a human - human please - i need help from a human - i need help from a real human - give me a human - i want to talk to human - i want a representative - i want to talk to a person - I don't wanna talk to a bot - I dont like to talk to a machine - I want to talk to a human - I want to talk to the founders - are there also humans working for your company? - can I speak to a person? - can i please speak to a human? - can you forward me to your team - can you please connect me to a real rasa employee? - can you put me in touch with a human? - do you have human support ? - gimme a proper human - give me a human now - human handoff - i dont wanna talk to a bot - i want to speak to a real person - i want to speak to customer service - i want to talk to a human - i want to talk to a person - i want to talk to human - i want to talk to someone at rasa - i want to talk to someone else - i want to talk to someone who is smarter than you - i would like to speak to a person - i'd rather speak with a real rasa employee - id like to talk to a real rasa employee - let me speak with a real person please - let me talk to a human - let me talk to a real person - please give me a human - service agent - someone from customer care - speak to a real person - talking to a bot is stupid - that's annoying I'd like to speak to someone real - thats not helping, can i talk to human? - wrong i want to speak to a human - can i speak to human - can i speak to your human - i want to chat with human - How do I talk to a human - talk with a human - Can i talk to a human instead - nevermind.... you're not human ... I need to talk to a live person - Can you get a human to assist me? - Can i talk to a human? - Can I talk to a human - Can I speak to a human? - can i speak to a human - no, i want to talk to human - can you hand a conversation over to a human? - can I talk to human? - can I talk to human - talk to human - i want human :( - can i talk to human - i want to talk to a human - i want to speak to human - can i talk to a real person? - connect me to a real person - I need a real person - can i took with a real person - let me speak to a real person - let me speak to a real person please - i want to talk to a real person - synonym: high examples: | - urgent - really important - escalated - escalate ================================================ FILE: data/rules.yml ================================================ version: "3.0" rules: - rule: handoff intent from other bot's handoff triggers greeting steps: - intent: handoff - action: utter_greet - rule: Ask the user to rephrase whenever they send a message with low NLU confidence steps: - intent: nlu_fallback - action: utter_default - rule: answer bot challenge steps: - intent: bot_challenge - action: utter_iamabot - rule: start open incident form steps: - or: - intent: open_incident - intent: password_reset - intent: problem_email - action: open_incident_form - active_loop: open_incident_form - rule: submit open incident form condition: - active_loop: open_incident_form steps: - action: open_incident_form - active_loop: null - action: action_open_incident - rule: start incident status form steps: - intent: incident_status - action: incident_status_form - active_loop: incident_status_form - rule: submit incident status form condition: - active_loop: incident_status_form steps: - action: incident_status_form - active_loop: null - action: action_check_incident_status ================================================ FILE: data/stories.yml ================================================ version: "3.0" stories: - story: answer out of scope steps: - intent: out_of_scope - action: utter_out_of_scope - story: give help steps: - intent: help - action: utter_help - story: thank steps: - intent: thank - action: utter_welcome - story: greet + give info steps: - intent: greet - action: utter_greet - action: utter_help - story: say goodbye steps: - intent: goodbye - action: utter_goodbye - story: open incident form interrupted steps: - or: - intent: open_incident - intent: password_reset - intent: problem_email - action: open_incident_form - active_loop: open_incident_form - intent: help - action: utter_help - action: open_incident_form - active_loop: null - action: action_open_incident - story: open incident form interrupted steps: - or: - intent: open_incident - intent: password_reset - intent: problem_email - action: open_incident_form - active_loop: open_incident_form - intent: out_of_scope - action: utter_out_of_scope - action: open_incident_form - active_loop: null - action: action_open_incident - story: incident status form interrupted steps: - intent: incident_status - action: incident_status_form - active_loop: incident_status_form - intent: help - action: utter_help - action: incident_status_form - active_loop: null - action: action_check_incident_status - story: incident status form interrupted steps: - intent: incident_status - action: incident_status_form - active_loop: incident_status_form - intent: out_of_scope - action: utter_out_of_scope - action: incident_status_form - active_loop: null - action: action_check_incident_status - story: incident status form switch to open incident steps: - intent: incident_status - action: incident_status_form - active_loop: incident_status_form - or: - intent: open_incident - intent: password_reset - intent: problem_email - action: open_incident_form - active_loop: open_incident_form - active_loop: null - action: action_open_incident - story: open incident form switch to incident status form steps: - or: - intent: open_incident - intent: password_reset - intent: problem_email - action: open_incident_form - active_loop: open_incident_form - intent: incident_status - action: incident_status_form - active_loop: incident_status_form - active_loop: null - action: action_check_incident_status ================================================ FILE: domain.yml ================================================ version: '3.0' session_config: session_expiration_time: 0.0 carry_over_slots_to_new_session: true intents: - greet - goodbye - bot_challenge - password_reset - inform - thank - help - problem_email - open_incident: use_entities: [] - incident_status - out_of_scope - restart - affirm - deny - trigger_handoff - human_handoff - handoff entities: - email - priority - handoff_to slots: confirm: type: bool influence_conversation: false mappings: - type: from_intent intent: affirm value: true conditions: - active_loop: open_incident_form requested_slot: confirm - type: from_intent intent: deny value: false conditions: - active_loop: open_incident_form requested_slot: confirm previous_email: type: text influence_conversation: false mappings: - type: custom caller_id: type: text influence_conversation: false mappings: - type: custom email: type: text influence_conversation: false mappings: - type: from_entity entity: email conditions: - active_loop: open_incident_form requested_slot: email - active_loop: incident_status_form requested_slot: email - type: from_intent intent: affirm value: true conditions: - active_loop: open_incident_form requested_slot: email - active_loop: incident_status_form requested_slot: email - type: from_intent intent: deny value: false conditions: - active_loop: open_incident_form requested_slot: email - active_loop: incident_status_form requested_slot: email - type: from_entity entity: email incident_title: type: text influence_conversation: false mappings: - type: from_trigger_intent intent: password_reset value: Problem resetting password conditions: - active_loop: open_incident_form requested_slot: incident_title - type: from_trigger_intent intent: problem_email value: Problem with email conditions: - active_loop: open_incident_form requested_slot: incident_title - type: from_text not_intent: - incident_status - bot_challenge - help - affirm - deny conditions: - active_loop: open_incident_form requested_slot: incident_title priority: type: text influence_conversation: false mappings: - type: from_entity entity: priority conditions: - active_loop: open_incident_form - type: from_entity entity: priority problem_description: type: text influence_conversation: false mappings: - type: from_text not_intent: - incident_status - bot_challenge - help - affirm - deny conditions: - active_loop: open_incident_form requested_slot: problem_description requested_slot: type: text influence_conversation: false mappings: - type: custom handoff_to: type: text influence_conversation: false mappings: - type: from_entity entity: handoff_to responses: utter_out_of_scope: - text: Sorry, I'm not sure how to respond to that. Type "help" for assistance. utter_greet: - text: Hallo! I'm your IT Helpdesk Assistant. utter_goodbye: - text: Goodbye! utter_iamabot: - text: I am a bot, powered by Rasa. utter_ask_email: - text: What is your email address? utter_ask_incident_title: - text: What should we use for the title of this incident? utter_ask_problem_description: - text: What is the problem description for the issue? utter_ask_priority: - text: What is the priority of this issue? buttons: - payload: /inform{"priority":"low"} title: low - payload: /inform{"priority":"medium"} title: medium - payload: /inform{"priority":"high"} title: high utter_no_priority: - text: Sorry "{priority}" is not a valid priority. Please try again. utter_no_email: - text: Sorry, "{email}" isn't in our incident management system. Please try again. utter_help: - text: "I can help you open a service request ticket, or check the status of your open incidents. \nYou can ask me things like: \n- Open an incident \n- Help me reset my password \n- I'm having a issue with my email \n- What's the status of the ticket I opened?" utter_welcome: - text: You're welcome! utter_default: - text: I didn't quite understand that. Could you rephrase? utter_ask_use_previous_email: - text: Would you like to use the last email address you used, {previous_email}? utter_ask_confirm: - text: "Should I open an incident with the following details? \n email: {email} \n problem description: {problem_description} \n title: {incident_title} \n priority: {priority}" buttons: - title: Yes payload: /affirm - title: No, cancel the incident payload: /deny utter_incident_creation_canceled: - text: Alright, I have cancelled the incident. utter_ask_whatelse: - text: What else can I help you with? utter_ask_handoff: - text: It looks like you want to be transferred to a human agent. utter_handoff: - text: Alright, I'll try to transfer you. utter_wouldve_handed_off: - text: If you were talking to me via chatroom, I would have handed you off to {handoffhost}. utter_no_handoff: - text: Since you haven't configured a host to hand off to, I can't send you anywhere! forms: open_incident_form: ignored_intents: [] required_slots: - email - priority - problem_description - incident_title - confirm incident_status_form: ignored_intents: [] required_slots: - email actions: - action_ask_email - action_check_incident_status - action_handoff - action_handoff_options - action_open_incident - validate_open_incident_form - validate_incident_status_form ================================================ FILE: endpoints.yml ================================================ # This file contains the different endpoints your bot can use. # Server where the models are pulled from. # https://rasa.com/docs/rasa/user-guide/running-the-server/#fetching-models-from-a-server/ #models: # url: http://my-server.com/models/default_core@latest # wait_time_between_pulls: 10 # [optional](default: 100) # Server which runs your custom actions. # https://rasa.com/docs/rasa/core/actions/#custom-actions/ action_endpoint: url: "http://localhost:5055/webhook" # Tracker store which is used to store the conversations. # By default the conversations are stored in memory. # https://rasa.com/docs/rasa/api/tracker-stores/ #tracker_store: # type: redis # url: <host of the redis instance, e.g. localhost> # port: <port of your redis instance, usually 6379> # db: <number of your database within redis, e.g. 0> # password: <password used for authentication> # use_ssl: <whether or not the communication is encrypted, default false> #tracker_store: # type: mongod # url: <url to your mongo instance, e.g. mongodb://localhost:27017> # db: <name of the db within your mongo instance, e.g. rasa> # username: <username used for authentication> # password: <password used for authentication> # Event broker which all conversation events should be streamed to. # https://rasa.com/docs/rasa/api/event-brokers/ #event_broker: # url: localhost # username: username # password: password # queue: queue ================================================ FILE: format_results.py ================================================ from pytablewriter import MarkdownTableWriter import json def intent_table(): writer = MarkdownTableWriter() writer.table_name = "Intent Cross-Validation Results (5 folds)" with open("results/intent_report.json", "r") as f: data = json.loads(f.read()) cols = ["support", "f1-score", "confused_with"] writer.headers = ["class"] + cols classes = list(data.keys()) try: classes.remove("accuracy") except: pass classes.sort(key=lambda x: data[x]["support"], reverse=True) def format_cell(data, c, k): if not data[c].get(k): return "N/A" if k == "confused_with": return ", ".join([f"{k}({v})" for k, v in data[c][k].items()]) else: return data[c][k] writer.value_matrix = [ [c] + [format_cell(data, c, k) for k in cols] for c in classes ] return writer.dumps() def entity_table(): writer = MarkdownTableWriter() writer.table_name = "Entity Cross-Validation Results (5 folds)" with open("results/DIETClassifier_report.json", "r") as f: data = json.loads(f.read()) cols = ["support", "f1-score", "precision", "recall"] writer.headers = ["entity"] + cols classes = list(data.keys()) classes.sort(key=lambda x: data[x]["support"], reverse=True) def format_cell(data, c, k): if not data[c].get(k): return "N/A" else: return data[c][k] writer.value_matrix = [ [c] + [format_cell(data, c, k) for k in cols] for c in classes ] return writer.dumps() intents = intent_table() entities = entity_table() with open("results.md", "w") as f: f.write(intents) f.write("\n\n\n") f.write(entities) ================================================ FILE: pyproject.toml ================================================ [tool.black] line-length = 79 include = '\.pyi?$' exclude = ''' /( \.git | \.hg | \.mypy_cache | \.tox | \.venv | _build | buck-out | build | dist )/ ''' ================================================ FILE: requirements-dev.txt ================================================ -r requirements.txt # lint/format/types black==19.10b0 flake8==3.7.8 pytype==2019.7.11 pre-commit ================================================ FILE: requirements.txt ================================================ rasa~=3.0.0 rasa-sdk~=3.0.0 # if you change this, make sure to change the Dockerfile to match -r actions/requirements-actions.txt ================================================ FILE: tests/test_conversations.yml ================================================ version: "2.0" stories: - story: bot challenge steps: - intent: bot_challenge user: |- Are you a bot? - action: utter_iamabot - story: out of scope steps: - intent: out_of_scope user: |- I want a pizza - action: utter_out_of_scope - story: open incident steps: - intent: greet user: |- hello - action: utter_greet - action: utter_help - intent: open_incident user: |- I need to open an incident - action: open_incident_form - active_loop: open_incident_form - active_loop: null - action: action_open_incident - story: open password reset incident steps: - intent: password_reset user: |- I'm having issues with my password - action: open_incident_form - active_loop: open_incident_form - active_loop: null - action: action_open_incident - story: email incident steps: - intent: greet user: |- hello - action: utter_greet - action: utter_help - intent: problem_email user: |- I have a problem with my email - action: open_incident_form - active_loop: open_incident_form - active_loop: null - action: action_open_incident - story: interrupted steps: - intent: problem_email user: |- I have a problem with my email - action: open_incident_form - active_loop: open_incident_form - intent: help - action: utter_help - action: open_incident_form - active_loop: null - action: action_open_incident - story: greet + thank steps: - intent: greet user: |- Hey there - action: utter_greet - action: utter_help - intent: thank user: |- Awesome, thanks! - action: utter_welcome - story: user requests a human handoff steps: - intent: human_handoff user: |- I want to talk to a person now - action: utter_ask_handoff - action: action_handoff_options - intent: deny user: |- uh no - action: utter_ask_whatelse - story: User says something out of scope steps: - intent: out_of_scope user: |- Can you get me a pizza - action: utter_out_of_scope - story: trigger handoff steps: - intent: human_handoff user: |- give me a human - action: utter_ask_handoff - action: action_handoff_options - intent: trigger_handoff - action: action_handoff - story: Test fallback steps: - intent: nlu_fallback - action: utter_default - story: incident status happy path steps: - intent: greet user: |- Hey there - action: utter_greet - action: utter_help - intent: incident_status user: |- Can you check the status of my incident - action: incident_status_form - active_loop: incident_status_form - active_loop: null - action: action_check_incident_status