Add timeout protection to outline generation

Prevents socket hang up when LLM is slow or unavailable.
- Timeout: 120 seconds (configurable via OUTLINE_TIMEOUT_SECONDS)
- Returns 504 Gateway Timeout error if exceeded
- Fixes: socket hang up errors in Next.js proxy

Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Vadym Samoilenko 2026-02-27 22:34:26 +00:00
parent 8adbc965a4
commit 0a5e971bf7

View file

@ -1,6 +1,9 @@
import asyncio
import os
from datetime import datetime
from typing import Optional
from fastapi import HTTPException
from models.llm_message import LLMSystemMessage, LLMUserMessage
from models.llm_tools import SearchWebTool
from services.llm_client import LLMClient
@ -137,30 +140,46 @@ async def generate_ppt_outline(
client = LLMClient()
# Timeout for outline generation (prevents socket hang up)
OUTLINE_TIMEOUT = int(os.getenv("OUTLINE_TIMEOUT_SECONDS", "120"))
try:
async for chunk in client.stream_structured(
model,
get_messages(
content,
n_slides,
language,
additional_context,
tone,
verbosity,
instructions,
include_title_slide,
brand_context,
available_layouts,
content_summary,
),
response_model.model_json_schema(),
strict=True,
tools=(
[SearchWebTool]
if (client.enable_web_grounding() and web_search)
else None
),
):
# Wrap streaming in timeout to prevent indefinite hangs
async def stream_with_timeout():
try:
async for chunk in client.stream_structured(
model,
get_messages(
content,
n_slides,
language,
additional_context,
tone,
verbosity,
instructions,
include_title_slide,
brand_context,
available_layouts,
content_summary,
),
response_model.model_json_schema(),
strict=True,
tools=(
[SearchWebTool]
if (client.enable_web_grounding() and web_search)
else None
),
):
yield chunk
except asyncio.TimeoutError:
yield HTTPException(
status_code=504,
detail=f"Outline generation timed out after {OUTLINE_TIMEOUT}s. Please try again or reduce the number of slides."
)
except Exception as e:
yield handle_llm_client_exceptions(e)
async for chunk in stream_with_timeout():
yield chunk
except Exception as e:
yield handle_llm_client_exceptions(e)