Chatbot Proof of Concept: How To Convince Your Colleagues And Your Clients That Rolling Your Own Is Better Than Rolling With What You Can Get

Chatbot Proof of Concept: How To Convince Your Colleagues And Your Clients That Rolling Your Own Is Better Than Rolling With What You Can Get

So. You've decided that you want to build your own chatbot, have you? Hmm.

Last week I wrote a post about a request from a client to include a chatbot into their website. As I noted, there are basically two approaches: roll your own, so create one ourselves, or roll with what you can get, which means to get a subscription to a service that provides chatbots.

As creators, we are naturally disposed towards building our own, but is it a good idea? We concluded that we won't know until we do it. And by the way, "doing it" is not "doing it all the way to the end". It is creating a proof of concept

So today let's take a wander down the winding path towards creating our own chatbot. Remember, it is still not decided if we are going to go ahead and build our own. So this article is more about how we create the proof of concept, rather than leading to a decision as to which approach is best in this use case.

And what is the use case?

Our client says that they want a chatbot that will search first on their website, and then on the internet at large. We have cornered that a little more, so as to include the "why" of the requirements. That is, the reason client would want to use a chatbot would be that users -- potential customers -- then stay on their website, as opposed to going off to a search engine and maybe registering at a competitor.

The client is a SME, and the website is primarily for attracting potential B2B customers. The website is but a tiny part of their marketing efforts, most marketing is offline, old-fashioned networking.

A chatbot provider such is used by enterprise or large organisations is going to be much too expensive. Plus they will need data, so any which way we will be implementing an API specifically for the chatbot provider.

As I said, we discussed how to approach this request and decided to put some time into a proof of concept. Now, such a proof of concept should be restrained in time and resources. It should be a very minimal implementation, that should work, should convince, but no more than that. It should be like a lab setting.

A proof of concept (PoC) is not an MVP (minimal viable product) -- it is minimal, yes, viable only in the sense that it is a working application. And a product? Totally not.

What we agreed on is that the complete PoC would be done within one weekend, together with an article on the subject. Yes, this article.

Let's get cracking.

Step 1 is the basic architecture of the application. I know, architecture is a big word for something small. But even mud huts have architecture, so bear with me.

I asked ChatGPT. We are Django people, and our favourite frontend right now is NextJS, so this was my prompt to 4o canvas:

can you write a chatbot in django / python with nextjs or react frontend that would answer questions from users with a website as first source of information, and the internet as second source

I will share the response verbatim:

### Django Backend

1. Create a Django project and app for your chatbot:

```bash
$ django-admin startproject chatbot_project
$ cd chatbot_project
$ python manage.py startapp chatbot
```

2. Add necessary packages to interact with search engines, for instance, `requests` for external APIs.

3. Set up models to store your website's content and manage queries.

```python
# chatbot/models.py
from django.db import models

class WebPage(models.Model):
    title = models.CharField(max_length=255)
    url = models.URLField()
    content = models.TextField()
```

4. Create a chatbot API endpoint to handle incoming user queries.

```python
# chatbot/views.py
from django.http import JsonResponse
from .models import WebPage
import requests

def search_local_content(query):
    # Very basic search function; for better results, use tools like Elasticsearch
    pages = WebPage.objects.filter(content__icontains=query)
    if pages.exists():
        return pages[0].content
    return None

def chatbot_view(request):
    query = request.GET.get('query')
    # Search local content first
    response = search_local_content(query)
    if response:
        return JsonResponse({'response': response})
    
    # Fallback to search on the internet
    search_response = requests.get('https://api.example.com/search', params={'query': query})
    return JsonResponse({'response': search_response.json().get('best_answer', 'Sorry, no answer found.')})
```

5. Set up URL routing to serve this view.

```python
# chatbot/urls.py
from django.urls import path
from . import views

urlpatterns = [
    path('chatbot/', views.chatbot_view, name='chatbot'),
]
```

6. Configure the backend to allow CORS so that the frontend can communicate with it.

```bash
$ pip install django-cors-headers
```

Add it to your `settings.py`:

```python
INSTALLED_APPS += [
    'corsheaders',
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
] + MIDDLEWARE

CORS_ALLOW_ALL_ORIGINS = True
```

