How to program a chatbot that reads all your website and answers questions based on its content
Tags: haystack, chatbot, qa, machinelearning
Published: 2021-12-27 07:24:49

I'm sure you often get questions from your visitors and think "but this is already on the website!". You then added a chatbot to your site to filter out the <a href="https://xatkit.com/pareto-principle-chatbot-intent-design/" target="_blank" rel="noopener">most common questions</a>. But what about all the rest. Adding more and more questions to the bot it takes time. So, is there an easy way to <strong>let the chatbot find the answer by itself</strong> for those questions that are already answered somewhere in the (hundreds? thousands?) pages, posts or other online documents you've already published?

YES!. The key is to plug <a href="https://haystack.deepset.ai/" target="_blank" rel="noopener" data-schema-attribute="">Haystack</a> to your chatbot. We have several <a href="https://huggingface.co/models?pipeline_tag=question-answering" target="_blank" rel="noopener">pretrained language models fine-tuned for Question Answering</a> that can be used to find an answer in a text but they only work when the text length is really small. Here it is where Haystack comes into play. Haystack architecture (see the featured image above) proposes a two-phase process:
<ul>
 	<li>A <em>Retriever</em> selects a set of candidate documents from all the available information. Among other options, we can rely on <a href="https://www.elastic.co/">ElasticSearch</a> to index the documents and return those that most likely contain the answer to the question</li>
 	<li>A <em>Reader </em>applies state-of-the-art QA models to try to infer an answer from each candidate</li>
</ul>
Now we just need to return these inferred solutions together with additional information (context, confidence level,...) to help our users understand why we think this is the answer they were looking for. All thanks to a completely <a href="https://github.com/deepset-ai/haystack" target="_blank" rel="noopener">free and open source NLP framework!</a>

Let's see how we can benefit from Haystack by creating a chatbot able to answer software design questions for the <a href="https://wordpress.org/" target="_blank" rel="noopener">WordPress</a> website <a href="https://modeling-languages.com/" target="_blank" rel="noopener" data-schema-attribute="">modeling-languages.com</a>. Haystack website is full of useful examples, so we'll adapt them in our scenario.
<h2>Creating the chatbot, the "front-end"</h2>
The easiest part is to create the chatbot. We'll obviously use <a href="https://github.com/xatkit-bot-platform/xatkit" target="_blank" rel="noopener" data-schema-attribute="">Xatkit</a> for this. The bot can have as many intents as you wish. The only part that we care about here is the <a href="https://xatkit.com/define-meaningful-fallbacks-for-your-chatbot/" target="_blank" rel="noopener" data-schema-attribute=""><em>default fallback </em></a>state. Here, instead of saying something useless, e.g. "sorry I didn't get your question, can you rephrase it and try again?", we will ask Haystack to find us a solution.

{% gist https://gist.github.com/jcabot/5d3ef3282e6430acf5a709755b188f49.js %}

Here we have all the pieces locally deployed but obviously each of them could be in a different server.
<h2>Loading the information to ElasticSearch</h2>
Before we can find an answer, we need to first power up ElasticSearch with the documents we want to use as information source for the bot. In this example, the documents will be all posts published in modeling-languages. We assume we have direct access to the WordPress database but otherwise we could write something similar using the <a href="https://developer.wordpress.org/rest-api/" target="_blank" rel="noopener" data-schema-attribute="">WordPress REST API </a>instead. Note that we split each post in different paragraphs to avoid chunks of text that could be too much for the QA models.

{% gist https://gist.github.com/jcabot/7ca0c3d437659eb27ca1b6e8fa047bbe.js %}

I deployed a <a href="https://flask.palletsprojects.com/en/2.0.x/" target="_blank" rel="noopener" data-schema-attribute="">Flask</a> server to facilitate calling all the endpoints on-demand, especially needed for those that the bot needs to interact with.
<h2>Finding the answer</h2>
The <em>Retriever</em> component will look for the most promising documents in ElasticSearch (by default, using the <a href="https://www.elastic.co/blog/practical-bm25-part-2-the-bm25-algorithm-and-its-variables" target="_blank" rel="noopener" data-schema-attribute="">BM25 algorithm</a> but there are other options). The <em>Reader</em> will look into each candidate and try to find the right answer in it. Thanks to the predefined <a href="https://haystack.deepset.ai/reference/pipelines" target="_blank" rel="noopener" data-schema-attribute="">Pipelines</a> provided by Haystack, putting everything together is really easy:

{% gist https://gist.github.com/jcabot/382c617c2cb753e47f23803d05c278e3.js %}

Once we have the answer, we create the response object that will be sent back to the chatbot. As a final step the chatbot will print this response to the user together with the URL of the post the answer comes from. This way, even if the answer is not perfect the user will have the option to go to the suggested URL.
<h2>But, does it work?</h2>
We've seen it's feasible to add the Haystack infrastructure to a chatbot. But what about the quality of the answers? Are they good enough?

The answer is that it does work reasonably well. The modeling-languages website was not the easiest one to try with. It's rather large (over 1000 posts that translate into around 8000 documents) with significant overlappings. And there are still some "legay" posts in Spanish that add to the confusion.

Let's see a couple of examples. In the first one I ask on how can I add a business rule (i.e. constraint) to a software design model. The first answer is technically correct (indeed, constraints are written on top of models) but rather useless. The next two are exactly what I was hoping to see an answer as they suggest me to use the Object Constraint Language to specify my constraints.

<img class="size-full wp-image-120111 aligncenter" src="https://xatkit.com/wp-content/uploads/2021/12/OCLExample.png" alt="Question Answering example on a WordPress site" width="375" height="632" />

The second question is more concrete but it has a more open answer. Note that all answers are taken from the same document (the only one that realy talks about this Temporal EMF tool). All answers are reasonable but the third one really nails it. And keep in mind that we're using an extractive QA model, meaning that the model aims to return a subset of the text containing the answer. Instead, a generative QA model (also available in Haystack) would be able to "build" the answer from partial answers, potentially spread out in more than document.

<img class="size-full wp-image-120112 aligncenter" src="https://xatkit.com/wp-content/uploads/2021/12/temporalExample.png" alt="QA haystack example with a more concrete question" width="374" height="625" />

In terms of <strong>performance, results were very satisfactory</strong>. The whole process took just a few seconds (once the initial model loading) but this is on my poor laptop. With proper configuration and tuning, the user should not notice a major delay. <a href="https://haystack.deepset.ai/guides/rest-api" target="_blank" rel="noopener">Haystack itself can also be deployed as a REST API</a> which should optimize even more the whole process.  And of course, you could always let the bot designer to configure whether to use Haystack in the default fallback or not, depending on a number of factors.