Skip to content

Make a Single Shared Bot Instance

Noah Petherbridge edited this page Jan 18, 2017 · 2 revisions

RiveScript is really fast at replying to messages, but if you have a large bot brain, the time it takes RiveScript to initialize and parse the reply files can be an inhibiting factor when a lot of users are chatting with your bot.

A common mistake I see users make is that they initialize and load a new RiveScript instance for each message their bot receives, only to then let the instance be garbage collected at the end (and rinse & repeat for future requests). A better way to design your application is to only load your RiveScript bot one time during application startup, and send all requests to that shared RiveScript instance. This reduces the latency of replying from "the time it takes to fully initialize the bot and then get a reply" to simply "the time it takes to get a reply."

As a side effect, by using a shared global instance, user variables can be automatically 'remembered' between messages: the user could tell the bot their name in one message, and then ask what their name is on the next and the bot would still remember it, because the bot instance was never deleted from memory.

Table of Contents

Do's and Dont's

Don't: Initialize RiveScript for Every Request

# This example is BAD because RiveScript has to be completely re-initialized
# on each and every message request. This is slow, inefficient, and will just
# thrash your hard disk again and again for no reason!

from rivescript import RiveScript
from flask import Flask, request, jsonify

# (Flask is a web application framework for Python, but this is just an example)
app = Flask(__name__)

# A web server exposes a RiveScript bot at '/bot/reply'
@app.route("/bot/reply")
def reply():
    # Form parameters.
    username = request.form.get("username")
    message  = request.form.get("message")

    # On demand (when the request came in), initialize and load the bot.
    bot = RiveScript()
    bot.load_directory("./brain")
    bot.sort_replies()

    reply = bot.reply(username, message)
    return jsonify({
        "status": "ok",
        "reply": reply,
    })

In this example, the web server is creating a new RiveScript instance, parsing all its replies and sorting them, and getting a reply for each and every request. This is very inefficient -- even if your bot's brain is small, you're putting unnecessary I/O load on your server as it has to read the filesystem and scan through the source codes every single time. User says "Hello", bot has to be reloaded from scratch. User says "How are you?", reload from scratch again.

Do: Keep One Global RiveScript Instance

# This is a much better example: we initialize RiveScript only one
# time in the global scope, so that when message requests come in
# all we need to do is just get their reply. No thrashing the hard
# disk or waiting for parse times on every message!

from rivescript import RiveScript
from flask import Flask, request, jsonify

# Initialize RiveScript on the global scope. (Feel free to architect
# this however you want if you don't like global variables, i.e.
# make a class-based application with RiveScript on a class attribute).
bot = RiveScript()
bot.load_directory("./brain")
bot.sort_replies()

app = Flask(__name__)

# A web server exposes a RiveScript bot at '/bot/reply'
@app.route("/bot/reply")
def reply():
    # Form parameters.
    username = request.form.get("username")
    message  = request.form.get("message")

    reply = bot.reply(username, message)
    return jsonify({
        "status": "ok",
        "reply": reply,
    })

By creating one shared globally accessible instance of RiveScript, you can save all the overhead of loading the bot for each request. Now the request handler simply calls RiveScript's reply() function and returns the answer. This can make the bot orders of magnitude more responsive in production when users are sending it messages.

Real World Examples

Here are some real examples in various programming languages for how to keep a single shared RiveScript instance in your application.

JavaScript

JavaScript, being an asynchronous language, uses a callback with methods such as loadFile(). A common design pattern is to load RiveScript first, and in its callback, initialize your application library later (such as Express.js). This way the application must load RiveScript first before it even begins listening for HTTP requests, etc.

  • Express.js JSON Server - the first thing this program does is initialize RiveScript, and once finished, it then initializes Express and sets up its routes and begins listening for requests.
  • Slack Bot - this script initializes RiveScript first, and then it initializes the Slack bot (logs it in and registers message handlers).
  • Telnet Server Bot - initializes RiveScript first, and then opens a TCP socket for listening.
  • Client side, web-based chat bot - this uses RiveScript.js embedded in a web browser for 100% client side processing. Even though you offload the work to the end user's browser, it's still more efficient to use a common instance.

Python

  • Flask JSON Server - similar to the examples on this very page, this is a Flask app that makes a global RiveScript bot available to be shared between all requests.
  • Twilio Bot - this is perhaps a more realistic production use of a Flask server hosting a RiveScript bot.

Go