From 6cba5a544acfe2589fb0e091ea8b47ec9cde691e Mon Sep 17 00:00:00 2001 From: Yufeng He <40085740+he-yufeng@users.noreply.github.com> Date: Fri, 5 Jun 2026 17:20:44 +0800 Subject: [PATCH] fix(agent): skip empty switch conditions (#15691) ## What - make `Switch` ignore conditions that have no evaluable items - add a regression for blank `cpn_id` items falling through to the else branch - keep the existing non-empty `and` condition behavior covered Fixes #15643. ## Verified - `python -m py_compile agent\component\switch.py test\unit_test\agent\component\test_switch.py` - `python -m pytest test\unit_test\agent\component\test_switch.py -q` -> `2 passed` - `python -m ruff check agent\component\switch.py test\unit_test\agent\component\test_switch.py` - `git diff --check` I also checked `python -m ruff format --check` on the touched files. It would reformat pre-existing style in `agent/component/switch.py` beyond this bug fix, so I kept the patch scoped instead of reformatting the whole file. --- agent/component/switch.py | 2 +- test/unit_test/agent/component/test_switch.py | 59 +++++++++++++++++++ 2 files changed, 60 insertions(+), 1 deletion(-) create mode 100644 test/unit_test/agent/component/test_switch.py diff --git a/agent/component/switch.py b/agent/component/switch.py index cf9956bdf7..315b43f9ab 100644 --- a/agent/component/switch.py +++ b/agent/component/switch.py @@ -88,7 +88,7 @@ class Switch(ComponentBase, ABC): self.set_output("_next", cond["to"]) return - if all(res): + if res and all(res): self.set_output("next", [self._canvas.get_component_name(cpn_id) for cpn_id in cond["to"]]) self.set_output("_next", cond["to"]) return diff --git a/test/unit_test/agent/component/test_switch.py b/test/unit_test/agent/component/test_switch.py new file mode 100644 index 0000000000..10dbfeafcf --- /dev/null +++ b/test/unit_test/agent/component/test_switch.py @@ -0,0 +1,59 @@ +from agent.component.switch import Switch, SwitchParam + + +class _Canvas: + def __init__(self, variables=None): + self.variables = variables or {} + + def is_canceled(self): + return False + + def get_variable_value(self, cpn_id): + return self.variables[cpn_id] + + def get_component_name(self, cpn_id): + return cpn_id + + +def _switch(param, variables=None): + cpn = Switch.__new__(Switch) + cpn._canvas = _Canvas(variables) + cpn._id = "switch" + cpn._param = param + return cpn + + +def test_switch_empty_condition_falls_through_to_else(): + param = SwitchParam() + param.conditions = [ + { + "logical_operator": "and", + "items": [{"cpn_id": "", "operator": "=", "value": "yes"}], + "to": ["case_target"], + } + ] + param.end_cpn_ids = ["else_target"] + + cpn = _switch(param) + cpn._invoke() + + assert cpn.output("_next") == ["else_target"] + assert cpn.output("next") == ["else_target"] + + +def test_switch_non_empty_and_condition_still_matches(): + param = SwitchParam() + param.conditions = [ + { + "logical_operator": "and", + "items": [{"cpn_id": "answer", "operator": "=", "value": "yes"}], + "to": ["case_target"], + } + ] + param.end_cpn_ids = ["else_target"] + + cpn = _switch(param, {"answer": "yes"}) + cpn._invoke() + + assert cpn.output("_next") == ["case_target"] + assert cpn.output("next") == ["case_target"]