I've just deployed a (veeery) minimally viable virtual assistant (explorer-ai.chat) using Rasa Open-Source. It's currently running as a simple web app from a network of Docker containers built on an E2 machine (Ubuntu 18.04) hosted by Google Cloud.
Background that will help you interact with the chatbot: It's intended to function as a virtual docent to the Exploratorium in San Francisco. In particular, it should be able to answer basic questions that a human docent might be asked by a patron. The user intents fall into these categories:
There are lots of issues with both NLU and dialog management that I'm working on fixing, but the purpose of this post is to understand behavior illustrated here:
Specifically, the bot often fails to respond to a user utterance at some point in the conversation. Here are some items that might be relevant:
rasa shell
command. (However, I should mention that the models are different in the deployed and local versions -- when I Dockerized the application for deployment, any models that I COPY
ed into the Docker container for the Rasa service were considered "invalid" (why?), so I decided to include a RUN rasa train
statement in the Dockerfile. But since the models were trained on the same data and using the same configurations, I don't see how the difference would matter.)Here's docker-compose.yml
for the network:
version: '3'
services:
rasa_server:
container_name: "rasa_server"
build:
context: backend
user: root
ports:
- "5005:5005"
expose:
- 5005
action_server:
container_name: "action_server"
build:
context: actions
ports:
- "5055:5055"
expose:
- 5055
web_server:
container_name: "web_server"
image: nginx
depends_on:
- "rasa_server"
- "action_server"
ports:
- "80:80"
- "443:443"
restart: always
volumes:
- ./web/conf/default.conf:/etc/nginx/conf.d/default.conf
- ./web/conf/explorer-ai.chat.conf:/etc/nginx/conf.d/explorer-ai.chat.conf
- ./web/conf/nginx.conf:/etc/nginx/nginx.conf
- ./web/html:/var/www/html
- ./certbot/www:/var/www/certbot
- ./certbot/conf:/etc/letsencrypt
certbot:
container_name: "certbot"
image: certbot/certbot
volumes:
- ./certbot/www:/var/www/cerbot:rw
- ./certbot/conf:/etc/letsencrypt:rw
Dockerfile
for backend
:
FROM rasa/rasa:3.3.1
WORKDIR /app
USER root
COPY ./models /app/models
COPY ./data /app/data
COPY . /app
RUN rasa train
ENTRYPOINT ["rasa", "run", "-m", "models", "--enable-api", "--cors", "*", "--debug"]
Dockerfile
for actions
(in case it's useful):
FROM rasa/rasa-sdk:3.3.0
WORKDIR /app
COPY ./requirements-actions.txt requirements.txt
COPY . /app
USER root
RUN pip install --no-cache-dir -r requirements.txt
USER 1001
EXPOSE 5055
ENTRYPOINT ["python", "-m", "rasa_sdk", "--actions", "actions"]
My Rasa (backend) credentials.yml
file includes
socketio:
user_message_evt: user_uttered
bot_message_evt: bot_uttered
session_persistence: false
My index.html
file contains:
<script
src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/3.1.2/socket.io.js"
integrity="sha512-YybopSVjZU0fe8TY4YDuQbP5bhwpGBE/T6eBUEZ0usM72IWBfWrgVI13qfX4V2A/W7Hdqnm7PIOYOwP9YHnICw=="
crossorigin="anonymous" referrerpolicy="no-referrer">
</script>
<script>
const socket = io("https://explorer-ai.chat");
const messages = document.getElementById('messages');
const form = document.getElementById('form');
const messageInput = document.getElementById('message-input');
function scrollToBottom() {
window.scrollTo(0, document.body.scrollHeight);
}
function appendMessage(msg, type) {
const item = document.createElement('div');
item.textContent = msg;
item.classList.add("message");
item.classList.add(`message_${type}`);
messages.appendChild(item);
scrollToBottom();
}
const welcome = "Hello! I'm Explorer AI, a virtual assistant for visitors to the " +
"Exploratorium in San Francisco. I've been trained to answer questions about the " +
"Exploratorium's exhibits and the concepts they illustrate.";
const how_help = "How may I assist you?";
appendMessage(welcome, "received");
appendMessage(how_help, "received");
form.addEventListener('submit', function (e) {
e.preventDefault();
const msg = messageInput.value;
if (msg) {
socket.emit('user_uttered', {
"message": msg,
});
messageInput.value = '';
appendMessage(msg, "sent");
}
});
socket.on('connect', function () {
console.log("Connected to Socket.io server");
});
socket.on('connect_error', (error) => {
console.log("Connection to Socket.io FAILED");
console.error(error);
});
socket.on('bot_uttered', function (response) {
console.log("Bot uttered:", response);
if (response.text) {
appendMessage(response.text, "received");
}
if (response.attachment) {
appendImage(response.attachment.payload.src, "received");
}
if (response.quick_replies) {
appendQuickReplies(response.quick_replies);
}
});
</script>
And here are my web_server
configurations:
server {
listen 80;
listen [::]:80;
server_name explorer-ai.chat www.explorer-ai.chat;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://explorer-ai.chat$request_uri;
}
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name explorer-ai.chat www.explorer-ai.chat;
ssl_certificate /etc/letsencrypt/live/explorer-ai.chat/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/explorer-ai.chat/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
root /var/www/html;
index index.html;
location /socket.io/ {
proxy_pass http://rasa_server:5005;
}
}
localhost
), I have to make slight changes to the Docker configurations in order to accommodate my M1 chip. In particular, my backend
service is build FROM khalosa/rasa-aarch64:3.3.1
. In this setting, I notice that the bot tends to (almost?) always ignore the first user utterance. The logs below are from such a local test.Here is a section of the log corresponding to the very beginning of the conversation above:
It seems that the correct action is predicted and a BotUttered
event was triggered. But there's no action from the web server during this time. (Also, why do all of the logged events have the same timestamp?) In fact, the next log entries from the web server are these, which occur before the user's next message.
Am I right in thinking this has something to do with the networking (Nginx, socket.io), and not the Rasa components?
Thanks so much for reading, and for any help you can provide! In case it's not obvious, I'm a noob. :)
The issue was that I didn't have the proxy configured correctly. The /socket.io/
location needed some additional directives. This ended up working:
location /socket.io/ {
proxy_pass http://rasa_server:5005;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
and, in order to avoid an unknown "connection_upgrade" variable
error, I also needed to add the following directive to the http
block my nginx.conf
file:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
I think that, previously, the proxy connection was falling back to HTTP long polling (the default for socket.io).