From b344ae1045ddee16991778cad9aa18eb83646aba Mon Sep 17 00:00:00 2001 From: abhinav-galileo Date: Thu, 11 Jun 2026 16:44:43 +0530 Subject: [PATCH 1/6] fix(evaluator-galileo): prefer cluster API URL for internal Luna auth --- .../luna/client.py | 22 ++++++-- .../galileo/tests/test_luna_evaluator.py | 54 +++++++++++++++++++ 2 files changed, 72 insertions(+), 4 deletions(-) diff --git a/evaluators/contrib/galileo/src/agent_control_evaluator_galileo/luna/client.py b/evaluators/contrib/galileo/src/agent_control_evaluator_galileo/luna/client.py index 11e4881a..e076f60a 100644 --- a/evaluators/contrib/galileo/src/agent_control_evaluator_galileo/luna/client.py +++ b/evaluators/contrib/galileo/src/agent_control_evaluator_galileo/luna/client.py @@ -167,6 +167,9 @@ class GalileoLunaClient: GALILEO_API_SECRET_KEY or GALILEO_API_SECRET: Galileo API internal JWT signing secret. GALILEO_API_KEY: Galileo API key fallback for public scorer invocation. GALILEO_LUNA_AUTH_MODE: Auth mode, either "public" or "internal". + GALILEO_LUNA_API_URL: Galileo Luna scorer invoke API URL override. + GALILEO_API_CLUSTER_URL: Internal Galileo API URL used in internal auth mode. + GALILEO_API_URL: Galileo API URL fallback. GALILEO_CONSOLE_URL: Galileo Console URL (optional, defaults to production). """ @@ -186,7 +189,8 @@ def __init__( reads from GALILEO_API_SECRET_KEY or GALILEO_API_SECRET. console_url: Galileo Console URL. If not provided, reads from GALILEO_CONSOLE_URL or uses the production console URL. - api_url: Galileo API URL. If not provided, reads from GALILEO_API_URL + api_url: Galileo API URL. If not provided, reads from GALILEO_LUNA_API_URL, + then GALILEO_API_CLUSTER_URL in internal auth mode, then GALILEO_API_URL, before deriving from the console URL. auth_mode: Auth mode to use. If not provided, reads from GALILEO_LUNA_AUTH_MODE, or infers from the single available credential. @@ -211,12 +215,22 @@ def __init__( self.console_url = ( console_url or os.getenv("GALILEO_CONSOLE_URL") or "https://console.galileo.ai" ) - self.api_base = (api_url or os.getenv("GALILEO_API_URL") or "").rstrip( - "/" - ) or self._derive_api_url(self.console_url) + self.api_base = self._resolve_api_base(api_url, resolved_auth_mode) self._client: httpx.AsyncClient | None = None logger.info("[GalileoLunaClient] Auth mode selected: %s", self.auth_mode) + def _resolve_api_base(self, api_url: str | None, auth_mode: AuthMode) -> str: + """Resolve the scorer invoke API base URL from explicit and environment config.""" + candidates = [api_url, os.getenv("GALILEO_LUNA_API_URL")] + if auth_mode == "internal": + candidates.append(os.getenv("GALILEO_API_CLUSTER_URL")) + candidates.append(os.getenv("GALILEO_API_URL")) + + for candidate in candidates: + if candidate and candidate.strip(): + return candidate.rstrip("/") + return self._derive_api_url(self.console_url) + @staticmethod def _resolve_auth_mode( auth_mode: AuthMode | None, diff --git a/evaluators/contrib/galileo/tests/test_luna_evaluator.py b/evaluators/contrib/galileo/tests/test_luna_evaluator.py index f123e214..0a2bf3cd 100644 --- a/evaluators/contrib/galileo/tests/test_luna_evaluator.py +++ b/evaluators/contrib/galileo/tests/test_luna_evaluator.py @@ -157,6 +157,60 @@ def test_client_uses_galileo_api_url_when_set(self) -> None: # Then: the explicit API URL wins over console URL derivation assert client.api_base == "https://api-test-luna.gcp-dev.galileo.ai" + def test_client_uses_luna_api_url_when_set(self) -> None: + from agent_control_evaluator_galileo.luna import GalileoLunaClient + + # Given: a Luna-specific API URL and a general API URL are both configured + with patch.dict( + os.environ, + { + "GALILEO_API_KEY": "test-key", + "GALILEO_LUNA_API_URL": "https://luna-api.example.com/", + "GALILEO_API_URL": "https://api.example.com", + }, + clear=True, + ): + client = GalileoLunaClient(console_url="https://console.example.com") + + # Then: the Luna-specific URL wins without changing the general API URL contract + assert client.api_base == "https://luna-api.example.com" + + def test_client_uses_cluster_url_for_internal_auth(self) -> None: + from agent_control_evaluator_galileo.luna import GalileoLunaClient + + # Given: internal auth and both cluster and public API URLs are configured + with patch.dict( + os.environ, + { + "GALILEO_API_SECRET_KEY": "test-secret", + "GALILEO_API_CLUSTER_URL": "https://api.default.svc.cluster.local:8088", + "GALILEO_API_URL": "https://api-public.example.com", + }, + clear=True, + ): + client = GalileoLunaClient(console_url="https://console.example.com") + + # Then: internal scorer invocation uses the cluster-local API base + assert client.api_base == "https://api.default.svc.cluster.local:8088" + + def test_client_ignores_cluster_url_for_public_auth(self) -> None: + from agent_control_evaluator_galileo.luna import GalileoLunaClient + + # Given: public auth with a cluster URL present in the environment + with patch.dict( + os.environ, + { + "GALILEO_API_KEY": "test-key", + "GALILEO_API_CLUSTER_URL": "https://api.default.svc.cluster.local:8088", + "GALILEO_API_URL": "https://api-public.example.com", + }, + clear=True, + ): + client = GalileoLunaClient(console_url="https://console.example.com") + + # Then: public scorer invocation still uses the public API URL + assert client.api_base == "https://api-public.example.com" + def test_client_derives_api_url_from_console_dash_hostname(self) -> None: from agent_control_evaluator_galileo.luna import GalileoLunaClient From 7255e1f33938a2e8087888245b9b32301ecd5895 Mon Sep 17 00:00:00 2001 From: abhinav-galileo Date: Thu, 11 Jun 2026 17:31:07 +0530 Subject: [PATCH 2/6] fix(evaluator-galileo): harden Luna client for in-cluster endpoints - Verify TLS against an optional CA bundle (ca_file arg or GALILEO_LUNA_CA_FILE) so internal API endpoints with internally-issued certificates work in internal auth mode. - Bound connection pooling (keepalive expiry 1s, connection limits) so requests do not reuse sockets the server already closed, and retry the idempotent scorer invoke once on connection errors. - Deprecate GALILEO_LUNA_AUTH_MODE; the auth mode is inferred from the configured credential, and setting both credentials remains an explicit error. - Trim whitespace when resolving API base URLs from the environment. - Update the Luna example README and cover URL precedence, TLS, retry, and deprecation with tests. --- .../luna/client.py | 85 ++++++++++-- .../galileo/tests/test_luna_coverage_gaps.py | 4 +- .../galileo/tests/test_luna_evaluator.py | 122 +++++++++++++++++- examples/galileo_luna/README.md | 22 ++-- 4 files changed, 207 insertions(+), 26 deletions(-) diff --git a/evaluators/contrib/galileo/src/agent_control_evaluator_galileo/luna/client.py b/evaluators/contrib/galileo/src/agent_control_evaluator_galileo/luna/client.py index e076f60a..2a995703 100644 --- a/evaluators/contrib/galileo/src/agent_control_evaluator_galileo/luna/client.py +++ b/evaluators/contrib/galileo/src/agent_control_evaluator_galileo/luna/client.py @@ -4,6 +4,8 @@ import logging import os +import ssl +import warnings from base64 import urlsafe_b64encode from hashlib import sha256 from hmac import new as hmac_new @@ -20,6 +22,11 @@ DEFAULT_TIMEOUT_SECS = 10.0 DEFAULT_INTERNAL_TOKEN_TTL_SECS = 3600 +# Keep pooled-connection reuse shorter than typical server keepalive/worker +# recycle windows so requests do not pick up sockets the server already closed. +DEFAULT_KEEPALIVE_EXPIRY_SECS = 1.0 +DEFAULT_MAX_CONNECTIONS = 100 +DEFAULT_MAX_KEEPALIVE_CONNECTIONS = 20 PUBLIC_SCORER_INVOKE_PATH = "/scorers/invoke" INTERNAL_SCORER_INVOKE_PATH = "/internal/scorers/invoke" AuthMode = Literal["public", "internal"] @@ -56,6 +63,13 @@ def _env_auth_mode() -> AuthMode | None: value = os.getenv("GALILEO_LUNA_AUTH_MODE") if value is None or value.strip() == "": return None + warnings.warn( + "GALILEO_LUNA_AUTH_MODE is deprecated. Configure exactly one credential " + "(GALILEO_API_KEY for public auth, GALILEO_API_SECRET_KEY for internal " + "auth) or pass auth_mode to GalileoLunaClient.", + DeprecationWarning, + stacklevel=2, + ) normalized = value.strip().lower() if normalized == "public": return "public" @@ -166,10 +180,13 @@ class GalileoLunaClient: Environment Variables: GALILEO_API_SECRET_KEY or GALILEO_API_SECRET: Galileo API internal JWT signing secret. GALILEO_API_KEY: Galileo API key fallback for public scorer invocation. - GALILEO_LUNA_AUTH_MODE: Auth mode, either "public" or "internal". + GALILEO_LUNA_AUTH_MODE: Deprecated. The auth mode is inferred from which + credential is configured; set exactly one credential instead. GALILEO_LUNA_API_URL: Galileo Luna scorer invoke API URL override. GALILEO_API_CLUSTER_URL: Internal Galileo API URL used in internal auth mode. GALILEO_API_URL: Galileo API URL fallback. + GALILEO_LUNA_CA_FILE: CA bundle used to verify the scorer API endpoint, for + deployments whose API serves an internally-issued TLS certificate. GALILEO_CONSOLE_URL: Galileo Console URL (optional, defaults to production). """ @@ -180,6 +197,7 @@ def __init__( console_url: str | None = None, api_url: str | None = None, auth_mode: AuthMode | None = None, + ca_file: str | None = None, ) -> None: """Initialize the Galileo Luna client. @@ -192,12 +210,16 @@ def __init__( api_url: Galileo API URL. If not provided, reads from GALILEO_LUNA_API_URL, then GALILEO_API_CLUSTER_URL in internal auth mode, then GALILEO_API_URL, before deriving from the console URL. - auth_mode: Auth mode to use. If not provided, reads from - GALILEO_LUNA_AUTH_MODE, or infers from the single available credential. + auth_mode: Auth mode to use. If not provided, inferred from the single + available credential. (Reading GALILEO_LUNA_AUTH_MODE from the + environment is deprecated.) + ca_file: CA bundle path used to verify the scorer API endpoint. If not + provided, reads from GALILEO_LUNA_CA_FILE. Leave unset for endpoints + with publicly-trusted certificates. Raises: ValueError: If credentials are missing, ambiguous, or incompatible with - the selected auth mode. + the selected auth mode, or if the CA bundle cannot be loaded. """ resolved_api_secret = ( api_secret or os.getenv("GALILEO_API_SECRET_KEY") or os.getenv("GALILEO_API_SECRET") @@ -216,6 +238,8 @@ def __init__( console_url or os.getenv("GALILEO_CONSOLE_URL") or "https://console.galileo.ai" ) self.api_base = self._resolve_api_base(api_url, resolved_auth_mode) + self.ca_file = (ca_file or os.getenv("GALILEO_LUNA_CA_FILE") or "").strip() or None + self._ssl_context = self._load_ssl_context(self.ca_file) self._client: httpx.AsyncClient | None = None logger.info("[GalileoLunaClient] Auth mode selected: %s", self.auth_mode) @@ -228,9 +252,19 @@ def _resolve_api_base(self, api_url: str | None, auth_mode: AuthMode) -> str: for candidate in candidates: if candidate and candidate.strip(): - return candidate.rstrip("/") + return candidate.strip().rstrip("/") return self._derive_api_url(self.console_url) + @staticmethod + def _load_ssl_context(ca_file: str | None) -> ssl.SSLContext | None: + """Build a TLS verification context from a CA bundle path, if configured.""" + if ca_file is None: + return None + try: + return ssl.create_default_context(cafile=ca_file) + except (OSError, ssl.SSLError) as exc: + raise ValueError(f"Failed to load CA bundle from {ca_file!r}: {exc}") from exc + @staticmethod def _resolve_auth_mode( auth_mode: AuthMode | None, @@ -255,9 +289,9 @@ def _resolve_auth_mode( if api_key and api_secret: raise ValueError( - "Both Galileo API key and API secret are configured. Set " - "GALILEO_LUNA_AUTH_MODE to 'public' or 'internal' to choose the " - "runtime auth mode explicitly." + "Both a Galileo API key and a Galileo API secret are configured. " + "Unset one credential so the auth mode can be inferred, or pass " + "auth_mode='public' or auth_mode='internal' explicitly." ) if api_secret: return "internal" @@ -298,9 +332,18 @@ async def _get_client(self) -> httpx.AsyncClient: headers = {"Content-Type": "application/json"} if self.auth_mode == "public" and self.api_key is not None: headers["Galileo-API-Key"] = self.api_key + verify: ssl.SSLContext | bool = ( + self._ssl_context if self._ssl_context is not None else True + ) self._client = httpx.AsyncClient( headers=headers, timeout=httpx.Timeout(DEFAULT_TIMEOUT_SECS), + limits=httpx.Limits( + max_connections=DEFAULT_MAX_CONNECTIONS, + max_keepalive_connections=DEFAULT_MAX_KEEPALIVE_CONNECTIONS, + keepalive_expiry=DEFAULT_KEEPALIVE_EXPIRY_SECS, + ), + verify=verify, ) return self._client @@ -371,12 +414,26 @@ async def invoke( try: client = await self._get_client() - response = await client.post( - endpoint, - json=request_body, - headers=request_headers, - timeout=timeout, - ) + try: + response = await client.post( + endpoint, + json=request_body, + headers=request_headers, + timeout=timeout, + ) + except (httpx.ConnectError, httpx.RemoteProtocolError) as exc: + # These errors occur before a response is received, typically when + # the server closed a pooled connection. The scorer invoke request + # is idempotent, so a single retry on a fresh connection is safe. + logger.warning( + "[GalileoLunaClient] Retrying once after connection error: %s", exc + ) + response = await client.post( + endpoint, + json=request_body, + headers=request_headers, + timeout=timeout, + ) response.raise_for_status() response_data = response.json() if not isinstance(response_data, dict): diff --git a/evaluators/contrib/galileo/tests/test_luna_coverage_gaps.py b/evaluators/contrib/galileo/tests/test_luna_coverage_gaps.py index 9d5f6766..ab304a74 100644 --- a/evaluators/contrib/galileo/tests/test_luna_coverage_gaps.py +++ b/evaluators/contrib/galileo/tests/test_luna_coverage_gaps.py @@ -457,7 +457,9 @@ def test_client_requires_explicit_mode_when_both_credentials_are_present(monkeyp monkeypatch.delenv("GALILEO_LUNA_AUTH_MODE", raising=False) from agent_control_evaluator_galileo.luna.client import GalileoLunaClient - with pytest.raises(ValueError, match="Both Galileo API key and API secret"): + with pytest.raises( + ValueError, match="Both a Galileo API key and a Galileo API secret are configured" + ): GalileoLunaClient() diff --git a/evaluators/contrib/galileo/tests/test_luna_evaluator.py b/evaluators/contrib/galileo/tests/test_luna_evaluator.py index 0a2bf3cd..1dc90c8d 100644 --- a/evaluators/contrib/galileo/tests/test_luna_evaluator.py +++ b/evaluators/contrib/galileo/tests/test_luna_evaluator.py @@ -135,7 +135,7 @@ def test_client_uses_protect_api_url_derivation(self) -> None: from agent_control_evaluator_galileo.luna import GalileoLunaClient # Given: the same console URL shape used by Protect - with patch.dict(os.environ, {"GALILEO_API_KEY": "test-key"}): + with patch.dict(os.environ, {"GALILEO_API_KEY": "test-key"}, clear=True): client = GalileoLunaClient(console_url="https://console.demo-v2.galileocloud.io") # Then: the API URL is derived the same way @@ -151,6 +151,7 @@ def test_client_uses_galileo_api_url_when_set(self) -> None: "GALILEO_API_KEY": "test-key", "GALILEO_API_URL": "https://api-test-luna.gcp-dev.galileo.ai/", }, + clear=True, ): client = GalileoLunaClient(console_url="https://console-test-luna.gcp-dev.galileo.ai") @@ -215,12 +216,127 @@ def test_client_derives_api_url_from_console_dash_hostname(self) -> None: from agent_control_evaluator_galileo.luna import GalileoLunaClient # Given: a console- devstack hostname - with patch.dict(os.environ, {"GALILEO_API_KEY": "test-key"}, clear=False): + with patch.dict(os.environ, {"GALILEO_API_KEY": "test-key"}, clear=True): client = GalileoLunaClient(console_url="https://console-test-luna.gcp-dev.galileo.ai") # Then: the matching api- hostname is used assert client.api_base == "https://api-test-luna.gcp-dev.galileo.ai" + def test_client_strips_whitespace_from_env_url(self) -> None: + from agent_control_evaluator_galileo.luna import GalileoLunaClient + + # Given: a URL override padded with whitespace and a trailing slash + with patch.dict( + os.environ, + { + "GALILEO_API_KEY": "test-key", + "GALILEO_LUNA_API_URL": " https://luna-api.example.com/ ", + }, + clear=True, + ): + client = GalileoLunaClient(console_url="https://console.example.com") + + # Then: the resolved base URL is trimmed and slash-free + assert client.api_base == "https://luna-api.example.com" + + def test_client_warns_when_deprecated_auth_mode_env_is_set(self) -> None: + from agent_control_evaluator_galileo.luna import GalileoLunaClient + + # Given: the deprecated auth-mode environment variable + with patch.dict( + os.environ, + {"GALILEO_API_KEY": "test-key", "GALILEO_LUNA_AUTH_MODE": "public"}, + clear=True, + ): + # When/Then: construction still works but emits a deprecation warning + with pytest.warns(DeprecationWarning, match="GALILEO_LUNA_AUTH_MODE is deprecated"): + client = GalileoLunaClient(console_url="https://console.example.com") + + assert client.auth_mode == "public" + + def test_client_rejects_unreadable_ca_bundle(self) -> None: + from agent_control_evaluator_galileo.luna import GalileoLunaClient + + # Given: a CA bundle path that does not exist + with patch.dict( + os.environ, + { + "GALILEO_API_SECRET_KEY": "test-secret", + "GALILEO_LUNA_CA_FILE": "/nonexistent/ca.pem", + }, + clear=True, + ): + # When/Then: client construction fails fast instead of at first request + with pytest.raises(ValueError, match="Failed to load CA bundle"): + GalileoLunaClient(console_url="https://console.example.com") + + @pytest.mark.asyncio + async def test_client_applies_ca_bundle_and_connection_limits(self) -> None: + import certifi + + from agent_control_evaluator_galileo.luna import GalileoLunaClient + from agent_control_evaluator_galileo.luna.client import DEFAULT_KEEPALIVE_EXPIRY_SECS + + captured: dict[str, object] = {} + real_async_client = httpx.AsyncClient + + def recording_client(**kwargs: object) -> httpx.AsyncClient: + captured.update(kwargs) + return real_async_client(**kwargs) + + # Given: internal auth with a CA bundle configured + with patch.dict(os.environ, {"GALILEO_API_SECRET_KEY": "test-secret"}, clear=True): + client = GalileoLunaClient( + console_url="https://console.example.com", ca_file=certifi.where() + ) + + with patch( + "agent_control_evaluator_galileo.luna.client.httpx.AsyncClient", recording_client + ): + try: + await client._get_client() + finally: + await client.close() + + # Then: TLS verification uses the configured CA bundle and pooled + # connections expire quickly so closed server sockets are not reused + assert captured["verify"] is client._ssl_context + limits = captured["limits"] + assert isinstance(limits, httpx.Limits) + assert limits.keepalive_expiry == DEFAULT_KEEPALIVE_EXPIRY_SECS + + @pytest.mark.asyncio + async def test_client_retries_once_after_server_disconnect(self) -> None: + from agent_control_evaluator_galileo.luna import GalileoLunaClient + + calls = {"count": 0} + + def handler(request: httpx.Request) -> httpx.Response: + calls["count"] += 1 + if calls["count"] == 1: + raise httpx.RemoteProtocolError( + "Server disconnected without sending a response.", request=request + ) + return httpx.Response( + 200, + json={"scorer_label": "toxicity", "score": 0.1, "status": "success"}, + ) + + # Given: a transport whose first attempt fails like a stale keepalive socket + with patch.dict(os.environ, {"GALILEO_API_KEY": "test-key"}, clear=True): + client = GalileoLunaClient(console_url="https://console.example.com") + client._client = httpx.AsyncClient(transport=httpx.MockTransport(handler)) + + try: + # When: invoking a scorer + response = await client.invoke(scorer_label="toxicity", output="hello") + finally: + await client.close() + + # Then: the request is retried once and succeeds + assert calls["count"] == 2 + assert response.score == 0.1 + @pytest.mark.asyncio async def test_client_posts_to_scorers_invoke_without_protect_fields(self) -> None: from agent_control_evaluator_galileo.luna import GalileoLunaClient @@ -242,7 +358,7 @@ def handler(request: httpx.Request) -> httpx.Response: ) # Given: a Luna client with a mock HTTP transport - with patch.dict(os.environ, {"GALILEO_API_KEY": "test-key"}): + with patch.dict(os.environ, {"GALILEO_API_KEY": "test-key"}, clear=True): client = GalileoLunaClient(console_url="https://console.demo-v2.galileocloud.io") client._client = httpx.AsyncClient( transport=httpx.MockTransport(handler), diff --git a/examples/galileo_luna/README.md b/examples/galileo_luna/README.md index 5ac97cda..9b078021 100644 --- a/examples/galileo_luna/README.md +++ b/examples/galileo_luna/README.md @@ -17,22 +17,28 @@ Start the Agent Control server from the repo root: make server-run ``` -Configure Galileo public API-key auth: +Configure exactly one Galileo credential. With an API key, the evaluator uses +public API-key auth and calls the public scorer API: ```bash -export GALILEO_LUNA_AUTH_MODE="public" export GALILEO_API_KEY="your-api-key" export GALILEO_CONSOLE_URL="https://console.demo-v2.galileocloud.io" ``` -For internal deployments, use internal auth instead: +With the Galileo API internal secret, it uses internal auth and calls the +internal scorer API. In-cluster deployments should point it at the +cluster-local API endpoint and, when that endpoint serves an internally-issued +TLS certificate, provide the CA bundle: ```bash -export GALILEO_LUNA_AUTH_MODE="internal" export GALILEO_API_SECRET_KEY="your-api-secret" -export GALILEO_API_URL="https://api.default.svc.cluster.local:8088" +export GALILEO_API_CLUSTER_URL="https://api.default.svc.cluster.local:8088" +export GALILEO_LUNA_CA_FILE="/etc/ssl/internal/ca.crt" ``` +`GALILEO_LUNA_API_URL` overrides the scorer API URL in either mode, and +`GALILEO_API_URL` remains the public API URL fallback. + Optional scorer settings: ```bash @@ -50,9 +56,9 @@ scalar as the scorer `output` field. If a selector returns structured data with `input` and/or `output` keys, those keys are sent directly and override `GALILEO_LUNA_PAYLOAD_FIELD`. -If both `GALILEO_API_KEY` and `GALILEO_API_SECRET_KEY`/`GALILEO_API_SECRET` are -set, `GALILEO_LUNA_AUTH_MODE` is required so the client does not silently choose -an auth path. +Setting both `GALILEO_API_KEY` and `GALILEO_API_SECRET_KEY`/`GALILEO_API_SECRET` +is an error; unset one so the auth mode can be inferred. `GALILEO_LUNA_AUTH_MODE` +is deprecated and only honored as a legacy override. Run: From 9d960852f624ca2853ef78983a6147d32e1e1b31 Mon Sep 17 00:00:00 2001 From: abhinav-galileo Date: Thu, 11 Jun 2026 17:46:02 +0530 Subject: [PATCH 3/6] fix(evaluator-galileo): align Luna auth config --- .../luna/client.py | 39 +++++-------------- .../galileo/tests/test_luna_coverage_gaps.py | 16 +++++--- .../galileo/tests/test_luna_evaluator.py | 32 --------------- examples/galileo_luna/README.md | 3 +- examples/galileo_luna/demo_agent.py | 17 ++------ examples/galileo_luna/setup_controls.py | 7 ++-- 6 files changed, 28 insertions(+), 86 deletions(-) diff --git a/evaluators/contrib/galileo/src/agent_control_evaluator_galileo/luna/client.py b/evaluators/contrib/galileo/src/agent_control_evaluator_galileo/luna/client.py index 2a995703..adb408fe 100644 --- a/evaluators/contrib/galileo/src/agent_control_evaluator_galileo/luna/client.py +++ b/evaluators/contrib/galileo/src/agent_control_evaluator_galileo/luna/client.py @@ -180,8 +180,6 @@ class GalileoLunaClient: Environment Variables: GALILEO_API_SECRET_KEY or GALILEO_API_SECRET: Galileo API internal JWT signing secret. GALILEO_API_KEY: Galileo API key fallback for public scorer invocation. - GALILEO_LUNA_AUTH_MODE: Deprecated. The auth mode is inferred from which - credential is configured; set exactly one credential instead. GALILEO_LUNA_API_URL: Galileo Luna scorer invoke API URL override. GALILEO_API_CLUSTER_URL: Internal Galileo API URL used in internal auth mode. GALILEO_API_URL: Galileo API URL fallback. @@ -211,8 +209,7 @@ def __init__( then GALILEO_API_CLUSTER_URL in internal auth mode, then GALILEO_API_URL, before deriving from the console URL. auth_mode: Auth mode to use. If not provided, inferred from the single - available credential. (Reading GALILEO_LUNA_AUTH_MODE from the - environment is deprecated.) + available credential. ca_file: CA bundle path used to verify the scorer API endpoint. If not provided, reads from GALILEO_LUNA_CA_FILE. Leave unset for endpoints with publicly-trusted certificates. @@ -274,16 +271,14 @@ def _resolve_auth_mode( ) -> AuthMode: if auth_mode == "public": if not api_key: - raise ValueError( - "GALILEO_API_KEY is required when GALILEO_LUNA_AUTH_MODE=public." - ) + raise ValueError("GALILEO_API_KEY is required for public Luna auth.") return "public" if auth_mode == "internal": if not api_secret: raise ValueError( - "GALILEO_API_SECRET_KEY or GALILEO_API_SECRET is required when " - "GALILEO_LUNA_AUTH_MODE=internal." + "GALILEO_API_SECRET_KEY or GALILEO_API_SECRET is required for " + "internal Luna auth." ) return "internal" @@ -414,26 +409,12 @@ async def invoke( try: client = await self._get_client() - try: - response = await client.post( - endpoint, - json=request_body, - headers=request_headers, - timeout=timeout, - ) - except (httpx.ConnectError, httpx.RemoteProtocolError) as exc: - # These errors occur before a response is received, typically when - # the server closed a pooled connection. The scorer invoke request - # is idempotent, so a single retry on a fresh connection is safe. - logger.warning( - "[GalileoLunaClient] Retrying once after connection error: %s", exc - ) - response = await client.post( - endpoint, - json=request_body, - headers=request_headers, - timeout=timeout, - ) + response = await client.post( + endpoint, + json=request_body, + headers=request_headers, + timeout=timeout, + ) response.raise_for_status() response_data = response.json() if not isinstance(response_data, dict): diff --git a/evaluators/contrib/galileo/tests/test_luna_coverage_gaps.py b/evaluators/contrib/galileo/tests/test_luna_coverage_gaps.py index ab304a74..e1518eec 100644 --- a/evaluators/contrib/galileo/tests/test_luna_coverage_gaps.py +++ b/evaluators/contrib/galileo/tests/test_luna_coverage_gaps.py @@ -470,7 +470,8 @@ def test_client_uses_explicit_public_mode_when_both_credentials_are_present(monk monkeypatch.setenv("GALILEO_LUNA_AUTH_MODE", "public") from agent_control_evaluator_galileo.luna.client import GalileoLunaClient - client = GalileoLunaClient() + with pytest.warns(DeprecationWarning, match="GALILEO_LUNA_AUTH_MODE is deprecated"): + client = GalileoLunaClient() assert client.auth_mode == "public" endpoint, request_headers = client._endpoint_and_headers(None) @@ -485,7 +486,8 @@ def test_client_uses_explicit_internal_mode_when_both_credentials_are_present(mo monkeypatch.setenv("GALILEO_LUNA_AUTH_MODE", "internal") from agent_control_evaluator_galileo.luna.client import GalileoLunaClient - client = GalileoLunaClient() + with pytest.warns(DeprecationWarning, match="GALILEO_LUNA_AUTH_MODE is deprecated"): + client = GalileoLunaClient() assert client.auth_mode == "internal" endpoint, request_headers = client._endpoint_and_headers(None) @@ -501,8 +503,9 @@ def test_client_rejects_mode_without_matching_credential(monkeypatch): monkeypatch.setenv("GALILEO_LUNA_AUTH_MODE", "internal") from agent_control_evaluator_galileo.luna.client import GalileoLunaClient - with pytest.raises(ValueError, match="GALILEO_API_SECRET_KEY"): - GalileoLunaClient() + with pytest.warns(DeprecationWarning, match="GALILEO_LUNA_AUTH_MODE is deprecated"): + with pytest.raises(ValueError, match="GALILEO_API_SECRET_KEY"): + GalileoLunaClient() def test_client_rejects_invalid_auth_mode(monkeypatch): @@ -511,8 +514,9 @@ def test_client_rejects_invalid_auth_mode(monkeypatch): monkeypatch.setenv("GALILEO_LUNA_AUTH_MODE", "sideways") from agent_control_evaluator_galileo.luna.client import GalileoLunaClient - with pytest.raises(ValueError, match="GALILEO_LUNA_AUTH_MODE"): - GalileoLunaClient() + with pytest.warns(DeprecationWarning, match="GALILEO_LUNA_AUTH_MODE is deprecated"): + with pytest.raises(ValueError, match="GALILEO_LUNA_AUTH_MODE"): + GalileoLunaClient() class TestDeriveApiUrl: diff --git a/evaluators/contrib/galileo/tests/test_luna_evaluator.py b/evaluators/contrib/galileo/tests/test_luna_evaluator.py index 1dc90c8d..f61020e1 100644 --- a/evaluators/contrib/galileo/tests/test_luna_evaluator.py +++ b/evaluators/contrib/galileo/tests/test_luna_evaluator.py @@ -305,38 +305,6 @@ def recording_client(**kwargs: object) -> httpx.AsyncClient: assert isinstance(limits, httpx.Limits) assert limits.keepalive_expiry == DEFAULT_KEEPALIVE_EXPIRY_SECS - @pytest.mark.asyncio - async def test_client_retries_once_after_server_disconnect(self) -> None: - from agent_control_evaluator_galileo.luna import GalileoLunaClient - - calls = {"count": 0} - - def handler(request: httpx.Request) -> httpx.Response: - calls["count"] += 1 - if calls["count"] == 1: - raise httpx.RemoteProtocolError( - "Server disconnected without sending a response.", request=request - ) - return httpx.Response( - 200, - json={"scorer_label": "toxicity", "score": 0.1, "status": "success"}, - ) - - # Given: a transport whose first attempt fails like a stale keepalive socket - with patch.dict(os.environ, {"GALILEO_API_KEY": "test-key"}, clear=True): - client = GalileoLunaClient(console_url="https://console.example.com") - client._client = httpx.AsyncClient(transport=httpx.MockTransport(handler)) - - try: - # When: invoking a scorer - response = await client.invoke(scorer_label="toxicity", output="hello") - finally: - await client.close() - - # Then: the request is retried once and succeeds - assert calls["count"] == 2 - assert response.score == 0.1 - @pytest.mark.asyncio async def test_client_posts_to_scorers_invoke_without_protect_fields(self) -> None: from agent_control_evaluator_galileo.luna import GalileoLunaClient diff --git a/examples/galileo_luna/README.md b/examples/galileo_luna/README.md index 9b078021..8fc1528b 100644 --- a/examples/galileo_luna/README.md +++ b/examples/galileo_luna/README.md @@ -57,8 +57,7 @@ scalar as the scorer `output` field. If a selector returns structured data with `GALILEO_LUNA_PAYLOAD_FIELD`. Setting both `GALILEO_API_KEY` and `GALILEO_API_SECRET_KEY`/`GALILEO_API_SECRET` -is an error; unset one so the auth mode can be inferred. `GALILEO_LUNA_AUTH_MODE` -is deprecated and only honored as a legacy override. +is an error; unset one so the auth mode can be inferred. Run: diff --git a/examples/galileo_luna/demo_agent.py b/examples/galileo_luna/demo_agent.py index 8c7f59b2..1c9b4ab4 100644 --- a/examples/galileo_luna/demo_agent.py +++ b/examples/galileo_luna/demo_agent.py @@ -21,7 +21,6 @@ AGENT_NAME = "galileo-luna-agent" SERVER_URL = os.getenv("AGENT_CONTROL_URL", "http://localhost:8000") -LUNA_AUTH_MODE = os.getenv("GALILEO_LUNA_AUTH_MODE") logging.basicConfig( level=logging.INFO, @@ -100,28 +99,20 @@ async def run_demo() -> None: "internal mode." ) return - if api_key and api_secret and LUNA_AUTH_MODE not in {"public", "internal"}: + if api_key and api_secret: print( "Both GALILEO_API_KEY and GALILEO_API_SECRET_KEY/GALILEO_API_SECRET are set. " - "Set GALILEO_LUNA_AUTH_MODE to 'public' or 'internal'." - ) - return - if LUNA_AUTH_MODE == "public" and not api_key: - print("GALILEO_API_KEY is required when GALILEO_LUNA_AUTH_MODE=public.") - return - if LUNA_AUTH_MODE == "internal" and not api_secret: - print( - "GALILEO_API_SECRET_KEY or GALILEO_API_SECRET is required when " - "GALILEO_LUNA_AUTH_MODE=internal." + "Unset one so the auth mode can be inferred." ) return + auth_mode = "public" if api_key else "internal" print("=" * 72) print("Direct Galileo Luna Evaluator Demo") print("=" * 72) print(f"Server: {SERVER_URL}") print(f"Agent: {AGENT_NAME}") - print(f"Auth: GALILEO_LUNA_AUTH_MODE={LUNA_AUTH_MODE or '(auto if one credential)'}") + print(f"Auth: {auth_mode}") print() init_agent() diff --git a/examples/galileo_luna/setup_controls.py b/examples/galileo_luna/setup_controls.py index fb4c6c76..b4534c58 100644 --- a/examples/galileo_luna/setup_controls.py +++ b/examples/galileo_luna/setup_controls.py @@ -4,8 +4,8 @@ Prerequisites: - Agent Control server running at AGENT_CONTROL_URL, default http://localhost:8000 - Galileo credentials set where demo_agent.py will run: - GALILEO_API_KEY with GALILEO_LUNA_AUTH_MODE=public, or - GALILEO_API_SECRET_KEY/GALILEO_API_SECRET with GALILEO_LUNA_AUTH_MODE=internal + GALILEO_API_KEY for public auth, or + GALILEO_API_SECRET_KEY/GALILEO_API_SECRET for internal auth Usage: uv run python setup_controls.py @@ -29,7 +29,6 @@ LUNA_SCORER_VERSION_ID = os.getenv("GALILEO_LUNA_SCORER_VERSION_ID") LUNA_THRESHOLD = float(os.getenv("GALILEO_LUNA_THRESHOLD", "0.5")) LUNA_PAYLOAD_FIELD = os.getenv("GALILEO_LUNA_PAYLOAD_FIELD", "output") -LUNA_AUTH_MODE = os.getenv("GALILEO_LUNA_AUTH_MODE") if LUNA_PAYLOAD_FIELD not in {"input", "output"}: raise ValueError("GALILEO_LUNA_PAYLOAD_FIELD must be either 'input' or 'output'.") @@ -175,7 +174,7 @@ async def setup_demo() -> None: f"threshold={LUNA_THRESHOLD}, " f"payload_field={LUNA_PAYLOAD_FIELD!r}" ) - print(f"Auth: GALILEO_LUNA_AUTH_MODE={LUNA_AUTH_MODE or '(auto if one credential)'}") + print("Auth: inferred from the single configured Galileo credential") async with AgentControlClient(base_url=SERVER_URL, timeout=30.0) as client: await client.health_check() From 454acdf57621767db6d1d69a78fb1e97ffe8fc29 Mon Sep 17 00:00:00 2001 From: abhinav-galileo Date: Thu, 11 Jun 2026 18:03:23 +0530 Subject: [PATCH 4/6] fix(evaluators): keep Luna API URL override generic --- .../luna/client.py | 17 ++++------- .../galileo/tests/test_luna_evaluator.py | 28 ++++--------------- examples/galileo_luna/README.md | 8 +++--- examples/galileo_luna/demo_agent.py | 4 +-- examples/galileo_luna/setup_controls.py | 2 +- 5 files changed, 18 insertions(+), 41 deletions(-) diff --git a/evaluators/contrib/galileo/src/agent_control_evaluator_galileo/luna/client.py b/evaluators/contrib/galileo/src/agent_control_evaluator_galileo/luna/client.py index adb408fe..2c34d8c3 100644 --- a/evaluators/contrib/galileo/src/agent_control_evaluator_galileo/luna/client.py +++ b/evaluators/contrib/galileo/src/agent_control_evaluator_galileo/luna/client.py @@ -178,10 +178,9 @@ class GalileoLunaClient: """Thin HTTP client for Galileo Luna direct scorer invocation. Environment Variables: - GALILEO_API_SECRET_KEY or GALILEO_API_SECRET: Galileo API internal JWT signing secret. + GALILEO_API_SECRET_KEY: Galileo API internal JWT signing secret. GALILEO_API_KEY: Galileo API key fallback for public scorer invocation. GALILEO_LUNA_API_URL: Galileo Luna scorer invoke API URL override. - GALILEO_API_CLUSTER_URL: Internal Galileo API URL used in internal auth mode. GALILEO_API_URL: Galileo API URL fallback. GALILEO_LUNA_CA_FILE: CA bundle used to verify the scorer API endpoint, for deployments whose API serves an internally-issued TLS certificate. @@ -202,12 +201,11 @@ def __init__( Args: api_key: Galileo API key. If not provided, reads from GALILEO_API_KEY. api_secret: Galileo API secret for internal JWT auth. If not provided, - reads from GALILEO_API_SECRET_KEY or GALILEO_API_SECRET. + reads from GALILEO_API_SECRET_KEY. console_url: Galileo Console URL. If not provided, reads from GALILEO_CONSOLE_URL or uses the production console URL. api_url: Galileo API URL. If not provided, reads from GALILEO_LUNA_API_URL, - then GALILEO_API_CLUSTER_URL in internal auth mode, then GALILEO_API_URL, - before deriving from the console URL. + then GALILEO_API_URL, before deriving from the console URL. auth_mode: Auth mode to use. If not provided, inferred from the single available credential. ca_file: CA bundle path used to verify the scorer API endpoint. If not @@ -234,17 +232,15 @@ def __init__( self.console_url = ( console_url or os.getenv("GALILEO_CONSOLE_URL") or "https://console.galileo.ai" ) - self.api_base = self._resolve_api_base(api_url, resolved_auth_mode) + self.api_base = self._resolve_api_base(api_url) self.ca_file = (ca_file or os.getenv("GALILEO_LUNA_CA_FILE") or "").strip() or None self._ssl_context = self._load_ssl_context(self.ca_file) self._client: httpx.AsyncClient | None = None logger.info("[GalileoLunaClient] Auth mode selected: %s", self.auth_mode) - def _resolve_api_base(self, api_url: str | None, auth_mode: AuthMode) -> str: + def _resolve_api_base(self, api_url: str | None) -> str: """Resolve the scorer invoke API base URL from explicit and environment config.""" candidates = [api_url, os.getenv("GALILEO_LUNA_API_URL")] - if auth_mode == "internal": - candidates.append(os.getenv("GALILEO_API_CLUSTER_URL")) candidates.append(os.getenv("GALILEO_API_URL")) for candidate in candidates: @@ -277,8 +273,7 @@ def _resolve_auth_mode( if auth_mode == "internal": if not api_secret: raise ValueError( - "GALILEO_API_SECRET_KEY or GALILEO_API_SECRET is required for " - "internal Luna auth." + "GALILEO_API_SECRET_KEY is required for internal Luna auth." ) return "internal" diff --git a/evaluators/contrib/galileo/tests/test_luna_evaluator.py b/evaluators/contrib/galileo/tests/test_luna_evaluator.py index f61020e1..7aab57d6 100644 --- a/evaluators/contrib/galileo/tests/test_luna_evaluator.py +++ b/evaluators/contrib/galileo/tests/test_luna_evaluator.py @@ -176,41 +176,23 @@ def test_client_uses_luna_api_url_when_set(self) -> None: # Then: the Luna-specific URL wins without changing the general API URL contract assert client.api_base == "https://luna-api.example.com" - def test_client_uses_cluster_url_for_internal_auth(self) -> None: + def test_client_uses_luna_api_url_for_internal_auth(self) -> None: from agent_control_evaluator_galileo.luna import GalileoLunaClient - # Given: internal auth and both cluster and public API URLs are configured + # Given: internal auth and both Luna-specific and general API URLs are configured with patch.dict( os.environ, { "GALILEO_API_SECRET_KEY": "test-secret", - "GALILEO_API_CLUSTER_URL": "https://api.default.svc.cluster.local:8088", + "GALILEO_LUNA_API_URL": "https://internal-api.example.com", "GALILEO_API_URL": "https://api-public.example.com", }, clear=True, ): client = GalileoLunaClient(console_url="https://console.example.com") - # Then: internal scorer invocation uses the cluster-local API base - assert client.api_base == "https://api.default.svc.cluster.local:8088" - - def test_client_ignores_cluster_url_for_public_auth(self) -> None: - from agent_control_evaluator_galileo.luna import GalileoLunaClient - - # Given: public auth with a cluster URL present in the environment - with patch.dict( - os.environ, - { - "GALILEO_API_KEY": "test-key", - "GALILEO_API_CLUSTER_URL": "https://api.default.svc.cluster.local:8088", - "GALILEO_API_URL": "https://api-public.example.com", - }, - clear=True, - ): - client = GalileoLunaClient(console_url="https://console.example.com") - - # Then: public scorer invocation still uses the public API URL - assert client.api_base == "https://api-public.example.com" + # Then: internal scorer invocation uses the Luna-specific API base + assert client.api_base == "https://internal-api.example.com" def test_client_derives_api_url_from_console_dash_hostname(self) -> None: from agent_control_evaluator_galileo.luna import GalileoLunaClient diff --git a/examples/galileo_luna/README.md b/examples/galileo_luna/README.md index 8fc1528b..de68d3f5 100644 --- a/examples/galileo_luna/README.md +++ b/examples/galileo_luna/README.md @@ -32,11 +32,11 @@ TLS certificate, provide the CA bundle: ```bash export GALILEO_API_SECRET_KEY="your-api-secret" -export GALILEO_API_CLUSTER_URL="https://api.default.svc.cluster.local:8088" +export GALILEO_LUNA_API_URL="https://api.default.svc.cluster.local:8088" export GALILEO_LUNA_CA_FILE="/etc/ssl/internal/ca.crt" ``` -`GALILEO_LUNA_API_URL` overrides the scorer API URL in either mode, and +`GALILEO_LUNA_API_URL` overrides the scorer API URL in either mode. `GALILEO_API_URL` remains the public API URL fallback. Optional scorer settings: @@ -56,8 +56,8 @@ scalar as the scorer `output` field. If a selector returns structured data with `input` and/or `output` keys, those keys are sent directly and override `GALILEO_LUNA_PAYLOAD_FIELD`. -Setting both `GALILEO_API_KEY` and `GALILEO_API_SECRET_KEY`/`GALILEO_API_SECRET` -is an error; unset one so the auth mode can be inferred. +Setting both `GALILEO_API_KEY` and `GALILEO_API_SECRET_KEY` is an error; unset +one so the auth mode can be inferred. Run: diff --git a/examples/galileo_luna/demo_agent.py b/examples/galileo_luna/demo_agent.py index 1c9b4ab4..6ae1f5bb 100644 --- a/examples/galileo_luna/demo_agent.py +++ b/examples/galileo_luna/demo_agent.py @@ -91,7 +91,7 @@ def init_agent() -> None: async def run_demo() -> None: """Run scripted scenarios.""" api_key = os.getenv("GALILEO_API_KEY") - api_secret = os.getenv("GALILEO_API_SECRET_KEY") or os.getenv("GALILEO_API_SECRET") + api_secret = os.getenv("GALILEO_API_SECRET_KEY") if not api_key and not api_secret: print( "Galileo credentials are required for the galileo.luna evaluator. " @@ -101,7 +101,7 @@ async def run_demo() -> None: return if api_key and api_secret: print( - "Both GALILEO_API_KEY and GALILEO_API_SECRET_KEY/GALILEO_API_SECRET are set. " + "Both GALILEO_API_KEY and GALILEO_API_SECRET_KEY are set. " "Unset one so the auth mode can be inferred." ) return diff --git a/examples/galileo_luna/setup_controls.py b/examples/galileo_luna/setup_controls.py index b4534c58..97eb4c13 100644 --- a/examples/galileo_luna/setup_controls.py +++ b/examples/galileo_luna/setup_controls.py @@ -5,7 +5,7 @@ - Agent Control server running at AGENT_CONTROL_URL, default http://localhost:8000 - Galileo credentials set where demo_agent.py will run: GALILEO_API_KEY for public auth, or - GALILEO_API_SECRET_KEY/GALILEO_API_SECRET for internal auth + GALILEO_API_SECRET_KEY for internal auth Usage: uv run python setup_controls.py From b16fddf98edcd0a4cf0f7a0d2d0378cca09d2a70 Mon Sep 17 00:00:00 2001 From: abhinav-galileo Date: Thu, 11 Jun 2026 18:12:43 +0530 Subject: [PATCH 5/6] docs(examples): clarify Luna evaluator configuration --- examples/galileo_luna/README.md | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/examples/galileo_luna/README.md b/examples/galileo_luna/README.md index de68d3f5..20df4005 100644 --- a/examples/galileo_luna/README.md +++ b/examples/galileo_luna/README.md @@ -17,27 +17,40 @@ Start the Agent Control server from the repo root: make server-run ``` -Configure exactly one Galileo credential. With an API key, the evaluator uses -public API-key auth and calls the public scorer API: +Configure exactly one Galileo credential. + +For most OSS users, only an API key is required. This uses public API-key auth +and calls the public scorer API: ```bash export GALILEO_API_KEY="your-api-key" export GALILEO_CONSOLE_URL="https://console.demo-v2.galileocloud.io" ``` -With the Galileo API internal secret, it uses internal auth and calls the -internal scorer API. In-cluster deployments should point it at the -cluster-local API endpoint and, when that endpoint serves an internally-issued -TLS certificate, provide the CA bundle: +`GALILEO_CONSOLE_URL` is optional when using the production console URL. +`GALILEO_LUNA_API_URL` is not required for this path. The client uses +`GALILEO_API_URL` when set, otherwise it derives the API URL from +`GALILEO_CONSOLE_URL`. + +For deployments that use service-to-service internal auth, provide the API +internal secret instead of an API key: ```bash export GALILEO_API_SECRET_KEY="your-api-secret" +``` + +Deployment tooling may also set a custom scorer API endpoint and CA bundle. Use +these only when the scorer API is not reachable through the default public API +URL derivation, or when the endpoint uses a private CA: + +```bash export GALILEO_LUNA_API_URL="https://api.default.svc.cluster.local:8088" export GALILEO_LUNA_CA_FILE="/etc/ssl/internal/ca.crt" ``` -`GALILEO_LUNA_API_URL` overrides the scorer API URL in either mode. -`GALILEO_API_URL` remains the public API URL fallback. +`GALILEO_LUNA_API_URL` overrides the scorer API URL in either auth mode. +`GALILEO_LUNA_CA_FILE` is only needed for endpoints that are not trusted by the +system CA store. Optional scorer settings: From 6842669b406e71ba747b850094d0141b28d8c498 Mon Sep 17 00:00:00 2001 From: abhinav-galileo Date: Thu, 11 Jun 2026 18:15:11 +0530 Subject: [PATCH 6/6] docs(examples): distinguish deployment-injected Luna secret --- .../agent_control_evaluator_galileo/luna/client.py | 6 +++--- examples/galileo_luna/README.md | 12 +++++++----- examples/galileo_luna/demo_agent.py | 4 ++-- examples/galileo_luna/setup_controls.py | 2 +- 4 files changed, 13 insertions(+), 11 deletions(-) diff --git a/evaluators/contrib/galileo/src/agent_control_evaluator_galileo/luna/client.py b/evaluators/contrib/galileo/src/agent_control_evaluator_galileo/luna/client.py index 2c34d8c3..4c7d4162 100644 --- a/evaluators/contrib/galileo/src/agent_control_evaluator_galileo/luna/client.py +++ b/evaluators/contrib/galileo/src/agent_control_evaluator_galileo/luna/client.py @@ -178,7 +178,7 @@ class GalileoLunaClient: """Thin HTTP client for Galileo Luna direct scorer invocation. Environment Variables: - GALILEO_API_SECRET_KEY: Galileo API internal JWT signing secret. + GALILEO_API_SECRET_KEY: Deployment-provided Galileo API internal JWT signing secret. GALILEO_API_KEY: Galileo API key fallback for public scorer invocation. GALILEO_LUNA_API_URL: Galileo Luna scorer invoke API URL override. GALILEO_API_URL: Galileo API URL fallback. @@ -200,8 +200,8 @@ def __init__( Args: api_key: Galileo API key. If not provided, reads from GALILEO_API_KEY. - api_secret: Galileo API secret for internal JWT auth. If not provided, - reads from GALILEO_API_SECRET_KEY. + api_secret: Deployment-provided Galileo API secret for internal JWT auth. + If not provided, reads from GALILEO_API_SECRET_KEY. console_url: Galileo Console URL. If not provided, reads from GALILEO_CONSOLE_URL or uses the production console URL. api_url: Galileo API URL. If not provided, reads from GALILEO_LUNA_API_URL, diff --git a/examples/galileo_luna/README.md b/examples/galileo_luna/README.md index 20df4005..b81b034f 100644 --- a/examples/galileo_luna/README.md +++ b/examples/galileo_luna/README.md @@ -32,16 +32,18 @@ export GALILEO_CONSOLE_URL="https://console.demo-v2.galileocloud.io" `GALILEO_API_URL` when set, otherwise it derives the API URL from `GALILEO_CONSOLE_URL`. -For deployments that use service-to-service internal auth, provide the API -internal secret instead of an API key: +For deployments that use service-to-service internal auth, the deployment +environment should inject the API internal secret instead of an API key: ```bash +# Set by deployment tooling, not by normal OSS users. export GALILEO_API_SECRET_KEY="your-api-secret" ``` -Deployment tooling may also set a custom scorer API endpoint and CA bundle. Use -these only when the scorer API is not reachable through the default public API -URL derivation, or when the endpoint uses a private CA: +OSS users do not need to set `GALILEO_API_SECRET_KEY` manually for the public +API-key path. Deployment tooling may also set a custom scorer API endpoint and +CA bundle. Use these only when the scorer API is not reachable through the +default public API URL derivation, or when the endpoint uses a private CA: ```bash export GALILEO_LUNA_API_URL="https://api.default.svc.cluster.local:8088" diff --git a/examples/galileo_luna/demo_agent.py b/examples/galileo_luna/demo_agent.py index 6ae1f5bb..0b6a0f8a 100644 --- a/examples/galileo_luna/demo_agent.py +++ b/examples/galileo_luna/demo_agent.py @@ -95,8 +95,8 @@ async def run_demo() -> None: if not api_key and not api_secret: print( "Galileo credentials are required for the galileo.luna evaluator. " - "Set GALILEO_API_KEY for public mode or GALILEO_API_SECRET_KEY for " - "internal mode." + "Set GALILEO_API_KEY for public mode. Deployments using internal " + "mode should inject GALILEO_API_SECRET_KEY." ) return if api_key and api_secret: diff --git a/examples/galileo_luna/setup_controls.py b/examples/galileo_luna/setup_controls.py index 97eb4c13..fe1434c8 100644 --- a/examples/galileo_luna/setup_controls.py +++ b/examples/galileo_luna/setup_controls.py @@ -5,7 +5,7 @@ - Agent Control server running at AGENT_CONTROL_URL, default http://localhost:8000 - Galileo credentials set where demo_agent.py will run: GALILEO_API_KEY for public auth, or - GALILEO_API_SECRET_KEY for internal auth + deployment-injected GALILEO_API_SECRET_KEY for internal auth Usage: uv run python setup_controls.py