@rotorsoft/chatbot

1.0.7 • Public • Published

chatbot

A functional js chatbot

This is just a semantic layer on top of the flow module showing how to implement a basic chatbot engine in JavaScript.

Chatbot actions represent statements and questions asked to the end user. Answers are used as transition payloads to drive the conversation.

The bot API provides the following methods:

  • ids(): Returns the ids (tenant, id, legid)
  • params(): Returns the parameters
  • body(): Returns any remaining arguments
  • state(): Returns a copy of the current state
  • activity(): Returns a copy of the current activity
  • version(): Returns the current version (events length)
  • events(): Returns array of events
  • last(): Last event or null if empty
  • play(events = []): Plays talked or answered from events array in order. Can be used to rehydrate the bot aggregate
  • talk(mode): Pushes and returns current talked event. Can be used to repeat questions and handle timeouts
  • answer({ version, mode, ...payload}): Pushes answered event with provided transition payload (when active and version matches last event), and then pushes next talked event in flow. Returns object with pushed events (and current state when flow ends).

Event Schemas

talked: {
  name: "talked",
  time: new Date().toISOString(),
  activity: 'current activity name, "end" when flow is done'
  type: 'current activity type',
  mode: 'how talk event is delivered (voice, chat, sms, etc)',
  say: 'optional array of statements to say',
  ask: 'optional string with something to ask',
  sms: 'optional sms string to deliver',
  transfer: 'optional string to transfer the conversation',
  asked: 'how many times this question has been asked',
  timeout: 'timeout to control retries',
}

answered: {
  name: "answered",
  time: new Date().toISOString(),
  activity: 'current activity name',
  mode: 'how answer was received (voice, chat, sms, etc)',
  ...payload: 'object with trasition payload'
}

How to use

This module exports two function closures: bot and reducer

The reducer handles transition payloads (answers to drive the chat) and action payloads to produce talked events with commands like say, ask, sms, transfer.

The bot is initialized with a map of actions, the reducer, and the following required arguments:

  • tenant: Tenant id (to be used in multitenant environments)
  • id: End user id
  • legid: Leg id
  • root: Name of root action
  • params: Map with flow parameters including languageCode

The optional array of events can also be passed as argument to rehydrate the flow from persisted storage.

const { bot, reducer } = require("@rotorsoft/chatbot");
const common = require("./actions/common");
const { main } = require("./actions/main");

const b = bot({ ...common, main }, reducer("en"), {
  tenant: "tenant123",
  id: "user1",
  legid: "chat456",
  root: "main",
  params: { name: "John", age: 20, languageCode: "en" },
});
const state = b.state();
console.log(state);

Test

npm test

The provided tests are self explanatory and should log a trace like this:

[ 0] main() { // [{"say":{"en":"Hi.","...}, authenticate(state,scope,{params}), begin({authenticate={}}), end()]
[ 0]    {"say":{"en":"Hi.","es":"Hola."}}
[ 'Hi.' ]
[ 3]    authenticate() { // [{"ask":{"en":"Am I s...}, (state,{recur})]
[ 3]       {"ask":{"en":"Am I speaking with John?","es":"Estoy hablando con John?"}}
[ 'Hi.' ] Am I speaking with John?
[ 3]       (state,{recur}) ... [ 4]       canComeToThePhone() { // [{"ask":{"en":"Ok, ca...}, (state,{recur})]
[ 4]          {"ask":{"en":"Ok, can John come to the phone?","es":"John puede venir al telefono?"}}
[] Ok, can John come to the phone?
[ 4]          (state,{recur}) ... [ 5]          canComeToThePhone:1() { // [{"ask":{"en":"Ok, ca...}, (state,{recur})]
[ 5]             {"ask":{"en":"Ok, can John come to the phone?","es":"John puede venir al telefono?"}}
[ "I didn't get that. Can you repeat?" ] Ok, can John come to the phone?
[ 5]             (state,{recur}) ... [ 6]             verifyPhone() { // [{"ask":{"en":"Is thi...}, (state,{recur})]
[ 6]                {"ask":{"en":"Is this the correct number for John?","es":"Es este el numero correcto para hablar con John?"}}
[] Is this the correct number for John?
[ 6]                (state,{recur}) ... [ 6]                {"say":{"en":"Ok, thank you. Could you please tell John that this is a test? We’ll try calling back at a later time.","es":"Ok, gracias. Le prodria decir a John que this is a test? Llamaremos en otra ...}
[
  'Ok, thank you. Could you please tell John that this is a test? We’ll try calling back at a later time.'
]
[ 6]             } // verifyPhone
[ 5]          } // canComeToThePhone:1
[ 4]       } // canComeToThePhone
[ 3]    } // authenticate
[ 2]    begin() {
[ 2]    } // begin
[ 1]    end() {
[ 1]       {"say":{"en":"Good bye.","es":"Adios."}}
[
  'Ok, thank you. Could you please tell John that this is a test? We’ll try calling back at a later time.',
  'Good bye.'
]
[ 1]    } // end
[ 0] } // main
{
  main: {},
  authenticate: { mode: undefined, text: undefined, value: undefined, intent: 'no' },
  canComeToThePhone: { mode: undefined, text: undefined, value: undefined, intent: 'no' },
  verifyPhone: { mode: undefined, text: undefined, value: undefined, intent: 'yes' },
  say: [
    'Ok, thank you. Could you please tell John that this is a test? We’ll try calling back at a later time.',
    'Good bye.'
  ],
  end: {}
}
    √ should verify phone when user cannot come to the phone (63ms)

Simplicity is the ultimate sophistication

Leonardo da Vinci


Contributing

In lieu of a formal style guide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code.

License

MIT

Package Sidebar

Install

npm i @rotorsoft/chatbot

Weekly Downloads

1

Version

1.0.7

License

MIT

Unpacked Size

22.2 kB

Total Files

9

Last publish

Collaborators

  • rotorsoft