Repository: RasaHQ/rasa
Pull request: IL action creation
**Proposed changes**:
- fixes #3178 
- user can pick action previously created in the same session as action correction
- user can add utterance template during interactive learning to be saved to domain file

**Status (please check what you already did)**:
- [x] made PR ready for code review
- [ ] added some tests for the functionality
- [x] updated the documentation
- [x] updated the changelog


Problem statement:
Adding an `utter_*` action in interactive learning
**Rasa version:**
rasa-core (0.13.6)
rasa-core-sdk (0.12.2)
rasa-nlu (0.14.6)

**Python Version:** Python 3.6.7

**Operating System:** Linux- Ubuntu 18.04.2 LTS

**Issue:** Interactive learning should not throw this warning for an `utter_*` action because it's an utterance template. So we should:
* change it so that new utter actions don't get run by the action server
* throw a warning to add text to your utterance template (or enter your text there directly)

```
? What is the next action of the bot?  (Use arrow keys)
 » <create new action>
   1.00 utter_unclear
   0.00 __main__.ApiAction
   0.00 action_deactivate_form
   0.00 action_default_ask_affirmation
   0.00 action_default_ask_rephrase
   0.00 action_default_fallback
   0.00 action_listen
   0.00 action_restart
   0.00 action_retrieve_image
   0.00 action_revert_fallback_events
   0.00 utter_ask_picture
   0.00 utter_did_that_help
   0.00 utter_goodbye
   0.00 utter_greet
   0.00 utter_happy
   0.00 utter_question_answer



------
? What is the next action of the bot?  <create new action>
? Please type the action name utter_answer_age
Thanks! The bot will now run utter_answer_age.

2019-03-29 14:17:36 ERROR    rasa_core.server  - **Encountered an exception while running action 'utter_answer_age'. Bot will continue, but the actions events are lost. Make sure to fix the exception in your custom code.**
? WARNING: You have created a new action: 'utter_answer_age', which was not successfully executed. If this action does not retu
rn any events, you do not need to do anything. If this is a custom action which returns events, you are recommended to implemen
t this action in your action server and try again.  (Y/n)
```

Hints from issue discussion:
Any update on this please?
well, this isn't an error. This is intended. If you don't have your action implemented it can't be executed.  However, this seems to be an `utter_` template, so you should be able to hit "no" here.
I'm going to keep this issue and rename it, because it actually shouldn't be throwing this warning if you add an action with an `utter_` prefix. 
Thank you @akelad for clarifying it.

