diff --git a/packages/gooddata-sdk/src/gooddata_sdk/__init__.py b/packages/gooddata-sdk/src/gooddata_sdk/__init__.py index 1be061a11..39295dcbb 100644 --- a/packages/gooddata-sdk/src/gooddata_sdk/__init__.py +++ b/packages/gooddata-sdk/src/gooddata_sdk/__init__.py @@ -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 ( diff --git a/packages/gooddata-sdk/src/gooddata_sdk/catalog/organization/entity_model/llm_provider.py b/packages/gooddata-sdk/src/gooddata_sdk/catalog/organization/entity_model/llm_provider.py index 0146b8f9b..a95332598 100644 --- a/packages/gooddata-sdk/src/gooddata_sdk/catalog/organization/entity_model/llm_provider.py +++ b/packages/gooddata-sdk/src/gooddata_sdk/catalog/organization/entity_model/llm_provider.py @@ -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, ), ) @@ -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]: diff --git a/packages/gooddata-sdk/src/gooddata_sdk/catalog/organization/entity_model/resolved_llm_provider.py b/packages/gooddata-sdk/src/gooddata_sdk/catalog/organization/entity_model/resolved_llm_provider.py new file mode 100644 index 000000000..a2a1a0d81 --- /dev/null +++ b/packages/gooddata-sdk/src/gooddata_sdk/catalog/organization/entity_model/resolved_llm_provider.py @@ -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) diff --git a/packages/gooddata-sdk/src/gooddata_sdk/catalog/organization/service.py b/packages/gooddata-sdk/src/gooddata_sdk/catalog/organization/service.py index d8e3a15f3..332876a0b 100644 --- a/packages/gooddata-sdk/src/gooddata_sdk/catalog/organization/service.py +++ b/packages/gooddata-sdk/src/gooddata_sdk/catalog/organization/service.py @@ -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 @@ -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]: diff --git a/packages/gooddata-sdk/tests/catalog/test_catalog_organization.py b/packages/gooddata-sdk/tests/catalog/test_catalog_organization.py index 58d4d2b66..dbce18717 100644 --- a/packages/gooddata-sdk/tests/catalog/test_catalog_organization.py +++ b/packages/gooddata-sdk/tests/catalog/test_catalog_organization.py @@ -11,6 +11,8 @@ CatalogLlmEndpoint, CatalogOrganization, CatalogOrganizationSetting, + CatalogResolvedLlmProvider, + CatalogResolvedLlms, CatalogRsaSpecification, CatalogWebhook, GoodDataSdk, @@ -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.