Agenda

Agenda is a declarative specification language for conversations, focused on specifying knowledge and actions, rather than sequential flows or conversation trees.

We imagine a conversational AI framework that is strapped down to the minimum possible requirements - the things you'd have to tell your agent even if it were human.

These requirements would be the bot's actions and knowledge.

  • Actions - what effects can the bot have on the world. This is usually done via API calls. Example: "The bot can order a pizza by calling http://order.com/pizza and has to give a phone, address and pizza size".
  • Knowledge - what the bot knows and will be able to let the users interacting with it know. Example: "The bot should know that our opening hours are 2pm to 10pm.".

Given the knowledge and actions, we create a bot that can interact with users, helping them perform actions and enabling access to knowledge. This means non-linear conversations, where the user can context switch without the bot forgetting its state - a stark difference from trying to come up with all the different paths a conversation could take, i.e. a conversation tree.

As much as possible, we try to avoid having the user need to give different formulations and phrasings as we believe NLP has reached a point where this is no longer required.

We optimize on:

  • composability; actions and knowledge can be disabled or enabled and don't interfere with each other
  • reuse; bots can share a part of their configuration
  • focus on business logic rather than NLP; no training examples.

Video introduction

Here

Target audience

Agenda is ideal for general purpose developers who want to set up a conversational interface and are not looking to start an NLP research project, or train their own models. The bots produced will communicate by text via a socket run by a python server, so can be embedded in any service in a short time.

That said those who want to leverage existing NLP services (e.g. an intent recognition service) can embed them within the configuration. This would look like this:

# The service would receive a post request, e.g.`{someKey: "I want pizza"}`
&my-custom-intent-recognizer
remote:
  state-remote: http://localhost:8000/listen-wants-pizza
  needs:
    - key: someKey
      value: incoming_utterance

Example

Let's imagine building a bot for pizza place. In terms of conversation trees, there are many options as to how this conversation can go, here are a couple of examples:

👩 hi there
🤖 Would you like to order pizza?
👩 yup
🤖 Alright. Are you vegan?
👩 no I am not
🤖 Cool. What is your name?
👩 Alice
🤖 Got it. How many pies would you like?
👩 2
🤖 Okay. What kind of toppings would you like?
👩 mushrooms
🤖 Cool. What pizza size would you like?
👩 large
🤖 Cool. What is your address?
👩 81 Mill Street Greenville SC
🤖 Got it. What is your phone number?
👩 212 222 2222
🤖 Cool. Thank you Alice! The phone I got is 212 222 2222.
 We are sending you 2 large pizzas with mushrooms to 81 Mill Street Greenville SC.
👩 Hi!
🤖 Would you like to order pizza?
👩 yes
🤖 Alright. Are you vegan?
👩 nope
🤖 Cool. What is your name?
👩 Alice, my address is 81 Mill Street Greenville SC
🤖 Cool. How many pies would you like?
👩 2 large pizzas with mushrooms
🤖 Cool. What is your phone number?
👩 212 222 2222
🤖 Okay. Thank you Alice! The phone I got is 212 222 2222.
  We are sending you you 2 large pizzas with mushrooms to 81 Mill Street Greenville SC.

Collecting data or making up transcriptions to cover all the options, even with machine learning is a pretty tedious task, and would be hard to maintain over time.

Instead one would prefer to say what is needed to order pizza, alongside the other goals of the conversation.

actions:
  - say: I'm transferring you to an agent.
    when:
      not: *wants-pizza
  - say: We currently do not sell vegan pizzas.
    when:
      all:
        - *wants-pizza
        - *is-vegan
  - say:
      say-remote: http://localhost:8000/order-pizza
      needs:
        - key: name
          value: *name
        - key: amount_of_pizzas
          value: *amount_of_pizzas
        - key: toppings
          value: *toppings
        - key: size
          value: *size
        - key: address
          value: *address
        - key: phone
          value: *phone
        - key: email
          value: *email
    when:
      all:
        - *wants-pizza
        - not: *is-vegan

Subsequently we can define how to ask and listen to each one of the needed details.

slots:
  - &name
    ack: Nice to meet you {value}!
    ask: What is your name?
    type: name
  - &address
    ask: What is your address?
    type: address
  - &phone
    ask: What is your phone number?
    type: phone
  - &email
    ask: What is your email?
    type: email
  - &amount_of_pizzas
    ask: How many pies would you like?
    amount-of: pie
  - &wants-pizza-question
    ask: Would you like to order pizza?
    type: boolean
  - &wants-pizza-intent
    intent:
      - I want to order pizza
      - I want pizza
  - &wants-pizza
    any:
      - *wants-pizza-question
      - *wants-pizza-intent
  - &is-vegan
    ask: Are you vegan?
    type: boolean
  - &toppings
    ask: What kind of toppings would you like?
    multiple-choice:
      - mushrooms
      - olives
      - tomatoes
      - onions
  - &size
    ask: What pizza size would you like?
    choice:
      - small
      - medium
      - large

Given this spec, agenda will create a bot that can handle the conversations above, and many other variations. No custom training or data collection is needed, and if requirements change, all the conversation designer needs to do is change the configuration.