Yeah, that was my question exactly. Since this is an utter_ action, I think it should not throw any exception when creating this action. Also it does not continue with template creation process after throwing this exception. In this case, the user has to edit the domain.yml manually to create a template for this new utter_ action. I believe, this is not the expected behavior.
yep, @paulaWesselmann will look into fixing it :) 
Thank you for the support.
@anoop2503 I'm working on a PR fixing this so that the user can add templates for 'utter_` actions and choose newly created actions from the same session.
Great, thank you @paulaWesselmann 
Hey @paulaWesselmann, hope this is still in progress.
Hey @anoop2503 if you need a quick fix for this please look at the PR. The review is in progress and will be merged once we get to it. 
sure, thank you @paulaWesselmann

Commit message: Update run-code-in-custom-actions.rst

Patch to review:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index b2af264964e1..d1f1811dfe01 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -11,8 +11,13 @@ This project adheres to `Semantic Versioning`_ starting with version 1.0.
 
 Added
 -----
+
+- option to create template for new utterance action in ``interactive learning``
+- you can now choose actions previously created in the same session
+in ``interactive learning``
 - add formatter 'black'
 
+
 Changed
 -------
 - changed removing punctuation logic in ``WhitespaceTokenizer``
diff --git a/docs/core/domains.rst b/docs/core/domains.rst
index f622a0f0c085..43535330c630 100644
--- a/docs/core/domains.rst
+++ b/docs/core/domains.rst
@@ -48,7 +48,7 @@ For example, an ``action`` can:
 * make an external API call
 * query a database
 
-see :ref:`customactions`
+see :ref:`actions`
 
 For a more complete example domain, check the :doc:`quickstart`.
 
@@ -74,7 +74,7 @@ a class ``MyAwesomeAction``, and module ``my_slots`` containing
 
 The ``name`` function of ``MyAwesomeAction`` needs to return
 ``my_custom_action`` in this example (for more details,
-see :ref:`customactions`).
+see :ref:`actions`).
 
 .. _utter_templates:
 
diff --git a/docs/core/fallbacks.rst b/docs/core/fallbacks.rst
index d72d173e8d8e..fdeb12ebdc39 100644
--- a/docs/core/fallbacks.rst
+++ b/docs/core/fallbacks.rst
@@ -105,7 +105,7 @@ Rasa Core provides the default implementations of
 The default implementation of ``action_default_ask_rephrase`` action utters
 the response template ``utter_ask_rephrase``, so be sure to specify this
 template in your domain file.
-The implementation of both actions can be overwritten with :ref:`customactions`.
+The implementation of both actions can be overwritten with :ref:`actions`.
 
 You can specify the core fallback action as well as the ultimate NLU
 fallback action as parameters to ``TwoStageFallbackPolicy`` in your
diff --git a/docs/core/interactive_learning.rst b/docs/core/interactive_learning.rst
index 693dea0a8f78..e1a642ca0084 100644
--- a/docs/core/interactive_learning.rst
+++ b/docs/core/interactive_learning.rst
@@ -129,7 +129,8 @@ model has assigned to each of the actions:
 .. code-block:: text
 
     ? What is the next action of the bot?  (Use arrow keys)
-     ❯ 0.53 action_show_venue_reviews
+     ❯ <create new action>
+       0.53 action_show_venue_reviews
        0.46 action_show_concert_reviews
        0.00 utter_goodbye
        0.00 action_search_concerts
@@ -143,17 +144,38 @@ model has assigned to each of the actions:
 
 
 
-In this case, the bot should ``action_show_concert_reviews`` (rather than venue
-reviews!) so we select that action.
+In this case, the bot should execute ``action_show_concert_reviews``
+(rather than venue reviews!) so we select that action.
+
+At this point we can also choose to create a new action, if the correct action
+is not yet implemented. There are two types of new actions that can be created
+1) create an ``utter_{}`` action which uses :ref:`utter_templates` or
+2) create a custom action.
+
+Only the first type is fully implemented by interactive learning.
+After choosing ``<create new action>`` we are prompted to type the action name,
+in order to create a template action we use the prefix ``utter_`` and name the
+action for example ``utter_compliment``. Now we get a new prompt asking for the
+text that the template for our new action should store to send back to the user.
+
+If we choose to create a new action which should return an event (e.g. set a slot)
+this behaviour won't be reflected in interactive learning unless we implement it first.
+The action needs to be implemented in our action server as :ref:`customactions`.
+We can still add the action name in interactive learning and continue the session,
+but no events will be returned.
 
 Now we can keep talking to the bot for as long as we like to create a longer
 conversation. At any point you can press ``Ctrl-C`` and the bot will
 provide you with exit options. You can write your newly-created stories and NLU
 data to files. You can also go back a step if you made a mistake when providing
-feedback.
-
-Make sure to combine the dumped stories and NLU examples with your original
-training data for the next training.
+feedback, start fresh or fork from a previous point in the story line.
+When starting fresh or forking the story line a new additional story will be
+created.
+
+If you choose your current file names to store data, the new data is merged
+with existing data, so that no old data is lost.
+If you choose to save the files separately, make sure to combine the dumped
+stories and NLU examples with your original training data for the next training.
 
 Visualization of conversations
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/docs/core/migrations.rst b/docs/core/migrations.rst
index e16ed842b40d..a0ef0fbe3049 100644
--- a/docs/core/migrations.rst
+++ b/docs/core/migrations.rst
@@ -184,7 +184,7 @@ General
   them. If your actions are written in python (in a file called actions.py) you
   can do this by running ``python -m rasa_core_sdk.endpoint --actions actions``
   and specifying the action endpoint in the ``endpoints.yml``
-  For more information please read :ref:`customactions`.
+  For more information please read :ref:`actions`.
 - For your custom actions, the imports have changed from
   ``from rasa_core.actions import Action`` to ``from rasa_core_sdk import Action`` and
   from ``from rasa_core.events import *`` to ``from rasa_core_sdk.events import *``
@@ -201,7 +201,7 @@ HTTP Server endpoints
   for these two, as the flow of information has been changed: Instead of you
   calling Rasa Core to update the tracker and receive the next action to be
   executed, Rasa Core will call your action server once it predicted an action.
-  More information can be found in the updated docs for :ref:`customactions`.
+  More information can be found in the updated docs for :ref:`actions`.
 
 
 Webhooks
diff --git a/docs/core/quickstart.rst b/docs/core/quickstart.rst
index f199abe6c5f2..2cf852186cf1 100644
--- a/docs/core/quickstart.rst
+++ b/docs/core/quickstart.rst
@@ -179,7 +179,7 @@ Rasa Core's job is to choose the right action to execute at each step
 of the conversation. Simple actions are just sending a message to a user.
 These simple actions are the ``actions`` in the domain, which start
 with ``utter_``. They will just respond with a message based on a template
-from the ``templates`` section. See :ref:`customactions` for how to build
+from the ``templates`` section. See :ref:`actions` for how to build
 more interesting actions.
 
 In our simple example we don't need ``slots`` and ``entities``,
diff --git a/docs/core/run-code-in-custom-actions.rst b/docs/core/run-code-in-custom-actions.rst
index 31d4403f86d4..c8449eb2a892 100644
--- a/docs/core/run-code-in-custom-actions.rst
+++ b/docs/core/run-code-in-custom-actions.rst
@@ -1,7 +1,7 @@
 :desc: Read more about how to run custom actions and code with open source Rasa
        Stack to integrate your contextual Assistant in your processes and databases. 
 
-.. _customactions:
+.. _actions:
 
 Actions
 =======
@@ -38,6 +38,8 @@ If you use an external NLG service, you don't need to specify the
 templates in the domain, but you still need to add the utterance names
 to the actions list of the domain.
 
+.. _customactions:
+
 Custom Actions
 --------------
 
diff --git a/rasa/core/training/interactive.py b/rasa/core/training/interactive.py
index fe5d857a553d..67477f188fc6 100644
--- a/rasa/core/training/interactive.py
+++ b/rasa/core/training/interactive.py
@@ -66,6 +66,9 @@
 # choose other intent, making sure this doesn't clash with an existing intent
 OTHER_INTENT = uuid.uuid4().hex
 OTHER_ACTION = uuid.uuid4().hex
+NEW_ACTION = uuid.uuid4().hex
+
+NEW_TEMPLATES = {}
 
 
 class RestartConversation(Exception):
@@ -176,20 +179,34 @@ async def send_action(
         return await endpoint.request(json=payload, method="post", subpath=subpath)
     except ClientError:
         if is_new_action:
-            warning_questions = questionary.confirm(
-                "WARNING: You have created a new action: '{}', "
-                "which was not successfully executed. "
-                "If this action does not return any events, "
-                "you do not need to do anything. "
-                "If this is a custom action which returns events, "
-                "you are recommended to implement this action "
-                "in your action server and try again."
-                "".format(action_name)
-            )
-            await _ask_questions(warning_questions, sender_id, endpoint)
+            if action_name in NEW_TEMPLATES:
+                warning_questions = questionary.confirm(
+                    "WARNING: You have created a new action: '{0}', "
+                    "with matching template: '{1}'. "
+                    "This action will not return its message in this session, "
+                    "but the new utterance will be saved to your domain file "
+                    "when you exit and save this session. "
+                    "You do not need to do anything further. "
+                    "".format(action_name, [*NEW_TEMPLATES[action_name]][0])
+                )
 
-            payload = ActionExecuted(action_name).as_dict()
+                await _ask_questions(warning_questions, sender_id, endpoint)
+                payload = ActionExecuted(action_name).as_dict()
+                return await send_event(endpoint, sender_id, payload)
+            else:
+                warning_questions = questionary.confirm(
+                    "WARNING: You have created a new action: '{}', "
+                    "which was not successfully executed. "
+                    "If this action does not return any events, "
+                    "you do not need to do anything. "
+                    "If this is a custom action which returns events, "
+                    "you are recommended to implement this action "
+                    "in your action server and try again."
+                    "".format(action_name)
+                )
+                await _ask_questions(warning_questions, sender_id, endpoint)
 
+            payload = ActionExecuted(action_name).as_dict()
             return await send_event(endpoint, sender_id, payload)
         else:
             logger.error("failed to execute action!")
@@ -329,6 +346,15 @@ async def _request_free_text_action(sender_id: Text, endpoint: EndpointConfig) -
     return await _ask_questions(question, sender_id, endpoint)
 
 
+async def _request_free_text_utterance(
+    sender_id: Text, endpoint: EndpointConfig, action: Text
+) -> Text:
+    question = questionary.text(
+        "Please type the message for your " "new action {}".format(action)
+    )
+    return await _ask_questions(question, sender_id, endpoint)
+
+
 async def _request_selection_from_intent_list(
     intent_list: List[Dict[Text, Text]], sender_id: Text, endpoint: EndpointConfig
 ) -> Text:
@@ -584,14 +610,37 @@ async def _request_action_from_user(
         for a in predictions
     ]
 
+    tracker = await retrieve_tracker(endpoint, sender_id)
+    evts = tracker.get("events", [])
+
+    session_actions_all = [a["name"] for a in _collect_actions(evts)]
+    session_actions_unique = list(set(session_actions_all))
+    old_actions = [action["value"] for action in choices]
+    new_actions = [
+        {"name": action, "value": OTHER_ACTION + action}
+        for action in session_actions_unique
+        if action not in old_actions
+    ]
     choices = [{"name": "<create new action>", "value": OTHER_ACTION}] + choices
     question = questionary.select("What is the next action of the bot?", choices)
 
     action_name = await _ask_questions(question, sender_id, endpoint)
-    is_new_action = action_name == OTHER_ACTION
+    is_new_action = action_name == NEW_ACTION
 
     if is_new_action:
+        # create new action
         action_name = await _request_free_text_action(sender_id, endpoint)
+        if "utter_" in action_name:
+            utter_message = await _request_free_text_utterance(
+                sender_id, endpoint, action_name
+            )
+            NEW_TEMPLATES[action_name] = {utter_message: utter_message}
+
+    elif action_name[:32] == OTHER_ACTION:
+        # action was newly created in the session, but not this turn
+        is_new_action = True
+        action_name = action_name[32:]
+
     print ("Thanks! The bot will now run {}.\n".format(action_name))
     return action_name, is_new_action
 
@@ -770,6 +819,7 @@ async def _write_domain_to_file(
 
     messages = _collect_messages(evts)
     actions = _collect_actions(evts)
+    templates = NEW_TEMPLATES
 
     # TODO for now there is no way to distinguish between action and form
     intent_properties = Domain.collect_intent_properties(
@@ -784,7 +834,7 @@ async def _write_domain_to_file(
         intent_properties=intent_properties,
         entities=_entities_from_messages(messages),
         slots=[],
-        templates={},
+        templates=templates,
         action_names=collected_actions,
         form_names=[],
     )


Code review comments:
diff_hunk: @@ -584,14 +610,37 @@ def _slot_history(tracker_dump: Dict[Text, Any]) -> List[Text]:
         for a in predictions
     ]
 
+    tracker = await retrieve_tracker(endpoint, sender_id)
+    evts = tracker.get("events", [])
path: rasa/core/training/interactive.py
text: @user1:
```suggestion
    events = tracker.get("events", [])
