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..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 @@ -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" @@ -164,9 +178,12 @@ 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: Deployment-provided 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_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). """ @@ -177,23 +194,27 @@ 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. 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. + 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_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. + api_url: Galileo API URL. If not provided, reads from GALILEO_LUNA_API_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 + 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") @@ -211,12 +232,32 @@ 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) + 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) -> str: + """Resolve the scorer invoke API base URL from explicit and environment config.""" + candidates = [api_url, os.getenv("GALILEO_LUNA_API_URL")] + candidates.append(os.getenv("GALILEO_API_URL")) + + for candidate in candidates: + if candidate and candidate.strip(): + 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, @@ -226,24 +267,21 @@ 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 is required for internal Luna auth." ) return "internal" 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" @@ -284,9 +322,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 diff --git a/evaluators/contrib/galileo/tests/test_luna_coverage_gaps.py b/evaluators/contrib/galileo/tests/test_luna_coverage_gaps.py index 9d5f6766..e1518eec 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() @@ -468,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) @@ -483,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) @@ -499,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): @@ -509,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 f123e214..7aab57d6 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,22 +151,142 @@ 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") # 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_luna_api_url_for_internal_auth(self) -> None: + from agent_control_evaluator_galileo.luna import GalileoLunaClient + + # 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_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 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 # 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_posts_to_scorers_invoke_without_protect_fields(self) -> None: from agent_control_evaluator_galileo.luna import GalileoLunaClient @@ -188,7 +308,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..b81b034f 100644 --- a/examples/galileo_luna/README.md +++ b/examples/galileo_luna/README.md @@ -17,22 +17,43 @@ Start the Agent Control server from the repo root: make server-run ``` -Configure Galileo public API-key auth: +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_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: +`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, the deployment +environment should inject the API internal secret instead of an API key: ```bash -export GALILEO_LUNA_AUTH_MODE="internal" +# Set by deployment tooling, not by normal OSS users. export GALILEO_API_SECRET_KEY="your-api-secret" -export GALILEO_API_URL="https://api.default.svc.cluster.local:8088" ``` +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" +export GALILEO_LUNA_CA_FILE="/etc/ssl/internal/ca.crt" +``` + +`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: ```bash @@ -50,9 +71,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`. -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` 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..0b6a0f8a 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, @@ -92,36 +91,28 @@ 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. " - "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 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." + "Both GALILEO_API_KEY and GALILEO_API_SECRET_KEY are set. " + "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..fe1434c8 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 + deployment-injected GALILEO_API_SECRET_KEY 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()