chatto icon indicating copy to clipboard operation
chatto copied to clipboard

rethink how we expose the fsm to extensions

Open ryancurrah opened this issue 4 years ago • 4 comments

I’ve been thinking about the FSM.Domain and how we pass it to extensions. When I did the internal refactor I felt like FSM package should be a private API.

Maybe the bot should have some REST APIs for the extension to work with FSM with some safeguards in place. Instead of directly sending the FSM.Domain to the extension. I do feel like this make it less complex for developing new extensions so the extension author does not need to learn how to work with the FSM directly.

Thoughts?

ryancurrah avatar Feb 23 '21 19:02 ryancurrah

I agree that FSM or at least a part of it should be private.

I don't know if I agree with the REST API approach, because we would have extensions making requests to the bot, and the bot making requests to the extensions, which could get problematic. The purpose of sending the entire Domain to the extension is to have all the available data about the bot and the user, for example:

{
    "fsm": {
        "state": 2,
        "slots": {
            "answer_1": "3"
        }
    },
    "extension": "val_ans_1",
    "question": {
        "sender": "cli",
        "text": "2"
    },
    "domain": {
        "state_table": {
            "any": -1,
            "initial": 0,
            "question_1": 1,
            "question_2": 2,
            "question_3": 3
        },
        "command_list": [
            "start",
            "end"
        ],
        "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."
        }
    }
}

It may seem complex but this way you can for example, go back to a certain state:

	if !(ans == "1" || ans == "2" || ans == "3") {
		return &extension.Response{
			FSM: &fsm.FSM{
				State: req.Domain.StateTable["question_1"],
				Slots: req.FSM.Slots,
			},
			Answers: []query.Answer{{Text: "Select one of the options"}},
		}
	}

Or do nothing with the FSM:

	return &extension.Response{
		FSM:     req.FSM,
		Answers: []query.Answer{{Text: message}},
	}

Use default messages, etc.

Maybe we could change the extension.Response to simplify the way extensions send messages and/or modify the FSM. Could be using "options", something like:

	return extension.NewResponse(
		WithAnswers([]query.Answer{{Text: "hello world"}}),
		WithFSM(req.Domain.StateTable["question_3"]),
	)

That way you wouldn't be "forced" to use the FSM or the Domain if you don't have to, and only reply with Answers.

I don't know. What do you think?

jaimeteb avatar Feb 24 '21 00:02 jaimeteb

Yeah I like the idea of the FSM part being optional. And maybe we make FSMRequest and FSMResponse models that are public and hide all the FSM logic in the internal package.

ryancurrah avatar Feb 27 '21 17:02 ryancurrah

I submitted #28, which simplifies the responses from extensions. The method NewExecuteCommandFuncResponse takes any number of ExecuteCommandFuncResponseOption arguments, so that you can modify only the parts of the response that you need to. For instance:

return req.NewExecuteCommandFuncResponse(
	// This doesn't modify the FSM and only returns
	// the "Hello Universe" message and an image.
	extension.WithAnswer("Hello Universe", "https://i.imgur.com/8MU0IUT.jpeg"),
)

And:

return req.NewExecuteCommandFuncResponse(
	// This returns the text "Select one of the options"
	// and makes the FSM go back to "question_1"
	extension.WithTextAnswer("Select one of the options"),
	extension.WithState(fsmDomain.StateTable["question_1"]),
)

Thoughts on this approach?

jaimeteb avatar Mar 02 '21 22:03 jaimeteb

Cool I’ll take a look tonight

ryancurrah avatar Mar 02 '21 23:03 ryancurrah