Spaces:
Sleeping
Sleeping
File size: 2,720 Bytes
2a0825b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | """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,
)
|