DeepDive.md - Grip

A code dive into Mycroft - Jan Newmarch

Overview

Mycroft is an open source digital assistant. Open source means what it says: the code is available and is hackable. So if you want to play with any aspect of Mycroft, you can. The only problem of delving into the source code is of figuring out what is doing what - a standard problem for any piece of complex software.

The Youtube video by Steve Penrod explains the overall structure of Mycroft. The tutorial by JarbasAI goes into more detail about building skills (as well as the standard Mycroft documentation)

But neither of these go down to the code level: what method in which object does what. This document is about that: how do skills get loaded and executed, which objects and which methods get called and what they do. I only cover how skills are loaded and how intents are called and executed.

The two main files are mycroft/skills/core.py and mycroft/skills/intent_service.py, containing the principal classes Mycroft and IntentService. Others will be mentioned as needed.

Debugging

If you want to see what Mycroft is doing, chuck in LOG statements. For example,

LOG.debug('My debug statement')

If Mycroft is run with debugging on (e.g. start-mycroft.sh debug) then the debug statements will appear in /var/log/mycroft/* by default.

A way of figuring out how a particular piece of code was called, is to include a traceback statement:

traceback.print_stack(file = open('/tmp/traceback.txt', 'w'))

You should be also able to use the trace module, but I haven't tried that.

Of course, you have the source code - that's pretty nice to read. But when messages start hopping around on the bus, it can screw your head a bit.

Bus

Mycroft uses a 'bus' to send messages between different components of the system. The 'bus' is actually a web socket: sending a message on the 'bus' is acting as a client to the web socket, while handling received messages is done by the web socket server. These are in files mycroft/messagebus/client/ws.py and mycroft/messagebus/server/ws.py.

You won't need to look at these, save to know that emit() puts a message on the bus and on() registers a handler for a message. What's put on the bus is (serialised) structures of type Message from mycroft/messagebus/message.py.

A Message has an attribute type which is a string, used as the event name to control who handles the message. An example of a serialized message is

{
 "data": {"status": "succeeded"},
 "context": null,
 "type": "skill.handler.complete"
}

Loading intents

In core.py the function load_skill() is responsible for creating a skill. After some sanity checks on a file, it checks if it contains the function create_skill(). If it does, it creates the skill and calls register_decorated() on the skill.

register_decorated() checks each method to see if it is an intent and if so calls register_intent(). This builds the parse data for the intent and puts on the bus a message of type register_intent for the adapt engine, and also calls add_event() with the intent's name and the handler when that intent is executed.

The add_event method adds the generic function wrapper which handles all intent executions. This takes the intent parser's name, and the handler for that intent.

In the meantime, IntentService.handle_register_intent() in intent_service.py handles the register_intent message, and passes the intent's parser data to the adapt parsing engine. It was registered as msg handler for register_intent in IntentService.__init()__

Executing intents

The input recognition loop hands an utterance to IntentService.handle_utterance(). The message data has an attribute utterances which is a list of parsed phrases, typically only one phrase such as ["turn light off"].

This calls _adapt_intent_match() to find the best_intent.

An intent returned from _adapt_intent_match() for the utterance "turn light off" could look like

{   'confidence': 1.0,
    'intent_type': 'lifx-mycroft.samclane:handle_toggle_intent',
    'lifx_mycroft_samclaneOff': 'off',
    'lifx_mycroft_samclaneTarget': 'light',
    'lifx_mycroft_samclaneTurn': 'turn',
    'target': None
    '__tags__': [   {   'confidence': 1.0,
                        'end_token': 0,
                        'entities': [   {   'confidence': 1.0,
                                            'data': [   (   'turn',
                                                            'YeelightTurn'),
                                                        (   'turn',
                                                            'lifx_mycroft_samclaneTurn')],
                                            'key': 'turn',
                                            'match': 'turn'}],
                        'from_context': False,
                        'key': 'turn',
                        'match': 'turn',
                        'start_token': 0},
                    {   'confidence': 1.0,
                        'end_token': 1,
                        'entities': [   {   'confidence': 1.0,
                                            'data': [   (   'light',
                                                            'lifx_mycroft_samclaneTarget')],
                                            'key': 'light',
                                            'match': 'light'}],
                        'from_context': False,
                        'key': 'light',
                        'match': 'light',
                        'start_token': 1},
                    {   'lifx_mycroft_samclaneOff': [   {   'confidence': 1.0,
                                                            'end_token': 2,
                                                            'entities': [   {   'confidence': 1.0,
                                                                                'data': [   (   'off',
                                                                                                'lifx_mycroft_samclaneOff'),
                                                                                                'YeelightOff'),

                                                                                'key': 'off',
                                                                                'match': 'off'}],
                                                            'from_context': False,
                                                            'key': 'off',
                                                            'match': 'off',
                                                            'start_token': 2}]}],


}

This intent has a confidence of 1.0 in its match to the LIFX skill lifx-mycroft.samclane with intent handle_toggle_intent. The first entities section shows that the word turn has matched both the Yeelight and lifx_mycroft_samclane skill's keywords for turn, while the second that the word light has only matched the lifx_mycroft_samclane skill's Target.

This is put back on the bus as part of a reply to the message to handle_utterance, with the message type being the intent's type, that is "type": "lifx-mycroft.samclane:handle_toggle_intent"

This is caught by the wrapper registered to handle intents, and calls the handler for that intent.

In the meantime, if _adapt_intent_match() can't find a best intent, then a message of type intent_failure will be put on the bus. This will be caught by the fallback system.

Messages of type intent_failure are handled by FallbackSkill.make_intent_failure_handler.handler() of core.py. The handler was registered in mycroft/skills/__main__.py by FallbackSkill.make_intent_failure_handler(). This handler loops through all registered fallback handlers until one succeeds. The fallback handlers return True or False to signal success or failure. This is unlike ordinary intent handlers which have no return value (i.e. None).