Repository: RasaHQ/rasa
Pull request: Make it possible to use `null` entities in stories
**Proposed changes**:
- fix #8300

**Status (please check what you already did)**:
- [x] added some tests for the functionality
- [ ] updated the documentation
- [x] updated the changelog (please check [changelog](https://github.com/RasaHQ/rasa/tree/main/changelog) for instructions)
- [x] reformat files using `black` (please check [Readme](https://github.com/RasaHQ/rasa#code-style) for instructions)


Problem statement:
Erro when specifying "null" entities in stories
**Rasa version**: 2.4.2

**Rasa SDK version** 2.4.1

**Python version**: 3.7.10

**Operating system** (windows, osx, ...): Linux-4.19.112+-x86_64-with-Ubuntu-18.04-bionic

**Issue**:
Rasa crashes when a story contains a "null" entity. That story would be used to handle the case where an intent is identified but an expected entity is not provided.

```
- story: test
  steps:
  - intent: test_intent
    entities:
    - test_entity: null
  - action: test_action
  ```

**Error (including full traceback)**:
```
 File "/content/rasa/bin/rasa", line 8, in <module>
    sys.exit(main())
  File "/content/rasa/lib/python3.7/site-packages/rasa/__main__.py", line 116, in main
    cmdline_arguments.func(cmdline_arguments)
  File "/content/rasa/lib/python3.7/site-packages/rasa/cli/train.py", line 58, in <lambda>
    train_parser.set_defaults(func=lambda args: train(args, can_exit=True))
  File "/content/rasa/lib/python3.7/site-packages/rasa/cli/train.py", line 102, in train
    finetuning_epoch_fraction=args.epoch_fraction,
  File "/content/rasa/lib/python3.7/site-packages/rasa/train.py", line 109, in train
    loop,
  File "/content/rasa/lib/python3.7/site-packages/rasa/utils/common.py", line 307, in run_in_loop
    result = loop.run_until_complete(f)
  File "uvloop/loop.pyx", line 1456, in uvloop.loop.Loop.run_until_complete
  File "/content/rasa/lib/python3.7/site-packages/rasa/train.py", line 174, in train_async
    finetuning_epoch_fraction=finetuning_epoch_fraction,
  File "/content/rasa/lib/python3.7/site-packages/rasa/train.py", line 271, in _train_async_internal
    new_fingerprint = await model.model_fingerprint(file_importer)
  File "/content/rasa/lib/python3.7/site-packages/rasa/model.py", line 343, in model_fingerprint
    FINGERPRINT_STORIES_KEY: stories.fingerprint(),
  File "/content/rasa/lib/python3.7/site-packages/rasa/shared/core/training_data/structures.py", line 414, in fingerprint
    stories_as_yaml = YAMLStoryWriter().stories_to_yaml(self.story_steps)
  File "/content/rasa/lib/python3.7/site-packages/rasa/shared/core/training_data/story_writer/yaml_story_writer.py", line 119, in stories_to_yaml
    stories.append(self.process_story_step(story_step))
  File "/content/rasa/lib/python3.7/site-packages/rasa/shared/core/training_data/story_writer/yaml_story_writer.py", line 149, in process_story_step
    processed = self.process_event(event)
  File "/content/rasa/lib/python3.7/site-packages/rasa/shared/core/training_data/story_writer/yaml_story_writer.py", line 163, in process_event
    return self.process_user_utterance(event, self._is_test_story)
  File "/content/rasa/lib/python3.7/site-packages/rasa/shared/core/training_data/story_writer/yaml_story_writer.py", line 230, in process_user_utterance
    if entity["value"]:
KeyError: 'value'
```

**Command or request that led to error**:
```
rasa train
```

Hints from issue discussion:
Thanks for the issue, @dakshvar22 will get back to you about it soon!

###### You may find help in the [docs](https://rasa.com/docs/) and the [forum,](https://forum.rasa.com/) too 🤗

Commit message: Updated `test_yaml_writer_stories_to_yaml_with_null_entities`

Patch to review:
diff --git a/changelog/8300.bugfix.md b/changelog/8300.bugfix.md
new file mode 100644
index 000000000000..2a654bac8125
--- /dev/null
+++ b/changelog/8300.bugfix.md
@@ -0,0 +1 @@
+Make it possible to use `null` entities in stories.
\ No newline at end of file
diff --git a/rasa/shared/core/training_data/story_writer/yaml_story_writer.py b/rasa/shared/core/training_data/story_writer/yaml_story_writer.py
index d1f5e4bf89bf..fce7c3adf196 100644
--- a/rasa/shared/core/training_data/story_writer/yaml_story_writer.py
+++ b/rasa/shared/core/training_data/story_writer/yaml_story_writer.py
@@ -227,7 +227,7 @@ def process_user_utterance(
         if len(user_utterance.entities) and not is_test_story:
             entities = []
             for entity in user_utterance.entities:
-                if entity["value"]:
+                if "value" in entity:
                     entities.append(OrderedDict([(entity["entity"], entity["value"])]))
                 else:
                     entities.append(entity["entity"])
diff --git a/tests/shared/core/training_data/story_writer/test_yaml_story_writer.py b/tests/shared/core/training_data/story_writer/test_yaml_story_writer.py
index 7aa201b5f4b6..eb9af9cb0a02 100644
--- a/tests/shared/core/training_data/story_writer/test_yaml_story_writer.py
+++ b/tests/shared/core/training_data/story_writer/test_yaml_story_writer.py
@@ -183,6 +183,33 @@ def test_yaml_writer_stories_to_yaml(domain: Domain):
     assert len(result["stories"]) == 1
 
 
+def test_yaml_writer_stories_to_yaml_with_null_entities(domain: Domain):
+    from collections import OrderedDict
+
+    writer = YAMLStoryWriter()
+    stories = textwrap.dedent(
+        """
+    version: "2.0"
+    stories:
+    - story: happy path
+      steps:
+      - intent: test_intent
+        entities:
+        - test_entity: null
+        - test_entity2: false
+    """
+    )
+
+    stories_yaml = YAMLStoryReader().read_from_string(stories)
+    result = writer.stories_to_yaml(stories_yaml)
+    assert isinstance(result, OrderedDict)
+    assert "stories" in result
+    assert len(result["stories"]) == 1
+    entities = result["stories"][0]["steps"][0]["entities"]
+    assert entities[0] == "test_entity"
+    assert entities[1] == OrderedDict({"test_entity2": False})
+
+
 def test_writing_end_to_end_stories(domain: Domain):
     story_name = "test_writing_end_to_end_stories"
     events = [


Code review comments:
diff_hunk: @@ -183,6 +183,33 @@ def test_yaml_writer_stories_to_yaml(domain: Domain):
     assert len(result["stories"]) == 1
 
 
+def test_yaml_writer_stories_to_yaml_with_null_entities(domain: Domain):
+    from collections import OrderedDict
path: tests/shared/core/training_data/story_writer/test_yaml_story_writer.py
text: @user1:
This could probably be top level right?