From 7e72d073293124afab19174deec2aade8c350e73 Mon Sep 17 00:00:00 2001 From: Vadym Samoilenko Date: Mon, 23 Mar 2026 19:17:36 +0000 Subject: [PATCH] Fix AI loop hanging: add asyncio.wait_for timeouts on LLM calls The autonomous conversation loop could hang indefinitely because self.response_timeout=30 was defined but never used in wait_for(). - autonomous_conversation_controller: wrap generate_persona_response() with asyncio.wait_for(timeout=120s); 30s was too short for production LLMs, raised to 120s; TimeoutError returns an error dict so the loop can continue or count toward consecutive_silence limit - conversation_decision_service: add asyncio.wait_for(timeout=60s) around LLMService.generate_content() for the decision call; add asyncio import and explicit TimeoutError handling Co-Authored-By: Claude Sonnet 4.6 --- .../autonomous_conversation_controller.py | 29 ++++++++++++------- .../services/conversation_decision_service.py | 17 +++++++---- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/backend/app/services/autonomous_conversation_controller.py b/backend/app/services/autonomous_conversation_controller.py index 8bab940b..5c66aa53 100755 --- a/backend/app/services/autonomous_conversation_controller.py +++ b/backend/app/services/autonomous_conversation_controller.py @@ -32,7 +32,7 @@ class AutonomousConversationController: # Timing configuration self.min_delay_between_actions = 3 # seconds self.max_delay_between_actions = 10 # seconds - self.response_timeout = 30 # seconds + self.response_timeout = 120 # seconds (LLM calls can be slow in production) # Edge case tracking self.consecutive_silence_count = 0 @@ -670,18 +670,25 @@ class AutonomousConversationController: messages = await FocusGroup.get_messages(self.focus_group_id) recent_messages = messages[-20:] if len(messages) > 20 else messages - # Generate response + # Generate response with timeout to prevent infinite hang try: - response_text = await generate_persona_response( - persona=persona, - current_topic=topic, - previous_messages=recent_messages, - temperature=0.7, - focus_group_id=self.focus_group_id, - llm_model=llm_model, - reasoning_effort=reasoning_effort, - verbosity=verbosity + response_text = await asyncio.wait_for( + generate_persona_response( + persona=persona, + current_topic=topic, + previous_messages=recent_messages, + temperature=0.7, + focus_group_id=self.focus_group_id, + llm_model=llm_model, + reasoning_effort=reasoning_effort, + verbosity=verbosity + ), + timeout=self.response_timeout ) + except asyncio.TimeoutError: + error_msg = f"Participant response generation timed out after {self.response_timeout}s" + self.logger.error(f"⏱️ {error_msg}") + return {"error": error_msg} except Exception as e: self.logger.error(f"Error in generate_persona_response: {str(e)}") import traceback diff --git a/backend/app/services/conversation_decision_service.py b/backend/app/services/conversation_decision_service.py index 11e9983e..3fc3daac 100755 --- a/backend/app/services/conversation_decision_service.py +++ b/backend/app/services/conversation_decision_service.py @@ -4,6 +4,7 @@ Uses LLM to make intelligent decisions about conversation flow, participant sele """ from typing import Dict, Any, Optional, List +import asyncio import json from app.services.llm_service import LLMService, LLMServiceError from app.services.conversation_context_service import ConversationContextService @@ -58,12 +59,15 @@ class ConversationDecisionService: focus_group = await FocusGroup.find_by_id(focus_group_id) llm_model = focus_group.get('llm_model') if focus_group else None - # Get LLM decision + # Get LLM decision with timeout to prevent infinite hang try: - response = await LLMService.generate_content( - prompt=prompt, - temperature=temperature, - model_name=llm_model + response = await asyncio.wait_for( + LLMService.generate_content( + prompt=prompt, + temperature=temperature, + model_name=llm_model + ), + timeout=60 ) # Parse the JSON response @@ -92,6 +96,9 @@ class ConversationDecisionService: return decision + except asyncio.TimeoutError: + print(f"⏱️ LLM decision timed out after 60s") + raise ConversationDecisionError("LLM decision timed out after 60s") except LLMServiceError as e: print(f"❌ LLM Service Error: {str(e)}") raise ConversationDecisionError(f"Error getting LLM decision: {str(e)}")