### Frontend with React/Next.js

1. Create a Next.js or React app:

```bash
$ npx create-next-app chatbot-frontend
```

2. Create a simple chat interface in your frontend.

```jsx
// pages/index.js (for Next.js)
import { useState } from 'react';

export default function Home() {
  const [query, setQuery] = useState('');
  const [response, setResponse] = useState('');

  const handleSubmit = async (e) => {
    e.preventDefault();
    const res = await fetch(`https://localhost:8000/chatbot/?query=${query}`);
    const data = await res.json();
    setResponse(data.response);
  };

  return (
    <div>
      <h1>Chatbot</h1>
      <form onSubmit={handleSubmit}>
        <input
          type="text"
          value={query}
          onChange={(e) => setQuery(e.target.value)}
          placeholder="Ask a question..."
        />
        <button type="submit">Ask</button>
      </form>
      <div>
        <h2>Response:</h2>
        <p>{response}</p>
      </div>
    </div>
  );
}
```

### Running the Project

1. Start the Django backend:

```bash
$ python manage.py runserver
```

2. Start the Next.js frontend:

```bash
$ npm run dev
```

Now you have a basic chatbot where the frontend interacts with the Django backend to answer questions. The chatbot will look for information on your website first and then fallback to searching the internet if no relevant answer is found.        

So I did as directed. I created a very simple Django project. Then I actually started with the second requirement, searching the internet if the query was not found on the client's own website.

As a company we had often used Google API's, such as translation, search, and of course geo-services. So naturally my first thought was to return there.

But I soon realised that the complexity of Google API's these days was just too much for the PoC I was attempting. Because, remember, I am on the clock. I need to produce a working prototype AND an article within one weekend. AND had family matters to attend to.

I had heard of BRAVE as a new browser and search engine. (I bet you have got to be BRAVE to take on the likes of Google). I discovered that they had a search API and a no-cost entry level tier. Just what I needed for my little PoC.

After integrating BRAVE my Django view looked like this:

def chatbot_view(request):
    query = request.GET.get('query')
    # always add a query for the client first
    query = f"{query}"
    # Search local content first
    response = search_local_content(query)
    if response:
        return JsonResponse({'response': {"web": {"results": response}}})
    
    # Fallback to search on the internet
    headers = {
        "Content-Type": "application/json",
        "X-Subscription-Token": settings.BRAVE_API_KEY,
    }
    search_response = requests.get(
        'https://api.search.brave.com/res/v1/web/search', 
        params={'q': query},
        headers=headers,
    )
    return JsonResponse({'response': search_response.json()})        

Very, very simple. For those in the know, yes, we don't even need serialisers and such. Not for my PoC at least.

After this I could call the endpoint I created, and got results. There is a sample below. BRAVE is pretty fast by the way.


"results": [
{
"title": "Models | Django documentation | Django",
"url": "https://docs.djangoproject.com/en/5.1/topics/db/models/",
"is_source_local": false,
"is_source_both": false,
"description": "The web framework for perfectionists with deadlines.",
"profile": {
"name": "Django Project",
"url": "https://docs.djangoproject.com/en/5.1/topics/db/models/",
"long_name": "docs.djangoproject.com",
"img": "https://imgs.search.brave.com/Mdbd5uw9Fgh9QIaFXC4Gs-a0ZQELDn6nEhcXTMcwayc/rs:fit:32:32:1:0/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvZjMyNGRmYzM5/NGMwYzUyYmQzODA1/MzA5ZTg0ZDYwNWZk/OWY1MzQ4NGIwYzkx/ZDE0MGNkZThiYTk0/N2ZiNjc3Mi9kb2Nz/LmRqYW5nb3Byb2pl/Y3QuY29tLw"
},
"language": "en",
"family_friendly": true,
"type": "search_result",
"subtype": "generic",
"meta_url": {
"scheme": "https",
"netloc": "docs.djangoproject.com",
"hostname": "docs.djangoproject.com",
"favicon": "https://imgs.search.brave.com/Mdbd5uw9Fgh9QIaFXC4Gs-a0ZQELDn6nEhcXTMcwayc/rs:fit:32:32:1:0/g:ce/aHR0cDovL2Zhdmlj/b25zLnNlYXJjaC5i/cmF2ZS5jb20vaWNv/bnMvZjMyNGRmYzM5/NGMwYzUyYmQzODA1/MzA5ZTg0ZDYwNWZk/OWY1MzQ4NGIwYzkx/ZDE0MGNkZThiYTk0/N2ZiNjc3Mi9kb2Nz/LmRqYW5nb3Byb2pl/Y3QuY29tLw",
"path": "? en  ? 5.1  ? topics  ? db  ? models"
},
"thumbnail": {
"src": "https://imgs.search.brave.com/yfAdqLfX0UTyzdbyd3_z40iV7lYXMosKbEbpxtBQsto/rs:fit:200:200:1:0/g:ce/aHR0cHM6Ly9zdGF0/aWMuZGphbmdvcHJv/amVjdC5jb20vaW1n/L2xvZ29zL2RqYW5n/by1sb2dvLW5lZ2F0/aXZlLjFkNTI4ZTJj/YjVmYi5wbmc",
"original": "https://static.djangoproject.com/img/logos/django-logo-negative.1d528e2cb5fb.png",
"logo": true
}
},
]        

That took me about an hour, an hour and a half in all.

Now for the first requirement: search in our own website before searching the internet.

I filled the database with the clients website pages. I wanted to rid myself of all complexities of multi-language, page structure etc, so I decided to get only the core data out of the pages: a title, a url, the body text.

I am an iPython user so this all took place in Python Shell.

In [12]: for i in tqdm(ls):
    ...:     item = i["fields"]
    ...:     if item["language_code"] == "nl":
    ...:         title = item["seo_title"]
    ...:         if not title:
    ...:             title=item["title"]
    ...:         if not title:
    ...:             continue
    ...:         WebPage.objects.create(
    ...:             title=title,
    ...:             url=item["rendered_path"],
    ...:             content=item["body"],
    ...:             )
    ...:
100%|████████████████████████████████████████| 457/457 [00:00<00:00, 6289.74it/s]

In [13]: WebPage.objects.count()
Out[13]: 160        

OK, so now I have about 160 pages of data to work with. I think it might have taken me 30 minutes. Again, I am on the clock.

Just to go back to the Django view, this is how I query the database. Very simple. Not at all production-ready, but hopefully enough to make my point:

def search_local_content(query):
    # Very basic search function; for better results, use tools like Elasticsearch
    ls = []
    pages = WebPage.objects.filter(content__icontains=query, client__name=settings.CLIENT_NAME)
    if pages.exists():
        d = {}
        for p in pages:
            content = strip_tags(p.content)
            d["title"] = p.title
            d["url"] = p.url
            d["description"] = highlight(content, query)
            ls.append(d)
        return ls
    return None        

I needed to make a crude serialiser here, because I needed to follow the pattern induced by the BRAVE API. Also, I wanted to highlight the search term.

Now it was time to turn to frontend.

I have a lot of experience with frontend, but that is mostly HTML, JavaScript and jQuery, CSS. Think 2005.

But I am a newbie at NextJS, so that was a little more difficult. Luckily, I have the tech requirements all set up on my little MacBook Pro so getting the frontend up and running was also quite quick. I think another hour, hour and a half.

Again, I kept everything totally simple. No services and such needed right now. So we just call the api directly in the one function this app has:

"use client"

import { useState } from 'react';
import Link from "next/link";
import Image from "next/image";
import logo from "./images/logo-horizontal.svg";

export default function Home() {
  const [query, setQuery] = useState('');
  const [response, setResponse] = useState('');

  const handleSubmit = async (e) => {
    e.preventDefault();
    const res = await fetch(`https://localhost:8000/api/v1/korero/ask/?query=${query}`);
    const data = await res.json();
    setResponse(data.response);
  };

  return (
    <div style={{ bottom: "21px", right: "21px", height: "610px" }} className="korero shadow-lg border-1 mt-11 p-1 rounded-3xl w-96 absolute">
      <button className="w-full text-right text-5xl pr-5">?</button>
			<Image className="ml-4" src={logo} width={170} height={100} />
      <form className="p-2 mt-7" onSubmit={handleSubmit}>
        <input
					className="rounded-lg border-1 border-gray-200 p-2 w-full"
          type="text"
          value={query}
          onChange={(e) => setQuery(e.target.value)}
          placeholder="Ask a question..."
        />
        <button className="text-5xl text-right w-full text-[#103060]" type="submit">?</button>
      </form>
					<div className="absolute max-h-80 w-full overflow-scroll mr-1">
					{response.web?.results.map((item, idx) => (
					<div className="rounded-3xl border-2 border-gray-200 bg-white p-11 m-4">
						<p dangerouslySetInnerHTML={{ __html: item.description}}></p>
						<Link 
							target="_blank" 
							className="text-blue-400 underline mt-2" 
							href={item.url}>
							{item.title}
						</Link>
					</div>
        ))}
				</div>
		</div>
  );
}
        

