Add timeouts to Gemini API calls to prevent 10+ minute hangs

Primary model (gemini-3.1-pro-preview): 45s timeout
Fallback model (gemini-3-flash-preview): 60s timeout

Without timeouts, the fallback model under high load would wait
indefinitely, causing analysis to hang for 10+ minutes per file.
asyncio.TimeoutError from the primary model is now handled the same
as other exceptions (falls through to fallback).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Vadym Samoilenko 2026-03-02 12:42:04 +00:00
parent 84d5b533f7
commit e8c0257ea6

View file

@ -1,3 +1,4 @@
import asyncio
import json
import logging
from typing import List, Tuple
@ -10,6 +11,10 @@ from app.models.schemas import SubReview, RagStatus
# Configure logging
logger = logging.getLogger(__name__)
# Timeout (seconds) for each Gemini API call
_PRIMARY_TIMEOUT = 45
_FALLBACK_TIMEOUT = 60
class GeminiService:
"""Service wrapper for Google Gemini API calls."""
@ -26,23 +31,34 @@ class GeminiService:
self.fallback_model = "gemini-3-flash-preview"
async def _generate_content(self, contents, config) -> any:
"""Call generate_content, falling back to fallback_model if the primary fails."""
"""Call generate_content, falling back to fallback_model if the primary fails or times out."""
try:
return await self.client.aio.models.generate_content(
model=self.model,
contents=contents,
config=config,
return await asyncio.wait_for(
self.client.aio.models.generate_content(
model=self.model,
contents=contents,
config=config,
),
timeout=_PRIMARY_TIMEOUT,
)
except asyncio.TimeoutError:
logger.warning(
f"[GEMINI API] Primary model {self.model} timed out after {_PRIMARY_TIMEOUT}s. "
f"Retrying with fallback {self.fallback_model}"
)
except Exception as e:
logger.warning(
f"[GEMINI API] Primary model {self.model} failed: {e}. "
f"Retrying with fallback {self.fallback_model}"
)
return await self.client.aio.models.generate_content(
return await asyncio.wait_for(
self.client.aio.models.generate_content(
model=self.fallback_model,
contents=contents,
config=config,
)
),
timeout=_FALLBACK_TIMEOUT,
)
async def analyze_with_image(
self,