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
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)
# Complete the onboarding process
@@ -144,9 +144,18 @@ class OnboardingCompletionService:
try:
persona_service = PersonaAnalysisService()
# Use user_id = 1 for now (assuming single user system)
persona_user_id = 1
persona_result = persona_service.generate_persona_from_onboarding(persona_user_id)
# If a persona already exists for this user, skip regeneration
try:
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:
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:
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
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}")
return {"error": str(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)
if "RESOURCE_EXHAUSTED" in msg or "429" in msg or "rate limit" in msg.lower():
# If RetryInfo is present with a retryDelay, honor it before re-raising
try:
import re, time
m = re.search(r"retryDelay':\s*'?(\d+)s" , msg)
if m:
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
if "RESOURCE_EXHAUSTED" in msg or "429" in msg or "quota" in msg.lower():
logger.error(f"Rate limit/quota error in Gemini Pro structured JSON generation: {msg}")
# Return error instead of retrying - quota exhausted means we need to wait or upgrade plan
return {"error": msg}
# For other errors, let tenacity handle retries
logger.error(f"Error in Gemini Pro structured JSON generation: {e}")
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 {
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
onComplete?: (result: any) => void; // Callback when task completes
onError?: (error: string) => void; // Callback when task fails
@@ -40,6 +41,8 @@ export interface UsePersonaPollingReturn {
export function usePersonaPolling(options: UsePersonaPollingOptions = {}): UsePersonaPollingReturn {
const {
interval = 2000, // 2 seconds default
maxAttempts = 180, // 6 minutes at 2s interval
maxDuration = 600000, // 10 minutes in milliseconds
onProgress,
onComplete,
onError
@@ -67,6 +70,9 @@ export function usePersonaPolling(options: UsePersonaPollingOptions = {}): UsePe
const intervalRef = useRef<NodeJS.Timeout | null>(null);
const attemptsRef = useRef(0);
const currentTaskIdRef = useRef<string | null>(null);
const startTimeRef = useRef<number>(0);
const stuckProgressRef = useRef<number>(0);
const stuckCountRef = useRef<number>(0);
const stopPolling = useCallback(() => {
console.log('stopPersonaPolling called');
@@ -78,6 +84,9 @@ export function usePersonaPolling(options: UsePersonaPollingOptions = {}): UsePe
setIsPolling(false);
attemptsRef.current = 0;
currentTaskIdRef.current = null;
startTimeRef.current = 0;
stuckProgressRef.current = 0;
stuckCountRef.current = 0;
}, []);
const startPolling = useCallback((taskId: string) => {
@@ -97,6 +106,9 @@ export function usePersonaPolling(options: UsePersonaPollingOptions = {}): UsePe
setResult(null);
setError(null);
attemptsRef.current = 0;
startTimeRef.current = Date.now();
stuckProgressRef.current = 0;
stuckCountRef.current = 0;
const poll = async () => {
if (!currentTaskIdRef.current) {
@@ -104,6 +116,25 @@ export function usePersonaPolling(options: UsePersonaPollingOptions = {}): UsePe
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 {
const response = await apiClient.get(`/api/onboarding/step4/persona-task/${currentTaskIdRef.current}`);
const status: PersonaTaskStatus = response.data;
@@ -113,6 +144,21 @@ export function usePersonaPolling(options: UsePersonaPollingOptions = {}): UsePe
setProgress(status.progress);
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
if (status.progress_messages && status.progress_messages.length > 0) {
console.log('Progress messages received:', status.progress_messages);
@@ -154,7 +200,7 @@ export function usePersonaPolling(options: UsePersonaPollingOptions = {}): UsePe
// Start polling immediately, then at intervals
poll();
intervalRef.current = setInterval(poll, interval);
}, [isPolling, interval, onProgress, onComplete, onError, stopPolling]);
}, [isPolling, interval, maxAttempts, maxDuration, onProgress, onComplete, onError, stopPolling]);
// Cleanup on unmount
useEffect(() => {