diff --git a/backend/packages/harness/deerflow/config/title_config.py b/backend/packages/harness/deerflow/config/title_config.py index 5aa750bc78..679183e6ce 100644 --- a/backend/packages/harness/deerflow/config/title_config.py +++ b/backend/packages/harness/deerflow/config/title_config.py @@ -1,6 +1,6 @@ """Configuration for automatic thread title generation.""" -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, ValidationInfo, field_validator class TitleConfig(BaseModel): @@ -31,6 +31,15 @@ class TitleConfig(BaseModel): description="Prompt template for LLM title generation when model_name is set", ) + @field_validator("max_words", "max_chars", mode="before") + @classmethod + def _reject_boolean_limits(cls, value: object, info: ValidationInfo) -> object: + # YAML `true`/`false` next to neighbouring boolean knobs must not coerce + # into integers (Pydantic would turn `true` into 1 before ge/le runs). + if isinstance(value, bool): + raise ValueError(f"{info.field_name} must be an integer, not a boolean") + return value + # Global configuration instance _title_config: TitleConfig = TitleConfig() diff --git a/backend/tests/test_title_generation.py b/backend/tests/test_title_generation.py index 53b0a50102..61ce71eab6 100644 --- a/backend/tests/test_title_generation.py +++ b/backend/tests/test_title_generation.py @@ -44,6 +44,19 @@ class TestTitleConfig: with pytest.raises(ValueError): TitleConfig(max_chars=201) + @pytest.mark.parametrize("field_name", ["max_words", "max_chars"]) + @pytest.mark.parametrize("bad_value", [True, False]) + def test_config_rejects_boolean_limits(self, field_name: str, bad_value: bool): + """YAML booleans must fail loudly instead of coercing into integers.""" + with pytest.raises(ValueError, match="must be an integer, not a boolean"): + TitleConfig(**{field_name: bad_value}) + + def test_config_still_accepts_numeric_strings(self): + """Numeric strings remain valid after the boolean guard.""" + config = TitleConfig(max_words="8", max_chars="80") + assert config.max_words == 8 + assert config.max_chars == 80 + def test_get_set_config(self): """Test global config getter and setter.""" original_config = get_title_config()