From ed2524453b64683def203ad025ead5ede13431fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uro=C5=A1=20Milovanovi=C4=87?= Date: Tue, 20 Jun 2023 10:42:29 +0200 Subject: [PATCH 1/9] [ATO-1153] Add license hashes to telemetry (#12512) * add license_hash to context * edit docs * edit hash * add changelog and test * Update rasa/plugin.py Co-authored-by: Anca Lita <27920906+ancalita@users.noreply.github.com> * Address review * fix tests * fix tests * fix context reuse --------- Co-authored-by: Anca Lita <27920906+ancalita@users.noreply.github.com> --- changelog/12512.misc.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/12512.misc.md diff --git a/changelog/12512.misc.md b/changelog/12512.misc.md new file mode 100644 index 000000000000..f7b599c806d4 --- /dev/null +++ b/changelog/12512.misc.md @@ -0,0 +1 @@ +Add plugin hook for getting the license hash from Rasa Plus and add the hash to telemetry context. From 7b195d2e6d8da3450869354da6e2cfec1df3da09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Uro=C5=A1=20Milovanovi=C4=87?= Date: Thu, 22 Jun 2023 17:29:22 +0200 Subject: [PATCH 2/9] [ATO-1151] Parentheses in button title overwrite button payload in rasa shell (#12534) * fix button question * fix lint * add changelog * edit changelog * fix test * skip flaky windows tests * add skip * skip additional * add another test to skip * skip lm featuizer on windows --- changelog/12534.bugfix.md | 2 ++ rasa/cli/utils.py | 2 +- tests/cli/test_utils.py | 17 +++++++++++++++++ tests/nlu/featurizers/test_lm_featurizer.py | 10 ++++++++++ tests/nlu/test_train.py | 1 + 5 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 changelog/12534.bugfix.md diff --git a/changelog/12534.bugfix.md b/changelog/12534.bugfix.md new file mode 100644 index 000000000000..27c8c3c85220 --- /dev/null +++ b/changelog/12534.bugfix.md @@ -0,0 +1,2 @@ +Rich responses containing buttons with parentheses characters are now correctly parsed. +Previously any characters found between the first identified pair of `()` in response button took precedence. diff --git a/rasa/cli/utils.py b/rasa/cli/utils.py index 3e50fbcb4927..b205c2072d17 100644 --- a/rasa/cli/utils.py +++ b/rasa/cli/utils.py @@ -382,7 +382,7 @@ async def payload_from_button_question(button_question: "Question") -> Text: response = await button_question.ask_async() if response != FREE_TEXT_INPUT_PROMPT: # Extract intent slash command if it's a button - response = response[response.find("(") + 1 : response.find(")")] + response = response[response.rfind("(") + 1 : response.rfind(")")] return response diff --git a/tests/cli/test_utils.py b/tests/cli/test_utils.py index f3234776557b..a91fe9b61107 100644 --- a/tests/cli/test_utils.py +++ b/tests/cli/test_utils.py @@ -30,6 +30,7 @@ import rasa.shared.utils.io from rasa.utils.common import TempDirectoryPath, get_temp_dir_name from tests.cli.conftest import RASA_EXE +from tests.conftest import AsyncMock @contextlib.contextmanager @@ -633,3 +634,19 @@ def test_validate_assistant_id_in_config_preserves_comment() -> None: # reset input files to original state rasa.shared.utils.io.write_yaml(original_config_data, config_file, True) + + +@pytest.mark.parametrize( + "text_input, button", + [ + ("hi this is test text\n", "hi this is test text"), + ("hi this is test text (/button_one)", "/button_one"), + ("hi this is test text (and something) (/button_one)", "/button_one"), + ], +) +async def test_payload_from_button_question(text_input: str, button: str) -> None: + """Test that the payload is extracted correctly from the text input.""" + question = AsyncMock() + question.ask_async.return_value = text_input + result = await rasa.cli.utils.payload_from_button_question(question) + assert result == button diff --git a/tests/nlu/featurizers/test_lm_featurizer.py b/tests/nlu/featurizers/test_lm_featurizer.py index 591961dc6d92..333d491e5aa9 100644 --- a/tests/nlu/featurizers/test_lm_featurizer.py +++ b/tests/nlu/featurizers/test_lm_featurizer.py @@ -361,6 +361,7 @@ def evaluate_message_shapes( assert intent_sentence_vec is None @pytest.mark.timeout(120, func_only=True) + @pytest.mark.skip_on_windows def test_lm_featurizer_shapes_in_process_training_data( self, model_name: Text, @@ -386,6 +387,7 @@ def test_lm_featurizer_shapes_in_process_training_data( ) @pytest.mark.timeout(120, func_only=True) + @pytest.mark.skip_on_windows def test_lm_featurizer_shapes_in_process_messages( self, model_name: Text, @@ -581,6 +583,7 @@ def check_subtokens( ) @pytest.mark.timeout(120, func_only=True) + @pytest.mark.skip_on_windows def test_lm_featurizer_num_sub_tokens_process_training_data( self, model_name: Text, @@ -606,6 +609,7 @@ def test_lm_featurizer_num_sub_tokens_process_training_data( ) @pytest.mark.timeout(120, func_only=True) + @pytest.mark.skip_on_windows def test_lm_featurizer_num_sub_tokens_process_messages( self, model_name: Text, @@ -635,6 +639,7 @@ def test_lm_featurizer_num_sub_tokens_process_messages( "input_sequence_length, model_name, should_overflow", [(20, "bert", False), (1000, "bert", True), (1000, "xlnet", False)], ) +@pytest.mark.skip_on_windows def test_sequence_length_overflow_train( input_sequence_length: int, model_name: Text, @@ -666,6 +671,7 @@ def test_sequence_length_overflow_train( (np.ones((1, 256, 5)), [256], "bert", False), ], ) +@pytest.mark.skip_on_windows def test_long_sequences_extra_padding( sequence_embeddings: np.ndarray, actual_sequence_lengths: List[int], @@ -703,6 +709,7 @@ def test_long_sequences_extra_padding( ([[1] * 200], 200, 200, False), ], ) +@pytest.mark.skip_on_windows def test_input_padding( token_ids: List[List[int]], max_sequence_length_model: int, @@ -730,6 +737,7 @@ def test_input_padding( (256, "bert", "bert-base-uncased", False), ], ) +@pytest.mark.skip_on_windows def test_log_longer_sequence( sequence_length: int, model_name: Text, @@ -760,6 +768,7 @@ def test_log_longer_sequence( "actual_sequence_length, max_input_sequence_length, zero_start_index", [(256, 512, 256), (700, 700, 700), (700, 512, 512)], ) +@pytest.mark.skip_on_windows def test_attention_mask( actual_sequence_length: int, max_input_sequence_length: int, @@ -792,6 +801,7 @@ def test_attention_mask( ) ], ) +@pytest.mark.skip_on_windows def test_lm_featurizer_correctly_handle_whitespace_token( text: Text, tokens: List[Tuple[Text, int]], diff --git a/tests/nlu/test_train.py b/tests/nlu/test_train.py index 72211b89341d..c0051bbff295 100644 --- a/tests/nlu/test_train.py +++ b/tests/nlu/test_train.py @@ -158,6 +158,7 @@ def test_all_components_are_in_at_least_one_test_pipeline(): @pytest.mark.timeout(600, func_only=True) @pytest.mark.parametrize("language, pipeline", pipelines_for_tests()) +@pytest.mark.skip_on_windows async def test_train_persist_load_parse( language: Optional[Text], pipeline: List[Dict], From bc4c70d493e52858c7d096fdaffd0a2393634b7f Mon Sep 17 00:00:00 2001 From: Anca Lita <27920906+ancalita@users.noreply.github.com> Date: Fri, 23 Jun 2023 09:22:40 +0100 Subject: [PATCH 3/9] prepared release of version 3.5.12 --- changelog/12512.misc.md | 1 - changelog/12534.bugfix.md | 2 -- 2 files changed, 3 deletions(-) delete mode 100644 changelog/12512.misc.md delete mode 100644 changelog/12534.bugfix.md diff --git a/changelog/12512.misc.md b/changelog/12512.misc.md deleted file mode 100644 index f7b599c806d4..000000000000 --- a/changelog/12512.misc.md +++ /dev/null @@ -1 +0,0 @@ -Add plugin hook for getting the license hash from Rasa Plus and add the hash to telemetry context. diff --git a/changelog/12534.bugfix.md b/changelog/12534.bugfix.md deleted file mode 100644 index 27c8c3c85220..000000000000 --- a/changelog/12534.bugfix.md +++ /dev/null @@ -1,2 +0,0 @@ -Rich responses containing buttons with parentheses characters are now correctly parsed. -Previously any characters found between the first identified pair of `()` in response button took precedence. From 1a4c731d3a35c080e6d8d4744f37c43412cdd782 Mon Sep 17 00:00:00 2001 From: d61h6k4 Date: Mon, 19 Jun 2023 15:37:01 +0200 Subject: [PATCH 4/9] Remove dependency on tensorflow-addons (#12514) Remove dependency on tensorflow-addons Copy tests for crf and metrics --- changelog/12514.improvement.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 changelog/12514.improvement.md diff --git a/changelog/12514.improvement.md b/changelog/12514.improvement.md new file mode 100644 index 000000000000..262b5161b53a --- /dev/null +++ b/changelog/12514.improvement.md @@ -0,0 +1 @@ +Remove tensorflow-addons from dependencies as it is now deprecated. \ No newline at end of file From 7da1276893e9ed1a7481415497227e77d7ccc1e0 Mon Sep 17 00:00:00 2001 From: Varun Shankar S Date: Thu, 22 Jun 2023 14:47:31 +0200 Subject: [PATCH 5/9] Bypass NLU pipeline when message is /intent or /intent + entities - [ENG 286] (#12480) * Bypass NLU pipeline when a message is /intent or /intent + entities --- changelog/12480.improvement.md | 1 + rasa/core/processor.py | 26 ++++++++++++++++---- tests/core/test_agent.py | 12 ++++++++-- tests/core/test_processor.py | 44 +++++++++++++++++++++++++--------- 4 files changed, 66 insertions(+), 17 deletions(-) create mode 100644 changelog/12480.improvement.md diff --git a/changelog/12480.improvement.md b/changelog/12480.improvement.md new file mode 100644 index 000000000000..8a55039f89cb --- /dev/null +++ b/changelog/12480.improvement.md @@ -0,0 +1 @@ +Skip executing the pipeline when the user message is of the form /intent or /intent + entities. \ No newline at end of file diff --git a/rasa/core/processor.py b/rasa/core/processor.py index ec6ef1260a3e..1225d6cad383 100644 --- a/rasa/core/processor.py +++ b/rasa/core/processor.py @@ -62,6 +62,9 @@ import rasa.core.actions.action import rasa.shared.core.trackers from rasa.shared.core.trackers import DialogueStateTracker, EventVerbosity +from rasa.shared.core.training_data.story_reader.yaml_story_reader import ( + YAMLStoryReader, +) from rasa.shared.nlu.constants import ( ENTITIES, INTENT, @@ -69,6 +72,7 @@ PREDICTED_CONFIDENCE_KEY, TEXT, ) +from rasa.shared.nlu.training_data.message import Message from rasa.utils.endpoints import EndpointConfig logger = logging.getLogger(__name__) @@ -715,11 +719,25 @@ async def parse_message( if self.http_interpreter: parse_data = await self.http_interpreter.parse(message) else: - if tracker is None: - tracker = DialogueStateTracker.from_events(message.sender_id, []) - parse_data = self._parse_message_with_graph( - message, tracker, only_output_properties + msg = YAMLStoryReader.unpack_regex_message( + message=Message({TEXT: message.text}) ) + # Intent is not explicitly present. Pass message to graph. + if msg.data.get(INTENT) is None: + if tracker is None: + tracker = DialogueStateTracker.from_events(message.sender_id, []) + parse_data = self._parse_message_with_graph( + message, tracker, only_output_properties + ) + else: + parse_data = { + TEXT: "", + INTENT: {INTENT_NAME_KEY: None, PREDICTED_CONFIDENCE_KEY: 0.0}, + ENTITIES: [], + } + parse_data.update( + msg.as_dict(only_output_properties=only_output_properties) + ) structlogger.debug( "processor.message.parse", diff --git a/tests/core/test_agent.py b/tests/core/test_agent.py index 21b42d690a96..9241f8f950b3 100644 --- a/tests/core/test_agent.py +++ b/tests/core/test_agent.py @@ -98,11 +98,19 @@ async def test_agent_train(default_agent: Agent): "start": 6, "end": 21, "value": "Rasa", - "extractor": "RegexMessageHandler", } ], }, - ) + ), + ( + "hi hello", + { + "text": "hi hello", + "intent": {"name": "greet", "confidence": 1.0}, + "text_tokens": [(0, 2), (3, 8)], + "entities": [], + }, + ), ], ) async def test_agent_parse_message( diff --git a/tests/core/test_processor.py b/tests/core/test_processor.py index 392d85c29745..640cbc28d973 100644 --- a/tests/core/test_processor.py +++ b/tests/core/test_processor.py @@ -18,6 +18,7 @@ from _pytest.logging import LogCaptureFixture from aioresponses import aioresponses from typing import Optional, Text, List, Callable, Type, Any +from unittest import mock from rasa.core.lock_store import InMemoryLockStore from rasa.core.policies.ensemble import DefaultPolicyPredictionEnsemble @@ -113,10 +114,26 @@ async def test_message_id_logging(default_processor: MessageProcessor): async def test_parsing(default_processor: MessageProcessor): - message = UserMessage('/greet{"name": "boy"}') - parsed = await default_processor.parse_message(message) - assert parsed["intent"][INTENT_NAME_KEY] == "greet" - assert parsed["entities"][0]["entity"] == "name" + with mock.patch( + "rasa.core.processor.MessageProcessor._parse_message_with_graph" + ) as mocked_function: + # Case1: message has intent and entities explicitly set. + message = UserMessage('/greet{"name": "boy"}') + parsed = await default_processor.parse_message(message) + assert parsed["intent"][INTENT_NAME_KEY] == "greet" + assert parsed["entities"][0]["entity"] == "name" + mocked_function.assert_not_called() + + # Case2: Normal user message. + parse_data = { + "text": "mocked", + "intent": {"name": None, "confidence": 0.0}, + "entities": [], + } + mocked_function.return_value = parse_data + message = UserMessage("hi hello how are you?") + parsed = await default_processor.parse_message(message) + mocked_function.assert_called() async def test_check_for_unseen_feature(default_processor: MessageProcessor): @@ -874,7 +891,7 @@ async def test_handle_message_with_session_start( # make sure the sequence of events is as expected with_model_ids_expected = with_model_ids( [ - ActionExecuted(ACTION_SESSION_START_NAME), + ActionExecuted(ACTION_SESSION_START_NAME, confidence=1.0), SessionStarted(), ActionExecuted(ACTION_LISTEN_NAME), UserUttered( @@ -886,15 +903,18 @@ async def test_handle_message_with_session_start( "start": 6, "end": 22, "value": "Core", - "extractor": "RegexMessageHandler", } ], ), SlotSet(entity, slot_1[entity]), DefinePrevUserUtteredFeaturization(False), - ActionExecuted("utter_greet"), - BotUttered("hey there Core!", metadata={"utter_action": "utter_greet"}), - ActionExecuted(ACTION_LISTEN_NAME), + ActionExecuted( + "utter_greet", policy="AugmentedMemoizationPolicy", confidence=1.0 + ), + BotUttered( + "hey there Core!", data={}, metadata={"utter_action": "utter_greet"} + ), + ActionExecuted(ACTION_LISTEN_NAME, confidence=1.0), ActionExecuted(ACTION_SESSION_START_NAME), SessionStarted(), # the initial SlotSet is reapplied after the SessionStarted sequence @@ -909,15 +929,17 @@ async def test_handle_message_with_session_start( "start": 6, "end": 42, "value": "post-session start hello", - "extractor": "RegexMessageHandler", } ], ), SlotSet(entity, slot_2[entity]), DefinePrevUserUtteredFeaturization(False), - ActionExecuted("utter_greet"), + ActionExecuted( + "utter_greet", policy="AugmentedMemoizationPolicy", confidence=1.0 + ), BotUttered( "hey there post-session start hello!", + data={}, metadata={"utter_action": "utter_greet"}, ), ActionExecuted(ACTION_LISTEN_NAME), From 9dd2e4aecdbb24154e60ac8668967caa1e67d97f Mon Sep 17 00:00:00 2001 From: souvik ghosh Date: Wed, 26 Apr 2023 23:39:51 +0200 Subject: [PATCH 6/9] add docs for action server plugins --- .../action-server/add-sanic-extensions.mdx | 54 +++++++++++++++++++ docs/sidebars.js | 1 + 2 files changed, 55 insertions(+) create mode 100644 docs/docs/action-server/add-sanic-extensions.mdx diff --git a/docs/docs/action-server/add-sanic-extensions.mdx b/docs/docs/action-server/add-sanic-extensions.mdx new file mode 100644 index 000000000000..4960f278ee07 --- /dev/null +++ b/docs/docs/action-server/add-sanic-extensions.mdx @@ -0,0 +1,54 @@ +--- +id: add-sanic-extensions +sidebar_label: Adding Sanic Extensions +title: Add Sanic Extensions using Pluggy +--- + +# Add Sanic extensions +With the use of Pluggy, you can now create additional sanic extensions by accessing the app object created by the action server. + +## Step by Step Guide on creating your own sanic extension in rasa_sdk +This example will show how to create a sanic listener using plugins. + +### Step 1 +Create a package in your action server project called `rasa_sdk_plugins`. Rasa SDK will try to instantiate this package in your project to start plugins. +If no plugins found, it will print an info that there are no plugins in your project. + +### Step 2 +Instantiate the package `rasa_sdk_plugins` and initialise the hooks. create an `__init__.py` Plugin manager will look for the module where the hooks are implemented + +``` +def init_hooks(manager: pluggy.PluginManager) -> None: + """Initialise hooks into rasa sdk.""" + import sys + logger.info("Finding hooks") + manager.register(sys.modules["rasa_sdk_plugins.your_module"]) +``` +### Step 3 +Implement the hook `attach_sanic_app_extensions`. This hook forwards the app object created by Sanic in the `rasa_sdk` and allows you to create additional routes, middlewares, listeners and background tasks. Here's an example of this implementation that creates a listener. + +In your `rasa_sdk_plugins.your_module.py` + +``` +from __future__ import annotations +import logging + +import pluggy + +from functools import partial + +logger = logging.getLogger(__name__) +hookimpl = pluggy.HookimplMarker("rasa_sdk") + +@hookimpl # type: ignore[misc] +def attach_sanic_app_extensions(app) -> bool: + logger.info("hook called") + app.register_listener( + partial(print), + "before_server_start", + ) + return app + +async def print(app, loop): + logger.info("BEFORE SERVER START") +``` diff --git a/docs/sidebars.js b/docs/sidebars.js index c9ad8dcbd077..aaf212855456 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -219,6 +219,7 @@ module.exports = { }, ], }, + 'action-server/add-sanic-extensions' ], }, ], From e5c7d0881fcaa0ac8fb19c46d56da6923bc5853a Mon Sep 17 00:00:00 2001 From: souvikg10 Date: Thu, 27 Apr 2023 00:45:28 +0200 Subject: [PATCH 7/9] docs: update docs for sanic extensions --- docs/docs/action-server/add-sanic-extensions.mdx | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/docs/action-server/add-sanic-extensions.mdx b/docs/docs/action-server/add-sanic-extensions.mdx index 4960f278ee07..1ad49b1a0572 100644 --- a/docs/docs/action-server/add-sanic-extensions.mdx +++ b/docs/docs/action-server/add-sanic-extensions.mdx @@ -1,20 +1,22 @@ --- id: add-sanic-extensions sidebar_label: Adding Sanic Extensions -title: Add Sanic Extensions using Pluggy +title: Add Sanic Extensions --- # Add Sanic extensions -With the use of Pluggy, you can now create additional sanic extensions by accessing the app object created by the action server. +With the use of Pluggy, you can now create additional sanic extensions by accessing the app object created by the action server. The hook implemented provides you access +to sanic app object created by the rasa sdk when starting the action server. Using this app object, you can now extend Sanic features such as middlewares, listeners, background tasks +and additional routes. -## Step by Step Guide on creating your own sanic extension in rasa_sdk +### Step by Step Guide on creating your own sanic extension in rasa_sdk This example will show how to create a sanic listener using plugins. -### Step 1 +#### Create package rasa_sdk_plugins Create a package in your action server project called `rasa_sdk_plugins`. Rasa SDK will try to instantiate this package in your project to start plugins. If no plugins found, it will print an info that there are no plugins in your project. -### Step 2 +#### Register modules containing the hooks Instantiate the package `rasa_sdk_plugins` and initialise the hooks. create an `__init__.py` Plugin manager will look for the module where the hooks are implemented ``` @@ -24,7 +26,7 @@ def init_hooks(manager: pluggy.PluginManager) -> None: logger.info("Finding hooks") manager.register(sys.modules["rasa_sdk_plugins.your_module"]) ``` -### Step 3 +#### Implement your hook Implement the hook `attach_sanic_app_extensions`. This hook forwards the app object created by Sanic in the `rasa_sdk` and allows you to create additional routes, middlewares, listeners and background tasks. Here's an example of this implementation that creates a listener. In your `rasa_sdk_plugins.your_module.py` From 78a8e596bd9792de9a191bbe7e4d175b9700a00a Mon Sep 17 00:00:00 2001 From: souvik ghosh Date: Mon, 26 Jun 2023 10:56:24 +0200 Subject: [PATCH 8/9] ref: add import on docs --- docs/docs/action-server/add-sanic-extensions.mdx | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/docs/action-server/add-sanic-extensions.mdx b/docs/docs/action-server/add-sanic-extensions.mdx index 1ad49b1a0572..2cbbd1aab094 100644 --- a/docs/docs/action-server/add-sanic-extensions.mdx +++ b/docs/docs/action-server/add-sanic-extensions.mdx @@ -23,6 +23,7 @@ Instantiate the package `rasa_sdk_plugins` and initialise the hooks. create an ` def init_hooks(manager: pluggy.PluginManager) -> None: """Initialise hooks into rasa sdk.""" import sys + import rasa_sdk_plugins.your_module logger.info("Finding hooks") manager.register(sys.modules["rasa_sdk_plugins.your_module"]) ``` From 6c3de9ada1c59a363e8c440a5839cc5201fe952e Mon Sep 17 00:00:00 2001 From: SOUVIK GHOSH Date: Mon, 26 Jun 2023 18:12:34 +0200 Subject: [PATCH 9/9] Apply suggestions from code review ref: add suggesstions in docs Co-authored-by: Anca Lita <27920906+ancalita@users.noreply.github.com> --- docs/docs/action-server/add-sanic-extensions.mdx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/docs/action-server/add-sanic-extensions.mdx b/docs/docs/action-server/add-sanic-extensions.mdx index 2cbbd1aab094..57673bd881c1 100644 --- a/docs/docs/action-server/add-sanic-extensions.mdx +++ b/docs/docs/action-server/add-sanic-extensions.mdx @@ -5,19 +5,19 @@ title: Add Sanic Extensions --- # Add Sanic extensions -With the use of Pluggy, you can now create additional sanic extensions by accessing the app object created by the action server. The hook implemented provides you access -to sanic app object created by the rasa sdk when starting the action server. Using this app object, you can now extend Sanic features such as middlewares, listeners, background tasks +You can now create additional Sanic extensions by accessing the app object created by the action server. The hook implemented provides you access +to the Sanic app object created by `rasa-sdk` when starting the action server. Using this app object, you can now extend Sanic features such as middlewares, listeners, background tasks and additional routes. ### Step by Step Guide on creating your own sanic extension in rasa_sdk This example will show how to create a sanic listener using plugins. #### Create package rasa_sdk_plugins -Create a package in your action server project called `rasa_sdk_plugins`. Rasa SDK will try to instantiate this package in your project to start plugins. -If no plugins found, it will print an info that there are no plugins in your project. +Create a package in your action server project which you must name `rasa_sdk_plugins`. Rasa SDK will try to instantiate this package in your project to start plugins. +If no plugins are found, it will print an info log that there are no plugins in your project. #### Register modules containing the hooks -Instantiate the package `rasa_sdk_plugins` and initialise the hooks. create an `__init__.py` Plugin manager will look for the module where the hooks are implemented +Create the package `rasa_sdk_plugins` and initialize the hooks by creating an `__init__.py` file where the plugin manager will look for the module where the hooks are implemented: ``` def init_hooks(manager: pluggy.PluginManager) -> None: @@ -30,7 +30,7 @@ def init_hooks(manager: pluggy.PluginManager) -> None: #### Implement your hook Implement the hook `attach_sanic_app_extensions`. This hook forwards the app object created by Sanic in the `rasa_sdk` and allows you to create additional routes, middlewares, listeners and background tasks. Here's an example of this implementation that creates a listener. -In your `rasa_sdk_plugins.your_module.py` +In your `rasa_sdk_plugins.your_module.py`: ``` from __future__ import annotations