From c8e463f001e5cf76c6b53931adc14b9d80363823 Mon Sep 17 00:00:00 2001 From: Tom Metcalfe Date: Wed, 10 Jul 2019 12:10:13 +0200 Subject: [PATCH 01/29] Add check for rewind event, update changelog --- CHANGELOG.rst | 1 + rasa/core/training/interactive.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fa915cbfec96..770c1bf283e0 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -23,6 +23,7 @@ Removed Fixed ----- +- interactive learning bug where reverted user utterances were dumped to nlu data [1.1.5] - 2019-07-10 diff --git a/rasa/core/training/interactive.py b/rasa/core/training/interactive.py index 96da8a1644df..dba5d24e4694 100644 --- a/rasa/core/training/interactive.py +++ b/rasa/core/training/interactive.py @@ -748,6 +748,9 @@ def _collect_messages(events: List[Dict[Text, Any]]) -> List[Message]: msg = Message.build(data["text"], data["intent"]["name"], data["entities"]) msgs.append(msg) + elif event.get("event") == UserUtteranceReverted.type_name: + msgs.pop() # user corrected the nlu, remove incorrect example + return msgs From d1698778654d81b7530d2ea9e112b0ab7e26a785 Mon Sep 17 00:00:00 2001 From: Tom Metcalfe Date: Tue, 16 Jul 2019 15:17:19 +0200 Subject: [PATCH 02/29] Use applied events for writing new stories --- rasa/core/training/interactive.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/rasa/core/training/interactive.py b/rasa/core/training/interactive.py index e0ffbea08bbd..442996700009 100644 --- a/rasa/core/training/interactive.py +++ b/rasa/core/training/interactive.py @@ -788,7 +788,17 @@ async def _write_stories_to_file( with open(export_story_path, append_write, encoding="utf-8") as f: for conversation in sub_conversations: parsed_events = rasa.core.events.deserialise_events(conversation) - s = Story.from_events(parsed_events) + applied_events = [] + + for i, event in enumerate(parsed_events): + if event.type_name == UserUtteranceReverted.type_name: + # user corrected their intent, so we have to remove + # the incorrect intent, rewind action and action_listen + applied_events = applied_events[:-2] + else: + applied_events.append(event) + + s = Story.from_events(applied_events) f.write("\n" + s.as_story_string(flat=True)) From 1bd954674e6fe7c9625013ff978b70eb1904c001 Mon Sep 17 00:00:00 2001 From: Tom Metcalfe Date: Fri, 19 Jul 2019 12:53:45 +0100 Subject: [PATCH 03/29] Use tracker to dump the story from applied events --- CHANGELOG.rst | 3 +-- rasa/core/training/interactive.py | 31 ++++++++++++------------------- 2 files changed, 13 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index ddab83977b94..a0494805d2e5 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -23,8 +23,7 @@ Removed Fixed ----- -- interactive learning bug where reverted user utterances were dumped to nlu data -- ``MappingPolicy`` now works correctly when used as part of a PolicyEnsemble +- interactive learning bug where reverted user utterances were dumped to training data - validation no longer throws an error during interactive learning [1.1.6] - 2019-07-12 diff --git a/rasa/core/training/interactive.py b/rasa/core/training/interactive.py index 442996700009..be4b34b174b1 100644 --- a/rasa/core/training/interactive.py +++ b/rasa/core/training/interactive.py @@ -572,9 +572,12 @@ async def _write_data_to_file(sender_id: Text, endpoint: EndpointConfig): tracker = await retrieve_tracker(endpoint, sender_id) events = tracker.get("events", []) - await _write_stories_to_file(story_path, events) + serialised_domain = await retrieve_domain(endpoint) + domain = Domain.from_dict(serialised_domain) + + await _write_stories_to_file(story_path, events, domain) await _write_nlu_to_file(nlu_path, events) - await _write_domain_to_file(domain_path, events, endpoint) + await _write_domain_to_file(domain_path, events, domain) logger.info("Successfully wrote stories and NLU data") @@ -772,7 +775,7 @@ def _collect_actions(events: List[Dict[Text, Any]]) -> List[Dict[Text, Any]]: async def _write_stories_to_file( - export_story_path: Text, events: List[Dict[Text, Any]] + export_story_path: Text, events: List[Dict[Text, Any]], domain: Domain ) -> None: """Write the conversation of the sender_id to the file paths.""" @@ -786,20 +789,13 @@ async def _write_stories_to_file( append_write = "w" # make a new file if not with open(export_story_path, append_write, encoding="utf-8") as f: - for conversation in sub_conversations: + for i, conversation in enumerate(sub_conversations): parsed_events = rasa.core.events.deserialise_events(conversation) - applied_events = [] - - for i, event in enumerate(parsed_events): - if event.type_name == UserUtteranceReverted.type_name: - # user corrected their intent, so we have to remove - # the incorrect intent, rewind action and action_listen - applied_events = applied_events[:-2] - else: - applied_events.append(event) + tracker = DialogueStateTracker.from_events("interactive_story_{}".format(i+1), + evts=parsed_events, + slots=domain.slots) - s = Story.from_events(applied_events) - f.write("\n" + s.as_story_string(flat=True)) + f.write("\n" + tracker.export_stories()) async def _write_nlu_to_file( @@ -851,15 +847,12 @@ def _intents_from_messages(messages): async def _write_domain_to_file( - domain_path: Text, events: List[Dict[Text, Any]], endpoint: EndpointConfig + domain_path: Text, events: List[Dict[Text, Any]], old_domain: Domain ) -> None: """Write an updated domain file to the file path.""" io_utils.create_path(domain_path) - domain = await retrieve_domain(endpoint) - old_domain = Domain.from_dict(domain) - messages = _collect_messages(events) actions = _collect_actions(events) templates = NEW_TEMPLATES From 7860e6b5ed541d320ac45ca4fbd138bda2f88a4f Mon Sep 17 00:00:00 2001 From: Tom Metcalfe Date: Fri, 19 Jul 2019 16:39:31 +0100 Subject: [PATCH 04/29] fix domain test in interactive --- tests/core/test_interactive.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/core/test_interactive.py b/tests/core/test_interactive.py index 1a267f9f51dc..f831b623f0ca 100644 --- a/tests/core/test_interactive.py +++ b/tests/core/test_interactive.py @@ -8,6 +8,7 @@ from rasa.core.training import interactive from rasa.utils.endpoints import EndpointConfig from rasa.core.actions.action import default_actions +from rasa.core.domain import Domain from tests.utilities import latest_request, json_of_latest_request @@ -311,7 +312,10 @@ async def test_interactive_domain_persistence(mock_endpoint, tmpdir): with aioresponses() as mocked: mocked.get(url, payload={}) - await interactive._write_domain_to_file(domain_path, events, mock_endpoint) + serialised_domain = await interactive.retrieve_domain(mock_endpoint) + old_domain = Domain.from_dict(serialised_domain) + + await interactive._write_domain_to_file(domain_path, events, old_domain) saved_domain = rasa.utils.io.read_config_file(domain_path) From 79990c12a6a0771a73ecf27c77c644c72f00e63a Mon Sep 17 00:00:00 2001 From: Tom Metcalfe Date: Fri, 19 Jul 2019 17:15:06 +0100 Subject: [PATCH 05/29] Add whitspace for linting --- rasa/core/training/interactive.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/rasa/core/training/interactive.py b/rasa/core/training/interactive.py index be4b34b174b1..c4f6c23b9f5e 100644 --- a/rasa/core/training/interactive.py +++ b/rasa/core/training/interactive.py @@ -791,9 +791,11 @@ async def _write_stories_to_file( with open(export_story_path, append_write, encoding="utf-8") as f: for i, conversation in enumerate(sub_conversations): parsed_events = rasa.core.events.deserialise_events(conversation) - tracker = DialogueStateTracker.from_events("interactive_story_{}".format(i+1), - evts=parsed_events, - slots=domain.slots) + tracker = DialogueStateTracker.from_events( + "interactive_story_{}".format(i + 1), + evts=parsed_events, + slots=domain.slots, + ) f.write("\n" + tracker.export_stories()) From 7dc3559c1359d09de883354d8433ba3d4e2a8d09 Mon Sep 17 00:00:00 2001 From: RanaMostafa Date: Mon, 22 Jul 2019 21:56:09 +0200 Subject: [PATCH 06/29] Added template for image --- rasa/core/nlg/template.py | 13 ++++++++++--- tests/core/test_nlg.py | 15 +++++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/rasa/core/nlg/template.py b/rasa/core/nlg/template.py index 2404a52e63f1..bd6880f720cc 100644 --- a/rasa/core/nlg/template.py +++ b/rasa/core/nlg/template.py @@ -104,15 +104,22 @@ def _fill_template_text( template_vars = self._template_variables(filled_slots, kwargs) # Filling the template variables in the template text - if template_vars and "text" in template: + if template_vars : try: # transforming template tags from # "{tag_name}" to "{0[tag_name]}" # as described here: # https://stackoverflow.com/questions/7934620/python-dots-in-the-name-of-variable-in-a-format-string#comment9695339_7934969 # assuming that slot_name do not contain newline character here - text = re.sub(r"{([^\n]+?)}", r"{0[\1]}", template["text"]) - template["text"] = text.format(template_vars) + print("Template: ",template) + print("Template vars: ", template_vars) + if "text" in template: + text = re.sub(r"{([^\n]+?)}", r"{0[\1]}", template["text"]) + template["text"] = text.format(template_vars) + elif "image" in template : + image = re.sub(r"{([^\n]+?)}", r"{0[\1]}", template["image"]) + template["image"] = image.format(template_vars) + print("Template after: ", template) except KeyError as e: logger.exception( "Failed to fill utterance template '{}'. " diff --git a/tests/core/test_nlg.py b/tests/core/test_nlg.py index bc32b2b776e5..7bf542478010 100644 --- a/tests/core/test_nlg.py +++ b/tests/core/test_nlg.py @@ -114,6 +114,20 @@ def test_nlg_fill_template_text(slot_name, slot_value): ) assert result == {"text": str(slot_value)} +@pytest.mark.parametrize( + "img_slot_name, img_slot_value", + [ + ("url", "https://www.exampleimg.com"), + ("img1", "https://www.appleimg.com"), + ], +) +def test_nlg_fill_template_image(img_slot_name, img_slot_value): + template = {"image": "{" + img_slot_name + "}"} + t = TemplatedNaturalLanguageGenerator(templates=dict()) + result = t._fill_template_text( + template=template, filled_slots={img_slot_name: img_slot_value} + ) + assert result == {"image": str(img_slot_value)} @pytest.mark.parametrize("slot_name, slot_value", [("tag_w_\n", "a")]) def test_nlg_fill_template_text_w_bad_slot_name2(slot_name, slot_value): @@ -123,3 +137,4 @@ def test_nlg_fill_template_text_w_bad_slot_name2(slot_name, slot_value): template={"text": template_text}, filled_slots={slot_name: slot_value} ) assert result["text"] == template_text + From 7cf6cf53608d59ef745d5576c7bfcb18e59a3257 Mon Sep 17 00:00:00 2001 From: RanaMostafa Date: Mon, 22 Jul 2019 22:00:49 +0200 Subject: [PATCH 07/29] Reformatted files using black command --- rasa/core/nlg/template.py | 10 +++++----- tests/core/test_nlg.py | 8 +++----- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/rasa/core/nlg/template.py b/rasa/core/nlg/template.py index bd6880f720cc..635b667d7671 100644 --- a/rasa/core/nlg/template.py +++ b/rasa/core/nlg/template.py @@ -104,22 +104,22 @@ def _fill_template_text( template_vars = self._template_variables(filled_slots, kwargs) # Filling the template variables in the template text - if template_vars : + if template_vars: try: # transforming template tags from # "{tag_name}" to "{0[tag_name]}" # as described here: # https://stackoverflow.com/questions/7934620/python-dots-in-the-name-of-variable-in-a-format-string#comment9695339_7934969 # assuming that slot_name do not contain newline character here - print("Template: ",template) - print("Template vars: ", template_vars) + print ("Template: ", template) + print ("Template vars: ", template_vars) if "text" in template: text = re.sub(r"{([^\n]+?)}", r"{0[\1]}", template["text"]) template["text"] = text.format(template_vars) - elif "image" in template : + elif "image" in template: image = re.sub(r"{([^\n]+?)}", r"{0[\1]}", template["image"]) template["image"] = image.format(template_vars) - print("Template after: ", template) + print ("Template after: ", template) except KeyError as e: logger.exception( "Failed to fill utterance template '{}'. " diff --git a/tests/core/test_nlg.py b/tests/core/test_nlg.py index 7bf542478010..cf50e821367f 100644 --- a/tests/core/test_nlg.py +++ b/tests/core/test_nlg.py @@ -114,12 +114,10 @@ def test_nlg_fill_template_text(slot_name, slot_value): ) assert result == {"text": str(slot_value)} + @pytest.mark.parametrize( "img_slot_name, img_slot_value", - [ - ("url", "https://www.exampleimg.com"), - ("img1", "https://www.appleimg.com"), - ], + [("url", "https://www.exampleimg.com"), ("img1", "https://www.appleimg.com")], ) def test_nlg_fill_template_image(img_slot_name, img_slot_value): template = {"image": "{" + img_slot_name + "}"} @@ -129,6 +127,7 @@ def test_nlg_fill_template_image(img_slot_name, img_slot_value): ) assert result == {"image": str(img_slot_value)} + @pytest.mark.parametrize("slot_name, slot_value", [("tag_w_\n", "a")]) def test_nlg_fill_template_text_w_bad_slot_name2(slot_name, slot_value): template_text = "{" + slot_name + "}" @@ -137,4 +136,3 @@ def test_nlg_fill_template_text_w_bad_slot_name2(slot_name, slot_value): template={"text": template_text}, filled_slots={slot_name: slot_value} ) assert result["text"] == template_text - From 03c118e50fb0f0c7356a23e4f1d08b89dfaf4867 Mon Sep 17 00:00:00 2001 From: RanaMostafa Date: Mon, 22 Jul 2019 22:49:22 +0200 Subject: [PATCH 08/29] Modified template for failed test cases and reformatted --- rasa/core/nlg/template.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/rasa/core/nlg/template.py b/rasa/core/nlg/template.py index 635b667d7671..ac61f609b233 100644 --- a/rasa/core/nlg/template.py +++ b/rasa/core/nlg/template.py @@ -104,22 +104,19 @@ def _fill_template_text( template_vars = self._template_variables(filled_slots, kwargs) # Filling the template variables in the template text - if template_vars: + if template_vars and (text in template or image in template): try: # transforming template tags from # "{tag_name}" to "{0[tag_name]}" # as described here: # https://stackoverflow.com/questions/7934620/python-dots-in-the-name-of-variable-in-a-format-string#comment9695339_7934969 # assuming that slot_name do not contain newline character here - print ("Template: ", template) - print ("Template vars: ", template_vars) if "text" in template: text = re.sub(r"{([^\n]+?)}", r"{0[\1]}", template["text"]) template["text"] = text.format(template_vars) elif "image" in template: image = re.sub(r"{([^\n]+?)}", r"{0[\1]}", template["image"]) template["image"] = image.format(template_vars) - print ("Template after: ", template) except KeyError as e: logger.exception( "Failed to fill utterance template '{}'. " From ba1f1d40cfc86672f2033022f404f0fd273d1c22 Mon Sep 17 00:00:00 2001 From: RanaMostafa Date: Mon, 22 Jul 2019 23:00:29 +0200 Subject: [PATCH 09/29] Fixed liniting error and reformatted --- rasa/core/nlg/template.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rasa/core/nlg/template.py b/rasa/core/nlg/template.py index ac61f609b233..fcd53e9584a4 100644 --- a/rasa/core/nlg/template.py +++ b/rasa/core/nlg/template.py @@ -104,7 +104,7 @@ def _fill_template_text( template_vars = self._template_variables(filled_slots, kwargs) # Filling the template variables in the template text - if template_vars and (text in template or image in template): + if template_vars and ("text" in template or "image" in template): try: # transforming template tags from # "{tag_name}" to "{0[tag_name]}" From 4fe29b029e36b1936f037b39cc45ae29600e185a Mon Sep 17 00:00:00 2001 From: RanaMostafa Date: Tue, 23 Jul 2019 13:29:47 +0200 Subject: [PATCH 10/29] Added another test case with both image and text + removed duplication in template.py --- rasa/core/nlg/template.py | 45 ++++++++++++++++++--------------------- tests/core/test_nlg.py | 23 ++++++++++++++++++++ 2 files changed, 44 insertions(+), 24 deletions(-) diff --git a/rasa/core/nlg/template.py b/rasa/core/nlg/template.py index fcd53e9584a4..aa17b7bc90d1 100644 --- a/rasa/core/nlg/template.py +++ b/rasa/core/nlg/template.py @@ -103,30 +103,27 @@ def _fill_template_text( # Getting the slot values in the template variables template_vars = self._template_variables(filled_slots, kwargs) - # Filling the template variables in the template text - if template_vars and ("text" in template or "image" in template): - try: - # transforming template tags from - # "{tag_name}" to "{0[tag_name]}" - # as described here: - # https://stackoverflow.com/questions/7934620/python-dots-in-the-name-of-variable-in-a-format-string#comment9695339_7934969 - # assuming that slot_name do not contain newline character here - if "text" in template: - text = re.sub(r"{([^\n]+?)}", r"{0[\1]}", template["text"]) - template["text"] = text.format(template_vars) - elif "image" in template: - image = re.sub(r"{([^\n]+?)}", r"{0[\1]}", template["image"]) - template["image"] = image.format(template_vars) - except KeyError as e: - logger.exception( - "Failed to fill utterance template '{}'. " - "Tried to replace '{}' but could not find " - "a value for it. There is no slot with this " - "name nor did you pass the value explicitly " - "when calling the template. Return template " - "without filling the template. " - "".format(template, e.args[0]) - ) + for key in ["text", "image"]: + # Filling the template variables in the template + if template_vars and (key in template): + try: + # transforming template tags from + # "{tag_name}" to "{0[tag_name]}" + # as described here: + # https://stackoverflow.com/questions/7934620/python-dots-in-the-name-of-variable-in-a-format-string#comment9695339_7934969 + # assuming that slot_name do not contain newline character here + template_key = re.sub(r"{([^\n]+?)}", r"{0[\1]}", template[key]) + template[key] = template_key.format(template_vars) + except KeyError as e: + logger.exception( + "Failed to fill utterance template '{}'. " + "Tried to replace '{}' but could not find " + "a value for it. There is no slot with this " + "name nor did you pass the value explicitly " + "when calling the template. Return template " + "without filling the template. " + "".format(template, e.args[0]) + ) return template @staticmethod diff --git a/tests/core/test_nlg.py b/tests/core/test_nlg.py index cf50e821367f..66e45911e204 100644 --- a/tests/core/test_nlg.py +++ b/tests/core/test_nlg.py @@ -136,3 +136,26 @@ def test_nlg_fill_template_text_w_bad_slot_name2(slot_name, slot_value): template={"text": template_text}, filled_slots={slot_name: slot_value} ) assert result["text"] == template_text + + +@pytest.mark.parametrize( + "text_slot_name, text_slot_value, img_slot_name, img_slot_value", + [ + ("tag_w_underscore", "a", "url", "https://www.exampleimg.com"), + ("tag with space", "bacon", "img1", "https://www.appleimg.com"), + ], +) +def test_nlg_fill_template_image_and_text( + text_slot_name, text_slot_value, img_slot_name, img_slot_value +): + img_template = {"image": "{" + img_slot_name + "}"} + text_template = {"text": "{" + text_slot_name + "}"} + t = TemplatedNaturalLanguageGenerator(templates=dict()) + img_result = t._fill_template_text( + template=img_template, filled_slots={img_slot_name: img_slot_value} + ) + text_result = t._fill_template_text( + template=text_template, filled_slots={text_slot_name: text_slot_value} + ) + assert img_result == {"image": str(img_slot_value)} + assert text_result == {"text": str(text_slot_value)} From 406dec017efbecaf74b42aaf81c3c6d3747119b5 Mon Sep 17 00:00:00 2001 From: RanaMostafa Date: Tue, 23 Jul 2019 13:46:30 +0200 Subject: [PATCH 11/29] Updated changelog --- CHANGELOG.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e6db607461fc..e800d648bc49 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -27,6 +27,7 @@ Fixed ----- - added timeout to terminal input channel to avoid freezing input in case of server errors +- Fill slots in other parts of utterance templates than text [1.1.7] - 2019-07-18 ^^^^^^^^^^^^^^^^^^^^ From e22ab45ee3f4b934c2f12b60de98e11d006e4856 Mon Sep 17 00:00:00 2001 From: RanaMostafa Date: Tue, 23 Jul 2019 20:57:08 +0200 Subject: [PATCH 12/29] Modified testcase to include text and image templates need to be filled + modified CHANGELOG to include only image template --- CHANGELOG.rst | 2 +- tests/core/test_nlg.py | 13 ++++--------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index d85cf5091644..c4876576d555 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -28,7 +28,7 @@ Fixed ----- - added timeout to terminal input channel to avoid freezing input in case of server errors -- Fill slots in other parts of utterance templates than text +- Fill slots for image template other than text [1.1.7] - 2019-07-18 ^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/core/test_nlg.py b/tests/core/test_nlg.py index 66e45911e204..f19869974e7f 100644 --- a/tests/core/test_nlg.py +++ b/tests/core/test_nlg.py @@ -148,14 +148,9 @@ def test_nlg_fill_template_text_w_bad_slot_name2(slot_name, slot_value): def test_nlg_fill_template_image_and_text( text_slot_name, text_slot_value, img_slot_name, img_slot_value ): - img_template = {"image": "{" + img_slot_name + "}"} - text_template = {"text": "{" + text_slot_name + "}"} + template = {"text": "{" + text_slot_name + "}","image": "{" + img_slot_name + "}"} t = TemplatedNaturalLanguageGenerator(templates=dict()) - img_result = t._fill_template_text( - template=img_template, filled_slots={img_slot_name: img_slot_value} - ) - text_result = t._fill_template_text( - template=text_template, filled_slots={text_slot_name: text_slot_value} + result = t._fill_template_text( + template=template, filled_slots={text_slot_name:text_slot_value,img_slot_name: img_slot_value} ) - assert img_result == {"image": str(img_slot_value)} - assert text_result == {"text": str(text_slot_value)} + assert result == {"text": str(text_slot_value),"image": str(img_slot_value)} From a1e6995f970da4d2a8d553bfe298c30f48985ed5 Mon Sep 17 00:00:00 2001 From: RanaMostafa Date: Tue, 23 Jul 2019 21:19:27 +0200 Subject: [PATCH 13/29] Reformatted test_nlg file --- tests/core/test_nlg.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/core/test_nlg.py b/tests/core/test_nlg.py index f19869974e7f..187a18aed1f7 100644 --- a/tests/core/test_nlg.py +++ b/tests/core/test_nlg.py @@ -148,9 +148,10 @@ def test_nlg_fill_template_text_w_bad_slot_name2(slot_name, slot_value): def test_nlg_fill_template_image_and_text( text_slot_name, text_slot_value, img_slot_name, img_slot_value ): - template = {"text": "{" + text_slot_name + "}","image": "{" + img_slot_name + "}"} + template = {"text": "{" + text_slot_name + "}", "image": "{" + img_slot_name + "}"} t = TemplatedNaturalLanguageGenerator(templates=dict()) result = t._fill_template_text( - template=template, filled_slots={text_slot_name:text_slot_value,img_slot_name: img_slot_value} + template=template, + filled_slots={text_slot_name: text_slot_value, img_slot_name: img_slot_value}, ) - assert result == {"text": str(text_slot_value),"image": str(img_slot_value)} + assert result == {"text": str(text_slot_value), "image": str(img_slot_value)} From 2d22669effc1fb642acecdcbc32ba20ce39827c0 Mon Sep 17 00:00:00 2001 From: RanaMostafa Date: Tue, 23 Jul 2019 22:11:17 +0200 Subject: [PATCH 14/29] Reverted to old test case --- tests/core/test_nlg.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/tests/core/test_nlg.py b/tests/core/test_nlg.py index 187a18aed1f7..66e45911e204 100644 --- a/tests/core/test_nlg.py +++ b/tests/core/test_nlg.py @@ -148,10 +148,14 @@ def test_nlg_fill_template_text_w_bad_slot_name2(slot_name, slot_value): def test_nlg_fill_template_image_and_text( text_slot_name, text_slot_value, img_slot_name, img_slot_value ): - template = {"text": "{" + text_slot_name + "}", "image": "{" + img_slot_name + "}"} + img_template = {"image": "{" + img_slot_name + "}"} + text_template = {"text": "{" + text_slot_name + "}"} t = TemplatedNaturalLanguageGenerator(templates=dict()) - result = t._fill_template_text( - template=template, - filled_slots={text_slot_name: text_slot_value, img_slot_name: img_slot_value}, + img_result = t._fill_template_text( + template=img_template, filled_slots={img_slot_name: img_slot_value} + ) + text_result = t._fill_template_text( + template=text_template, filled_slots={text_slot_name: text_slot_value} ) - assert result == {"text": str(text_slot_value), "image": str(img_slot_value)} + assert img_result == {"image": str(img_slot_value)} + assert text_result == {"text": str(text_slot_value)} From 50a97aa552593bd3de2a0028a452103a40c444de Mon Sep 17 00:00:00 2001 From: RanaMostafa Date: Tue, 23 Jul 2019 22:30:48 +0200 Subject: [PATCH 15/29] Add new test case back --- tests/core/test_nlg.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/tests/core/test_nlg.py b/tests/core/test_nlg.py index 66e45911e204..187a18aed1f7 100644 --- a/tests/core/test_nlg.py +++ b/tests/core/test_nlg.py @@ -148,14 +148,10 @@ def test_nlg_fill_template_text_w_bad_slot_name2(slot_name, slot_value): def test_nlg_fill_template_image_and_text( text_slot_name, text_slot_value, img_slot_name, img_slot_value ): - img_template = {"image": "{" + img_slot_name + "}"} - text_template = {"text": "{" + text_slot_name + "}"} + template = {"text": "{" + text_slot_name + "}", "image": "{" + img_slot_name + "}"} t = TemplatedNaturalLanguageGenerator(templates=dict()) - img_result = t._fill_template_text( - template=img_template, filled_slots={img_slot_name: img_slot_value} - ) - text_result = t._fill_template_text( - template=text_template, filled_slots={text_slot_name: text_slot_value} + result = t._fill_template_text( + template=template, + filled_slots={text_slot_name: text_slot_value, img_slot_name: img_slot_value}, ) - assert img_result == {"image": str(img_slot_value)} - assert text_result == {"text": str(text_slot_value)} + assert result == {"text": str(text_slot_value), "image": str(img_slot_value)} From de37d9e0af5cb660b9e08f8e840ed0351ca2f9e1 Mon Sep 17 00:00:00 2001 From: RanaMostafa Date: Wed, 24 Jul 2019 12:11:32 +0200 Subject: [PATCH 16/29] Fixed merge conflicts + reformatted files --- rasa/core/nlg/template.py | 2 +- tests/core/test_nlg.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/rasa/core/nlg/template.py b/rasa/core/nlg/template.py index d15bd3c6ee3a..5271008762ea 100644 --- a/rasa/core/nlg/template.py +++ b/rasa/core/nlg/template.py @@ -103,7 +103,7 @@ def _fill_template( # Getting the slot values in the template variables template_vars = self._template_variables(filled_slots, kwargs) - for key in ["text", "image","custom"]: + for key in ["text", "image", "custom"]: # Filling the template variables in the template if template_vars and (key in template): try: diff --git a/tests/core/test_nlg.py b/tests/core/test_nlg.py index cf585c2f115e..fe5b093a05dd 100644 --- a/tests/core/test_nlg.py +++ b/tests/core/test_nlg.py @@ -126,7 +126,8 @@ def test_nlg_fill_template_image(img_slot_name, img_slot_value): template=template, filled_slots={img_slot_name: img_slot_value} ) assert result == {"image": str(img_slot_value)} - + + @pytest.mark.parametrize( "slot_name, slot_value", [ @@ -164,7 +165,6 @@ def test_nlg_fill_template_custom(slot_name, slot_value): } - @pytest.mark.parametrize("slot_name, slot_value", [("tag_w_\n", "a")]) def test_nlg_fill_template_w_bad_slot_name2(slot_name, slot_value): template_text = "{" + slot_name + "}" @@ -187,7 +187,7 @@ def test_nlg_fill_template_image_and_text( ): template = {"text": "{" + text_slot_name + "}", "image": "{" + img_slot_name + "}"} t = TemplatedNaturalLanguageGenerator(templates=dict()) - result = t._fill_template_text( + result = t._fill_template( template=template, filled_slots={text_slot_name: text_slot_value, img_slot_name: img_slot_value}, ) From 49f03a36a7d458346c4ee77cac9b379e8b6bebb0 Mon Sep 17 00:00:00 2001 From: RanaMostafa Date: Wed, 24 Jul 2019 12:44:55 +0200 Subject: [PATCH 17/29] Modified test case --- tests/core/test_nlg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/core/test_nlg.py b/tests/core/test_nlg.py index fe5b093a05dd..b830a416d8e9 100644 --- a/tests/core/test_nlg.py +++ b/tests/core/test_nlg.py @@ -122,7 +122,7 @@ def test_nlg_fill_template_text(slot_name, slot_value): def test_nlg_fill_template_image(img_slot_name, img_slot_value): template = {"image": "{" + img_slot_name + "}"} t = TemplatedNaturalLanguageGenerator(templates=dict()) - result = t._fill_template_text( + result = t._fill_template( template=template, filled_slots={img_slot_name: img_slot_value} ) assert result == {"image": str(img_slot_value)} From e2d5147ffc6792a1e4a1f6885006369d05e31ae9 Mon Sep 17 00:00:00 2001 From: RanaMostafa Date: Wed, 24 Jul 2019 13:30:58 +0200 Subject: [PATCH 18/29] Add a new test case with text and custom --- tests/core/test_nlg.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/tests/core/test_nlg.py b/tests/core/test_nlg.py index b830a416d8e9..5e4676427f88 100644 --- a/tests/core/test_nlg.py +++ b/tests/core/test_nlg.py @@ -192,3 +192,34 @@ def test_nlg_fill_template_image_and_text( filled_slots={text_slot_name: text_slot_value, img_slot_name: img_slot_value}, ) assert result == {"text": str(text_slot_value), "image": str(img_slot_value)} + + +@pytest.mark.parametrize( + "text_slot_name, text_slot_value, cust_slot_name, cust_slot_value", + [ + ("tag_w_underscore", "a", "tag.with.dot", "chocolate"), + ("tag with space", "bacon", "tag-w-dash", "apple pie"), + ], +) +def test_nlg_fill_template_text_and_custom( + text_slot_name, text_slot_value, cust_slot_name, cust_slot_value +): + template = { + "text": "{" + text_slot_name + "}", + "custom": { + "field": "{" + cust_slot_name + "}", + "properties": {"field_prefixed": "prefix_{" + cust_slot_name + "}"}, + }, + } + t = TemplatedNaturalLanguageGenerator(templates=dict()) + result = t._fill_template( + template=template, + filled_slots={text_slot_name: text_slot_value, cust_slot_name: cust_slot_value}, + ) + assert result == { + "text": str(text_slot_value), + "custom": { + "field": str(cust_slot_value), + "properties": {"field_prefixed": "prefix_" + str(cust_slot_value)}, + }, + } From ebe681eb27f35bf145f803def310d49a77dee8bd Mon Sep 17 00:00:00 2001 From: RanaMostafa Date: Wed, 24 Jul 2019 14:58:11 +0200 Subject: [PATCH 19/29] Removed exception in template file --- rasa/core/nlg/template.py | 23 ++++------------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/rasa/core/nlg/template.py b/rasa/core/nlg/template.py index 5271008762ea..d4d5ee87b10e 100644 --- a/rasa/core/nlg/template.py +++ b/rasa/core/nlg/template.py @@ -103,26 +103,11 @@ def _fill_template( # Getting the slot values in the template variables template_vars = self._template_variables(filled_slots, kwargs) - for key in ["text", "image", "custom"]: - # Filling the template variables in the template - if template_vars and (key in template): - try: - # transforming template tags from - # "{tag_name}" to "{0[tag_name]}" - # as described here: - # https://stackoverflow.com/questions/7934620/python-dots-in-the-name-of-variable-in-a-format-string#comment9695339_7934969 - # assuming that slot_name do not contain newline character here + keys_to_interpolate = ["text", "image", "custom"] + if template_vars: + for key in keys_to_interpolate: + if key in template: template[key] = interpolate(template[key], template_vars) - except KeyError as e: - logger.exception( - "Failed to fill utterance template '{}'. " - "Tried to replace '{}' but could not find " - "a value for it. There is no slot with this " - "name nor did you pass the value explicitly " - "when calling the template. Return template " - "without filling the template. " - "".format(template, e.args[0]) - ) return template @staticmethod From 20c17e9c78314038fe542d80d95c4b9f59997412 Mon Sep 17 00:00:00 2001 From: RanaMostafa Date: Wed, 24 Jul 2019 15:56:47 +0200 Subject: [PATCH 20/29] Implemented templates for quick_replies + attachment + button --- rasa/core/nlg/template.py | 9 ++++++++- tests/core/test_nlg.py | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/rasa/core/nlg/template.py b/rasa/core/nlg/template.py index d4d5ee87b10e..fe5869d4317f 100644 --- a/rasa/core/nlg/template.py +++ b/rasa/core/nlg/template.py @@ -103,7 +103,14 @@ def _fill_template( # Getting the slot values in the template variables template_vars = self._template_variables(filled_slots, kwargs) - keys_to_interpolate = ["text", "image", "custom"] + keys_to_interpolate = [ + "text", + "image", + "custom", + "button", + "attachment", + "quick_replies", + ] if template_vars: for key in keys_to_interpolate: if key in template: diff --git a/tests/core/test_nlg.py b/tests/core/test_nlg.py index 5e4676427f88..86cb176223b1 100644 --- a/tests/core/test_nlg.py +++ b/tests/core/test_nlg.py @@ -223,3 +223,42 @@ def test_nlg_fill_template_text_and_custom( "properties": {"field_prefixed": "prefix_" + str(cust_slot_value)}, }, } + + +@pytest.mark.parametrize( + "attach_slot_name, attach_slot_value", [("attach_file", "https://attach.pdf")] +) +def test_nlg_fill_template_attachment(attach_slot_name, attach_slot_value): + template = {"attachment": "{" + attach_slot_name + "}"} + t = TemplatedNaturalLanguageGenerator(templates=dict()) + result = t._fill_template( + template=template, filled_slots={attach_slot_name: attach_slot_value} + ) + assert result == {"attachment": str(attach_slot_value)} + + +@pytest.mark.parametrize( + "attach_slot_name, attach_slot_value", [("button_1", "button1")] +) +def test_nlg_fill_template_button(button_slot_name, button_slot_value): + template = {"button": "{" + button_slot_name + "}"} + t = TemplatedNaturalLanguageGenerator(templates=dict()) + result = t._fill_template( + template=template, filled_slots={button_slot_name: button_slot_value} + ) + assert result == {"button": str(button_slot_value)} + + +@pytest.mark.parametrize( + "quick_replies_slot_name, quick_replies_slot_value", [("qreply", "reply 1")] +) +def test_nlg_fill_template_quick_replies( + quick_replies_slot_name, quick_replies_slot_value +): + template = {"quick_replies": "{" + quick_replies_slot_name + "}"} + t = TemplatedNaturalLanguageGenerator(templates=dict()) + result = t._fill_template( + template=template, + filled_slots={quick_replies_slot_name: quick_replies_slot_value}, + ) + assert result == {"quick_replies": str(quick_replies_slot_value)} From 9dd63131abc478f27fffe2b35eb17efc96ef89c7 Mon Sep 17 00:00:00 2001 From: RanaMostafa Date: Wed, 24 Jul 2019 16:42:54 +0200 Subject: [PATCH 21/29] Modified change log + corrected one of test cases --- CHANGELOG.rst | 2 +- tests/core/test_nlg.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index cea65eb7886f..fa6d57d817b3 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -29,7 +29,7 @@ Fixed ----- - added timeout to terminal input channel to avoid freezing input in case of server errors -- fill slots for image templates +- fill slots for image,buttons,quick_replies and attachment templates - ``rasa train core`` in comparison mode stores the model files compressed (``tar.gz`` files) diff --git a/tests/core/test_nlg.py b/tests/core/test_nlg.py index 86cb176223b1..48bead0ce91a 100644 --- a/tests/core/test_nlg.py +++ b/tests/core/test_nlg.py @@ -238,7 +238,7 @@ def test_nlg_fill_template_attachment(attach_slot_name, attach_slot_value): @pytest.mark.parametrize( - "attach_slot_name, attach_slot_value", [("button_1", "button1")] + "button_slot_name, button_slot_value", [("button_1", "button1")] ) def test_nlg_fill_template_button(button_slot_name, button_slot_value): template = {"button": "{" + button_slot_name + "}"} From be92e9146048335fdb2ae1963e06f88c68f448a6 Mon Sep 17 00:00:00 2001 From: Tom Metcalfe Date: Wed, 24 Jul 2019 15:44:19 +0100 Subject: [PATCH 22/29] Handle empty stories --- rasa/core/training/interactive.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/rasa/core/training/interactive.py b/rasa/core/training/interactive.py index c4f6c23b9f5e..fe6d4504372d 100644 --- a/rasa/core/training/interactive.py +++ b/rasa/core/training/interactive.py @@ -789,15 +789,17 @@ async def _write_stories_to_file( append_write = "w" # make a new file if not with open(export_story_path, append_write, encoding="utf-8") as f: - for i, conversation in enumerate(sub_conversations): + i = 1 + for conversation in sub_conversations: parsed_events = rasa.core.events.deserialise_events(conversation) tracker = DialogueStateTracker.from_events( - "interactive_story_{}".format(i + 1), + "interactive_story_{}".format(i), evts=parsed_events, slots=domain.slots, ) - - f.write("\n" + tracker.export_stories()) + if len(tracker.applied_events()) > 0: + i +=1 + f.write("\n" + tracker.export_stories()) async def _write_nlu_to_file( From 4a8f95263debb442d48bc53d7e3e49def6fb8991 Mon Sep 17 00:00:00 2001 From: Tom Metcalfe Date: Wed, 24 Jul 2019 16:41:14 +0100 Subject: [PATCH 23/29] black --- rasa/core/training/interactive.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/rasa/core/training/interactive.py b/rasa/core/training/interactive.py index fe6d4504372d..43b149a7dbc0 100644 --- a/rasa/core/training/interactive.py +++ b/rasa/core/training/interactive.py @@ -793,12 +793,10 @@ async def _write_stories_to_file( for conversation in sub_conversations: parsed_events = rasa.core.events.deserialise_events(conversation) tracker = DialogueStateTracker.from_events( - "interactive_story_{}".format(i), - evts=parsed_events, - slots=domain.slots, + "interactive_story_{}".format(i), evts=parsed_events, slots=domain.slots ) if len(tracker.applied_events()) > 0: - i +=1 + i += 1 f.write("\n" + tracker.export_stories()) From cff3194688f7f3664ff133d63625f5560b58aefd Mon Sep 17 00:00:00 2001 From: Rana Mostafa Date: Wed, 24 Jul 2019 18:33:58 +0200 Subject: [PATCH 24/29] Update CHANGELOG.rst Co-Authored-By: Tobias Wochinger --- CHANGELOG.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index fa6d57d817b3..a491c9c7ff95 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -29,7 +29,7 @@ Fixed ----- - added timeout to terminal input channel to avoid freezing input in case of server errors -- fill slots for image,buttons,quick_replies and attachment templates +- fill slots for image, buttons, quick_replies and attachments in templates - ``rasa train core`` in comparison mode stores the model files compressed (``tar.gz`` files) From 2b77fd7389d927c67b72d1ebade57599f9bf4cc2 Mon Sep 17 00:00:00 2001 From: Tobias Wochinger Date: Wed, 24 Jul 2019 19:07:35 +0200 Subject: [PATCH 25/29] use domain in update --- rasa/core/processor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rasa/core/processor.py b/rasa/core/processor.py index 1b85cfed5ac0..d98ecd4191f3 100644 --- a/rasa/core/processor.py +++ b/rasa/core/processor.py @@ -522,7 +522,7 @@ def _log_action_on_tracker(self, tracker, action_name, events, policy, confidenc # the timestamp would indicate a time before the time # of the action executed e.timestamp = time.time() - tracker.update(e) + tracker.update(e, self.domain) def _get_tracker(self, sender_id: Text) -> Optional[DialogueStateTracker]: sender_id = sender_id or UserMessage.DEFAULT_SENDER_ID From 0a60a86115d8e622e9d14a368c8358734c954655 Mon Sep 17 00:00:00 2001 From: Tobias Wochinger Date: Wed, 24 Jul 2019 19:09:20 +0200 Subject: [PATCH 26/29] update changelog --- CHANGELOG.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f219c13016e8..ee03303e814b 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -30,7 +30,7 @@ Fixed - added timeout to terminal input channel to avoid freezing input in case of server errors - ``rasa train core`` in comparison mode stores the model files compressed (``tar.gz`` files) - +- slot setting in interactive learning with the TwoStageFallbackPolicy [1.1.7] - 2019-07-18 ^^^^^^^^^^^^^^^^^^^^ From b2d92546d6bb4b9903dfa83b0f2a8b20cff94acf Mon Sep 17 00:00:00 2001 From: Ella Date: Thu, 25 Jul 2019 09:41:05 +0200 Subject: [PATCH 27/29] don't pop empty lists --- rasa/core/training/interactive.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rasa/core/training/interactive.py b/rasa/core/training/interactive.py index 43b149a7dbc0..a38b68ee6dc5 100644 --- a/rasa/core/training/interactive.py +++ b/rasa/core/training/interactive.py @@ -762,7 +762,7 @@ def _collect_messages(events: List[Dict[Text, Any]]) -> List[Message]: msg = Message.build(data["text"], data["intent"]["name"], data["entities"]) msgs.append(msg) - elif event.get("event") == UserUtteranceReverted.type_name: + elif event.get("event") == UserUtteranceReverted.type_name and msgs: msgs.pop() # user corrected the nlu, remove incorrect example return msgs From 91535f6e8e2eecbb0df46e96bd2aed9de6d72748 Mon Sep 17 00:00:00 2001 From: Ella Date: Thu, 25 Jul 2019 09:48:41 +0200 Subject: [PATCH 28/29] only print if at least a user uttered --- rasa/core/training/interactive.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rasa/core/training/interactive.py b/rasa/core/training/interactive.py index a38b68ee6dc5..24569b00cc3f 100644 --- a/rasa/core/training/interactive.py +++ b/rasa/core/training/interactive.py @@ -795,7 +795,8 @@ async def _write_stories_to_file( tracker = DialogueStateTracker.from_events( "interactive_story_{}".format(i), evts=parsed_events, slots=domain.slots ) - if len(tracker.applied_events()) > 0: + + if any(isinstance(event, UserUttered) for event in tracker.applied_events()): i += 1 f.write("\n" + tracker.export_stories()) From bee372d8220f81dbac55a3fb80c62c4286ea04dd Mon Sep 17 00:00:00 2001 From: Ella Date: Thu, 25 Jul 2019 09:52:21 +0200 Subject: [PATCH 29/29] black --- rasa/core/training/interactive.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/rasa/core/training/interactive.py b/rasa/core/training/interactive.py index 24569b00cc3f..6831dc2db90b 100644 --- a/rasa/core/training/interactive.py +++ b/rasa/core/training/interactive.py @@ -796,7 +796,9 @@ async def _write_stories_to_file( "interactive_story_{}".format(i), evts=parsed_events, slots=domain.slots ) - if any(isinstance(event, UserUttered) for event in tracker.applied_events()): + if any( + isinstance(event, UserUttered) for event in tracker.applied_events() + ): i += 1 f.write("\n" + tracker.export_stories())