Using Agent to call CustomAction programmatically

Hi folks,

currently you can start a pretrained bot by using Python the following way:

interpreter = RasaNLUInterpreter(
    model_directory=os.path.join(execution_path, 'model_unpacked', 'nlu'),
    config_file=os.path.join(execution_path, 'config.yml')
)
agent = Agent.load(
    os.path.join(execution_path, 'model_unpacked'),
    interpreter=interpreter,
    action_endpoint=action_endpoint
)

you then can let the agent handle a message like this:

await agent.handle_text(message, sender_id=sender_id)

Compared to e.g the Rest API, this reduces the duration of a request/task tremenduously. If you want to use CustomActions, you can achieve this the following way:

action_endpoint = EndpointConfig(url=rasa_action_url)

meaning, that a webserver is started that can be reached via the core by sending a HTTP request. Depending on the situation, this can be very costly.

I am wondering if there is a way to avoid using a webserver for the CustomActions and instead call it programmatically?

Any help would be appreciated!

Kind regards
Julian

Hi folks,

I found a solution together with ella. Actually you have to mimic the executor's call to a RemoteAction in rasa/core/actions/action.py the following way.

Usually, the action server would be called by RemoteAction's

response = await self.action_endpoint.request(
    json=json_body, method="post", timeout=DEFAULT_REQUEST_TIMEOUT
)

You can overcome this behaviour by using:

sys.path.insert(1, os.environ['bot_execution_path'])
executor = ActionExecutor()
executor.register_package(os.environ['action_package_name'])
response = await executor.run(json_body)

The first line ensures, that the register_package method works properly and is capable of importing every module and submodule specified in your action file. I am using environment variables because it is convenient for my solution but you may change this as per your needs.

I compared the classic REST approach with the programmatical one. Here are the results:

Local development environment

OS: Windows 10
Model size: ~220 MB
Action used: No
Average interaction duration REST: 900 ms
Average interaction duration Agent: 150 ms

OS: Windows 10
Model size: ~220 MB
Action used: Yes
Average interaction duration REST: 1,45 s
Average interaction duration Agent: 350 ms

Remote development environment

OS: Ubuntu 18.04
Model size: ~50 MB
Action used: No
Average interaction duration REST: 700 ms
Average interaction duration Agent: 123 ms

OS: Ubuntu 18.04
Model size: ~50 MB
Action used: Yes
Average interaction duration REST: 1,04 s
Average interaction duration Agent: 180 ms

The average was computed by sending 100 requests to each setup. Of course, the duration depends on what your action actually consists of if it is used. Mine did several requests to Office 365 to check mail accounts for updates.

It has to be said, that aiohttp works best in this setup in terms of the requesting thing and the effort I put in should only be considered if every 100 milliseconds are worthy in terms of dialogue management.

@tmbo @erohmensing Maybe interesting for you.

Kind regards
Julian

2 Likes

A small addition:

If your CustomAction raises an ActionExecutionRejection, you have to slightly modify your code:

                try:
                    response = await executor.run(json_body)
                except:
                    action_name = json_body.get("next_action")
                    exception = ActionExecutionRejection(
                        action_name, "Modified CustomAction threw ActionExecutionRejection."
                    )
                    logger.error(exception.message)
                    raise exception

By using that, you have to keep in mind, that variables which are declared as class variablesfor e.g. a certain CustomAction like self.this = [] might not work as expected because of the executor’s behaviour. I have to dig deeper into that.

1 Like