diff --git a/.env.template b/.env.template index 3573637d..71f1b969 100644 --- a/.env.template +++ b/.env.template @@ -113,7 +113,6 @@ LLM_OPENAI_API_KEY=your-api-key-here # DERIVER_MODEL_CONFIG__MAX_OUTPUT_TOKENS=4096 # DERIVER_LOG_OBSERVATIONS=false # DERIVER_MAX_INPUT_TOKENS=25000 -# Required for non-blank reasoning.custom_instructions; unset disables non-blank custom instructions # DERIVER_MAX_CUSTOM_INSTRUCTIONS_TOKENS=2000 # DERIVER_WORKING_REPRESENTATION_MAX_OBSERVATIONS=100 # DERIVER_REPRESENTATION_BATCH_MAX_TOKENS=1024 diff --git a/config.toml.example b/config.toml.example index ba35d02a..e34ae4b3 100644 --- a/config.toml.example +++ b/config.toml.example @@ -86,7 +86,7 @@ STALE_SESSION_TIMEOUT_MINUTES = 5 DEDUPLICATE = true LOG_OBSERVATIONS = false MAX_INPUT_TOKENS = 25000 -MAX_CUSTOM_INSTRUCTIONS_TOKENS = 2000 # Required for non-blank reasoning.custom_instructions; max supported value is 2000 +MAX_CUSTOM_INSTRUCTIONS_TOKENS = 2000 WORKING_REPRESENTATION_MAX_OBSERVATIONS = 100 REPRESENTATION_BATCH_MAX_TOKENS = 1024 FLUSH_ENABLED = false # Bypass batch token threshold, process work immediately diff --git a/src/config.py b/src/config.py index eb16ec55..ad0bb0a0 100644 --- a/src/config.py +++ b/src/config.py @@ -732,10 +732,9 @@ class DeriverSettings(HonchoSettings): LOG_OBSERVATIONS: bool = False MAX_INPUT_TOKENS: Annotated[int, Field(default=25000, gt=0, le=25000)] = 25000 - # Optional so deployments must opt in to accepting non-blank custom instructions. MAX_CUSTOM_INSTRUCTIONS_TOKENS: Annotated[ - int | None, Field(default=None, gt=0, le=2000) - ] = None + int, Field(default=2000, ge=0, le=2000) + ] = 2000 # Maximum number of observations to return in working representation # This is applied to both explicit and deductive observations @@ -768,25 +767,8 @@ class DeriverSettings(HonchoSettings): raise ValueError( f"REPRESENTATION_BATCH_MAX_TOKENS ({self.REPRESENTATION_BATCH_MAX_TOKENS}) cannot exceed max deriver input tokens ({self.MAX_INPUT_TOKENS})" ) - if ( - self.MAX_CUSTOM_INSTRUCTIONS_TOKENS is not None - and self.MAX_CUSTOM_INSTRUCTIONS_TOKENS > self.MAX_INPUT_TOKENS - ): - raise ValueError( - f"MAX_CUSTOM_INSTRUCTIONS_TOKENS ({self.MAX_CUSTOM_INSTRUCTIONS_TOKENS}) " - + f"cannot exceed max deriver input tokens ({self.MAX_INPUT_TOKENS})" - ) return self - @property - def effective_max_custom_instructions_tokens(self) -> int: - if self.MAX_CUSTOM_INSTRUCTIONS_TOKENS is None: - raise ValueError( - "DERIVER.MAX_CUSTOM_INSTRUCTIONS_TOKENS is not set; set " - + "[deriver].MAX_CUSTOM_INSTRUCTIONS_TOKENS in config.toml" - ) - return self.MAX_CUSTOM_INSTRUCTIONS_TOKENS - class PeerCardSettings(HonchoSettings): model_config = SettingsConfigDict(env_prefix="PEER_CARD_", extra="ignore") # pyright: ignore diff --git a/src/schemas/configuration.py b/src/schemas/configuration.py index 7a3c98bf..b8291cab 100644 --- a/src/schemas/configuration.py +++ b/src/schemas/configuration.py @@ -26,7 +26,7 @@ class ReasoningConfiguration(BaseModel): ) custom_instructions: str | None = Field( default=None, - description="Optional custom instructions for the reasoning system on this workspace/session/message. Non-blank values require an explicit deriver custom-instruction token cap and are rejected if they exceed it.", + description="Optional custom instructions for the reasoning system on this workspace/session/message. Rejected if they exceed the deriver custom-instruction token cap.", ) @field_validator("custom_instructions") @@ -92,7 +92,7 @@ def _validate_custom_instructions_budget( return custom_instructions max_tokens = settings.DERIVER.MAX_CUSTOM_INSTRUCTIONS_TOKENS - if max_tokens is None: + if max_tokens <= 0: raise ValueError("custom_instructions are not enabled for this deployment") if estimate_tokens(custom_instructions) > max_tokens: diff --git a/tests/test_config.py b/tests/test_config.py index f18efb4b..86ea0994 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -6,7 +6,7 @@ from src.config import ConfiguredModelSettings, DeriverSettings def _make_deriver_settings( *, MAX_INPUT_TOKENS: int = 25000, - MAX_CUSTOM_INSTRUCTIONS_TOKENS: int | None = None, + MAX_CUSTOM_INSTRUCTIONS_TOKENS: int = 2000, REPRESENTATION_BATCH_MAX_TOKENS: int = 1024, ) -> DeriverSettings: return DeriverSettings( @@ -20,38 +20,17 @@ def _make_deriver_settings( ) -def test_effective_custom_instructions_tokens_requires_explicit_limit() -> None: - settings = _make_deriver_settings() - - with pytest.raises( - ValueError, - match=r"set \[deriver\]\.MAX_CUSTOM_INSTRUCTIONS_TOKENS in config\.toml", - ): - _ = settings.effective_max_custom_instructions_tokens - - -def test_effective_custom_instructions_tokens_uses_explicit_limit() -> None: - settings = _make_deriver_settings(MAX_CUSTOM_INSTRUCTIONS_TOKENS=2000) - - assert settings.effective_max_custom_instructions_tokens == 2000 - - -def test_deriver_default_input_budget_accommodates_custom_instruction_cap() -> None: +def test_deriver_defaults_enable_custom_instructions_at_supported_cap() -> None: settings = _make_deriver_settings() assert settings.MAX_INPUT_TOKENS == 25000 + assert settings.MAX_CUSTOM_INSTRUCTIONS_TOKENS == 2000 -def test_custom_instructions_tokens_cannot_exceed_input_budget() -> None: - with pytest.raises( - ValueError, - match=r"MAX_CUSTOM_INSTRUCTIONS_TOKENS.*cannot exceed max deriver input tokens", - ): - _make_deriver_settings( - MAX_INPUT_TOKENS=400, - MAX_CUSTOM_INSTRUCTIONS_TOKENS=500, - REPRESENTATION_BATCH_MAX_TOKENS=128, - ) +def test_custom_instructions_tokens_can_be_disabled_with_zero() -> None: + settings = _make_deriver_settings(MAX_CUSTOM_INSTRUCTIONS_TOKENS=0) + + assert settings.MAX_CUSTOM_INSTRUCTIONS_TOKENS == 0 def test_custom_instructions_tokens_cannot_exceed_supported_cap() -> None: diff --git a/tests/test_schema_validations.py b/tests/test_schema_validations.py index 3bab9ffe..5ccceaf8 100644 --- a/tests/test_schema_validations.py +++ b/tests/test_schema_validations.py @@ -208,12 +208,10 @@ class TestResolvedConfigurationMigration: class TestReasoningCustomInstructionsValidation: - def test_nonblank_custom_instructions_require_enabled_deployment_cap( + def test_nonblank_custom_instructions_rejected_when_cap_is_zero( self, monkeypatch: pytest.MonkeyPatch ) -> None: - monkeypatch.setattr( - settings.DERIVER, "MAX_CUSTOM_INSTRUCTIONS_TOKENS", None, raising=False - ) + monkeypatch.setattr(settings.DERIVER, "MAX_CUSTOM_INSTRUCTIONS_TOKENS", 0) with pytest.raises(ValidationError) as exc_info: ReasoningConfiguration(custom_instructions="Prefer concrete facts.") @@ -225,14 +223,11 @@ class TestReasoningCustomInstructionsValidation: in error["msg"] for error in errors ) - assert "[deriver].MAX_CUSTOM_INSTRUCTIONS_TOKENS" not in str(exc_info.value) def test_reasoning_configuration_rejects_oversized_custom_instructions( self, monkeypatch: pytest.MonkeyPatch ) -> None: - monkeypatch.setattr( - settings.DERIVER, "MAX_CUSTOM_INSTRUCTIONS_TOKENS", 1, raising=False - ) + monkeypatch.setattr(settings.DERIVER, "MAX_CUSTOM_INSTRUCTIONS_TOKENS", 1) with pytest.raises(ValidationError) as exc_info: ReasoningConfiguration( @@ -247,9 +242,7 @@ class TestReasoningCustomInstructionsValidation: def test_oversized_custom_instructions_are_rejected( self, monkeypatch: pytest.MonkeyPatch ) -> None: - monkeypatch.setattr( - settings.DERIVER, "MAX_CUSTOM_INSTRUCTIONS_TOKENS", 1, raising=False - ) + monkeypatch.setattr(settings.DERIVER, "MAX_CUSTOM_INSTRUCTIONS_TOKENS", 1) payload = { "reasoning": { @@ -277,12 +270,8 @@ class TestReasoningCustomInstructionsValidation: def test_blank_custom_instructions_do_not_require_token_cap( self, monkeypatch: pytest.MonkeyPatch, custom_instructions: str ) -> None: - monkeypatch.setattr( - settings.DERIVER, "MAX_CUSTOM_INSTRUCTIONS_TOKENS", None, raising=False - ) + monkeypatch.setattr(settings.DERIVER, "MAX_CUSTOM_INSTRUCTIONS_TOKENS", 0) - configuration = ReasoningConfiguration( - custom_instructions=custom_instructions - ) + configuration = ReasoningConfiguration(custom_instructions=custom_instructions) assert configuration.custom_instructions == custom_instructions