Spaces:
Running
Running
| """Shared OpenAI-compatible client factory for the Modal endpoints. | |
| All three models (LFM2.5 text, Higgs TTS, Qwen3-VL OCR) speak the OpenAI HTTP | |
| API, so a single ``OpenAI`` client talks to each — only the base URL differs. | |
| The one project-specific concern lives here: **Modal cold starts.** An idle | |
| container answers the first request with a 502/503/504 while it boots (~30-60 s, | |
| see app.py COLD_START_HINT). The SDK already retries 5xx/connection errors with | |
| exponential backoff; we just raise ``max_retries`` and ``timeout`` well past the | |
| SDK defaults (2 retries) so a cold boot is absorbed rather than surfaced. | |
| This module is intentionally self-contained (no intra-package imports) so it can | |
| be imported both as ``openai_client`` (from the Space root) and | |
| ``space.openai_client`` (from the repo root) — the same dual-path trick the | |
| ``ocr`` package relies on. | |
| """ | |
| import os | |
| from openai import OpenAI | |
| TIMEOUT = 900 # seconds; headroom for a Modal cold start | |
| MAX_RETRIES = 15 # SDK backoff caps each sleep at ~8 s -> covers a ~1-2 min boot | |
| # Modal endpoints don't authenticate the OpenAI API key, but the SDK requires a | |
| # non-empty one. (Proxy auth, below, is a separate Modal-Key/Modal-Secret pair.) | |
| DUMMY_API_KEY = "EMPTY" | |
| # Modal proxy auth: when an endpoint is deployed with requires_proxy_auth=True, | |
| # every request must carry these two headers, sourced from a proxy auth token. | |
| MODAL_KEY_ENV = "MODAL_KEY" | |
| MODAL_SECRET_ENV = "MODAL_SECRET" | |
| def proxy_auth_headers() -> dict[str, str]: | |
| """Modal proxy-auth headers from the environment, or empty if not configured. | |
| Both halves are required, so a lone key/secret yields nothing — the client | |
| stays unauthenticated and keeps working against unprotected endpoints until | |
| both ``MODAL_KEY`` and ``MODAL_SECRET`` are set.""" | |
| key = os.environ.get(MODAL_KEY_ENV) | |
| secret = os.environ.get(MODAL_SECRET_ENV) | |
| if key and secret: | |
| return {"Modal-Key": key, "Modal-Secret": secret} | |
| return {} | |
| def make_client( | |
| base_url: str, | |
| *, | |
| timeout: float = TIMEOUT, | |
| max_retries: int = MAX_RETRIES, | |
| ) -> OpenAI: | |
| """Build an OpenAI client pointed at a Modal server. | |
| ``base_url`` may be the server root or already include the ``/v1`` suffix; | |
| both are accepted (and a trailing slash is fine). Modal proxy-auth headers | |
| are attached automatically when configured (see ``proxy_auth_headers``). | |
| """ | |
| base = base_url.rstrip("/") | |
| if not base.endswith("/v1"): | |
| base = f"{base}/v1" | |
| headers = proxy_auth_headers() | |
| return OpenAI( | |
| base_url=base, | |
| api_key=DUMMY_API_KEY, | |
| timeout=timeout, | |
| max_retries=max_retries, | |
| default_headers=headers or None, | |
| ) | |