Making a chatbot app

The goal

An app that allows users to learn bus routes and stops via text messages.

Why text?

  1. Don't need to download an app
  2. Resilient against poor cell service
  3. Works on any phone hardware

Basic architecture

Twilio

Chatbot

server

Google Maps

 Knows about bus routes

Provides distances between points and address validation

Forwards text messages to our server

Has questions about bus routes

  • "Is there a bus to Wendy's?"
  • "How can I get from downtown Wheeling to Reynolds?"
  • "how long get too higlnds"
  • "lol ur dumb"

How do we talk to computers?

How do we talk to computers?

ChatGPT doesn't know everything yet

How do we make our own text app that is as easy to use as ChatGPT but knows what we need it to know?

Options

and of course lots of other options like
Python or Haskell NLP libraries, Microsoft Bot Framework, AWS Step Functions + Lex, etc etc

Option A

Traditional NodeJS app with NLP.js

NLP.js

  • HUGE library with a ton of functionality
  • Not intended for chatbot orchestration (oops)
  • Integrates with Microsoft Bot Framework, among others
  • Documentation is extensive but disorganized
  • I DO NOT RECOMMEND THIS LIBRARY FOR THIS PURPOSE but it helped me learn the basic vocabulary of NLP so...

Utterances

What the user actually types

  • "Is there a bus to Wendy's?"
  • "How can I get from downtown Wheeling to Reynolds?"
  • "how long get too higlnds"
  • "lol ur dumb"

Intent

What the user intends to communicate

  • "Is there a bus to Wendy's?"
  • "How can I get from downtown Wheeling to Reynolds?"
  • "how long get too higlnds"
  • "lol ur dumb"

bus.findStop

bus.routeBetween

bus.timeEstimate

appraisal.stupid

Entities

Data from an utterance needed to return an answer relevant to the intent. The program may need to extrapolate on the data to be useful.

  • "Is there a bus to Wendy's?"
  • "How can I get from downtown Wheeling to Reynolds?"
  • "how long get too higlnds"
  • "lol ur dumb"

Wendy's (in Elm Grove?)

Reynolds (Memorial Hospital ?)

Reynolds (Rapid Care?)

The Highlands?

none

(Transportation Center?) in downtown Wheeling

Corpus

{ 
  "name": "Corpus",
  "data" : [
	{
      "intent": "agent.age",
      "utterances": [
        "your age",
        "how old is your platform",
        "how old are you",
        "what's your age",
        "I'd like to know your age",
        "tell me your age"
      ],
      "answers": [
        "I'm very young",
        "I was created recently",
        "Age is just a number. You're only as old as you feel"
      ]
    }
  ]
}

A collection of data used to teach the program what utterances correspond to which intents and how to respond to those intents.

Entity Recognition

What if we need to access part of the user's utterance?

{ "intent": "bus.findstop",
  "utterances": [
   "Where is the nearest stop to @address",
   "Where is the nearest bus stop to @address",
   "bus stop near @address",
   "What is the stop closest to @address"
  ],
  "slotFilling": {
     "address": {
       "mandatory": true,
       "question": "What is your specific location or address?"
     }
   },
   "answers": [
        "Uhhhh, I dunno what the closest stop
         to {{ address }} is. Sorry."
   ]
}
"entities": {
  "address": {
    "trim": [
      {
        "position": "afterLast",
        "words": ["to"]
      },
      {
        "position": "afterLast",
        "words": ["near"]
      }
    ]
  }
}

Test-driven development

describe("findClosestStop", () => {
  test('can find the stop nearest to a set of coordinates', async () => {
    const stop = findClosestStop(testStops, [28.965797, 41.010086])
    
    expect(stop.coordinates).toEqual([28.973865, 41.011122])
    expect(stop.name).toEqual("A")
  })
})
describe.skip("fetchPlace (live test using Google Maps API)", () => {
  test('can get a canonical place from the name of a location', async () => {
    const resp = await fetchPlace("Reynolds Hospital")
    expect(resp.name).toBe("Reynolds Memorial Hospital")
    expect(resp.address).toBe("800 Wheeling Ave, Glen Dale, WV 26038, United States")
    expect(resp.coordinates).toEqual([39.9461073, -80.75260469999999])
  })

Test-driven development

describe("'where is the closest stop to wendys'", () => {
    test('is about finding a bus stop', async () => {
      fetchPlace.mockResolvedValue({
        name: "Wendy's",
        address: "Elm Grove Mall",
        coordinates: [28.965797, 41.010086]
      })
      findClosestStop.mockReturnValue({
        name: "B",
        location: "somewhere over the rainbow"
      })
  
      const response = await nlp.process('en', 'Where is the closest stop to wendys')
    
      expect(response.intent).toBe('bus.findstop')
      expect(response.answer).toMatch(/Wendy's \(Elm Grove Mall\)/)
      expect(response.answer).toMatch(/B stop/)
      expect(response.answer).toMatch(/over the rainbow/)
    })
  })

Adding functionality

{ "intent": "bus.findstop",
  "utterances": [
    "Where is the nearest bus stop to @address",
    "What is the stop closest to @address"
  ],
  "slotFilling": {
    "address": {
      "mandatory": true,
      "question": "What is your specific location or address?"
    }
  },
  "answers": [
    "The nearest bus stop to {{ placeName }} ({{ placeAddress}}) 
    is the {{stopName}} stop {{ stopLocation }}."
  ],
  "actions": [
    {
      "name": "handleFindStop",
      "parameters": []
    }
  ]
}
export async function handleFindStop(data) {
  const address = data.context.address
  if (address) {
    const realAddress = await fetchPlace(address)
    data.context.placeName = realAddress.name
    data.context.placeAddress = realAddress.address
    const closestStop = 
          findClosestStop(elmgrove, realAddress.coordinates)
    data.context.stopLocation = closestStop.location
    data.context.stopName = closestStop.name
  }
  return data
}

What part do I need to change to get the effect I want?

Connector

NLP

Bot

NLU manager

Entity Recognition

NLU manager

Slot manager

Stemmer

Action manager

Action manager

Sentiment

QnA

onIntent callback

Pipeline

NLG manager

Corpus

Settings

Confirming the user's input

What if the program guesses incorrectly?

I tried a few options:

  • use the "bot" library to create chatbot scripts
  • use actions
  • use slots

None of these were satisfying.

 

In general, NLP.js is not for a chatbot that needs to know about the state of a conversation and branch based on that.

DEMO

Option B

Low-code Chatbot SAAS

BotPress

  • Software as a Service
  • Very fun, fancy graphical UI
  • Within a few years will probably a) be bought out by a larger firm and become really expensive or b) die
  • AI-assisted coding
  • Made for chatbot orchestration, unlike NLP.js

DEMO

Made with Slides.com