diff --git a/app/services/llm_service.py b/app/services/llm_service.py index e955f87..7ba8668 100644 --- a/app/services/llm_service.py +++ b/app/services/llm_service.py @@ -72,7 +72,20 @@ def _complete(self, model: Optional[str], messages: List[dict], temperature: flo resolved = self._resolve_model(model) self._validate_model_environment(resolved) logger.debug("Requesting completion from model: %s", resolved) - return completion(model=resolved, messages=messages, temperature=temperature) + try: + return completion(model=resolved, messages=messages, temperature=temperature) + except litellm.BadRequestError as e: + # Some models (e.g. OpenAI's o1 / gpt-5 series) only accept the default + # temperature and reject any explicit value. Retry once letting the + # model use its own default rather than failing the whole request. + if "temperature" in str(e).lower(): + logger.warning( + "Model '%s' rejected temperature=%s; retrying with the model default.", + resolved, + temperature, + ) + return completion(model=resolved, messages=messages) + raise def get_artist_recommendations(self, prompt: str, artists: List[Artist], model: str = None): """First step: Get relevant artists based on the prompt""" diff --git a/tests/test_llm_service.py b/tests/test_llm_service.py index 3c7da3c..c717d91 100644 --- a/tests/test_llm_service.py +++ b/tests/test_llm_service.py @@ -173,3 +173,45 @@ def test_model_defaults_to_configured_value(mock_completion, sample_artists, mon _, kwargs = mock_completion.call_args assert kwargs["model"] == "gpt-4" config.get_settings.cache_clear() + + +def test_complete_retries_without_temperature_when_unsupported(mock_completion, sample_artists): + """Models that reject a custom temperature are retried with the model default.""" + import litellm # pylint: disable=import-outside-toplevel + + error = litellm.BadRequestError( + "OpenAIException - Unsupported value: 'temperature' does not support 0.7 with this model.", + model="gpt-5.6-sol", + llm_provider="openai", + ) + good_response = Mock() + good_response.choices = [Mock(message=Mock(content='{"artists": ["Artist1"]}'))] + + calls = [] + + def side_effect(*args, **kwargs): # pylint: disable=unused-argument + calls.append(kwargs) + if "temperature" in kwargs: + raise error + return good_response + + mock_completion.side_effect = side_effect + + service = LLMService() + result = service.get_artist_recommendations("Test prompt", sample_artists, model="gpt-5.6-sol") + + assert result == ["Artist1"] + assert len(calls) == 2 # first attempt (with temperature) failed, retry succeeded + assert "temperature" not in calls[1] + + +def test_complete_reraises_unrelated_bad_request(mock_completion, sample_artists): + """A BadRequestError unrelated to temperature is not swallowed.""" + import litellm # pylint: disable=import-outside-toplevel + + error = litellm.BadRequestError("context_length_exceeded", model="gpt-4", llm_provider="openai") + mock_completion.side_effect = error + + service = LLMService() + with pytest.raises(litellm.BadRequestError, match="context_length_exceeded"): + service.get_artist_recommendations("Test prompt", sample_artists, model="gpt-4")