Skip to content

Extensions

The extensions in Chatto are pieces of code that can be executed instead of messages, and can also alter the state of the conversation. In the fsm.yml file, the extensions are contained in the extension field, under transitions.

You must specify what extension server to use (see bot configuration), and the name of the extension:

  - from:
      - search_pokemon
    into: initial
    command: any
    extension:                  # this transition will execute an extension
      server: pokemon           # from the "pokemon" extension server
      name: search_pokemon      # with the name "search_pokemon"

Extensions are executed as services, and can be written in Go, using the chatto/extensions and chatto/query packages, or they can be written in any language, as long as the services are compatible.

Go

In Golang, the format for a Chatto extension server is as follows:

package main

import (
    "log"

    "github.com/jaimeteb/chatto/extensions"
    "github.com/jaimeteb/chatto/query"
)

// GreetFunc returns the message "Hello Universe" and an image
// and does not modify the Finite State Machine (FSM)
func GreetFunc(req *extensions.ExecuteExtensionRequest) (res *extensions.ExecuteExtensionResponse) {
    return &extensions.ExecuteExtensionResponse{
        FSM: req.FSM,
        Answers: []query.Answer{{
            Text:  "Hello Universe",
            Image: "https://i.imgur.com/pPdjh6x.jpg",
        }},
    }
}

// registeredExtensions maps the name "any" to the GreetFunc extension
var registeredExtensions = extensions.RegisteredExtensions{
    "any": GreetFunc,
}

func main() {
    // Run the extensions via REST
    log.Fatal(extensions.ServeREST(registeredExtensions))
}

You must use either ServeRPC or ServeREST in the main function in order to run the extension server and pass your own extensions.RegisteredExtensions, which maps the extension names to their respective functions.

There are currently two ways to serve the extensions:

When running the extensions, use the flag -port to specify a service port (extensions will use port 8770 by default).

The extension functions must have the signature:

func(*extensions.ExecuteExtensionRequest) *extensions.ExecuteExtensionResponse

Where:

  • ExecuteExtensionRequest contains:
    • The current FSM
    • The channel that received the request
    • The requested extension
    • The input question (the sender and the text)
    • The Domain (fsm.yml data)
  • ExecuteExtensionResponse must contain:
    • The resulting FSM
    • The answers (messages) to be sent to the user

In this example, the extension any simply returns "Hello Universe" and an image, and does not modify the current FSM.

Other languages

Since extensions are services, they can be written in any language. Here is an example in Python, that is equivalent to the one shown above in Go.

Python example

from flask import Flask, Response, request, jsonify

app = Flask(__name__)

def greet_func(data: dict) -> dict:
    return jsonify({
        "fsm": data.get("fsm"),
        "answers": [
            {
                "text": "Hello Universe",
                "image": "https://i.imgur.com/pPdjh6x.jpg",
            }
        ]
    })

registered_extensions = {
    "any": greet_func,
}

@app.route("/extensions", methods=["GET"])
def get_all_funcs():
    return jsonify(list(registered_extensions.keys()))

@app.route("/extension", methods=["POST"])
def get_func():
    data = request.get_json()
    req = data.get("extension")
    f = registered_extensions.get(req)
    if not f:
        return Response(status=400)
    return f(data)

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8770)

In this case, the Flask app emulates the function of ServeREST, while greet_func and registered_extensions correspond to GreetFunc and registeredExtensions respectively, from the Go example.

Extension REST

An extension REST service must implement these routes:

  • GET /extensions

    This route should return an array with the names of the registered extensions.

    Example response:

    [
        "val_ans_1",
        "val_ans_2",
        "score"
    ]
    

  • POST /extension

    This route should return the resulting Finite State Machine object after the extension's execution, along with the answers.

    Example request body:

    {
        "fsm": {
            "state": 2,
            "slots": {
                "answer_1": "3"
            }
        },
        "extension": "val_ans_1",
        "channel": "rest",
        "question": {
            "sender": "cli",
            "text": "2"
        },
        "domain": {
            "state_table": {
                "any": -1,
                "initial": 0,
                "question_1": 1,
                "question_2": 2,
                "question_3": 3
            },
            "default_messages": {
                "unknown": "Not sure I understood, try again please.",
                "unsure": "Not sure I understood, try again please.",
                "error": "I'm sorry, there was an error."
            }
        }
    }
    

    Example response:

    {
        "fsm": {
            "state": 2,
            "slots": {
                "answer_1": "3"
            }
        },
        "answers": [
            {
                "text": "Select one of the options."
            }
        ]
    }
    

Answers

The answers returned from the extensions follow the same rules as the fsm.yml messages. In Go, you can use the helper query.Answers function to create answers from query.Answer, strings or maps.

func GreetFunc(req *extensions.ExecuteExtensionRequest) (res *extensions.ExecuteExtensionResponse) {
    return &extensions.ExecuteExtensionResponse{
        FSM: req.FSM,
        // Answers: query.Answers("Hello Universe"),        // This is a simple string answer
        // Answers: query.Answers("Hello", "Universe"),     // This is a slice of answers
        Answers: query.Answers(query.Answer{                // This is a text/image answer
            Text:  "Hello Universe",
            Image: "https://i.imgur.com/pPdjh6x.jpg",
        })
    }
}

In REST Extensions in other languages, answers must meet the corresponfing JSON notation. The following JSON are valid answers messages.

{
    "answers": [
        {
            "text": "A simple answer message"
        }
    ]
}

{
    "answers": [
        {
            "text": "A list"
        },
        {
            "text": "of answers"
        }
    ]
}

{
    "answers": [
        {
            "text": "An image will be attached"
        },
        {
            "text": "to one of these answers",
            "image": "https://i.imgur.com/8MU0IUT.jpeg"
        }
    ]
}