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:

**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":"}`
6. The handoff host should be configured to respond to this message with something like,
"Hi, I'm , 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 :
```
To test the container locally, you can then run the action server container with:
```bash
docker run -p 5055:5055 :
```
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
================================================
================================================
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:
# port:
# db:
# password:
# use_ssl:
#tracker_store:
# type: mongod
# url:
# db:
# username:
# password:
# 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