Skip to content
Open
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
1 change: 1 addition & 0 deletions packages/gooddata-sdk/src/gooddata_sdk/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@
CatalogLlmProviderPatchDocument,
CatalogOpenAiApiKeyAuth,
CatalogOpenAiProviderConfig,
CatalogResolvedLlmProvider,
)
from gooddata_sdk.catalog.organization.entity_model.organization import CatalogOrganization
from gooddata_sdk.catalog.organization.entity_model.setting import CatalogOrganizationSetting
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from typing import Any, Union

from attr import define
from attr import define, field
from gooddata_api_client.model.aws_bedrock_provider_config import AwsBedrockProviderConfig
from gooddata_api_client.model.azure_foundry_provider_auth import AzureFoundryProviderAuth
from gooddata_api_client.model.azure_foundry_provider_config import AzureFoundryProviderConfig
Expand Down Expand Up @@ -337,3 +337,35 @@ class CatalogLlmProviderPatchAttributes(Base):
@staticmethod
def client_class() -> type[JsonApiLlmProviderInAttributes]:
return JsonApiLlmProviderInAttributes


# --- Resolved provider (read-only, returned by workspace-level resolution) ---


@define(kw_only=True)
class CatalogResolvedLlmProvider(Base):
"""Active LLM provider resolved for a workspace.

Returned by :meth:`CatalogWorkspaceContentService.resolve_llm_providers`.
This is a read-only object — it represents the live configuration resolved
for the workspace, not the stored entity.
"""

id: str
title: str
models: list[CatalogLlmProviderModel] = field(factory=list)

@classmethod
def from_api(cls, entity: dict[str, Any]) -> CatalogResolvedLlmProvider:
raw_models = safeget(entity, ["models"]) or []
return cls(
id=entity["id"],
title=entity["title"],
models=[
CatalogLlmProviderModel(
id=safeget(m, ["id"]) or "",
family=safeget(m, ["family"]) or "",
)
for m in raw_models
],
)
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from gooddata_sdk.catalog.data_source.validation.data_source import DataSourceValidator
from gooddata_sdk.catalog.depends_on import CatalogDependsOn, CatalogDependsOnDateFilter
from gooddata_sdk.catalog.filter_by import CatalogFilterBy
from gooddata_sdk.catalog.organization.entity_model.llm_provider import CatalogResolvedLlmProvider
from gooddata_sdk.catalog.types import ValidObjects
from gooddata_sdk.catalog.validate_by_item import CatalogValidateByItem
from gooddata_sdk.catalog.workspace.declarative_model.workspace.analytics_model.analytics_model import (
Expand All @@ -38,7 +39,7 @@
from gooddata_sdk.compute.model.execution import ExecutionDefinition, compute_model_to_api_model
from gooddata_sdk.compute.model.filter import Filter
from gooddata_sdk.compute.model.metric import Metric
from gooddata_sdk.utils import load_all_entities
from gooddata_sdk.utils import load_all_entities, safeget

ValidObjectTypes = Union[Attribute, Metric, Filter, CatalogLabel, CatalogFact, CatalogMetric]

Expand Down Expand Up @@ -685,3 +686,27 @@ def get_label_elements(
workspace_id, request, _check_return_type=False, **paging_params
)
return [v["title"] for v in values["elements"]]

def resolve_llm_providers(self, workspace_id: str) -> CatalogResolvedLlmProvider | None:
"""Resolve the active LLM provider configuration for a workspace.

Calls ``GET /api/v1/actions/workspaces/{workspaceId}/ai/resolveLlmProviders``
and returns the resolved active LLM provider, or ``None`` when no LLM
provider is configured for the workspace.

This is the replacement for the now-removed ``resolveLlmEndpoints`` endpoint.

Args:
workspace_id (str):
Workspace identifier e.g. ``"demo"``.

Returns:
CatalogResolvedLlmProvider | None:
The resolved active LLM provider, or ``None`` if the workspace
has no LLM provider configured.
"""
response = self._actions_api.resolve_llm_providers(workspace_id, _check_return_type=False)
data = safeget(response, ["data"])
if data is None:
return None
return CatalogResolvedLlmProvider.from_api(data)
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
interactions:
- request:
body: null
headers:
Accept:
- application/json
Accept-Encoding:
- br, gzip, deflate
X-GDC-VALIDATE-RELATIONS:
- 'true'
X-Requested-With:
- XMLHttpRequest
method: GET
uri: http://localhost:3000/api/v1/actions/workspaces/demo/ai/resolveLlmProviders
response:
body:
string:
data: null
headers:
Content-Type:
- application/json
DATE: &id001
- PLACEHOLDER
Expires:
- '0'
Pragma:
- no-cache
X-Content-Type-Options:
- nosniff
X-GDC-TRACE-ID: *id001
status:
code: 200
message: OK
version: 1
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
CatalogDependsOn,
CatalogDependsOnDateFilter,
CatalogEntityIdentifier,
CatalogResolvedLlmProvider,
CatalogValidateByItem,
CatalogWorkspace,
DataSourceValidator,
Expand Down Expand Up @@ -502,3 +503,21 @@ def test_export_definition_analytics_layout(test_config):
assert deep_eq(analytics_o.analytics.export_definitions, analytics_e.analytics.export_definitions)
finally:
safe_delete(_refresh_workspaces, sdk)


@gd_vcr.use_cassette(str(_fixtures_dir / "test_resolve_llm_providers.yaml"))
def test_resolve_llm_providers_integration(test_config):
"""Exercise the resolveLlmProviders action (replaces the removed resolveLlmEndpoints).

The endpoint returns the active LLM provider for the workspace, or None when
no LLM provider is configured. Both outcomes are valid — the test just
verifies the SDK can call the endpoint without error and that the return
value is either None or a well-formed CatalogResolvedLlmProvider.
"""
sdk = GoodDataSdk.create(host_=test_config["host"], token_=test_config["token"])
result = sdk.catalog_workspace_content.resolve_llm_providers(test_config["workspace"])
assert result is None or isinstance(result, CatalogResolvedLlmProvider)
if result is not None:
assert result.id
assert result.title
assert isinstance(result.models, list)
Loading