From acd1d71e52d4671750b001dc37398115c7336e4d Mon Sep 17 00:00:00 2001 From: Manohar Paturi <186662190+ManoharPaturi@users.noreply.github.com> Date: Wed, 23 Sep 2026 19:07:29 +0530 Subject: [PATCH] fix(eval): validate tool_names and mode in tool_called_check Signed-off-by: Manohar Paturi <186662190+ManoharPaturi@users.noreply.github.com> --- .../core/agent_framework/_evaluation.py | 10 ++++++++++ .../core/tests/core/test_local_eval.py | 19 +++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/python/packages/core/agent_framework/_evaluation.py b/python/packages/core/agent_framework/_evaluation.py index 1c57b9f72d8..eeac08f8ff8 100644 --- a/python/packages/core/agent_framework/_evaluation.py +++ b/python/packages/core/agent_framework/_evaluation.py @@ -1064,12 +1064,22 @@ def tool_called_check(*tool_names: str, mode: Literal["all", "any"] = "all") -> Returns: A check function for use with ``LocalEvaluator``. + Raises: + ValueError: If no tool names are provided, if any tool name is not a non-empty string, + or if mode is not 'all' or 'any'. + Example: .. code-block:: python check = tool_called_check("get_weather", "get_flight_price") """ + if not tool_names: + raise ValueError("At least one tool name must be provided to tool_called_check.") + if any(not isinstance(t, str) or not t.strip() for t in tool_names): + raise ValueError("Tool names must be non-empty strings.") + if mode not in ("all", "any"): + raise ValueError(f"Invalid mode: {mode!r}. Must be 'all' or 'any'.") def _check(item: EvalItem) -> CheckResult: expected = set(tool_names) diff --git a/python/packages/core/tests/core/test_local_eval.py b/python/packages/core/tests/core/test_local_eval.py index 9875b2aeeee..87021a830e8 100644 --- a/python/packages/core/tests/core/test_local_eval.py +++ b/python/packages/core/tests/core/test_local_eval.py @@ -973,6 +973,25 @@ async def test_any_mode_none_called(self): assert "None of expected tools" in result.reason # type: ignore[union-attr] # ty: ignore[unresolved-attribute] +class TestToolCalledCheckValidation: + """Tests for tool_called_check argument validation.""" + + def test_tool_called_check_requires_at_least_one_tool(self): + with pytest.raises(ValueError, match="At least one tool name"): + tool_called_check() + + def test_tool_called_check_rejects_empty_tool_name(self): + with pytest.raises(ValueError, match="non-empty strings"): + tool_called_check("valid_tool", "") + + with pytest.raises(ValueError, match="non-empty strings"): + tool_called_check(" ") + + def test_tool_called_check_rejects_invalid_mode(self): + with pytest.raises(ValueError, match="Invalid mode"): + tool_called_check("tool_a", mode="invalid") # type: ignore[arg-type] + + class TestCoerceResultScoreError: """Tests for _coerce_result handling non-numeric score."""