feat: improve Gemini client error handling and retry logic

• Add google-api-core dependency
• Add specific exception handling
• Create InvalidResponseError class
• Update retry decorators
• Fix empty response handling
This commit is contained in:
yangdx 2025-11-08 22:10:09 +08:00
parent cf732dbfc6
commit 3d9de5ed03
3 changed files with 38 additions and 3 deletions

View file

@ -33,18 +33,27 @@ from lightrag.utils import (
import pipmaster as pm import pipmaster as pm
# Install the Google Gemini client on demand # Install the Google Gemini client and its dependencies on demand
if not pm.is_installed("google-genai"): if not pm.is_installed("google-genai"):
pm.install("google-genai") pm.install("google-genai")
if not pm.is_installed("google-api-core"):
pm.install("google-api-core")
from google import genai # type: ignore from google import genai # type: ignore
from google.genai import types # type: ignore from google.genai import types # type: ignore
from google.api_core import exceptions as google_api_exceptions # type: ignore
DEFAULT_GEMINI_ENDPOINT = "https://generativelanguage.googleapis.com" DEFAULT_GEMINI_ENDPOINT = "https://generativelanguage.googleapis.com"
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
class InvalidResponseError(Exception):
"""Custom exception class for triggering retry mechanism when Gemini returns empty responses"""
pass
@lru_cache(maxsize=8) @lru_cache(maxsize=8)
def _get_gemini_client( def _get_gemini_client(
api_key: str, base_url: str | None, timeout: int | None = None api_key: str, base_url: str | None, timeout: int | None = None
@ -176,6 +185,21 @@ def _extract_response_text(
return ("\n".join(regular_parts), "\n".join(thought_parts)) return ("\n".join(regular_parts), "\n".join(thought_parts))
@retry(
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=4, max=60),
retry=(
retry_if_exception_type(google_api_exceptions.InternalServerError)
| retry_if_exception_type(google_api_exceptions.ServiceUnavailable)
| retry_if_exception_type(google_api_exceptions.ResourceExhausted)
| retry_if_exception_type(google_api_exceptions.GatewayTimeout)
| retry_if_exception_type(google_api_exceptions.BadGateway)
| retry_if_exception_type(google_api_exceptions.DeadlineExceeded)
| retry_if_exception_type(google_api_exceptions.Aborted)
| retry_if_exception_type(google_api_exceptions.Unknown)
| retry_if_exception_type(InvalidResponseError)
),
)
async def gemini_complete_if_cache( async def gemini_complete_if_cache(
model: str, model: str,
prompt: str, prompt: str,
@ -382,7 +406,7 @@ async def gemini_complete_if_cache(
final_text = regular_text or "" final_text = regular_text or ""
if not final_text: if not final_text:
raise RuntimeError("Gemini response did not contain any text content.") raise InvalidResponseError("Gemini response did not contain any text content.")
if "\\u" in final_text: if "\\u" in final_text:
final_text = safe_unicode_decode(final_text.encode("utf-8")) final_text = safe_unicode_decode(final_text.encode("utf-8"))
@ -434,7 +458,14 @@ async def gemini_model_complete(
stop=stop_after_attempt(3), stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=4, max=60), wait=wait_exponential(multiplier=1, min=4, max=60),
retry=( retry=(
retry_if_exception_type(Exception) # Gemini uses generic exceptions retry_if_exception_type(google_api_exceptions.InternalServerError)
| retry_if_exception_type(google_api_exceptions.ServiceUnavailable)
| retry_if_exception_type(google_api_exceptions.ResourceExhausted)
| retry_if_exception_type(google_api_exceptions.GatewayTimeout)
| retry_if_exception_type(google_api_exceptions.BadGateway)
| retry_if_exception_type(google_api_exceptions.DeadlineExceeded)
| retry_if_exception_type(google_api_exceptions.Aborted)
| retry_if_exception_type(google_api_exceptions.Unknown)
), ),
) )
async def gemini_embed( async def gemini_embed(

View file

@ -24,6 +24,7 @@ dependencies = [
"aiohttp", "aiohttp",
"configparser", "configparser",
"future", "future",
"google-api-core>=2.0.0,<3.0.0",
"google-genai>=1.0.0,<2.0.0", "google-genai>=1.0.0,<2.0.0",
"json_repair", "json_repair",
"nano-vectordb", "nano-vectordb",
@ -60,6 +61,7 @@ api = [
"tenacity", "tenacity",
"tiktoken", "tiktoken",
"xlsxwriter>=3.1.0", "xlsxwriter>=3.1.0",
"google-api-core>=2.0.0,<3.0.0",
"google-genai>=1.0.0,<2.0.0", "google-genai>=1.0.0,<2.0.0",
# API-specific dependencies # API-specific dependencies
"aiofiles", "aiofiles",
@ -108,6 +110,7 @@ offline-llm = [
"aioboto3>=12.0.0,<16.0.0", "aioboto3>=12.0.0,<16.0.0",
"voyageai>=0.2.0,<1.0.0", "voyageai>=0.2.0,<1.0.0",
"llama-index>=0.9.0,<1.0.0", "llama-index>=0.9.0,<1.0.0",
"google-api-core>=2.0.0,<3.0.0",
"google-genai>=1.0.0,<2.0.0", "google-genai>=1.0.0,<2.0.0",
] ]

View file

@ -10,6 +10,7 @@
# LLM provider dependencies (with version constraints matching pyproject.toml) # LLM provider dependencies (with version constraints matching pyproject.toml)
aioboto3>=12.0.0,<16.0.0 aioboto3>=12.0.0,<16.0.0
anthropic>=0.18.0,<1.0.0 anthropic>=0.18.0,<1.0.0
google-api-core>=2.0.0,<3.0.0
google-genai>=1.0.0,<2.0.0 google-genai>=1.0.0,<2.0.0
llama-index>=0.9.0,<1.0.0 llama-index>=0.9.0,<1.0.0
ollama>=0.1.0,<1.0.0 ollama>=0.1.0,<1.0.0