Code Monkey home page Code Monkey logo

wolf-core's Introduction

Wolf Core npm version Build Status

Wolf allows you, the developer, to define the bot conversation with ease. There is one configuration point, which is hot-loadable, enabling you to change the bot behavior while the node process is still running.

Wolf facilitates information gathering, either by asking a question or accepting a piece of information parsed by NLP. The library is also an abstraction layer that ensures stability, which means if the Botbuilder SDKv4 interface changes, the bot behavior can stay the same.

Please see Roadmap for more details and planned features. If you do not see a feature, please feel free to open an issue.

Wolf Core will be framework agnostic in v3, making it easy to integrate with backend services like express, bot framework, dialogflow, etc.
For now, wolf v2 is coupled with Microsoft Bot Builder v4.


Guiding Principles:

  • Functional: Wolf stages are pure functions. Side-effects are allowed but is defined and managed by you, the user.
  • Stateless: Wolf stages are stateless meaning that the data is passed in on every function invocation, making hot-swappable possible, and testing extremely easy.
  • Declaritive: You specify what you want (abilities and slots), and what to do after you have the information. Wolf will figure out how to get the information and complete the ability for you.

Purpose

Developing intelligent chatbots often lead to complex dialog trees which results in prompting the user for many pieces of information. Most frameworks require you to keep track of the state yourself as well as hard-coding static dialog flows to gather these pieces of information. Development often turns into creating a complex state machine where you must check the current state to determine which prompt the chatbot should issue next.

Wolf aims to provide a highly flexible and convenient framework for enabling state driven dynamic prompt flow. Simply define all the slots to be filled (information required from the user, prompts to issue, and actions to take after the information is fulfilled) and Wolf will handle the rest to ensure all information is collected. Slot can be defined as dependencies on other slots if desired. A collection of slots are grouped by abilities which also can have dependencies on another to help drive dynamic conversation flow.

All functions from wolf-core are pure functions.

AlarmBot demo with hot-loading abilities and Redux DevTools to visualize bot state in development.


Bring Your Own Natural Language Processing.. BYONLP

This library takes the guesswork out of complex conversation flows, and allows you to declaritively define your slots. However, it does not parse user intent or entities for you. Wolf takes in the result of NLP (which can be as simple as regex or as complex as a tensorflow-backed model), and determines the next slot or ability to complete.

In order for Wolf to accept your NLP, the result to follow a specific object shape. This shape is typed as NlpResult, and it is as follows:

{
  intent: string,
  entities: [
    {
      value: any,     // normalized value
      text: string,   // raw value
      name: string    // entity name (should match slot name)
    }    
  ]  
}

Please note: NLP entity name should match slot name for Wolf to detect matches!


Ability Structure

Slot: A slot is structure that represents any piece of information that is required from the user and obtained through conversation or a system. This can be the user's name, address, etc.. A slot structure has a few properties which allows Wolf to dynamically search for possible matches. Anatomy of a slot:

  • name: name of slot. should match an entity name from your NLP
  • order: optimal order to fill slot. (ascending order)
  • query: string to prompt user to obtain information.
  • validate: function to test if the information is valid before fulfilling.
  • retry: string(s) to prompt user if validator does not pass.
  • onFill: function that returns string to present to user on slot fulfill.

Here is an example of a slot from the alarm example:

name: 'alarmName',
query: () => { return 'What is the name of the alarm?'},
retry: (turnCount) => {
  // array of retry phrases to send to user
  const phrase = ['Please try a new name (attempt: 2)', 'Try harder.. (attempt: 3)']
  if (turnCount > phrase.length - 1) {
    return phrase[phrase.length - 1]
  }
  return phrase[turnCount]
},
validate: (value) => {
  // validator that must pass before slot is fulfilled
  if (value.toLowerCase() === 'wolf') {
    return { valid: false, reason: `${value} can not be used.`}
  }
  return { valid: true, reason: null }
},
onFill: (value) => `ok! name is set to ${value}.`

Ability: An ability is a logical unit that contains a collection of slots and runs a function when the slots are filled. An ability also has other features like kicking off another ability once it is completed

  • name: name of the ability should match an intent name from your NLP
  • slots: collection of Slots
  • nextAbility?: a function that specifies the next ability to kick off and a message to let the user know.
  • onComplete: function (sync or asynchronous) that runs upon all slots being filled.

