Skip to content
Closed
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
5 changes: 5 additions & 0 deletions packages/gooddata-sdk/src/gooddata_sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,11 @@
CatalogOpenAiApiKeyAuth,
CatalogOpenAiProviderConfig,
)
from gooddata_sdk.catalog.organization.entity_model.resolved_llm_provider import (
CatalogResolvedLlm,
CatalogResolvedLlmProvider,
CatalogResolvedLlms,
)
from gooddata_sdk.catalog.organization.entity_model.organization import CatalogOrganization
from gooddata_sdk.catalog.organization.entity_model.setting import CatalogOrganizationSetting
from gooddata_sdk.catalog.organization.layout.export_template import (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,18 +236,18 @@ def init(
id: str,
models: list[CatalogLlmProviderModel],
provider_config: CatalogLlmProviderConfig,
default_model_id: str,
name: str | None = None,
description: str | None = None,
default_model_id: str | None = None,
) -> CatalogLlmProvider:
return cls(
id=id,
attributes=CatalogLlmProviderAttributes(
models=models,
provider_config=provider_config,
default_model_id=default_model_id,
name=name,
description=description,
default_model_id=default_model_id,
),
)

Expand Down Expand Up @@ -314,9 +314,9 @@ def init(
class CatalogLlmProviderAttributes(Base):
models: list[CatalogLlmProviderModel]
provider_config: CatalogLlmProviderConfig
default_model_id: str
name: str | None = None
description: str | None = None
default_model_id: str | None = None

@staticmethod
def client_class() -> type[JsonApiLlmProviderInAttributes]:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# (C) 2026 GoodData Corporation
from __future__ import annotations

from typing import Any

from attr import define

from gooddata_sdk.catalog.organization.entity_model.llm_provider import CatalogLlmProviderModel
from gooddata_sdk.utils import safeget


@define(kw_only=True)
class CatalogResolvedLlm:
"""Resolved LLM base — carries id and title shared by both providers and endpoints."""

id: str
title: str | None = None

@classmethod
def from_api(cls, data: dict[str, Any]) -> CatalogResolvedLlm:
return cls(
id=data["id"],
title=safeget(data, ["title"]),
)


@define(kw_only=True)
class CatalogResolvedLlmProvider(CatalogResolvedLlm):
"""Resolved LLM provider — extends CatalogResolvedLlm with available models."""

models: list[CatalogLlmProviderModel] = []

@classmethod
def from_api(cls, data: dict[str, Any]) -> CatalogResolvedLlmProvider:
raw_models = safeget(data, ["models"]) or []
models = [
CatalogLlmProviderModel(
id=safeget(m, ["id"]),
family=safeget(m, ["family"]),
)
for m in raw_models
]
return cls(
id=data["id"],
title=safeget(data, ["title"]),
models=models,
)


@define(kw_only=True)
class CatalogResolvedLlms:
"""Wrapper for a list of resolved LLMs returned by the resolveLlmProviders endpoint."""

data: list[CatalogResolvedLlm] | None = None

@classmethod
def from_api(cls, raw: dict[str, Any]) -> CatalogResolvedLlms:
raw_data = safeget(raw, ["data"])
if raw_data is None:
return cls(data=None)
items: list[CatalogResolvedLlm] = []
for item in raw_data:
item_type = safeget(item, ["type"])
if item_type == "LLM_PROVIDER" or safeget(item, ["models"]) is not None:
items.append(CatalogResolvedLlmProvider.from_api(item))
else:
items.append(CatalogResolvedLlm.from_api(item))
return cls(data=items)
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
CatalogLlmProviderPatch,
CatalogLlmProviderPatchDocument,
)
from gooddata_sdk.catalog.organization.entity_model.resolved_llm_provider import CatalogResolvedLlms
from gooddata_sdk.catalog.organization.entity_model.setting import CatalogOrganizationSetting
from gooddata_sdk.catalog.organization.layout.identity_provider import CatalogDeclarativeIdentityProvider
from gooddata_sdk.catalog.organization.layout.notification_channel import CatalogDeclarativeNotificationChannel
Expand Down Expand Up @@ -732,6 +733,18 @@ def delete_llm_provider(self, id: str) -> None:
"""
self._entities_api.delete_entity_llm_providers(id, _check_return_type=False)

def resolve_llm_providers(self, workspace_id: str) -> CatalogResolvedLlms:
"""Resolve active LLM providers for a workspace.

Args:
workspace_id: Workspace identifier

Returns:
CatalogResolvedLlms: Resolved LLMs for the workspace
"""
response = self._actions_api.resolve_llm_providers(workspace_id, _check_return_type=False)
return CatalogResolvedLlms.from_api(response)

# Layout APIs

def get_declarative_notification_channels(self) -> list[CatalogDeclarativeNotificationChannel]:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
CatalogLlmEndpoint,
CatalogOrganization,
CatalogOrganizationSetting,
CatalogResolvedLlmProvider,
CatalogResolvedLlms,
CatalogRsaSpecification,
CatalogWebhook,
GoodDataSdk,
Expand Down Expand Up @@ -556,6 +558,18 @@ def test_delete_llm_endpoint(test_config):
pass


@gd_vcr.use_cassette(str(_fixtures_dir / "resolve_llm_providers.yaml"))
def test_resolve_llm_providers(test_config):
sdk = GoodDataSdk.create(host_=test_config["host"], token_=test_config["token"])

workspace_id = test_config["workspace_id"]
result = sdk.catalog_organization.resolve_llm_providers(workspace_id)
assert isinstance(result, CatalogResolvedLlms)
assert result.data is not None
assert len(result.data) > 0
assert all(isinstance(item, CatalogResolvedLlmProvider) for item in result.data)


#
# The following tests are commented out as they require the organization to have the FEDERATED_IDENTITY_MANAGEMENT
# entitlement enabled which cannot be done via SDK and must be done by GoodData support.
Expand Down
Loading