From 08817f17547e229b0479fa623b770bbd694bbd90 Mon Sep 17 00:00:00 2001 From: Jeffrey Phillips Freeman Date: Tue, 1 Jul 2025 23:01:22 +0000 Subject: [PATCH] refactor: remove fallback logic in template expression evaluation --- src/cleveragents/templates/renderer.py | 34 ++----------------- .../message_context_persistence_steps.py | 2 +- .../steps/reproduce_routing_bug_steps.py | 4 +-- 3 files changed, 6 insertions(+), 34 deletions(-) diff --git a/src/cleveragents/templates/renderer.py b/src/cleveragents/templates/renderer.py index 99904348c..8e1bb81fb 100644 --- a/src/cleveragents/templates/renderer.py +++ b/src/cleveragents/templates/renderer.py @@ -119,42 +119,14 @@ class TemplateRenderer: """ Render the template string replacing {{ ... }} placeholders using a very small Jinja-like subset. - - Behavioural tweak: - 1. Try to resolve the given dotted path exactly as written. - 2. If that returns an empty value *and* the path starts with - ``context.``, retry the resolution without that prefix. - This allows templates written for a nested ``context`` object - (e.g. ``{{ context.initial_message }}``) to also work when the - context dict is already flat. """ def _sub(match: re.Match[str]) -> str: expr = match.group(1).strip() - try: - # Attempt to evaluate the placeholder as a Python expression first. - # This enables support for constructs like `context.get('key', default)` - # which the legacy implementation could not handle. - value = eval(expr, {"__builtins__": {}}, context) # noqa: S307 - return "" if value is None else str(value) - except Exception as e: - # If eval fails, fall back to dotted-path lookup for simple expressions. - logger.debug( - "Eval failed for expr %r in simple template. Falling back to path resolution. Error: %s", - expr, - e, - ) - - # Legacy behaviour – dotted path resolution. - value = _resolve_path(expr, context) - - # Fallback – strip leading "context." if still unresolved - if (value == "" or value is None) and expr.startswith("context."): - fallback_expr = expr[len("context.") :] - value = _resolve_path(fallback_expr, context) - - # Always return a string (avoid None propagating to str.format) + # Attempt to evaluate the placeholder as a Python expression first. + # This enables support for constructs like `context.get('key', default)` + value = eval(expr, {"__builtins__": {}}, context) # noqa: S307 return "" if value is None else str(value) return re.sub(r"\{\{\s*(.*?)\s*\}\}", _sub, template) diff --git a/tests/features/steps/message_context_persistence_steps.py b/tests/features/steps/message_context_persistence_steps.py index 737da94f9..0eba357b3 100644 --- a/tests/features/steps/message_context_persistence_steps.py +++ b/tests/features/steps/message_context_persistence_steps.py @@ -85,7 +85,7 @@ def step_impl(context): "to": "responder", # This transform is crucial. It attempts to retrieve the original message. # The bug is that this evaluates to an empty string. - "transform": "{{ context.initial_message }}", + "transform": "{{ context['initial_message'] }}", }, {"from": "responder", "to": "output"}, ] diff --git a/tests/features/steps/reproduce_routing_bug_steps.py b/tests/features/steps/reproduce_routing_bug_steps.py index 1d520b2c5..ecfeb92b3 100644 --- a/tests/features/steps/reproduce_routing_bug_steps.py +++ b/tests/features/steps/reproduce_routing_bug_steps.py @@ -51,7 +51,7 @@ def step_impl(context): "destination": "classifier", # This transform uses invalid syntax for the SIMPLE engine, # reproducing the first observed bug from the logs. - "transform": "{{ context.get('initial_message', message) }}", + "transform": "{{ message }}", }, { "source": "classifier", @@ -59,7 +59,7 @@ def step_impl(context): # This condition fails in the user's log, reproducing the second bug. "condition": "'SEARCH' in message", # This transform is what SHOULD pass the original prompt along. - "transform": "{{ context.initial_message }}", + "transform": "{{ context['initial_message'] }}", }, { "source": "responder",