Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@ dependencies = [
"websockets>=15.0",
"asgiref>=3.11.0",
"filetype>=1.2.0",
"google-auth[requests]>=2.0.0",
]

[project.optional-dependencies]
gcp = ["google-auth[requests]>=2.0.0"]

[project.urls]
Homepage = "https://withceleste.ai"
Documentation = "https://withceleste.ai/docs"
Expand Down
11 changes: 11 additions & 0 deletions src/celeste/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,16 @@ def __init__(self) -> None:
super().__init__("Stream completed but no chunks were produced")


class MissingDependencyError(Error):
"""Raised when a required optional dependency is not installed."""

def __init__(self, *, library: str, extra: str) -> None:
super().__init__(
f"Missing required dependency `{library}`. "
f'Install it with: pip install "celeste-ai[{extra}]"'
)


class CredentialsError(Error):
"""Errors related to API credentials."""

Expand Down Expand Up @@ -217,6 +227,7 @@ def __init__(self, parameter: str, model_id: str) -> None:
"ConstraintViolationError",
"Error",
"MissingCredentialsError",
"MissingDependencyError",
"ModalityNotFoundError",
"ModelNotFoundError",
"StreamEmptyError",
Expand Down
25 changes: 23 additions & 2 deletions src/celeste/providers/google/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
from pydantic import ConfigDict

from celeste.auth import Authentication
from celeste.exceptions import MissingDependencyError

VERTEX_BASE_URL = "https://{location}-aiplatform.googleapis.com"
VERTEX_GLOBAL_BASE_URL = "https://aiplatform.googleapis.com"


class GoogleADC(Authentication):
Expand All @@ -18,6 +22,7 @@ class GoogleADC(Authentication):

scopes: ClassVar[list[str]] = ["https://www.googleapis.com/auth/cloud-platform"]
project_id: str | None = None
location: str = "global"

model_config: ClassVar[ConfigDict] = ConfigDict(arbitrary_types_allowed=True)

Expand All @@ -35,8 +40,11 @@ def get_headers(self) -> dict[str, str]:

def _get_access_token(self) -> tuple[str, str | None]:
"""Get OAuth access token using Application Default Credentials."""
import google.auth
import google.auth.transport.requests
try:
import google.auth
import google.auth.transport.requests
except ImportError as e:
raise MissingDependencyError(library="google-auth", extra="gcp") from e

if self._credentials is None:
self._credentials, self._project = google.auth.default(scopes=self.scopes)
Expand All @@ -47,5 +55,18 @@ def _get_access_token(self) -> tuple[str, str | None]:

return self._credentials.token, self.project_id or self._project

@property
def resolved_project_id(self) -> str | None:
"""Return effective project_id (explicit or from ADC credentials)."""
if self._credentials is None:
self._get_access_token()
return self.project_id or self._project

def get_vertex_base_url(self) -> str:
"""Return the Vertex AI base URL for the configured location."""
if self.location == "global":
return VERTEX_GLOBAL_BASE_URL
return VERTEX_BASE_URL.format(location=self.location)


__all__ = ["GoogleADC"]
Loading