core.condition_evaluator
Safe condition expression evaluation for conditional prompt execution.
Uses AST parsing to safely evaluate conditions without eval()/exec(), supporting comparisons, boolean logic, function calls, and method access.
Safe condition expression evaluation for conditional prompt execution.
Uses AST parsing to safely evaluate conditions without eval()/exec(), supporting comparisons, boolean logic, function calls, and method access.
- class ConditionEvaluator(results_by_name)[source]
Bases:
objectSafely evaluates condition expressions for conditional prompt execution.
Security model: Never uses eval() or exec() on user input. Conditions are parsed using AST and evaluated using a restricted set of operators, functions, and method calls.
- Syntax:
{{prompt_name.property}} == “value” {{prompt_name.property}} != “value” {{prompt_name.property}} contains “substring” {{prompt_name.property}} not contains “substring” {{prompt_name.property}} matches “regex” len({{prompt_name.response}}) > 100 {{a.status}} == “success” and {{b.status}} == “success” {{a.status}} == “success” or {{b.status}} == “success” not {{prompt_name.has_response}}
- String methods:
{{prompt_name.response}}.startswith(“prefix”) {{prompt_name.response}}.endswith(“suffix”) {{prompt_name.response}}.lower() == “value” {{prompt_name.response}}.split(“,”)[0]
- JSON functions:
json_get({{prompt_name.response}}, “key.nested[0]”) json_get_default({{prompt_name.response}}, “key”, “default”) json_has({{prompt_name.response}}, “key”) json_keys({{prompt_name.response}}) “key” in json_keys({{prompt_name.response}})
- Available properties:
status: “success”, “failed”, “skipped”
response: The AI response text
attempts: Number of retry attempts (int)
error: Error message if failed (str)
has_response: True if response exists and non-empty (bool)
Note
For JSON responses, use
json_has(),json_get(), or"key" in json_keys(...)to query structure. Thecontainsandinoperators always perform substring matching on the stringified response text, which can produce false matches against dict/list repr strings (e.g."True" in {{s.response}}matches"{'pass': True}"because the four characters appear in the repr, not because of a value).- ALLOWED_OPERATORS = {<class 'ast.And'>: <function ConditionEvaluator.<lambda>>, <class 'ast.Eq'>: <built-in function eq>, <class 'ast.Gt'>: <built-in function gt>, <class 'ast.GtE'>: <built-in function ge>, <class 'ast.Lt'>: <built-in function lt>, <class 'ast.LtE'>: <built-in function le>, <class 'ast.NotEq'>: <built-in function ne>, <class 'ast.Or'>: <function ConditionEvaluator.<lambda>>}
- ALLOWED_UNARY_OPERATORS = {<class 'ast.Not'>: <built-in function not_>}
- ALLOWED_FUNCTIONS = {'abs': <built-in function abs>, 'bool': <function ConditionEvaluator.<lambda>>, 'count': <function ConditionEvaluator.<lambda>>, 'find': <function ConditionEvaluator.<lambda>>, 'float': <function ConditionEvaluator.<lambda>>, 'int': <function ConditionEvaluator.<lambda>>, 'is_empty': <function ConditionEvaluator.<lambda>>, 'is_null': <function ConditionEvaluator.<lambda>>, 'json_get': <function ConditionEvaluator.<lambda>>, 'json_get_default': <function ConditionEvaluator.<lambda>>, 'json_has': <function ConditionEvaluator.<lambda>>, 'json_keys': <function ConditionEvaluator.<lambda>>, 'json_parse': <function ConditionEvaluator.<lambda>>, 'json_type': <function ConditionEvaluator.<lambda>>, 'json_values': <function ConditionEvaluator.<lambda>>, 'len': <built-in function len>, 'lower': <function ConditionEvaluator.<lambda>>, 'lstrip': <function ConditionEvaluator.<lambda>>, 'max': <built-in function max>, 'min': <built-in function min>, 'replace': <function ConditionEvaluator.<lambda>>, 'rfind': <function ConditionEvaluator.<lambda>>, 'round': <built-in function round>, 'rsplit': <function ConditionEvaluator.<lambda>>, 'rstrip': <function ConditionEvaluator.<lambda>>, 'slice': <function ConditionEvaluator.<lambda>>, 'split': <function ConditionEvaluator.<lambda>>, 'str': <function ConditionEvaluator.<lambda>>, 'strip': <function ConditionEvaluator.<lambda>>, 'trim': <function ConditionEvaluator.<lambda>>, 'upper': <function ConditionEvaluator.<lambda>>}
- ALLOWED_STRING_METHODS = frozenset({'capitalize', 'center', 'count', 'endswith', 'find', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'islower', 'isnumeric', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'replace', 'rfind', 'rindex', 'rjust', 'rsplit', 'rstrip', 'split', 'startswith', 'strip', 'title', 'upper', 'zfill'})
- ALLOWED_LIST_METHODS = frozenset({'count', 'index'})
- ALLOWED_DICT_METHODS = frozenset({'get', 'keys', 'values'})
- VARIABLE_PATTERN = re.compile('\\{\\{(\\w+)\\.(\\w+)\\}\\}')
- evaluate_with_trace(condition)[source]
Evaluate a condition expression and return the resolved trace.
Like evaluate(), but also returns the resolved expression showing what each {{name.property}} was substituted with.
- Parameters:
condition (str) – The condition string to evaluate.
- Returns:
Tuple of (result, error_message, resolved_trace). - result: True if condition passes, False otherwise. - error_message: None if successful, error string if failed. - resolved_trace: The condition after variable substitution,
e.g. ‘“failed” == “success”’, or None if no condition.
- Return type:
Classes
- class ConditionEvaluator(results_by_name)[source]
Bases:
objectSafely evaluates condition expressions for conditional prompt execution.
Security model: Never uses eval() or exec() on user input. Conditions are parsed using AST and evaluated using a restricted set of operators, functions, and method calls.
- Syntax:
{{prompt_name.property}} == “value” {{prompt_name.property}} != “value” {{prompt_name.property}} contains “substring” {{prompt_name.property}} not contains “substring” {{prompt_name.property}} matches “regex” len({{prompt_name.response}}) > 100 {{a.status}} == “success” and {{b.status}} == “success” {{a.status}} == “success” or {{b.status}} == “success” not {{prompt_name.has_response}}
- String methods:
{{prompt_name.response}}.startswith(“prefix”) {{prompt_name.response}}.endswith(“suffix”) {{prompt_name.response}}.lower() == “value” {{prompt_name.response}}.split(“,”)[0]
- JSON functions:
json_get({{prompt_name.response}}, “key.nested[0]”) json_get_default({{prompt_name.response}}, “key”, “default”) json_has({{prompt_name.response}}, “key”) json_keys({{prompt_name.response}}) “key” in json_keys({{prompt_name.response}})
- Available properties:
status: “success”, “failed”, “skipped”
response: The AI response text
attempts: Number of retry attempts (int)
error: Error message if failed (str)
has_response: True if response exists and non-empty (bool)
Note
For JSON responses, use
json_has(),json_get(), or"key" in json_keys(...)to query structure. Thecontainsandinoperators always perform substring matching on the stringified response text, which can produce false matches against dict/list repr strings (e.g."True" in {{s.response}}matches"{'pass': True}"because the four characters appear in the repr, not because of a value).- ALLOWED_OPERATORS = {<class 'ast.And'>: <function ConditionEvaluator.<lambda>>, <class 'ast.Eq'>: <built-in function eq>, <class 'ast.Gt'>: <built-in function gt>, <class 'ast.GtE'>: <built-in function ge>, <class 'ast.Lt'>: <built-in function lt>, <class 'ast.LtE'>: <built-in function le>, <class 'ast.NotEq'>: <built-in function ne>, <class 'ast.Or'>: <function ConditionEvaluator.<lambda>>}
- ALLOWED_UNARY_OPERATORS = {<class 'ast.Not'>: <built-in function not_>}
- ALLOWED_FUNCTIONS = {'abs': <built-in function abs>, 'bool': <function ConditionEvaluator.<lambda>>, 'count': <function ConditionEvaluator.<lambda>>, 'find': <function ConditionEvaluator.<lambda>>, 'float': <function ConditionEvaluator.<lambda>>, 'int': <function ConditionEvaluator.<lambda>>, 'is_empty': <function ConditionEvaluator.<lambda>>, 'is_null': <function ConditionEvaluator.<lambda>>, 'json_get': <function ConditionEvaluator.<lambda>>, 'json_get_default': <function ConditionEvaluator.<lambda>>, 'json_has': <function ConditionEvaluator.<lambda>>, 'json_keys': <function ConditionEvaluator.<lambda>>, 'json_parse': <function ConditionEvaluator.<lambda>>, 'json_type': <function ConditionEvaluator.<lambda>>, 'json_values': <function ConditionEvaluator.<lambda>>, 'len': <built-in function len>, 'lower': <function ConditionEvaluator.<lambda>>, 'lstrip': <function ConditionEvaluator.<lambda>>, 'max': <built-in function max>, 'min': <built-in function min>, 'replace': <function ConditionEvaluator.<lambda>>, 'rfind': <function ConditionEvaluator.<lambda>>, 'round': <built-in function round>, 'rsplit': <function ConditionEvaluator.<lambda>>, 'rstrip': <function ConditionEvaluator.<lambda>>, 'slice': <function ConditionEvaluator.<lambda>>, 'split': <function ConditionEvaluator.<lambda>>, 'str': <function ConditionEvaluator.<lambda>>, 'strip': <function ConditionEvaluator.<lambda>>, 'trim': <function ConditionEvaluator.<lambda>>, 'upper': <function ConditionEvaluator.<lambda>>}
- ALLOWED_STRING_METHODS = frozenset({'capitalize', 'center', 'count', 'endswith', 'find', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'islower', 'isnumeric', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'replace', 'rfind', 'rindex', 'rjust', 'rsplit', 'rstrip', 'split', 'startswith', 'strip', 'title', 'upper', 'zfill'})
- ALLOWED_LIST_METHODS = frozenset({'count', 'index'})
- ALLOWED_DICT_METHODS = frozenset({'get', 'keys', 'values'})
- VARIABLE_PATTERN = re.compile('\\{\\{(\\w+)\\.(\\w+)\\}\\}')
- evaluate_with_trace(condition)[source]
Evaluate a condition expression and return the resolved trace.
Like evaluate(), but also returns the resolved expression showing what each {{name.property}} was substituted with.
- Parameters:
condition (str) – The condition string to evaluate.
- Returns:
Tuple of (result, error_message, resolved_trace). - result: True if condition passes, False otherwise. - error_message: None if successful, error string if failed. - resolved_trace: The condition after variable substitution,
e.g. ‘“failed” == “success”’, or None if no condition.
- Return type: