diff --git a/run_agent.py b/run_agent.py index 080156051..e98a6e798 100644 --- a/run_agent.py +++ b/run_agent.py @@ -1268,6 +1268,19 @@ class AIAgent: try: _config_context_length = int(_config_context_length) except (TypeError, ValueError): + logger.warning( + "Invalid model.context_length in config.yaml: %r — " + "must be a plain integer (e.g. 256000, not '256K'). " + "Falling back to auto-detection.", + _config_context_length, + ) + import sys + print( + f"\n⚠ Invalid model.context_length in config.yaml: {_config_context_length!r}\n" + f" Must be a plain integer (e.g. 256000, not '256K').\n" + f" Falling back to auto-detected context window.\n", + file=sys.stderr, + ) _config_context_length = None # Store for reuse in switch_model (so config override persists across model switches) @@ -1296,7 +1309,20 @@ class AIAgent: try: _config_context_length = int(_cp_ctx) except (TypeError, ValueError): - pass + logger.warning( + "Invalid context_length for model %r in " + "custom_providers: %r — must be a plain " + "integer (e.g. 256000, not '256K'). " + "Falling back to auto-detection.", + self.model, _cp_ctx, + ) + import sys + print( + f"\n⚠ Invalid context_length for model {self.model!r} in custom_providers: {_cp_ctx!r}\n" + f" Must be a plain integer (e.g. 256000, not '256K').\n" + f" Falling back to auto-detected context window.\n", + file=sys.stderr, + ) break # Select context engine: config-driven (like memory providers). diff --git a/tests/run_agent/test_invalid_context_length_warning.py b/tests/run_agent/test_invalid_context_length_warning.py new file mode 100644 index 000000000..1ed72c951 --- /dev/null +++ b/tests/run_agent/test_invalid_context_length_warning.py @@ -0,0 +1,111 @@ +"""Tests that invalid context_length values in config produce visible warnings.""" + +from unittest.mock import patch, MagicMock, call + + +def _build_agent(model_cfg, custom_providers=None, model="anthropic/claude-opus-4.6"): + """Build an AIAgent with the given model config.""" + cfg = {"model": model_cfg} + if custom_providers is not None: + cfg["custom_providers"] = custom_providers + + with ( + patch("hermes_cli.config.load_config", return_value=cfg), + patch("agent.model_metadata.get_model_context_length", return_value=128_000), + patch("run_agent.get_tool_definitions", return_value=[]), + patch("run_agent.check_toolset_requirements", return_value={}), + patch("run_agent.OpenAI"), + ): + from run_agent import AIAgent + + agent = AIAgent( + model=model, + api_key="test-key-1234567890", + quiet_mode=True, + skip_context_files=True, + skip_memory=True, + ) + return agent + + +def test_valid_integer_context_length_no_warning(): + """Plain integer context_length should work silently.""" + with patch("run_agent.logger") as mock_logger: + agent = _build_agent({"default": "gpt5.4", "provider": "custom", + "base_url": "http://localhost:4000/v1", + "context_length": 256000}) + assert agent._config_context_length == 256000 + # No warning about invalid context_length + for c in mock_logger.warning.call_args_list: + assert "Invalid" not in str(c) + + +def test_string_k_suffix_context_length_warns(): + """context_length: '256K' should warn the user clearly.""" + with patch("run_agent.logger") as mock_logger: + agent = _build_agent({"default": "gpt5.4", "provider": "custom", + "base_url": "http://localhost:4000/v1", + "context_length": "256K"}) + assert agent._config_context_length is None + # Should have warned + warning_calls = [c for c in mock_logger.warning.call_args_list + if "Invalid" in str(c) and "256K" in str(c)] + assert len(warning_calls) == 1 + assert "plain integer" in str(warning_calls[0]) + + +def test_string_numeric_context_length_works(): + """context_length: '256000' (string) should parse fine via int().""" + with patch("run_agent.logger") as mock_logger: + agent = _build_agent({"default": "gpt5.4", "provider": "custom", + "base_url": "http://localhost:4000/v1", + "context_length": "256000"}) + assert agent._config_context_length == 256000 + for c in mock_logger.warning.call_args_list: + assert "Invalid" not in str(c) + + +def test_custom_providers_invalid_context_length_warns(): + """Invalid context_length in custom_providers should warn.""" + custom_providers = [ + { + "name": "LiteLLM", + "base_url": "http://localhost:4000/v1", + "models": { + "gpt5.4": {"context_length": "256K"} + }, + } + ] + with patch("run_agent.logger") as mock_logger: + agent = _build_agent( + {"default": "gpt5.4", "provider": "custom", + "base_url": "http://localhost:4000/v1"}, + custom_providers=custom_providers, + model="gpt5.4", + ) + warning_calls = [c for c in mock_logger.warning.call_args_list + if "Invalid" in str(c) and "256K" in str(c)] + assert len(warning_calls) == 1 + assert "custom_providers" in str(warning_calls[0]) + + +def test_custom_providers_valid_context_length(): + """Valid integer in custom_providers should work silently.""" + custom_providers = [ + { + "name": "LiteLLM", + "base_url": "http://localhost:4000/v1", + "models": { + "gpt5.4": {"context_length": 256000} + }, + } + ] + with patch("run_agent.logger") as mock_logger: + agent = _build_agent( + {"default": "gpt5.4", "provider": "custom", + "base_url": "http://localhost:4000/v1"}, + custom_providers=custom_providers, + model="gpt5.4", + ) + for c in mock_logger.warning.call_args_list: + assert "Invalid" not in str(c)