""" One place every OpenAI client is built, so none of them runs on SDK-default timeouts (server-26#153). The OpenAI SDK's defaults are a 600s read timeout and 2 retries, which it also applies to timeouts: one hung Whisper request could hold a call's pipeline for half an hour, twice over with stt_retry_on_degenerate. That unbounded tail is what recorrelation_sweep.py's MIN_MINUTES_SINCE_PIPELINE_START had to guess against. With these settings it is derived instead (see recorrelation_sweep.pipeline_worst_case_seconds). A timeout raises openai.APITimeoutError, an ordinary Exception, so each call site's existing failure path handles it unchanged: ai_health.classify() reads "Request timed out." as transient. The Gemini client is built in app/internal/gemini.py with the same intent. """ from typing import Optional from app.config import settings def _timeout(timeout: Optional[float]) -> float: return settings.openai_timeout_seconds if timeout is None else timeout def openai_client(timeout: Optional[float] = None): """Synchronous OpenAI client. `timeout` overrides openai_timeout_seconds (STT passes its own).""" from openai import OpenAI # lazy, like every call site before this module return OpenAI( api_key=settings.openai_api_key, timeout=_timeout(timeout), max_retries=settings.openai_max_retries, ) def stt_client(api_key: Optional[str], base_url: Optional[str] = None): """ Synchronous client for an OpenAI-compatible transcription endpoint (server-26#297) -- OpenAI itself, Groq, or a self-hosted server. Same timeout/retry limits as every other client here, with STT's own timeout. """ from openai import OpenAI kwargs = {} if base_url: kwargs["base_url"] = base_url return OpenAI( api_key=api_key, timeout=settings.stt_timeout_seconds, max_retries=settings.openai_max_retries, **kwargs, ) def async_openai_client(timeout: Optional[float] = None): """Async OpenAI client, same limits as openai_client().""" from openai import AsyncOpenAI return AsyncOpenAI( api_key=settings.openai_api_key, timeout=_timeout(timeout), max_retries=settings.openai_max_retries, )