diff --git a/backend/src/plugins/llm_analysis/service.py b/backend/src/plugins/llm_analysis/service.py index 70bebe54..8d5ec1fe 100644 --- a/backend/src/plugins/llm_analysis/service.py +++ b/backend/src/plugins/llm_analysis/service.py @@ -953,12 +953,19 @@ class LLMClient: # @POST Returns a parsed JSON dictionary. # @SIDE_EFFECT Calls external LLM API. def _should_retry(exception: Exception) -> bool: - """Custom retry predicate that excludes authentication errors.""" + """Custom retry predicate that excludes non-recoverable errors.""" # Don't retry on authentication errors if isinstance(exception, OpenAIAuthenticationError): return False - # Retry on rate limit errors and other exceptions - return isinstance(exception, (RateLimitError, Exception)) + # Don't retry on null content / model errors — retrying won't help + msg = str(exception).lower() + if "null content" in msg or "none" in msg: + return False + # Retry on rate limit errors + if isinstance(exception, RateLimitError): + return True + # For other exceptions, limit retries + return True @retry( stop=stop_after_attempt(5), @@ -1058,6 +1065,10 @@ class LLMClient: content = response.choices[0].message.content logger.debug(f"[get_json_completion] Raw content to parse: {content}") + # LLM returned null content — likely content filter or rate limit + if content is None: + raise RuntimeError("LLM returned null content (content filter or rate limit)") + try: return json.loads(content) except json.JSONDecodeError: