fix: increase LLM timeouts, refs NOISSUE

This commit is contained in:
2026-04-11 22:19:42 +02:00
parent 0770b254b1
commit c437ae0173
3 changed files with 82 additions and 4 deletions

View File

@@ -185,6 +185,7 @@ class LLMServiceClient:
def __init__(self, ollama_url: str | None = None, model: str | None = None):
self.ollama_url = (ollama_url or settings.ollama_url).rstrip('/')
self.model = model or settings.OLLAMA_MODEL
self.request_timeout_seconds = settings.llm_request_timeout_seconds
self.toolbox = LLMToolbox()
self.live_tool_executor = LLMLiveToolExecutor()
@@ -290,13 +291,16 @@ class LLMServiceClient:
try:
import aiohttp
async with aiohttp.ClientSession() as session:
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=self.request_timeout_seconds)) as session:
async with session.post(f'{self.ollama_url}/api/chat', json=request_payload) as resp:
payload = await resp.json()
if 200 <= resp.status < 300:
return (payload.get('message') or {}).get('content', ''), payload, None
return None, payload, str(payload.get('error') or payload)
except Exception as exc:
if exc.__class__.__name__ == 'TimeoutError':
message = f'LLM request timed out after {self.request_timeout_seconds} seconds'
return None, {'error': message}, message
return None, {'error': str(exc)}, str(exc)
@staticmethod

View File

