Fix: Persona Polling Issue

This commit is contained in:
ajaysi
2025-10-12 14:27:15 +05:30
parent 2a3ad8addc
commit 08ce9588f4
5 changed files with 75 additions and 20 deletions

View File

@@ -37,7 +37,7 @@ class OnboardingCompletionService:
# Validate API keys are configured # Validate API keys are configured
self._validate_api_keys() self._validate_api_keys()
# Generate writing persona from onboarding data # Generate writing persona from onboarding data only if not already present
persona_generated = await self._generate_persona_from_onboarding(user_id) persona_generated = await self._generate_persona_from_onboarding(user_id)
# Complete the onboarding process # Complete the onboarding process
@@ -144,9 +144,18 @@ class OnboardingCompletionService:
try: try:
persona_service = PersonaAnalysisService() persona_service = PersonaAnalysisService()
# Use user_id = 1 for now (assuming single user system) # If a persona already exists for this user, skip regeneration
persona_user_id = 1 try:
persona_result = persona_service.generate_persona_from_onboarding(persona_user_id) existing = persona_service.get_user_personas(int(user_id))
if existing and len(existing) > 0:
logger.info("Persona already exists for user %s; skipping regeneration during completion", user_id)
return False
except Exception:
# Non-fatal; proceed to attempt generation
pass
# Generate persona for this user
persona_result = persona_service.generate_persona_from_onboarding(int(user_id))
if "error" not in persona_result: if "error" not in persona_result:
logger.info(f"✅ Writing persona generated during onboarding completion: {persona_result.get('persona_id')}") logger.info(f"✅ Writing persona generated during onboarding completion: {persona_result.get('persona_id')}")

View File

@@ -531,7 +531,13 @@ async def execute_persona_generation_task(task_id: str, persona_request: Persona
) )
if "error" in core_persona: if "error" in core_persona:
update_task_status(task_id, "failed", 0, f"Core persona generation failed: {core_persona['error']}") error_msg = core_persona['error']
# Check if this is a quota/rate limit error
if "RESOURCE_EXHAUSTED" in str(error_msg) or "429" in str(error_msg) or "quota" in str(error_msg).lower():
update_task_status(task_id, "failed", 0, f"Quota exhausted: {error_msg}", error=str(error_msg))
logger.error(f"Task {task_id}: Quota exhausted, marking as failed immediately")
else:
update_task_status(task_id, "failed", 0, f"Core persona generation failed: {error_msg}", error=str(error_msg))
return return
update_task_status(task_id, "running", 40, "Core persona generated successfully") update_task_status(task_id, "running", 40, "Core persona generated successfully")

View File