If you are a programmer you can probably see that I use tailwind and that there is some styling going on. That is the version I am at right now at the time of writing this article. The first iteration looked like this:

This is the first working version. Although it does indeed work, it doesn't look like a chatbot at all. So fails the "convince" test big time.

It badly needed some TLC.

I think the styling took the longest time. I started by creating what was to look like a chatbot in action, and bit by bit added styles matching our client's new website. Before I had figured out how to present it, and to implement it, maybe 4 - 5 hours had passed. For this article however, I decided it would be best to use the branding and colour schemes of our company website.

So, now it looks like this:

I used our company website as a example for this article. But for the client, there is a version with their new website's colour scheme and branding

By the way, while the illustration above looks like the real thing, it is totally not. I simply took a screenshot from a page of our own company website and used it as a background image for the PoC. Then I adjusted the styles to match our colour scheme. For the client's website, I did the same. Switching between the two is very simple:

body {
  color: var(--foreground);
  background: url("images/dws-bg.jpg") no-repeat;
/*  background: url("images/cb-bg.jpg") no-repeat;*/
  font-family: Arial, Helvetica, sans-serif;
}

div.korero{
    /*dws dark sand*/
/*    background: #F1EAE5;*/
    /*dws terra*/
    background: #F7BEA1;
    /*client*/
/*    background: #D3E086;*/
}
        

