Fix empty error messages from Google GenAI SDK
Catch genai_errors.APIError specifically and extract e.code and e.message attributes for proper error logging. The generic str(e) was returning empty strings for Google API errors, making debugging impossible. - Import google.genai.errors for specific exception handling - Add APIError catch before generic Exception in generate_content() - Add APIError catch before generic Exception in generate_contextual_response() - Properly categorize errors by HTTP code for retry logic (429/500+ retryable) - Fix time.sleep to await asyncio.sleep in contextual response handler 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
ca93d24ac5
commit
b50c0fa2a4
1 changed files with 87 additions and 17 deletions
|
|
@ -11,6 +11,7 @@ import asyncio
|
|||
import logging
|
||||
import base64
|
||||
from google import genai
|
||||
from google.genai import errors as genai_errors
|
||||
from openai import AsyncOpenAI
|
||||
import httpx
|
||||
from typing import Dict, Any, Optional, Union, List
|
||||
|
|
@ -269,20 +270,48 @@ class LLMService:
|
|||
logger.info(f"LLM content generation succeeded on attempt {attempt_num}/{max_retries}")
|
||||
return result
|
||||
|
||||
except genai_errors.APIError as e:
|
||||
# Google GenAI SDK specific error handling
|
||||
last_error = e
|
||||
error_code = getattr(e, 'code', 'unknown')
|
||||
error_message = getattr(e, 'message', str(e)) or str(e) or repr(e)
|
||||
|
||||
logger.warning(f"LLM attempt {attempt_num}/{max_retries} failed: [Google API {error_code}] {error_message}")
|
||||
|
||||
# Retryable: 429 rate limit, 500+ server errors
|
||||
is_retryable = (
|
||||
error_code == 429 or
|
||||
(isinstance(error_code, int) and error_code >= 500)
|
||||
)
|
||||
|
||||
if is_retryable:
|
||||
if attempt < max_retries - 1:
|
||||
wait_time = 2 ** attempt
|
||||
logger.info(f"Retryable Google API error. Waiting {wait_time}s before retry {attempt_num + 1}/{max_retries}")
|
||||
await asyncio.sleep(wait_time)
|
||||
continue
|
||||
else:
|
||||
logger.error(f"Retryable Google API error [{error_code}] but max retries ({max_retries}) reached")
|
||||
else:
|
||||
# 400, 403, 404, etc. - non-retryable
|
||||
logger.error(f"Non-retryable Google API error [{error_code}]: {error_message}")
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
# Fallback for OpenAI and other non-Google errors
|
||||
last_error = e
|
||||
error_message = str(e).lower()
|
||||
|
||||
|
||||
logger.warning(f"LLM attempt {attempt_num}/{max_retries} failed: {str(e)}")
|
||||
|
||||
|
||||
# Check if this is a retryable error (API internal errors, rate limiting, etc.)
|
||||
if ("500" in error_message or
|
||||
"internal error" in error_message or
|
||||
if ("500" in error_message or
|
||||
"internal error" in error_message or
|
||||
"internal server error" in error_message or
|
||||
"service unavailable" in error_message or
|
||||
"timeout" in error_message or
|
||||
"rate" in error_message):
|
||||
|
||||
|
||||
if attempt < max_retries - 1:
|
||||
# Wait before retrying (exponential backoff)
|
||||
wait_time = 2 ** attempt # 1s, 2s, 4s
|
||||
|
|
@ -294,10 +323,17 @@ class LLMService:
|
|||
else:
|
||||
logger.error(f"Non-retryable error detected: {str(e)}")
|
||||
break
|
||||
|
||||
|
||||
# If we've exhausted all retries or hit a non-retryable error, raise the last error
|
||||
logger.error(f"LLM content generation failed after {max_retries} attempts. Final error: {str(last_error)}")
|
||||
raise LLMServiceError(f"Error generating content: {str(last_error)}")
|
||||
error_detail = ""
|
||||
if isinstance(last_error, genai_errors.APIError):
|
||||
error_code = getattr(last_error, 'code', 'unknown')
|
||||
error_msg = getattr(last_error, 'message', str(last_error)) or str(last_error) or repr(last_error)
|
||||
error_detail = f"[Google API {error_code}] {error_msg}"
|
||||
else:
|
||||
error_detail = str(last_error)
|
||||
logger.error(f"LLM content generation failed after {max_retries} attempts. Final error: {error_detail}")
|
||||
raise LLMServiceError(f"Error generating content: {error_detail}")
|
||||
|
||||
@staticmethod
|
||||
def parse_json_response(response_text: str) -> Union[Dict[str, Any], List[Any]]:
|
||||
|
|
@ -795,34 +831,68 @@ class LLMService:
|
|||
print(f" - Result repr: {repr(result[:50]) if result else 'NONE'}")
|
||||
return result
|
||||
|
||||
except genai_errors.APIError as e:
|
||||
# Google GenAI SDK specific error handling
|
||||
last_error = e
|
||||
error_code = getattr(e, 'code', 'unknown')
|
||||
error_message = getattr(e, 'message', str(e)) or str(e) or repr(e)
|
||||
|
||||
logger.warning(f"Contextual multimodal attempt {attempt_num}/{max_retries} failed: [Google API {error_code}] {error_message}")
|
||||
|
||||
# Retryable: 429 rate limit, 500+ server errors
|
||||
is_retryable = (
|
||||
error_code == 429 or
|
||||
(isinstance(error_code, int) and error_code >= 500)
|
||||
)
|
||||
|
||||
if is_retryable:
|
||||
if attempt < max_retries - 1:
|
||||
wait_time = 2 ** attempt
|
||||
logger.info(f"Retryable Google API error. Waiting {wait_time}s before retry {attempt_num + 1}/{max_retries}")
|
||||
await asyncio.sleep(wait_time)
|
||||
continue
|
||||
else:
|
||||
logger.error(f"Retryable Google API error [{error_code}] but max retries ({max_retries}) reached")
|
||||
else:
|
||||
logger.error(f"Non-retryable Google API error [{error_code}]: {error_message}")
|
||||
break
|
||||
|
||||
except Exception as e:
|
||||
# Fallback for non-Google errors
|
||||
last_error = e
|
||||
error_message = str(e).lower()
|
||||
|
||||
|
||||
logger.warning(f"Contextual multimodal attempt {attempt_num}/{max_retries} failed: {str(e)}")
|
||||
|
||||
|
||||
# Check if this is a retryable error
|
||||
if ("500" in error_message or
|
||||
"internal error" in error_message or
|
||||
if ("500" in error_message or
|
||||
"internal error" in error_message or
|
||||
"internal server error" in error_message or
|
||||
"service unavailable" in error_message or
|
||||
"timeout" in error_message or
|
||||
"rate" in error_message):
|
||||
|
||||
|
||||
if attempt < max_retries - 1:
|
||||
wait_time = 2 ** attempt
|
||||
logger.info(f"Retryable error detected. Waiting {wait_time} seconds before retry {attempt_num + 1}/{max_retries}")
|
||||
time.sleep(wait_time)
|
||||
await asyncio.sleep(wait_time)
|
||||
continue
|
||||
else:
|
||||
logger.error(f"Retryable error detected but max retries ({max_retries}) reached")
|
||||
else:
|
||||
logger.error(f"Non-retryable error detected: {str(e)}")
|
||||
break
|
||||
|
||||
|
||||
# If multimodal failed, raise the error
|
||||
logger.error(f"Contextual multimodal generation failed after {max_retries} attempts. Final error: {str(last_error)}")
|
||||
raise LLMServiceError(f"Error generating contextual multimodal content: {str(last_error)}")
|
||||
error_detail = ""
|
||||
if isinstance(last_error, genai_errors.APIError):
|
||||
error_code = getattr(last_error, 'code', 'unknown')
|
||||
error_msg = getattr(last_error, 'message', str(last_error)) or str(last_error) or repr(last_error)
|
||||
error_detail = f"[Google API {error_code}] {error_msg}"
|
||||
else:
|
||||
error_detail = str(last_error)
|
||||
logger.error(f"Contextual multimodal generation failed after {max_retries} attempts. Final error: {error_detail}")
|
||||
raise LLMServiceError(f"Error generating contextual multimodal content: {error_detail}")
|
||||
|
||||
else:
|
||||
# No images, use standard text generation
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue