mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-08-10 08:21:54 +00:00
fix(flow): report the real CEL error for failures inside map literals (#6793)
Some checks failed
Build uv cache / build-cache (3.11) (push) Has been cancelled
Build uv cache / build-cache (3.12) (push) Has been cancelled
Build uv cache / build-cache (3.10) (push) Has been cancelled
Build uv cache / build-cache (3.13) (push) Has been cancelled
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Vulnerability Scan / pip-audit (push) Has been cancelled
Some checks failed
Build uv cache / build-cache (3.11) (push) Has been cancelled
Build uv cache / build-cache (3.12) (push) Has been cancelled
Build uv cache / build-cache (3.10) (push) Has been cancelled
Build uv cache / build-cache (3.13) (push) Has been cancelled
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled
Vulnerability Scan / pip-audit (push) Has been cancelled
This commit is contained in:
@@ -21,6 +21,25 @@ _CEL_MACROS_WITH_LOCAL_BINDINGS = frozenset(
|
||||
)
|
||||
|
||||
|
||||
def _find_cel_eval_error(value: Any) -> Exception | None:
|
||||
from celpy.evaluation import CELEvalError
|
||||
|
||||
if isinstance(value, CELEvalError):
|
||||
return value
|
||||
if isinstance(value, dict):
|
||||
for key, item in value.items():
|
||||
if (error := _find_cel_eval_error(key)) is not None:
|
||||
return error
|
||||
if (error := _find_cel_eval_error(item)) is not None:
|
||||
return error
|
||||
return None
|
||||
if isinstance(value, (list, tuple)):
|
||||
for item in value:
|
||||
if (error := _find_cel_eval_error(item)) is not None:
|
||||
return error
|
||||
return None
|
||||
|
||||
|
||||
def _stringify_cel_value(value: Any) -> str:
|
||||
from celpy.adapter import CELJSONEncoder
|
||||
|
||||
@@ -336,6 +355,8 @@ class Expression:
|
||||
Expression._compile_cel(expression, environment=environment)
|
||||
)
|
||||
result = program.evaluate(cast(Context, json_to_cel(context)))
|
||||
if (eval_error := _find_cel_eval_error(result)) is not None:
|
||||
raise eval_error
|
||||
return json.loads(json.dumps(result, cls=CELJSONEncoder))
|
||||
except Exception as e:
|
||||
raise ExpressionError(
|
||||
|
||||
@@ -2952,6 +2952,52 @@ def test_expression_template_empty_context_overrides_stored_context():
|
||||
expression.render_template({})
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"expression",
|
||||
[
|
||||
"{'a': 1/0}",
|
||||
"{'a': 1, 'b': state.missing}",
|
||||
"{'a': {'b': 1/0}}",
|
||||
"{'a': [1/0]}",
|
||||
],
|
||||
)
|
||||
def test_expression_raises_for_cel_eval_error_returned_as_data(expression):
|
||||
"""celpy returns a map literal holding a CELEvalError instead of raising it."""
|
||||
from crewai.flow.expressions import Expression, ExpressionError
|
||||
|
||||
with pytest.raises(ExpressionError, match="failed to evaluate CEL expression"):
|
||||
Expression(expression, context={"state": {"score": 90}}).evaluate()
|
||||
|
||||
|
||||
def test_expression_nested_cel_eval_error_reports_underlying_cause():
|
||||
from crewai.flow.expressions import Expression, ExpressionError
|
||||
|
||||
expression = Expression("{'a': 1/0}", context={"state": {}})
|
||||
|
||||
with pytest.raises(ExpressionError, match="modulus or divide by zero"):
|
||||
expression.evaluate()
|
||||
|
||||
|
||||
def test_expression_keeps_short_circuited_cel_errors():
|
||||
"""Errors that CEL logic intentionally silences must still evaluate."""
|
||||
from crewai.flow.expressions import Expression
|
||||
|
||||
context = {"state": {"tags": ["a", "b"]}}
|
||||
|
||||
assert Expression("{'ok': false && 1/0 == 1}", context=context).evaluate() == {
|
||||
"ok": False
|
||||
}
|
||||
assert Expression("{'ok': true || 1/0 == 1}", context=context).evaluate() == {
|
||||
"ok": True
|
||||
}
|
||||
assert (
|
||||
Expression(
|
||||
"state.tags.exists(t, t == 'a' || 1/0 == 1)", context=context
|
||||
).evaluate()
|
||||
is True
|
||||
)
|
||||
|
||||
|
||||
def test_expression_action_can_route_like_if_else():
|
||||
yaml_str = f"""
|
||||
schema: crewai.flow/v1
|
||||
|
||||
Reference in New Issue
Block a user