Packages & Modules
Modules are the skills of Leon, this is thanks to them Leon is able to do things according to what you say. In this section you will have a deeper look into modules.
#
PackagesPackages contain an infinity of modules. A package is nothing more than a folder containing modules, you can consider them as a category of modules.
E.g. the Checker package contains modules such as the Is It Down one because this package includes modules related to the "checking" purposes.
The full packages list is available here.
#
Directory StructurePackages are listed in the packages
directory. Let's take the Video Downloader package as example.
Note the package name must be lowercase and in English.
packages/videodownloader
: package name.packages/videodownloader/config/config.json
: package configuration. It contains the configuration of each of its module.packages/videodownloader/config/config.sample.json
: sample configuration file. This file is copied during the setup to generate the file above.packages/videodownloader/data/answers
: folder containing answers translations of each module.packages/videodownloader/data/db
: folder containing the package database.packages/videodownloader/data/expressions
: folder containing understanding dataset of each module. Those dataset are used to train the understanding model.packages/videodownloader/test
: folder containing tests of each module.packages/videodownloader/README.md
: file containing package/modules purposes.packages/videodownloader/__init__.py
: empty file allowing to load the package as a Python package.packages/videodownloader/version.txt
: file containing the package version.packages/videodownloader/youtube.py
: YouTube module.
Each package:
- Has its own version.
- Has its own configuration (for each module).
- Has its own dataset (for each module).
- Has its own tests (for each module).
#
Versioning- Leon's packages follow the SemVer.
- Each new module increases the MINOR version number of a package (e.g. 1.0.0 -> 1.1.0).
- Each time a MAJOR or MINOR version number of a package is increased, then increase the MINOR one of the whole project (e.g. 1.0.0 -> 2.0.0 | 1.1.0 -> 1.1.0).
- Each time a PATCH version number of a package is increased, then increase the PATCH version number of the whole project (e.g. 1.0.0 -> 1.0.1 -> 1.0.1).
#
ModulesModules are the skills of Leon. They contain one or several action(s) to be able to accomplish specific job(s).
When Leon understands what you told him, he:
- Triggers a module action.
- Do the job.
- Returns you the output of that execution.
Each module has its own purpose and its own configuration. Do not hesitate to browse the packages list to understand their goals.
Today, modules are written in Python but in the future they could also support other languages thanks to the bridges.
#
ConfigurationLet's take the Video Downloader package again as example.
packages/videodownloader/config/config.json
In each package configuration file, you can add new keys/value as much as you want while you are creating a module.
In this example, the keys api_key
and playlist_id
have been added to the YouTube module configuration.
It allows the module to pick the values to request the YouTube API.
To have access to these properties, you can use the utils.config(key) function.
Options are used when it needs interaction between a module and the core. They can be used for the synchronizer for example.
Tip
Do not hesitate to take a look at the other modules to have a better understanding.
#
Dataset & TranslationsTo reply and understand you, Leon needs his dataset and translations. Indeed, his dataset are divided into two parts: expressions and answers.
- Every module has their own expressions and answers.
- Each of these dataset has their own translations.
- Translations are represented by the filename of these dataset, such as
en.json
,fr.json
, etc.
#
ExpressionsExpressions are the data used to train the Leon's understanding. When you execute the training script, all of the expressions of each module are browsed to generate the classifier.
Expressions are wrapped inside a module action. This is how Leon understands which action he needs to do.
Note that each expression of each module action has its own confidence.
E.g. Who Am I module English expressions belonging to the Leon package. These expressions are wrapped inside the
run
action.
#
FallbacksDespite the expressions you wrote, it might be possible Leon still does not understand some of them. This is where fallbacks jump in the game.
In the core/langs.json file, you can find the list of the supported languages with several properties:
short
: the short language code.min_confidence
: the minimum confidence of the Leon's comprehension. If the confidence is smaller than the given one, Leon replies you he is not sure about what you said.fallbacks
: force the module selection. Use thewords
key to determine on which words you want Leon pick up a module. And use thepackage
,module
andaction
keys to define which module action should be executed on the given words.
#
AnswersAnswers are the data used by Leon to provide you results binded with the modules outputs. In addition, answers contains sub properties to have different kind of answers per module.
E.g. part of the Greeting module English answers belonging to the Leon package.
#
HTMLIt is possible to use HTML in your answers.
E.g. part of the GitHub module English answers belonging to the Trend package.
#
Create a ModuleTip
- Creating a module is one of the best way to contribute in Leon! Before doing that, please make sure you review this document ❤️
- For example, you could think of creating a To-Do List module (although this one already exists). Check out the roadmap to see what is in the pipeline.
- Don't hesitate to open an issue if you have any questions.
Each module is included in a package (e.g. packages/{PACKAGE NAME}/{MODULE NAME}.py
).
#
StepsHere are the basics steps to create a module. For those steps, we will take a tweets grabber module as example.
#
1. Define the Purpose(s)- I want to create a tweets grabber module. When I say or write:
- I want Leon tells me my 5 latest tweets with the stats of each.
- It seems this module does not correspond to any existing package (category). So I create the Twitter package by creating the
packages/twitter
folder. - To do so, I make sure it follows the package directory structure and contains the required files mentioned in that structure.
Tip
If your module is more advanced and must contain multiple purposes, do not hesitate to create several actions.
#
2. Name Your Module- I choose to name my module
Tweets Grabber
.
#
3. Write the Code- To request the Twitter API, I need API credentials. So I set the Twitter API key(s) in the
packages/twitter/config/config.json
file I previously created in the step 1. - In addition, I create the
packages/twitter/tweetsgrabber.py
file, define my action(s) and I write the code of my module. - While I'm writing the code, I edit
server/src/query-object.sample.json
and from the project root directory I use the following command:
#
4. Write the Tests- Now that I'm satisfied with my module, I create the
packages/twitter/test/tweetsgrabber.spec.js
file. - I write my module tests in that file.
#
5. Shortly Explain How To Use- In the
packages/twitter/README.md
file, I add a short description of the purpose of my module. - I briefly explain how to use the module (which kind of sentences can we say, if there is any configuration to do, etc.).
#
6. Share- I share my module to the world by contributing.
#
Actions (Module Functions)In the module file, you must add an action (function) that will be the entry point of the execution. An action takes the input string (query) and the entities as parameters.
When you have only one action in your module, the usual action name is run
:
When you have several actions in your modules:
Tip
Don't forget that Leon knows which action he must execute thanks to the expressions you define.
#
Naming Convention- The module filename must contain only lowercase alphabetic characters and must use the English language.
E.g. Meaning of Life module filename:
meaningoflife.py
- Actions names must use snakecase (lowercase alphabetic characters and `` only) and must use the English language.
E.g. To-Do List module actions:
create_list
,add_todo
,complete_todo
, etc.
#
Query ObjectEvery time you communicate to Leon, he will creates a temporary query object JSON file with the following properties:
lang
: short code of the used language.package
: used package name.module
: used module name.action
: used action name.query
: your sentence.entities
: an array of the entities Leon has extracted from your sentence. An entity can be whatever you define as an entity (custom entity) or it can be a built-in entity such as a date duration, a number, a domain name, etc.
The server/src/query-object.sample.json file is here to let you execute and test the behavior of your module code on the fly during its creation. Edit it according to your module properties.
#
EntitiesEntities are chunks that Leon extracts from your sentences. These entities are shared to your actions so you can manipulate them to give more sense to your modules.
There are different types of entities that are listed below:
#
Built-In EntitiesBuilt-in entities are the ones already included in Leon. Leon extracts entities from queries automatically.
The full list is available here.
Tip
Feel free to see some examples to understand how built-in entities are used. Those are perfect examples:
As you can see, you can iterate over the entities to grab the information you need (domain names, dates, etc.).
#
Custom EntitiesCustom entities are the ones you define yourself according to specific use cases. You can create your own entities in the translations files located in: packages/{PACKAGE NAME}/data/expressions/{LANG FILE}.json
. In that file, custom entities are included in the actions properties at the same level as the expressions.
They are represented by an array of objects:
As you can see, a custom entity is made of a type
, a name
and more depending of its type.
E.g. see the create_list action entities of the To-Do List module.
Custom entities have two types listed below:
#
Trim EntitiesTrim entities allow you to cut/trim parts of the query to extract only the text you want. This is done thanks to these conditions:
{ "type": "between", "from": [], "to": [] }
{ "type": "after", "from": "" }
{ "type": "after_first", "from": "" }
{ "type": "after_last", "from": "" }
{ "type": "before", "to": "" }
{ "type": "before_first", "to": "" }
{ "type": "before_last", "to": "" }
To illustrate that, let's say we are creating a To-Do List module. To do so, we will define a custom entity list
.
When we have the following queries:
We want to extract the text shopping
to associate it as a list
entity. We use the between
condition to catch what is between a
or my
and list
:
In the module file packages/{PACKAGE NAME}/todolist.py
:
Tip
You can take a look at the real To-Do List module of the Calendar package.
#
Regex EntitiesRegex entities allow you to grab parts of the query via a regular expression.
Let's say we create a Color Picker module. To do so, we will define a regex entity color
.
When we have the following query:
We want to extract the strings red
and blue
to associate it as color
entities. We use a regex to catch these colors:
In the module file (packages/{PACKAGE NAME}/colorpicker.py
):
#
Persistent DataLeon uses TinyDB to deal with packages databases. Each package can have its own database and the database is managed by modules.
For more information, you can refer to the:
- utils.db() function.
- TinyDB documentation.
- YouTube module as example.
#
Installing Third Party Python PackagesLeon runs in a virtual environment to ensure that the project's packages/dependencies doesn't conflict with the ones installed system wide.
To install third party packages, kindly follow these steps;
- Open a terminal window at the
bridges/python
directory. - Perform
pipenv install {PACKAGE NAME}=={PACKAGE VERSION}
. Note that it must use a specific package version. - Import the newly installed package in the required module file with
import {PACKAGE NAME}
.
Kindly note that {PACKAGE NAME}
and {PACKAGE VERSION}
are placeholders. They should be replaced with the name and version of the actual package you wish to install.
#
OutputsEvery module does something, and the outputs allow the core to understand what the module did and what is the state of its execution. This is thanks to the outputs that Leon knows what to do next.
The core understands two types of outputs:
inter
, which are the intermediate outputs. You can have as many intermediate outputs as you want.end
, which is the final output. You must only have one final output, that allows Leon to know that the module execution is done.
Outputs are represented by the utils.output() function.
#
Test a Module#
On The FlyTo test the behavior of your module while you are creating it, you can run this following command from the project root:
For example, for the Is It Down module the query object file could look like that:
Tip
Don't forget to take a look at this list to see how entities are formatted.
#
End-to-EndModules come with their own tests. They are represented by a unique file for every module that can be found at: packages/{PACKAGE NAME}/test/{MODULE NAME}.spec.js
.
As you may noticed, JavaScript is used to test modules because the core is written in JavaScript and we do end-to-end tests by processing a query to the NLU. Then the Leon's brain is executed and returns the output. This is the output, especially the codes that have been interpreted by your module that you need to consider.
Leon uses Jest as a testing framework.
Here are two tests examples of the Is It Down module:
These tests can be found in packages/checker/test/isitdown.spec.js
Once you finished to write your tests, you can execute the following command to run them:
#
Utils FunctionsUtils functions are available in bridges/python/utils.py.
To use the following functions, do not forget to import the Python utils module at the beginning of your Leon's module:
Tip
You can also contribute by improving these functions or by adding new ones to make the modules creation even better.
#
getqueryobj()Returns the query object.
#
translate(key, d = { })Randomly pick up a module answer from the packages/{PACKAGE NAME}/data/answers/{LANG}.json
file via the given key, do string interpolation via the given data object and return the plain string answer.
key
: module answer key to pick up the right string.d
: data object for string interpolation.
#
output(type, code, speech = '')Communicate the module data to the core.
type
(inter|end): output type to inform the core if the module execution is done or not. Theend
type must appears one time.code
: output code to provide an additional information about the type of output. Usually used by the modules tests.speech
: plain string answer.
#
http(method, url, headers = None)Send HTTP request with the user-agent Leon/{VERSION NUMBER}
. It uses the Request Python library.
method
: HTTP method.url
: URL to request.headers
: HTTP headers.
#
config(key)Get a module configuration from the packages/{PACKAGE NAME}/config/config.json
file.
key
: module configuration key.
#
createdldir()Create the downloads folder of the current module. When Leon needs to download something, it is saved into: downloads/{PACKAGE NAME}/{MODULE NAME}
.
#
db(dbtype = 'tinydb')Create a new dedicated database for a specific package.
dbtype
(tinydb): database type. Only supports TinyDB today.