```

diff_hunk: @@ -74,7 +74,7 @@ a class ``MyAwesomeAction``, and module ``my_slots`` containing
 
 The ``name`` function of ``MyAwesomeAction`` needs to return
 ``my_custom_action`` in this example (for more details,
-see :ref:`customactions`).
+see :ref:`actions`).
path: docs/core/domains.rst
text: @user1:
hm but in this case i actually think they should be referring to custom actions right? Because it's specifically custom actions. Same with the above reference

diff_hunk: @@ -176,20 +179,34 @@ class Abort(Exception):
         return await endpoint.request(json=payload, method="post", subpath=subpath)
     except ClientError:
         if is_new_action:
-            warning_questions = questionary.confirm(
-                "WARNING: You have created a new action: '{}', "
-                "which was not successfully executed. "
-                "If this action does not return any events, "
-                "you do not need to do anything. "
-                "If this is a custom action which returns events, "
-                "you are recommended to implement this action "
-                "in your action server and try again."
-                "".format(action_name)
-            )
-            await _ask_questions(warning_questions, sender_id, endpoint)
+            if action_name in NEW_TEMPLATES:
+                warning_questions = questionary.confirm(
+                    "WARNING: You have created a new action: '{0}', "
+                    "with matching template: '{1}'. "
+                    "This action will not return its message in this session, "
+                    "but the new utterance will be saved to your domain file "
+                    "when you exit and save this session. "
+                    "You do not need to do anything further. "
+                    "".format(action_name, [*NEW_TEMPLATES[action_name]][0])
+                )
 
-            payload = ActionExecuted(action_name).as_dict()
+                await _ask_questions(warning_questions, sender_id, endpoint)
+                payload = ActionExecuted(action_name).as_dict()
+                return await send_event(endpoint, sender_id, payload)
path: rasa/core/training/interactive.py
text: @user1:
I actually think these two lines can go away right? Duplicate code from further down

diff_hunk: @@ -584,14 +610,37 @@ def _slot_history(tracker_dump: Dict[Text, Any]) -> List[Text]:
         for a in predictions
     ]
 
+    tracker = await retrieve_tracker(endpoint, sender_id)
+    evts = tracker.get("events", [])
+
+    session_actions_all = [a["name"] for a in _collect_actions(evts)]
path: rasa/core/training/interactive.py
text: @user1:
```suggestion
    session_actions_all = [a["name"] for a in _collect_actions(events)]
```

diff_hunk: @@ -329,6 +346,15 @@ def _selection_choices_from_intent_prediction(
     return await _ask_questions(question, sender_id, endpoint)
 
 
+async def _request_free_text_utterance(
+    sender_id: Text, endpoint: EndpointConfig, action: Text
+) -> Text:
+    question = questionary.text(
+        "Please type the message for your " "new action {}".format(action)
path: rasa/core/training/interactive.py
text: @user1:
hm something went weird with the formatting here -- Also should we say "for your new utter template" instead? or just template?