ankira / openai_client.py
nofater's picture
mvp
2a0825b
Raw
History Blame Contribute Delete
2.72 kB
"""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,
)