Here is an example of an ability from the alarm example:

name: 'addAlarm',
    slots: [
      // .. see `alarmName` slot example above
    ],
    onComplete: (convoState, submittedData) => {
      return new Promise((resolve, reject) => {
        const value = submittedData
        const alarms = convoState.alarms || []
        // add alarm to convoState
        convoState.alarms = [
          ...alarms,
          value          
        ]                                             
        
        // demonstrate async supported
        setTimeout(() => {
          resolve(`Your ${value.alarmName} alarm is added!`)
        }, 2000)
      })
    }

Install

Open a pre-existing Microsft Bot Framework v4 project directory and run:

npm install wolf-core

How to Use

  1. Install wolf-core.
  2. Import Wolf into a pre-existing Microsft Bot Framework v4 bot.
import { wolfMiddleware, getMessages, createWolfStore, IncomingSlotData } from 'wolf-core'
  1. Create an abilities definition (see example alarmBot abilities)
  2. Import the abilities definition
import abilities from './abilities'
  1. Setup the Wolf middleware
// Wolf middleware
adapter.use(...wolfMiddleware(
  conversationState,
  (context) => nlp(context.activity.text),
  (context) => abilities,
  'listAbility',
  createWolfStore()
))
  1. Handle the output messages in the server.post
server.post('/api/messages', (req, res) => {
  adapter.processActivity(req, res, async (context) => {
    try {
      if (context.activity.type !== 'message') {
        return
      }
      const messages = getMessages(context) // retrieve output messages from Wolf
      await context.sendActivities(messages.messageActivityArray) // send messages to user
    } catch (err) {
      console.error(err.stack)
    }
  })
})

Setup Redux Dev Tools

  1. Have a pre-existing v4 bot running with Wolf (see above)
  2. Setup the dev tool server
/**
 * Starting dev tools server
 */
const remotedev = require('remotedev-server')
const { composeWithDevTools } = require('remote-redux-devtools')
remotedev({ hostname: 'localhost', port: 8100 })
const composeEnhancers = composeWithDevTools({ realtime: true, port: 8100, latency: 0 })
  1. Edit the fifth argument (createWolfStore) for the wolfMiddleware
// Wolf middleware
adapter.use(...wolfMiddleware(
  conversationState,
  (context) => nlp(context.activity.text),
  () => {
    delete require.cache[require.resolve('./abilities')]
    const abilities = require('./abilities')
    return abilities.default ? abilities.default : abilities
  },
  'listAbility',
  createWolfStore([], composeEnhancers) // replace createWolfStore()
))
  1. Download Redux DevTools from the Chrome store.
  2. In the Chrome browser, click on the DevTools icon (top right) > 'Open Remote DevTools'
  3. Settings (bottom right) > tick 'Use custom (local) server' and fill in information > Submit
Host name: localhost, Port: 8100  // port edefined in step 2
  1. To view the state in a chart display, change 'Inspector' dropdown to 'Chart' option
  2. Run the bot and the websocket server should start with chart visuals.

Note: See alarmBot example with Redux Dev Tools enabled.


Testing

Testing a bot has never been easier with Wolf-Rive testing package. Any Wolf enabled v4 bot has the ability to utilize this testing package which allows users to write end-to-end testing of input and output conversation flows.

All example bots have their own /tests which utilize wolf-rive package. Please refer to examples and Wolf-Rive for full testing details.


Resources

See Wolf Core Concepts for more information about middleware usage.

See examples for full implementation.

  • simpleBot - Basic example.
  • alarmBot - Redux DevTools and hot-loading.
  • profileBot - More complex example. SlotData push model, setting up api endpoint to accept slotdata by conversationId.

Contribution

Please refer to Wolf Wiki for roadmap and contribution information.

wolf-core's People

Contributors

howlowck avatar kslhacks avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

Forkers

buhongw7583c

wolf-core's Issues

passing `convoState` in `nextAbility`

Is your feature request related to a problem? Please describe.
user should have access to convoState when deciding which ability to start next.
However in order to have an accurate convoState, nextAbility has to be executed after the slot's onComplete is executed.

onComplete can be async, and the result of onComplete is done in the wolfMiddleware.

Describe the solution you'd like
We should need to make another stage (say, kickoff) to be run after execute and before outtake. this proposed stage, is to find the nextAbility and kickoff the ability with the next query (or finish the ability if no slot is present)

