eniagreen / botml

Powerful markup language for modern chatbots.
https://codename.co/botml
MIT License
101 stars 20 forks source link
bot chatbot dialogue markup-language

Botml

An open source and production-ready markup language
for designing modern chatbots.


npm npm downloads David travis JavaScript Style Guide test License


Botml is a declarative and powerful markup language for designing modern chatbots (a.k.a. conversational bots).

Anyone (developers and non-developers) can use it to create and teach bots how to behave. Define the behavior of your chatbot using the right tiny bit of formatting and engage the conversation in no time. See for yourself: a calculator bot written in only two lines.

Table of Contents

Install

This project uses node and npm. Go check them out if you don't have them locally installed.

$ npm i -g botml

This will install both the botml node package and the bot client.

Optionally, if you use Atom as an editor, you may want to install syntax highlighting with the language-botml package.

Usage

Either run the cli:

$ bot

or use it in your code:

const Botml = require('botml/lib/botml')
const bot = new Botml('alice.bot')
bot.start()

or even load it statically in your browser:

<script src='https://unpkg.com/botml'></script>
<script>
const bot = new Botml()
// either load an URI:
// bot.load('https://raw.githubusercontent.com/codename-co/botml/master/examples/hello.bot')
// or load the script directly:
bot.parse('> *\n< yes?\n')
bot.start()
bot.send('hey')
</script>

Features

Existing features are of two sorts: basic features that cover a basic bot needs, and advanced features that enable richer conversational capabilities.

Basic features:

Advanced features:

Format

The format aims to achieve the most by using the least. With the right and minimal set of conventions, it can be very powerful.

The general syntax follows 3 important conventions:

  1. The text must be written into blocks of lines separated by at least two line breaks ;
  2. Each line must start with a one-character symbol that defines its type ;
  3. A block type is inferred by the symbol of its heading line.

The most basic .bot file would be:

! BOTML 1

> Hello
< Hello human!

▶️ Try this script on bubl.es

Blocks

Specification

The specification line is needed to tell Botml that it can load the file.

This must be the first line of any .bot file.

! BOTML 1

The 1 stands for the current version of the format.

Comment

Comments can help make your .bot file clearer.

They can be used as standalone blocks or can be inserted within actionable blocks.

They cannot be used inline.

# COMMENT

Dialogue

Dialogues are the core concept of any bot. It defines how the human and the bot can interact.

A dialogue must start with a > line, that defines what sentence(s) can activate the bot to respond.

There must be one or multiple < lines after that define the bot response(s).

There can be multiple back and forth by repeating this sequence within the same block.

> MESSAGE
< MESSAGE

Example:

> Hi
< Hello there. Who are you?
> *
< Nice to meet you.

▶️ Try this script on bubl.es

Random replies

Random replies in dialogues make a bot feel less rigid. When answering to a human, the bot chooses randomly in the reply candidates. Only one of the multiple reply candidates can be chosen by the bot.

> MESSAGE
< REPLY CANDIDATE #1
< REPLY CANDIDATE #2

Example:

> Hello
< Hi there
< Howdy?

▶️ Try this script on bubl.es

List

Lists are helpful to assemble similar notions, or alternatives.

A list must start with a = line, that defines the list name.

It must have at least one list item but can have more. Each list item starts with the - symbol.

= LIST_NAME
- LIST_ITEM
- LIST_ITEM

A list item can reference yet another list.

= LIST_NAME
- LIST_ITEM
- [ANOTHER_LIST]

It can be referenced in a > line for referencing multiple variants of an input pattern.

It can be referenced in a < line for referencing randomly one of the list items as a response.

It can be referenced in a ? line (prompt) for referencing multiple predefined choices.

Referencing a list is done by wrapping the list name with brackets: [LIST_NAME].

Example:

= citrus
- oranges
- lemons
- grapefruits

= random_fruit
- apples
- apricots
- bananas
- [citrus]

> I like [random_fruit]
< Oh. I prefer [random_fruit].

# which is the equivalent to:
#  > I like apples
#  > I like apricots
#  > I like bananas
#  > I like oranges
#  > I like lemons
#  > I like grapefruits
#  < Oh. I prefer apples
#  < Oh. I prefer apricots
#  < Oh. I prefer bananas
#  < Oh. I prefer oranges
#  < Oh. I prefer lemons
#  < Oh. I prefer grapefruits
#
#  > I like [random_fruit]
#  < Oh. I prefer [random_fruit].

▶️ Try this script on bubl.es

Lists can also be used in prompts.

Prompt

Prompts are predefined quick replies in reaction to a specific situation.

They must be placed after a < line, at the end of a dialogue.

They must reference a list to access all the quick replies.

The number of quick replies should be kept minimal.

= LIST_NAME
- LIST_ITEM
- LIST_ITEM

? [LIST_NAME]

Example:

= pizza_types
- Peperroni
- Margherita
- Hawaiian

> I need a pizza
< What kind of pizza?
? [pizza_types]

▶️ Try this script on bubl.es

Service

Services can leverage external APIs endpoints.

A service must be declared in its own block starting with the @ sign. It consists of a name and an JSON-formatted API endpoint (over http or https).

It can (and most of the time should) accept a parameter by using the $ sign within its endpoint.

@ SERVICE_NAME ENDPOINT

It can be consumed within a dialogue.

When the ENDPOINT has a $ sign, it must accept a parameter whose value will replace the $ sign.

The result of the service call can be filtered using an optional OUTPUT. It's a selector whose format is /(\.\w)+/.

@ SERVICE_NAME( PARAMETER )[ OUTPUT ]

Example:

@ geo_domain http://freegeoip.net/json/$

> Where is *{domain}
@ geo_domain($domain).city
< It is running from $.

Script

Scripts can be used to evaluate code.