What I have is a working PoC that looks as if it has been implemented. Up to this point, I had spent about 6, maybe 6.5 hours. The requirements have been met:

  1. enable users to search within the client website
  2. if nothing found, search on the internet

But. Is it enough. Does it convince me, and would it convince our client.

I have my doubts.

So what is a chatbot. I think it should fulfil the "chat" part of the word. The experience the user has should be of a conversation, not a search engine. Right now we have a search engine that is dressed up as a chatbot.

I have an idea. Let's ask ChatGPT.

I am thinking of a 2-step process for requirement number 1. We do a search within the own website, which returns pages. We enter the contents of the page as context into ChatGPT, and then ask our question. At Django Web Studio, we have implemented the ChatGPT API many times, and have even built an application specifically to supply GPT content to our applications. So it should be easy for us to implement our 2-step process.

Let's try this out on the ChatGPT web interface to see if this delivers the desired conversational mode. This is my first message:

in the next message, I will enter a text. In the message after that, I will enter a question. Answer the question based on the text.

ChatGPT:

Understood! Please go ahead and provide the text.

Me:

Arcadis is a global leader in sustainable design, engineering, and consultancy solutions for natural and built assets. With a mission to improve quality of life, Arcadis operates in over 30 countries, employing more than 36,000 professionals dedicated to addressing today's most pressing challenges, including climate change, urbanisation, and digital transformation.\n\n\n As part of this mission, Geluidsverwachting provides a specialised app designed to address noise and shadow flicker concerns around wind parks. This tool enhances transparency and community engagement, thereby improving the overall acceptance of wind energy projects.\n\n\n With the increase in new wind parks, nearby residents often express concerns about noise pollution and shadow flicker from the turbines. This can lead to unrest and decreased support for sustainable energy projects. The specific challenges were:\n\n\n To tackle these challenges, Arcadis collaborated with Django Web Studio to develop the Geluidsverwachting app, which provides hyperlocal forecasts for wind turbine noise and shadow flicker. The project included several key features:\n\n\n The app delivers accurate noise level forecasts up to 48 hours in advance. It provides real-time updates on shadow flicker, helping residents understand and anticipate the impact of wind turbines on their daily lives.\n\n\n Residents can report their experiences with noise and shadow flicker directly through the app, providing valuable data for analysis. This feedback helps map out the impact of noise and meteorological conditions on the experience of the residents.\n\n\n The app shows how wind turbine noise compares to other environmental sounds, helping to put the noise into perspective. It also displays the amount of electricity being generated, linking the benefits of wind energy to local impacts.\n\n\n The app facilitates better interaction between wind park operators, local authorities, and residents, ensuring that community concerns are heard and addressed. Continuous monitoring and feedback loops help quickly identify and mitigate issues.\n\n\n The Geluidsverwachting app has transformed the way noise and shadow flicker concerns are managed around wind parks. By providing real-time, accurate information and facilitating direct feedback, the app has increased transparency and improved community engagement. This innovative solution has not only enhanced the acceptance of wind energy projects but also ensured that residents' concerns are taken seriously, contributing to a more sustainable and harmonious energy transition.

