44
55import logging
66import os
7+ import ssl
8+ import warnings
79from base64 import urlsafe_b64encode
810from hashlib import sha256
911from hmac import new as hmac_new
2022
2123DEFAULT_TIMEOUT_SECS = 10.0
2224DEFAULT_INTERNAL_TOKEN_TTL_SECS = 3600
25+ # Keep pooled-connection reuse shorter than typical server keepalive/worker
26+ # recycle windows so requests do not pick up sockets the server already closed.
27+ DEFAULT_KEEPALIVE_EXPIRY_SECS = 1.0
28+ DEFAULT_MAX_CONNECTIONS = 100
29+ DEFAULT_MAX_KEEPALIVE_CONNECTIONS = 20
2330PUBLIC_SCORER_INVOKE_PATH = "/scorers/invoke"
2431INTERNAL_SCORER_INVOKE_PATH = "/internal/scorers/invoke"
2532AuthMode = Literal ["public" , "internal" ]
@@ -56,6 +63,13 @@ def _env_auth_mode() -> AuthMode | None:
5663 value = os .getenv ("GALILEO_LUNA_AUTH_MODE" )
5764 if value is None or value .strip () == "" :
5865 return None
66+ deprecation_message = (
67+ "GALILEO_LUNA_AUTH_MODE is deprecated. Configure exactly one credential "
68+ "(GALILEO_API_KEY for public auth, GALILEO_API_SECRET_KEY for internal "
69+ "auth) or pass auth_mode to GalileoLunaClient."
70+ )
71+ warnings .warn (deprecation_message , DeprecationWarning , stacklevel = 2 )
72+ logger .warning (deprecation_message )
5973 normalized = value .strip ().lower ()
6074 if normalized == "public" :
6175 return "public"
@@ -164,9 +178,12 @@ class GalileoLunaClient:
164178 """Thin HTTP client for Galileo Luna direct scorer invocation.
165179
166180 Environment Variables:
167- GALILEO_API_SECRET_KEY or GALILEO_API_SECRET: Galileo API internal JWT signing secret.
181+ GALILEO_API_SECRET_KEY: Deployment-provided Galileo API internal JWT signing secret.
168182 GALILEO_API_KEY: Galileo API key fallback for public scorer invocation.
169- GALILEO_LUNA_AUTH_MODE: Auth mode, either "public" or "internal".
183+ GALILEO_LUNA_API_URL: Galileo Luna scorer invoke API URL override.
184+ GALILEO_API_URL: Galileo API URL fallback.
185+ GALILEO_LUNA_CA_FILE: CA bundle used to verify the scorer API endpoint, for
186+ deployments whose API serves an internally-issued TLS certificate.
170187 GALILEO_CONSOLE_URL: Galileo Console URL (optional, defaults to production).
171188 """
172189
@@ -177,23 +194,27 @@ def __init__(
177194 console_url : str | None = None ,
178195 api_url : str | None = None ,
179196 auth_mode : AuthMode | None = None ,
197+ ca_file : str | None = None ,
180198 ) -> None :
181199 """Initialize the Galileo Luna client.
182200
183201 Args:
184202 api_key: Galileo API key. If not provided, reads from GALILEO_API_KEY.
185- api_secret: Galileo API secret for internal JWT auth. If not provided,
186- reads from GALILEO_API_SECRET_KEY or GALILEO_API_SECRET .
203+ api_secret: Deployment-provided Galileo API secret for internal JWT auth.
204+ If not provided, reads from GALILEO_API_SECRET_KEY.
187205 console_url: Galileo Console URL. If not provided, reads from
188206 GALILEO_CONSOLE_URL or uses the production console URL.
189- api_url: Galileo API URL. If not provided, reads from GALILEO_API_URL
190- before deriving from the console URL.
191- auth_mode: Auth mode to use. If not provided, reads from
192- GALILEO_LUNA_AUTH_MODE, or infers from the single available credential.
207+ api_url: Galileo API URL. If not provided, reads from GALILEO_LUNA_API_URL,
208+ then GALILEO_API_URL, before deriving from the console URL.
209+ auth_mode: Auth mode to use. If not provided, inferred from the single
210+ available credential.
211+ ca_file: CA bundle path used to verify the scorer API endpoint. If not
212+ provided, reads from GALILEO_LUNA_CA_FILE. Leave unset for endpoints
213+ with publicly-trusted certificates.
193214
194215 Raises:
195216 ValueError: If credentials are missing, ambiguous, or incompatible with
196- the selected auth mode.
217+ the selected auth mode, or if the CA bundle cannot be loaded .
197218 """
198219 resolved_api_secret = (
199220 api_secret or os .getenv ("GALILEO_API_SECRET_KEY" ) or os .getenv ("GALILEO_API_SECRET" )
@@ -211,12 +232,32 @@ def __init__(
211232 self .console_url = (
212233 console_url or os .getenv ("GALILEO_CONSOLE_URL" ) or "https://console.galileo.ai"
213234 )
214- self .api_base = ( api_url or os . getenv ( "GALILEO_API_URL" ) or "" ). rstrip (
215- "/"
216- ) or self ._derive_api_url (self .console_url )
235+ self .api_base = self . _resolve_api_base ( api_url )
236+ self . ca_file = ( ca_file or os . getenv ( "GALILEO_LUNA_CA_FILE" ) or "" ). strip () or None
237+ self . _ssl_context = self ._load_ssl_context (self .ca_file )
217238 self ._client : httpx .AsyncClient | None = None
218239 logger .info ("[GalileoLunaClient] Auth mode selected: %s" , self .auth_mode )
219240
241+ def _resolve_api_base (self , api_url : str | None ) -> str :
242+ """Resolve the scorer invoke API base URL from explicit and environment config."""
243+ candidates = [api_url , os .getenv ("GALILEO_LUNA_API_URL" )]
244+ candidates .append (os .getenv ("GALILEO_API_URL" ))
245+
246+ for candidate in candidates :
247+ if candidate and candidate .strip ():
248+ return candidate .strip ().rstrip ("/" )
249+ return self ._derive_api_url (self .console_url )
250+
251+ @staticmethod
252+ def _load_ssl_context (ca_file : str | None ) -> ssl .SSLContext | None :
253+ """Build a TLS verification context from a CA bundle path, if configured."""
254+ if ca_file is None :
255+ return None
256+ try :
257+ return ssl .create_default_context (cafile = ca_file )
258+ except (OSError , ssl .SSLError ) as exc :
259+ raise ValueError (f"Failed to load CA bundle from { ca_file !r} : { exc } " ) from exc
260+
220261 @staticmethod
221262 def _resolve_auth_mode (
222263 auth_mode : AuthMode | None ,
@@ -226,24 +267,21 @@ def _resolve_auth_mode(
226267 ) -> AuthMode :
227268 if auth_mode == "public" :
228269 if not api_key :
229- raise ValueError (
230- "GALILEO_API_KEY is required when GALILEO_LUNA_AUTH_MODE=public."
231- )
270+ raise ValueError ("GALILEO_API_KEY is required for public Luna auth." )
232271 return "public"
233272
234273 if auth_mode == "internal" :
235274 if not api_secret :
236275 raise ValueError (
237- "GALILEO_API_SECRET_KEY or GALILEO_API_SECRET is required when "
238- "GALILEO_LUNA_AUTH_MODE=internal."
276+ "GALILEO_API_SECRET_KEY is required for internal Luna auth."
239277 )
240278 return "internal"
241279
242280 if api_key and api_secret :
243281 raise ValueError (
244- "Both Galileo API key and API secret are configured. Set "
245- "GALILEO_LUNA_AUTH_MODE to 'public' or 'internal' to choose the "
246- "runtime auth mode explicitly."
282+ "Both a Galileo API key and a Galileo API secret are configured. "
283+ "Unset one credential so the auth mode can be inferred, or pass "
284+ "auth_mode='public' or auth_mode='internal' explicitly."
247285 )
248286 if api_secret :
249287 return "internal"
@@ -284,9 +322,18 @@ async def _get_client(self) -> httpx.AsyncClient:
284322 headers = {"Content-Type" : "application/json" }
285323 if self .auth_mode == "public" and self .api_key is not None :
286324 headers ["Galileo-API-Key" ] = self .api_key
325+ verify : ssl .SSLContext | bool = (
326+ self ._ssl_context if self ._ssl_context is not None else True
327+ )
287328 self ._client = httpx .AsyncClient (
288329 headers = headers ,
289330 timeout = httpx .Timeout (DEFAULT_TIMEOUT_SECS ),
331+ limits = httpx .Limits (
332+ max_connections = DEFAULT_MAX_CONNECTIONS ,
333+ max_keepalive_connections = DEFAULT_MAX_KEEPALIVE_CONNECTIONS ,
334+ keepalive_expiry = DEFAULT_KEEPALIVE_EXPIRY_SECS ,
335+ ),
336+ verify = verify ,
290337 )
291338 return self ._client
292339
0 commit comments