@@ -475,20 +475,14 @@ def gemini_structured_json_response(prompt, schema, temperature=0.7, top_p=0.9,
logger.error(f"API key error in Gemini Pro structured JSON generation: {e}") logger.error(f"API key error in Gemini Pro structured JSON generation: {e}")
return {"error": str(e)} return {"error": str(e)}
except Exception as e: except Exception as e:
# Let tenacity handle retries, especially for 429 RESOURCE_EXHAUSTED # Check if this is a quota/rate limit error
msg = str(e) msg = str(e)
if "RESOURCE_EXHAUSTED" in msg or "429" in msg or "rate limit" in msg.lower(): if "RESOURCE_EXHAUSTED" in msg or "429" in msg or "quota" in msg.lower():
# If RetryInfo is present with a retryDelay, honor it before re-raising logger.error(f"Rate limit/quota error in Gemini Pro structured JSON generation: {msg}")
try: # Return error instead of retrying - quota exhausted means we need to wait or upgrade plan
import re, time return {"error": msg}
m = re.search(r"retryDelay':\s*'?(\d+)s" , msg) # For other errors, let tenacity handle retries
if m: logger.error(f"Error in Gemini Pro structured JSON generation: {e}")
delay_s = int(m.group(1))
logger.warning(f"Rate limit hit, sleeping {delay_s}s before retry...")
time.sleep(delay_s)
except Exception:
pass
# Re-raise to trigger tenacity's backoff/retry
raise raise

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 B

After

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -19,7 +19,8 @@ export interface PersonaTaskStatus {
export interface UsePersonaPollingOptions { export interface UsePersonaPollingOptions {
interval?: number; // Polling interval in milliseconds interval?: number; // Polling interval in milliseconds
maxAttempts?: number; // Maximum number of polling attempts maxAttempts?: number; // Maximum number of polling attempts (default: 180 = 6 minutes at 2s interval)
maxDuration?: number; // Maximum polling duration in milliseconds (default: 10 minutes)
onProgress?: (message: string, progress: number) => void; // Callback for progress updates onProgress?: (message: string, progress: number) => void; // Callback for progress updates
onComplete?: (result: any) => void; // Callback when task completes onComplete?: (result: any) => void; // Callback when task completes
onError?: (error: string) => void; // Callback when task fails onError?: (error: string) => void; // Callback when task fails
@@ -40,6 +41,8 @@ export interface UsePersonaPollingReturn {
export function usePersonaPolling(options: UsePersonaPollingOptions = {}): UsePersonaPollingReturn { export function usePersonaPolling(options: UsePersonaPollingOptions = {}): UsePersonaPollingReturn {
const { const {
interval = 2000, // 2 seconds default interval = 2000, // 2 seconds default
maxAttempts = 180, // 6 minutes at 2s interval
maxDuration = 600000, // 10 minutes in milliseconds
onProgress, onProgress,
onComplete, onComplete,
onError onError
@@ -67,6 +70,9 @@ export function usePersonaPolling(options: UsePersonaPollingOptions = {}): UsePe
const intervalRef = useRef<NodeJS.Timeout | null>(null); const intervalRef = useRef<NodeJS.Timeout | null>(null);
const attemptsRef = useRef(0); const attemptsRef = useRef(0);
const currentTaskIdRef = useRef<string | null>(null); const currentTaskIdRef = useRef<string | null>(null);
const startTimeRef = useRef<number>(0);
const stuckProgressRef = useRef<number>(0);
const stuckCountRef = useRef<number>(0);
const stopPolling = useCallback(() => { const stopPolling = useCallback(() => {
console.log('stopPersonaPolling called'); console.log('stopPersonaPolling called');
@@ -78,6 +84,9 @@ export function usePersonaPolling(options: UsePersonaPollingOptions = {}): UsePe
setIsPolling(false); setIsPolling(false);
attemptsRef.current = 0; attemptsRef.current = 0;
currentTaskIdRef.current = null; currentTaskIdRef.current = null;
startTimeRef.current = 0;
stuckProgressRef.current = 0;
stuckCountRef.current = 0;
}, []); }, []);
const startPolling = useCallback((taskId: string) => { const startPolling = useCallback((taskId: string) => {
@@ -97,6 +106,9 @@ export function usePersonaPolling(options: UsePersonaPollingOptions = {}): UsePe
setResult(null); setResult(null);
setError(null); setError(null);
attemptsRef.current = 0; attemptsRef.current = 0;
startTimeRef.current = Date.now();
stuckProgressRef.current = 0;
stuckCountRef.current = 0;
const poll = async () => { const poll = async () => {
if (!currentTaskIdRef.current) { if (!currentTaskIdRef.current) {
@@ -104,6 +116,25 @@ export function usePersonaPolling(options: UsePersonaPollingOptions = {}): UsePe
return; return;
} }
// Check max attempts
if (attemptsRef.current >= maxAttempts) {
console.error('Persona polling: Max attempts reached');
setError('Persona generation timed out - please try again later');
onError?.('Persona generation timed out after maximum attempts');
stopPolling();
return;
}
// Check max duration
const elapsed = Date.now() - startTimeRef.current;
if (elapsed >= maxDuration) {
console.error('Persona polling: Max duration reached');
setError('Persona generation timed out - please try again later');
onError?.('Persona generation exceeded maximum duration');
stopPolling();
return;
}
try { try {
const response = await apiClient.get(`/api/onboarding/step4/persona-task/${currentTaskIdRef.current}`); const response = await apiClient.get(`/api/onboarding/step4/persona-task/${currentTaskIdRef.current}`);
const status: PersonaTaskStatus = response.data; const status: PersonaTaskStatus = response.data;
@@ -113,6 +144,21 @@ export function usePersonaPolling(options: UsePersonaPollingOptions = {}): UsePe
setProgress(status.progress); setProgress(status.progress);
setCurrentStep(status.current_step); setCurrentStep(status.current_step);
// Detect stuck progress (same progress for 20+ consecutive polls = ~40 seconds)
if (status.progress === stuckProgressRef.current) {
stuckCountRef.current++;
if (stuckCountRef.current >= 20) {
console.error('Persona polling: Progress stuck at', status.progress, 'for too long');
setError('Persona generation appears stuck - please try again or contact support');
onError?.('Persona generation stuck - no progress for extended period');
stopPolling();
return;
}
} else {
stuckProgressRef.current = status.progress;
stuckCountRef.current = 0;
}
// Update progress messages // Update progress messages
if (status.progress_messages && status.progress_messages.length > 0) { if (status.progress_messages && status.progress_messages.length > 0) {
console.log('Progress messages received:', status.progress_messages); console.log('Progress messages received:', status.progress_messages);
@@ -154,7 +200,7 @@ export function usePersonaPolling(options: UsePersonaPollingOptions = {}): UsePe
// Start polling immediately, then at intervals // Start polling immediately, then at intervals
poll(); poll();
intervalRef.current = setInterval(poll, interval); intervalRef.current = setInterval(poll, interval);
}, [isPolling, interval, onProgress, onComplete, onError, stopPolling]); }, [isPolling, interval, maxAttempts, maxDuration, onProgress, onComplete, onError, stopPolling]);
// Cleanup on unmount // Cleanup on unmount
useEffect(() => { useEffect(() => {