Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

correctly (don't) interpolate non-text values in custom responses #6280

Merged
merged 5 commits into from
Jul 28, 2020
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog/6280.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed ``TypeError: expected string or bytes-like object`` issue caused by integer, boolean, and null values in templates.
49 changes: 34 additions & 15 deletions rasa/core/nlg/interpolator.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,23 @@


def interpolate_text(template: Text, values: Dict[Text, Text]) -> Text:
# 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
# black list character and make sure to not to allow
# (a) newline in slot name
# (b) { or } in slot name
"""Interpolate values into templates with placeholders.

Transform 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
Block list characters, making sure not to allow:
(a) newline in slot name
(b) { or } in slot name

Args:
template: The piece of text that should be interpolated.
values: A dictionary of keys and the values that those
keys should be replaced with.

Returns:
The piece of text with any replacements made.
"""

try:
text = re.sub(r"{([^\n{}]+?)}", r"{0[\1]}", template)
text = text.format(values)
Expand All @@ -27,20 +37,29 @@ def interpolate_text(template: Text, values: Dict[Text, Text]) -> Text:
return text
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])
f"Failed to fill utterance template '{template}'. "
f"Tried to replace '{e.args[0]}' but could not find "
f"a value for it. There is no slot with this "
f"name nor did you pass the value explicitly "
f"when calling the template. Return template "
f"without filling the template. "
)
return template


def interpolate(
template: Union[Dict[Text, Any], Text], values: Dict[Text, Text]
) -> Union[Dict[Text, Any], Text]:
"""Recursively process template and interpolate any text keys.

Args:
template: The template that should be interpolated.
values: A dictionary of keys and the values that those
keys should be replaced with.

Returns:
The template with any replacements made.
"""
if isinstance(template, str):
return interpolate_text(template, values)
elif isinstance(template, dict):
Expand All @@ -49,7 +68,7 @@ def interpolate(
interpolate(v, values)
elif isinstance(v, list):
template[k] = [interpolate(i, values) for i in v]
else:
elif isinstance(v, str):
template[k] = interpolate_text(v, values)
return template
return template
7 changes: 4 additions & 3 deletions rasa/core/nlg/template.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import copy
import logging
from collections import defaultdict

from rasa.core.trackers import DialogueStateTracker
from typing import Text, Any, Dict, Optional, List

from rasa.core.nlg import interpolator
from rasa.core.nlg.generator import NaturalLanguageGenerator
from rasa.core.nlg.interpolator import interpolate_text, interpolate

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -116,7 +115,9 @@ def _fill_template(
if template_vars:
for key in keys_to_interpolate:
if key in template:
template[key] = interpolate(template[key], template_vars)
template[key] = interpolator.interpolate(
template[key], template_vars
)
return template

@staticmethod
Expand Down
8 changes: 7 additions & 1 deletion tests/core/test_nlg.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,9 @@ def test_nlg_fill_template_custom(slot_name: Text, slot_value: Any):
"custom": {
"field": f"{{{slot_name}}}",
"properties": {"field_prefixed": f"prefix_{{{slot_name}}}"},
"bool_field": True,
"int_field:": 42,
"empty_field": None,
}
}
t = TemplatedNaturalLanguageGenerator(templates=dict())
Expand All @@ -146,7 +149,10 @@ def test_nlg_fill_template_custom(slot_name: Text, slot_value: Any):
assert result == {
"custom": {
"field": str(slot_value),
"properties": {"field_prefixed": f"prefix_{str(slot_value)}"},
"properties": {"field_prefixed": f"prefix_{slot_value}"},
"bool_field": True,
"int_field:": 42,
"empty_field": None,
}
}

Expand Down