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
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
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.