The language of the code is dependent of the language used of the parser used. The botml parser is in Javascript, thus Javascript code can be used.

It must be inline within dialogues wrapped in ```.

It can access named variables:

Example:

> It will cost you #{price} USD
< `$price / 1000`k USD is a lot!

▶️ Try this script on bubl.es

Variable

Variables are the way to detect, format, store and reuse meaningful information.

A variable can be captured within a > line (dialogue).

It must be either textual ($), numeric (#) or alphanumeric (*).

It can be used in < lines.

> My name is *{name}
< Nice to meet you, $name

> I am #{age} years old
< Seems that you are $age

▶️ Try this script on bubl.es

The variable format is ${VARIABLE_NAME} (with its numeric and alphanumeric equivalents). But for convenient of use, the format $VARIABLE_NAME can be used too for textual and numeric variables.

A special $ variable always refers to the last matching value of a dialogue or the result of the previous line (the result of a service consumption for instance).

Regular Expression

Regular expressions can be used in > lines to have more control on what to detect.

A regular expression must be wrapped in / and cannot be mixed with basic expressions.

> /^I (?:.+\s)?(\w+) (?:.+\s)?(it|this)/
< Cool bro.

▶️ Try this script on bubl.es

In fact, the XRegExp library is used under the hood, giving you access to leading named captures, inline comments and mode modifiers.

Dialogue workflow

Dialogue workflows are a superset of the classical dialogues.

A workflow can be used to determine a precise flow of conversation.

It must start with a ~ line, that defines the list name.

Only one workflow can start with a < dialogue. Such a workflow will be activated and used by default when the user connects to the bot.

# grocery shopping
> I want to buy *{items}
< How many $items?
> #{count} $items
> #{count}
< There you go.

▶️ Try this script on bubl.es

Conditional branches

Conditional branches are instructions that direct the bot to another part of the dialogue based on test conditions.

Conditional branches start with --- and listen for all typed information then test it with all cases. Each case being separated by ---:

---
  > first input case
  < first reply
---
  > second input case
  < second reply
---
> last input case
< last reply

Conditional branches work great with another feature labelled checkpoint.

A checkpoint is a marker ~ CHECKPOINT_NAME in the workflow, that makes returning to it at a later stage of the current dialog a breeze. It can be referred to with ~ [CHECKPOINT_NAME], which redirects the flow to the checkpoint mark:

~ ask_howdy
< Hello stranger.
~ checkpoint_name
< How are you?
> meh
< So you feel bad huh
~ [checkpoint_name]

# Example of workflow working:

# < Hello stranger.
# < How are you?
# > meh
# < So you feel bad huh
# < How are you?
# > ...

▶️ Try this script on bubl.es

Both checkpoints and lists make working with conditional branches something really interesting:

= mood
- good
- meh
- great
- ok

= exceptions
- fantastic
- better than ever

~ ask_howdy
< hello stranger.
~ listen_howdy
< how are you?
? [mood]
---
  > meh
  < So you feel bad huh
  ~ [listen_howdy]
---
  > good
  < Oh, it is not bad ;)
  ~ [rechecker]
---
  > [exceptions]
  < Seems you really cool guy!
---
  > ok
  < Hmm, just ok? Okay then...
---
> great
< Nice! Let's continue then...

~ rechecker
< Maybe it is more than good?
> excellent
< Much better!

▶️ Try this script on bubl.es

It is also possible to use the "not equal" sign ! in conditional branching:

= mood
- [bad_mood]
- [good_mood]

= bad_mood
- meh
- bad

= good_mood
- great
- ok

~ ask_howdy
< hello stranger.
~ listen_howdy
< how are you?
? [mood]
---
  > ![mood]
  < I asked you how you are ; please let's start over with another answer?
  ~ [listen_howdy]
---
  > [good_mood]
  < Oh, it is awesome ;)
---
> [bad_mood]
< Hmm... bye then...

▶️ Try this script on bubl.es

Conditional branching also work well with scripts as long as a value is returned. If a script returns the value true, the corresponding branch will be activated. If all tests are false conditional branching will skip all branches and continue with the current workflow:

~ ask_for_email
> start email workflow
~ listen_email
< Your email please?
> *{email}
---
  ` !/^.+@.+\..+/.test('$email')
  < This email $email seems not legit!
  ~ [listen_email]
---
< Cool. We'll reach you over at $email

▶️ Try this script on bubl.es

Trigger

Triggers are a way for the bot to be integrated with an other program.

There are two types of events: standard events and custom events.

Standard events are as follow:

Custom events can be triggered within dialogues.

A custom event must have a name.

It can have parameters. Parameters can rely on named variables.

@ trigger( 'EVENT_NAME' [, PARAMETER] )

Example:

> hello
< hi
@ trigger('said_hi')

Then handle the 'said_hi' event in your code according to your needs:

bot.on('said_hi', () => console.log('The bot said hi'));

Stanford TokensRegex

TokensRegex is a framework for defining advanced patterns based of priori Natural Language Processing such as Named Entities and Parts-of-Speech tagging.

> ([ner: PERSON]+) /was|is/ /an?/ []{0,3} /painter|artist/
< An accomplished artist you say.

This feature is enabled through code integration. See an example.

Natural Language Processing

NLP can be enabled through code integration. See an example.

Examples

See the examples/ directory.

Develop

Start the unit tests with:

npm test
npm run autotest  # this is for continuous testing

Use the CLI for trying botml scripts from the console:

node lib/cli.js
node lib/cli.js examples/echo.bot  # load script from a local file
debug=true node lib/cli.js  # add logging verbosity

In CLI mode, a few special commands can help with debugging too:

Contribute

Feel free to dive in! Open an issue or submit PRs.

License

Botml is MIT licensed.