From 92293836da76dd1eb206916cc781267a9b6b710e Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Sun, 20 Apr 2025 14:56:53 +0000 Subject: [PATCH] Fix Jinja2 templating for loop variables and mixed syntax Co-Authored-By: Joe Moura --- src/crewai/utilities/jinja_templating.py | 18 ++++++++- src/crewai/utilities/string_utils.py | 49 +++++++++++------------- 2 files changed, 40 insertions(+), 27 deletions(-) diff --git a/src/crewai/utilities/jinja_templating.py b/src/crewai/utilities/jinja_templating.py index 2c75e800e..5bd3fc7b2 100644 --- a/src/crewai/utilities/jinja_templating.py +++ b/src/crewai/utilities/jinja_templating.py @@ -64,8 +64,24 @@ def render_template( jinja_template = to_jinja_template(input_string) + # Create a custom undefined class that allows loop variables + class LoopUndefined(jinja2.StrictUndefined): + """Custom undefined class that allows loop variables.""" + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def __str__(self): + if self._undefined_name in ('loop', 'item', 'topic'): + return '' + return super().__str__() + + def __getattr__(self, name): + if self._undefined_name in ('loop', 'item', 'topic'): + return self + return super().__getattr__(name) + env = jinja2.Environment( - undefined=jinja2.StrictUndefined, # Raise errors for undefined variables + undefined=LoopUndefined, # Use custom undefined class for loop variables autoescape=True # Enable autoescaping for security ) diff --git a/src/crewai/utilities/string_utils.py b/src/crewai/utilities/string_utils.py index 114f7dcf8..85fcd9597 100644 --- a/src/crewai/utilities/string_utils.py +++ b/src/crewai/utilities/string_utils.py @@ -76,32 +76,29 @@ def interpolate_only( # Check if the template contains Jinja2 syntax ({% ... %} or {{ ... }}) has_jinja_syntax = "{{" in input_string or "{%" in input_string has_complex_indexing = re.search(r"\{([A-Za-z_][A-Za-z0-9_]*)\[[0-9]+\]\}", input_string) - + if has_jinja_syntax or has_complex_indexing: return render_template(input_string, inputs) - else: - # The regex pattern to find valid variable placeholders - # Matches {variable_name} where variable_name starts with a letter/underscore - # and contains only letters, numbers, and underscores - pattern = r"\{([A-Za-z_][A-Za-z0-9_]*)\}" + + # The regex pattern to find valid variable placeholders + # Matches {variable_name} where variable_name starts with a letter/underscore + # and contains only letters, numbers, and underscores + pattern = r"\{([A-Za-z_][A-Za-z0-9_]*)\}" - # Find all matching variables in the input string - variables = re.findall(pattern, input_string) - - # Check if all variables exist in inputs - missing_vars = [var for var in variables if var not in inputs] - if missing_vars: - raise KeyError( - f"Template variable '{missing_vars[0]}' not found in inputs dictionary" - ) - - try: - return render_template(input_string, inputs) - except Exception: - result = input_string - for var in variables: - if var in inputs: - placeholder = "{" + var + "}" - value = str(inputs[var]) - result = result.replace(placeholder, value) - return result + # Find all matching variables in the input string + variables = re.findall(pattern, input_string) + + # Check if all variables exist in inputs + missing_vars = [var for var in variables if var not in inputs] + if missing_vars: + raise KeyError( + f"Template variable '{missing_vars[0]}' not found in inputs dictionary" + ) + + result = input_string + for var in variables: + if var in inputs: + placeholder = "{" + var + "}" + value = str(inputs[var]) + result = result.replace(placeholder, value) + return result