Since the nextAbility could finish (when it has no slot), and requires another execute... and so on and so forth. this means the execute and kickoff is in a loop until there is no more nextAbility to finish.

Create Wolf Middleware

Description: Refactor src/ code into middleware and store OutakeResult in convoState for user to access.

Define interface ActionResult

Description:

interface ActionResult {
  actionResult: PendingWolfState
  func: () => Promise // returns ability.onComplete promise

Dependent ability conversations

Description: Conversation flow should have multiple abilities and slots to represent a conversation with many pieces of information required from the user. The abilities should be able to have dependencies on others abilities to represent a structured conversation flows.

Example:
Abilities:

  1. Onboard user (name)
  2. Departure Information (date, airport)
  3. Return Information (date, airport)

Abilities 2 and 3 have a dependency on ability 1

User: I would like to book a flight from ORD to EWR.
Bot: I can certainly help you with that, but first.. what is your name?
User: Foobar
Bot: Thank you Foobar!
Bot: What date would you like to fly from ORD to EWR
User: 8/1/2018
Bot: When would you like to return?
User: 8/2/2018
Bot: Okay your round trip flight from ORD to EWR is booked! You will depart on 8/1/2018 and return on 8/2/2018.

Correction Support

Description: Ability for users to correct prefilled slots

  • Most recent (Oh, I meant JFK)
  • Past filled slots (I want to change my departure airport)

Outtake messages are sent out of order

Describe the bug
During turns when the bot is outputting multiple messages, the messages our being outputted

To Reproduce
Steps to reproduce the behavior:

  • Start the alarm bot
  • "add alarm called Hao"
  • "kevin"

(the messages are sometime out of order)

Desktop (please complete the following information):

  • OS: Windows

Slot Entity Types

Description: Allow slots to list entity types or "tags", which will allow Wolf to more intelligently match entities with potential slot matches and improve fill accuracy.

Current implementation: slot names are tightly coupled with entity types.

Change OuttakeResult

Description: Refactor outtake to return

{ messageArr: string[], richMessageArr: MessageQueueItem[] }

Epic: Slot Data API

Description: Define and implement a set of APIs that allow developers to set/change/update slot data. Wolf should handle this state updates in S1 before proceeding to S2.

Tasks:

  • #75 Implement post /api/slotdata [EXAMPLE BOT]
  • #77 Save lookup into storage [EXAMPLE BOT]
  • #79 Implement slotData processing in S1
  • #84 Run all onCompletes
  • #80 Cleanup apiStorage
  • #76 Implement post /api/new-conversation

Epic slated for v2 (breaking changes with middleware interface)

Update Outtake signature

Description: Remove context.reply() bind as second argument in outtake. preview1.2 changes this to context.sendActivity().

Outtake now returns a messageArray so the user can implement replies from the bot. This allows wolf to avoid using async await

Update Documentation

Description: Update repository documentation

Tasks:

