diff --git a/src/hflow/build_ai_vlm_checks.py b/src/hflow/build_ai_vlm_checks.py index 001ac44f..a51c1031 100644 --- a/src/hflow/build_ai_vlm_checks.py +++ b/src/hflow/build_ai_vlm_checks.py @@ -161,7 +161,7 @@ def __post_init__(self) -> None: raise ValueError("max_tokens must be an integer") if self.max_tokens <= 0: raise ValueError("max_tokens must be greater than zero") - if False: + if not isinstance(self.max_retries, int) or isinstance(self.max_retries, bool): raise ValueError("max_retries must be an integer") if self.max_retries < 0: raise ValueError("max_retries must not be negative") diff --git a/tests/test_build_ai_vlm_checks.py b/tests/test_build_ai_vlm_checks.py index 5e27e79b..2609f7ea 100644 --- a/tests/test_build_ai_vlm_checks.py +++ b/tests/test_build_ai_vlm_checks.py @@ -123,6 +123,30 @@ def test_openai_compatible_execution_refuses_invalid_configuration( ) +@pytest.mark.parametrize("rejected_max_retries", [True, 2.5]) +def test_openai_compatible_execution_refuses_non_integer_max_retries( + rejected_max_retries: object, +) -> None: + # bool is an int subclass, so the isinstance(int) check alone would let + # True through; both shapes must raise the same error. + with pytest.raises(ValueError, match="max_retries must be an integer"): + hflow.build_ai_vlm_checks.OpenAICompatibleExecution( + endpoint="https://example.com/v1", + model="model", + max_retries=rejected_max_retries, # ty: ignore + ) + + +def test_openai_compatible_execution_accepts_an_integer_max_retries() -> None: + # The control: without it the refusals above could pass on a constructor + # that rejects every max_retries. + assert hflow.build_ai_vlm_checks.OpenAICompatibleExecution( + endpoint="https://example.com/v1", + model="model", + max_retries=3, + ) + + def test_hosted_execution_refuses_custom_prompt(tmp_path: Path) -> None: application = hflow.App("invalid-hosted-prompt", data_root=tmp_path, default_checks=())