For Joe's pizza place, the actual API that orders the pizza is not a part of the bot code, so it is convenient to do this in an external server for the user to define. This allows combining agenda with any backend coding language, or use multiple backends within the same bot.

Setup

You can use agenda on your own machine, or use the version hosted by Hyro at agenda.hyro.ai.

git clone https://github.com/hyroai/agenda.git
pip install -e ./agenda
pip install cloud-utils@https://github.com/hyroai/cloud-utils/tarball/master
yarn install --cwd=./config_to_bot/debugger

In addition run yarn install in each example that you wish to run in config_to_bot/examples

Issues with dependencies

  • spacy requires to run: python -m spacy download en_core_web_sm

Running pizza example

  • Running remote functions server:
cd ./agenda/config-to-bot/examples/pizza
yarn install
yarn start
  • Running bot's server: python config_to_bot/main.py
  • Running bot designer:
cd ./config_to_bot/debugger
yarn install
yarn start

Configuration basics

Agenda configurations are written in yaml files, the only thing which is nontrivial about yaml files is how variables work. Variable is defined using &, as in &my-variable and are dereferenced using *, e.g. *my-variable (basically a glorified copy paste).

The configuration is built of 3 main parts: actions, knowledge and slots, and an additional part to debugging - debug.

knowledge

Currently supports questions and answers.

knowledge:
  - faq:
      - question: What is your opening hours?
        answer: 2pm to 10pm every day.

slots

If you have a value that is recurring in more than one place, you can factor it out to the slots section.

slots:
  - &wants-pizza
    ask: Would you like to order pizza?
    type: boolean

actions:
  - say: I can only help with pizza reservations.
    when:
      not: *wants-pizza
  - say: you want pizza!
    when: *wants-pizza

Types of slots

Currently supported types:

Person name

&name
ack: Nice to meet you {}!
ask: What is your name?
type: name

Address

&address
ask: What is your address?
type: address

Phone

&phone
ask: What is your phone number?
type: phone

Email

&email
ask: What is your email?
type: email

Amount of

&amount_of_something
ask: How many dogs do you have?
amount-of: dogs

Boolean

&user-wants-pizza
ask: Would you like to order pizza?
type: boolean

Intent

&user-wants-to-complain
intent:
  - I have a complaint
  - I want to complain

Choice

&size-of-shirt
ask: What size of t-shirt would you like?
choice:
  - small
  - medium
  - large

Dynamic choice

The list of choices can also be dynamically generated by a remote function based on other slots values.

For example:

&toppings
ask: "Which toppings would you like? you can choose from: {}"
choice:
  state-remote: http://localhost/toppings
  needs:
    - key: is-vegan
      value:
        ask: Are you vegan?
        type: boolean

The server will be called with a post request with a JSON body containing known items from the needs dictionary. It must return a JSON string list with possible choices or null to indicate "unknown".

Date time choice

When the choice list consists exclusively of ISO 8601 formatted strings. The bot will try to understand date/time utterances and resolve it to unique choice.

For example for the list: ["2022-04-25T11:00:00","2022-04-25T12:00:00"]:

  • if the user says "12 pm" the bot will understand the second option.
  • if the user says "25th" the bot will ask the user to rephrase because it can not resolve the ambiguity.

Multiple choice

&choice-of-toppings
ask: What kind of toppings would you like?
multiple-choice:
  - mushrooms
  - olives
  - tomatoes
  - onions

Compound slots

Slots can be combined into compound slots via operators.

List of available operators:

  • not
  • all
  • any
  • greater_equals
  • equals

For example:

slots:
  - &age
    ask: What is your age?
    type: number
  - &gender
    ask: What is your gender?
    multiple-choice:
      - male
      - female
      - non binary
  - &male_and_above_18
    all:
      - is: age
        greater_than: 18
      - is: gender
        equals: female

actions

The actions part lists what the bot can do on behalf of the user. When deciding what to say, the bot will try to match the first relevant action, fulfill its dependencies and performing it, either a saying something or calling a remote API.

Actions might have some dependencies, defined under the needs section.

In addition - it might not make sense to do some things unless some conditions are met. This is what the when section is for.

For example:

actions:
  - say: I can only help with pizza reservations.
    when:
      not: *wants-pizza
  - say:
      say-remote: http://localhost:8000/order-pizza
      needs:
        - key: toppings
          value:
            ask: What kind of toppings would you like?
            multiple-choice:
              - mushrooms
              - olives
              - tomatoes
              - onions
        - key: size
          value:
            ask: What pizza size would you like?
            choice:
              - small
              - medium
              - large
    when: *wants-pizza

The remote server will be invoked with an HTTP post and a JSON body. The JSON will contain key-value mapping of known values from the needs definition. The server should respond with a string, or null to indicate nothing to say.

debug

This part can be used in conjunction with the debugger UI to peek into the bot's state.

When using it you need to label the subgraph you are watching like so:

debug:
  - key: some-label-for-the-slot-below
    value: *my-slot
  - key: some-other-label
    value: *another-slot

The result shows the value the bot has in store at that moment of the conversation, alongisde what this part of the bot wants to say (not all parts participate every turn). You'll see a small checkmark if the bot-part has participated in the last turn.