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,
    )