@@ -222,6 +222,7 @@ class Settings(BaseSettings):
# Ollama settings computed from environment
OLLAMA_URL: str = "http://ollama:11434"
OLLAMA_MODEL: str = "llama3"
LLM_REQUEST_TIMEOUT_SECONDS: int = 240
LLM_GUARDRAIL_PROMPT: str = (
"You are operating inside AI Software Factory. Follow the requested schema exactly, "
"treat provided tool outputs as authoritative, and do not invent repositories, issues, pull requests, or delivery facts."
@@ -613,6 +614,11 @@ class Settings(BaseSettings):
"""Get the maximum number of queued prompts to process in one batch."""
return max(int(_resolve_runtime_setting_value('PROMPT_QUEUE_MAX_BATCH_SIZE', self.PROMPT_QUEUE_MAX_BATCH_SIZE)), 1)
@property
def llm_request_timeout_seconds(self) -> int:
"""Get the outbound provider timeout for one LLM request."""
return max(int(_resolve_runtime_setting_value('LLM_REQUEST_TIMEOUT_SECONDS', self.LLM_REQUEST_TIMEOUT_SECONDS)), 1)
@property
def projects_root(self) -> Path:
"""Get the root directory for generated project artifacts."""

View File

@@ -36,6 +36,7 @@ try:
from .agents.orchestrator import AgentOrchestrator
from .agents.n8n_setup import N8NSetupAgent
from .agents.prompt_workflow import PromptWorkflowManager
from .agents.telegram import TelegramHandler
from .agents.ui_manager import UIManager
from .models import ProjectHistory, ProjectLog, SystemLog
except ImportError:
@@ -49,6 +50,7 @@ except ImportError:
from agents.orchestrator import AgentOrchestrator
from agents.n8n_setup import N8NSetupAgent
from agents.prompt_workflow import PromptWorkflowManager
from agents.telegram import TelegramHandler
from agents.ui_manager import UIManager
from models import ProjectHistory, ProjectLog, SystemLog
@@ -256,6 +258,63 @@ def _ensure_summary_mentions_pull_request(summary_message: str, pull_request: di
return f"{summary_message}{separator} Review PR: {pr_url}"
def _should_queue_telegram_request(request: FreeformSoftwareRequest) -> bool:
"""Return whether a Telegram request should be accepted for background processing."""
return (
request.source == 'telegram'
and bool(request.chat_id)
and bool(database_module.settings.telegram_bot_token)
and not request.process_now
)
def _schedule_prompt_queue_processing() -> None:
"""Kick off background queue processing without blocking the current HTTP request."""
if database_module.settings.prompt_queue_enabled and not database_module.settings.prompt_queue_auto_process:
return
limit = database_module.settings.prompt_queue_max_batch_size if database_module.settings.prompt_queue_enabled else 1
force = database_module.settings.prompt_queue_force_process if database_module.settings.prompt_queue_enabled else True
task = asyncio.create_task(_process_prompt_queue_batch(limit=limit, force=force))
def _log_task_result(completed_task: asyncio.Task) -> None:
try:
completed_task.result()
except Exception as exc:
db = database_module.get_db_sync()
try:
DatabaseManager(db).log_system_event('prompt-queue', 'ERROR', f'Background queue processing failed: {exc}')
finally:
db.close()
task.add_done_callback(_log_task_result)
async def _notify_telegram_queue_result(request: FreeformSoftwareRequest, *, response: dict | None = None, error_message: str | None = None) -> None:
"""Send the final queued result back to Telegram when chat metadata is available."""
if request.source != 'telegram' or not request.chat_id or not database_module.settings.telegram_bot_token:
return
if response is not None:
message = (
response.get('summary_message')
or (response.get('data') or {}).get('summary_message')
or response.get('message')
or 'Software generation completed.'
)
else:
message = f"Software generation failed: {error_message or 'Unknown error'}"
result = await TelegramHandler(webhook_url=database_module.settings.backend_public_url).send_message(
bot_token=database_module.settings.telegram_bot_token,
chat_id=request.chat_id,
text=message,
)
if result.get('status') == 'error':
db = database_module.get_db_sync()
try:
DatabaseManager(db).log_system_event('telegram', 'ERROR', f"Unable to send queued Telegram update: {result.get('message')}")
finally:
db.close()
def _serialize_system_log(log: SystemLog) -> dict:
"""Serialize a system log row."""
return {
@@ -732,6 +791,7 @@ async def _process_prompt_queue_batch(limit: int = 1, force: bool = False) -> di
process_now=True,
)
response = await _run_freeform_generation(request, work_db, queue_item_id=claimed['id'])
await _notify_telegram_queue_result(request, response=response)
processed.append(
{
'queue_item_id': claimed['id'],
@@ -741,6 +801,7 @@ async def _process_prompt_queue_batch(limit: int = 1, force: bool = False) -> di
)
except Exception as exc:
DatabaseManager(work_db).fail_queued_prompt(claimed['id'], str(exc))
await _notify_telegram_queue_result(request, error_message=str(exc))
processed.append({'queue_item_id': claimed['id'], 'status': 'failed', 'error': str(exc)})
finally:
work_db.close()
@@ -960,7 +1021,7 @@ async def generate_software_from_text(request: FreeformSoftwareRequest, db: DbSe
},
}
if request.source == 'telegram' and database_module.settings.prompt_queue_enabled and not request.process_now:
if _should_queue_telegram_request(request):
manager = DatabaseManager(db)
queue_item = manager.enqueue_prompt(
prompt_text=request.prompt_text,
@@ -969,12 +1030,19 @@ async def generate_software_from_text(request: FreeformSoftwareRequest, db: DbSe
chat_type=request.chat_type,
source_context={'chat_id': request.chat_id, 'chat_type': request.chat_type},
)
queue_gate = await _get_queue_gate_status(force=False)
if not database_module.settings.prompt_queue_enabled or database_module.settings.prompt_queue_auto_process:
_schedule_prompt_queue_processing()
return {
'status': 'queued',
'message': 'Prompt queued for energy-aware processing.',
'message': (
'Prompt accepted for background processing.'
if not database_module.settings.prompt_queue_enabled else
'Prompt queued for background processing.'
),
'queue_item': queue_item,
'queue_summary': manager.get_prompt_queue_summary(),
'queue_gate': await _get_queue_gate_status(force=False),
'queue_gate': queue_gate,
'source': {
'type': request.source,
'chat_id': request.chat_id,