ChatGPT:

Got it! Please enter your question now.

Me:

how does Arcadis identify noise pollution

ChatGPT:

Arcadis identifies noise pollution through the Geluidsverwachting app, which was developed in collaboration with Django Web Studio. The app provides hyperlocal forecasts for wind turbine noise, delivering accurate noise level predictions up to 48 hours in advance. It also includes real-time updates on noise levels, allowing residents to monitor and anticipate the impact of wind turbines. Additionally, residents can report their experiences with noise directly through the app, which provides valuable data for analysis. This feedback helps map out the impact of noise and meteorological conditions, allowing for continuous monitoring and mitigation of noise pollution.

That's much better. But one last question, ChatGPT...

how much would a prompt and response cost for an average of 500 words

ChatGPT answers:

The cost of a prompt and response using language models like mine depends on the number of tokens processed. In general, 1 token is roughly equivalent to 4 characters of English text, which means a typical word is about 1.3 tokens on average.
For a 500-word prompt and response, we can estimate the token usage as follows:
- 500 words x 1.3 tokens per word = 650 tokens.
Since both the prompt (your input) and the response (the generated output) count towards the total token usage, let's assume that you might use 500 words for both prompt and response together. This would bring the token total to approximately 1,300 tokens.
The pricing for models such as GPT-4 is usually based on per 1,000 tokens. For example, if the model costs $0.03 per 1,000 tokens, processing 1,300 tokens would cost around:
- (1,300 tokens / 1,000) x $0.03 = $0.039 (or 3.9 cents).
So, for an average prompt and response totaling around 500 words, it might cost around 4 to 6 cents, depending on the specific pricing structure and token length.

I get it. So if we implement this plan, each interaction will cost at least 6 cents. And people playing around with our chatbot for an hour could end up costing our client a lot of money. We would need to cap such use. But our client's website does not have too much traffic, so it might work financially.

Let's wrap up. What do we have at the moment?

  • A working model of the backend and the frontend of a simple chatbot, that queries a client's website first, and then the larger internet
  • The beginnings of a frontend system to switch between multiple clients, each with the correct branding and colour scheme
  • Same as above for backend. We have created a simple client concept. Basically we have the beginnings of a whitelabel system.
  • We have a way to demo the chatbot to the client
  • We have investigated what it would mean (tech-wise and price-wise) to make the chatbot more conversational by implementing a 2-step process, where the results of a query are fed to the ChatGPT API as context together with the end-user's question.

I think we are done for the moment. All-in-all I spent about 8 hours creating the PoC, and an additional 4 hours writing. Now and again I went back to the PoC while I was writing.

Doing is knowing.





I must admit I spent another hour and a half prepping for the meeting we have with the client. Note to self: know when to stop.

回复

要查看或添加评论,请登录

Geert Dekkers ???? ????的更多文章

社区洞察

其他会员也浏览了