  • Wiki: bootstrap
  • Wiki: roadmap
  • Wiki: contributions + conduct
  • Wiki: engineering guidelines
  • Main README.md

Implement retry multiple slot matches

Description: Currently..MatchNotValidData currently holds information on one slot that has been matched but fails validation.

Desired behavior: MatchNotValidData can hold multiple (array) slots that have been matched but fails validation which will allow Wolf to recognize that a match has been picked up.

Discussion: What order should we retry and attempt to fill these matches? One by one? all at once?

Cancellation Support

Description: Ability for Wolf to identify and execute cancellation flows mid activeAbility

Rename ability acknowledge

Description: ability.acknowledge: () => string -> ability.onComplete: () => Promise

Must resolve to string or null

run onComplete for every abilities completed on current turn

Is your feature request related to a problem? Please describe.
if a user fills in multiple slots on a single turn, (either through utterance or through slotData getter function), the bot should be able to run the onComplete functions on all abilities that are completed.

Describe the solution you'd like
the result of runOnComplete should return an array of messages for the middleware to add.
and the addMessage now should be addMessages to take that array of messages.

Redux Observable

Description: Also allows for actions to kick off other actions and allow asynchronous processes.

Basic Tests

Description: Create basic unit tests for current code base

remove reduxDevTool dependency on wolf

Is your feature request related to a problem? Please describe.
the user shouldn't have to build and install the redux dev tool everytime they install botbuilder-wolf

Describe the solution you'd like
Delegate the starting of devServer and the redux devtool redux middleware to the user.
provide the user a simple function to call (like wolfStoreBuilder), and allows the user to decorate the function by passing in the devtool middleware, and then pass the decorated function into wolfMiddleware

Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered.

Additional context
Add any other context or screenshots about the feature request here.

Implement post /api/slotdata/

Description: Implement server.post('/api/slotdata', (req: any, res: any)

  • Accept payload and test values.
  • Values that pass validators should be stored in a global lookup table for S1 to retrieve (one turn life).
  • Regardless of pass/fail return descriptive response back to caller.

Feature: Epic #74

Refactor slot structure

Description: Refactor slot interface in src/types/ and example bot alarmBot
Property names should be self-descriptive

Example: entity -> name

Define acknowledge structure in ability.ts

Description: Move ability acknowledge to ability.ts.
Separate <ability>/index.ts can be removed. Users should handle storing value to convoState on their own inside onComplete function.

Refactor state and store

Description: Refactor all state naming to be consistent and accurately reflect state objects throughout the stages.

convertsationState -> convertsationStore
state -> conversationState

  • look into where to initialize pendingWolfState = wolfState. pendingWolfState should not have any references outside of the Wolf stages. Currently used in nlp logic in bot.ts..

Refactor Nlp Interface

Description: Refactor Nlp interface and usage to promote self documenting naming conventions.
I.e. entity.entity is ambiguous.

Confirmation Slots Order

Description: Currently, confirmation slots must be ordered after the "calling slot" to ensure the confirmation slot is not picked up by the evaluate stage as the next pending slot to be completed.

Desired results: confirmation slots should only be prompted for then called by the "calling slot".

NLP results that has no intent but detected entities breaks the bot

Describe the bug
Example: alarmBot
if a user just says "called kevin", NLP will result {'intent': null, entities: {name: 'alarmName', value: 'kevin'}
this causes fillSlot to identify an "invalid" slot within the defaultActivity, which causes the validateSlot to try to run the retry off of the slot, but there is no such slot in the given ability.

To Reproduce
Steps to reproduce the behavior:

  1. User says "called Kevin"
  2. Bot breaks

Expected behavior
Bot just runs the default ability

Allow Multiple Abilities

Is your feature request related to a problem? Please describe.
NA

Describe the solution you'd like
A user can say "added an alarm called wakeup at 6am. And removed alarm called workout". Wolf should ideally do two abilities in the single turn.

Describe alternatives you've considered
NA

Additional context
NA

Allow Ability to have other properties

Is your feature request related to a problem? Please describe.
Currently there is not a way to "inject" other dependencies onto the onComplete function. Any developer could want to use the context of the onComplete Function.

Describe the solution you'd like
Add a open string key with any on the Wolf Ability

Describe alternatives you've considered
In the functional scope: this works until you have to write unit tests.

Additional context
This is for better unit tests for the developers who wants to test the onComplete function

Add ImmutableJS?

Is your feature request related to a problem? Please describe.
NA

Describe the solution you'd like
We are mutating the pendingWolfState sometimes. To ensure we are not mutating states, we should use ImmutableJS for our pendingWolfState

Describe alternatives you've considered
Alternative is keep what we have, but try really hard to make sure we are not mutating the state.

Additional context
NA

Epic: Hot-loading Abilities Data

Description: On any given turn, the developer should be able to hot-swap the abilities.ts data and Wolf should be able to run uninterrupted with a seamless transition to the user.

Construction of the new abilities.ts (dynamic or static) is independent of this feature, but this epic should define best patterns for passing in the new abilities.ts data.

Tasks:

`shouldRetry` property for slots

Is your feature request related to a problem? Please describe.
Dev should be able to define a function that based on turn count or existing wolf state, whether wolf needs to run the retry function if a response is invalid.

This is helpful if Dev wants the only retry 3 times, and go to another slot.

Describe the solution you'd like

Describe alternatives you've considered
We could the logic in the retryQuery function but it feels dirty.

Additional context

Implement slot.query

Description: slot.query:string -> slot.query: (pass the convostate getter function, etc..) => string

slot.query should be a function with access to convo, allowing dynamic prompting queries.
Return: string

Save lookup onto storage

Description: Create new MemoryStorage() to save slot data api data. Lookup data should be made available for S1. (read-only)

Feature: #74

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.