diff --git a/langfuse/api/__init__.py b/langfuse/api/__init__.py index 256f1230b..9ae93bffd 100644 --- a/langfuse/api/__init__.py +++ b/langfuse/api/__init__.py @@ -58,6 +58,7 @@ BlobStorageExportSource, BlobStorageIntegrationDeletionResponse, BlobStorageIntegrationFileType, + BlobStorageIntegrationFileTypeResponse, BlobStorageIntegrationResponse, BlobStorageIntegrationStatusResponse, BlobStorageIntegrationType, @@ -352,6 +353,7 @@ "BlobStorageExportSource": ".blob_storage_integrations", "BlobStorageIntegrationDeletionResponse": ".blob_storage_integrations", "BlobStorageIntegrationFileType": ".blob_storage_integrations", + "BlobStorageIntegrationFileTypeResponse": ".blob_storage_integrations", "BlobStorageIntegrationResponse": ".blob_storage_integrations", "BlobStorageIntegrationStatusResponse": ".blob_storage_integrations", "BlobStorageIntegrationType": ".blob_storage_integrations", @@ -676,6 +678,7 @@ def __dir__(): "BlobStorageExportSource", "BlobStorageIntegrationDeletionResponse", "BlobStorageIntegrationFileType", + "BlobStorageIntegrationFileTypeResponse", "BlobStorageIntegrationResponse", "BlobStorageIntegrationStatusResponse", "BlobStorageIntegrationType", diff --git a/langfuse/api/blob_storage_integrations/__init__.py b/langfuse/api/blob_storage_integrations/__init__.py index d92046ef2..a14abaab5 100644 --- a/langfuse/api/blob_storage_integrations/__init__.py +++ b/langfuse/api/blob_storage_integrations/__init__.py @@ -13,6 +13,7 @@ BlobStorageExportSource, BlobStorageIntegrationDeletionResponse, BlobStorageIntegrationFileType, + BlobStorageIntegrationFileTypeResponse, BlobStorageIntegrationResponse, BlobStorageIntegrationStatusResponse, BlobStorageIntegrationType, @@ -27,6 +28,7 @@ "BlobStorageExportSource": ".types", "BlobStorageIntegrationDeletionResponse": ".types", "BlobStorageIntegrationFileType": ".types", + "BlobStorageIntegrationFileTypeResponse": ".types", "BlobStorageIntegrationResponse": ".types", "BlobStorageIntegrationStatusResponse": ".types", "BlobStorageIntegrationType": ".types", @@ -70,6 +72,7 @@ def __dir__(): "BlobStorageExportSource", "BlobStorageIntegrationDeletionResponse", "BlobStorageIntegrationFileType", + "BlobStorageIntegrationFileTypeResponse", "BlobStorageIntegrationResponse", "BlobStorageIntegrationStatusResponse", "BlobStorageIntegrationType", diff --git a/langfuse/api/blob_storage_integrations/client.py b/langfuse/api/blob_storage_integrations/client.py index bf7ab406d..9e8051d8c 100644 --- a/langfuse/api/blob_storage_integrations/client.py +++ b/langfuse/api/blob_storage_integrations/client.py @@ -144,7 +144,7 @@ def upsert_blob_storage_integration( Path prefix for exported files (must end with forward slash if provided) export_start_date : typing.Optional[dt.datetime] - Custom start date for exports (required when exportMode is FROM_CUSTOM_DATE) + Custom start date for exports (required when exportMode is FROM_CUSTOM_DATE). Must not be in the future (27 h tolerance for timezone differences). compressed : typing.Optional[bool] Enable gzip compression for exported files (.csv.gz, .json.gz, .jsonl.gz). Defaults to true. @@ -421,7 +421,7 @@ async def upsert_blob_storage_integration( Path prefix for exported files (must end with forward slash if provided) export_start_date : typing.Optional[dt.datetime] - Custom start date for exports (required when exportMode is FROM_CUSTOM_DATE) + Custom start date for exports (required when exportMode is FROM_CUSTOM_DATE). Must not be in the future (27 h tolerance for timezone differences). compressed : typing.Optional[bool] Enable gzip compression for exported files (.csv.gz, .json.gz, .jsonl.gz). Defaults to true. diff --git a/langfuse/api/blob_storage_integrations/raw_client.py b/langfuse/api/blob_storage_integrations/raw_client.py index 2899c8b7c..9eeb8c2f1 100644 --- a/langfuse/api/blob_storage_integrations/raw_client.py +++ b/langfuse/api/blob_storage_integrations/raw_client.py @@ -201,7 +201,7 @@ def upsert_blob_storage_integration( Path prefix for exported files (must end with forward slash if provided) export_start_date : typing.Optional[dt.datetime] - Custom start date for exports (required when exportMode is FROM_CUSTOM_DATE) + Custom start date for exports (required when exportMode is FROM_CUSTOM_DATE). Must not be in the future (27 h tolerance for timezone differences). compressed : typing.Optional[bool] Enable gzip compression for exported files (.csv.gz, .json.gz, .jsonl.gz). Defaults to true. @@ -696,7 +696,7 @@ async def upsert_blob_storage_integration( Path prefix for exported files (must end with forward slash if provided) export_start_date : typing.Optional[dt.datetime] - Custom start date for exports (required when exportMode is FROM_CUSTOM_DATE) + Custom start date for exports (required when exportMode is FROM_CUSTOM_DATE). Must not be in the future (27 h tolerance for timezone differences). compressed : typing.Optional[bool] Enable gzip compression for exported files (.csv.gz, .json.gz, .jsonl.gz). Defaults to true. diff --git a/langfuse/api/blob_storage_integrations/types/__init__.py b/langfuse/api/blob_storage_integrations/types/__init__.py index 3a2a0e1ec..0da7964dd 100644 --- a/langfuse/api/blob_storage_integrations/types/__init__.py +++ b/langfuse/api/blob_storage_integrations/types/__init__.py @@ -14,6 +14,9 @@ BlobStorageIntegrationDeletionResponse, ) from .blob_storage_integration_file_type import BlobStorageIntegrationFileType + from .blob_storage_integration_file_type_response import ( + BlobStorageIntegrationFileTypeResponse, + ) from .blob_storage_integration_response import BlobStorageIntegrationResponse from .blob_storage_integration_status_response import ( BlobStorageIntegrationStatusResponse, @@ -31,6 +34,7 @@ "BlobStorageExportSource": ".blob_storage_export_source", "BlobStorageIntegrationDeletionResponse": ".blob_storage_integration_deletion_response", "BlobStorageIntegrationFileType": ".blob_storage_integration_file_type", + "BlobStorageIntegrationFileTypeResponse": ".blob_storage_integration_file_type_response", "BlobStorageIntegrationResponse": ".blob_storage_integration_response", "BlobStorageIntegrationStatusResponse": ".blob_storage_integration_status_response", "BlobStorageIntegrationType": ".blob_storage_integration_type", @@ -74,6 +78,7 @@ def __dir__(): "BlobStorageExportSource", "BlobStorageIntegrationDeletionResponse", "BlobStorageIntegrationFileType", + "BlobStorageIntegrationFileTypeResponse", "BlobStorageIntegrationResponse", "BlobStorageIntegrationStatusResponse", "BlobStorageIntegrationType", diff --git a/langfuse/api/blob_storage_integrations/types/blob_storage_integration_file_type_response.py b/langfuse/api/blob_storage_integrations/types/blob_storage_integration_file_type_response.py new file mode 100644 index 000000000..cfcafa5f3 --- /dev/null +++ b/langfuse/api/blob_storage_integrations/types/blob_storage_integration_file_type_response.py @@ -0,0 +1,34 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core import enum + +T_Result = typing.TypeVar("T_Result") + + +class BlobStorageIntegrationFileTypeResponse(enum.StrEnum): + """ + File type reported for an existing integration. Includes `PARQUET`, which a project may enable through the Langfuse UI but cannot yet be set via this API (the request `fileType` omits it). + """ + + JSON = "JSON" + CSV = "CSV" + JSONL = "JSONL" + PARQUET = "PARQUET" + + def visit( + self, + json: typing.Callable[[], T_Result], + csv: typing.Callable[[], T_Result], + jsonl: typing.Callable[[], T_Result], + parquet: typing.Callable[[], T_Result], + ) -> T_Result: + if self is BlobStorageIntegrationFileTypeResponse.JSON: + return json() + if self is BlobStorageIntegrationFileTypeResponse.CSV: + return csv() + if self is BlobStorageIntegrationFileTypeResponse.JSONL: + return jsonl() + if self is BlobStorageIntegrationFileTypeResponse.PARQUET: + return parquet() diff --git a/langfuse/api/blob_storage_integrations/types/blob_storage_integration_response.py b/langfuse/api/blob_storage_integrations/types/blob_storage_integration_response.py index 9c87ba629..3cbaba167 100644 --- a/langfuse/api/blob_storage_integrations/types/blob_storage_integration_response.py +++ b/langfuse/api/blob_storage_integrations/types/blob_storage_integration_response.py @@ -11,7 +11,9 @@ from .blob_storage_export_frequency import BlobStorageExportFrequency from .blob_storage_export_mode import BlobStorageExportMode from .blob_storage_export_source import BlobStorageExportSource -from .blob_storage_integration_file_type import BlobStorageIntegrationFileType +from .blob_storage_integration_file_type_response import ( + BlobStorageIntegrationFileTypeResponse, +) from .blob_storage_integration_type import BlobStorageIntegrationType @@ -34,7 +36,7 @@ class BlobStorageIntegrationResponse(UniversalBaseModel): bool, FieldMetadata(alias="forcePathStyle") ] file_type: typing_extensions.Annotated[ - BlobStorageIntegrationFileType, FieldMetadata(alias="fileType") + BlobStorageIntegrationFileTypeResponse, FieldMetadata(alias="fileType") ] export_mode: typing_extensions.Annotated[ BlobStorageExportMode, FieldMetadata(alias="exportMode") diff --git a/langfuse/api/blob_storage_integrations/types/blob_storage_sync_status.py b/langfuse/api/blob_storage_integrations/types/blob_storage_sync_status.py index 559e41450..b0fc97524 100644 --- a/langfuse/api/blob_storage_integrations/types/blob_storage_sync_status.py +++ b/langfuse/api/blob_storage_integrations/types/blob_storage_sync_status.py @@ -12,8 +12,9 @@ class BlobStorageSyncStatus(enum.StrEnum): Sync status of the blob storage integration: - `disabled` — integration is not enabled - `error` — last export failed (see `lastError` for details) - - `idle` — enabled but has never exported yet + - `running` — an export job is currently being processed - `queued` — next export is overdue (`nextSyncAt` is in the past) and waiting to be picked up by the worker + - `idle` — enabled but has never exported yet and no export is queued - `up_to_date` — all available data has been exported; next export is scheduled for the future **ETL usage**: poll this endpoint and check for `up_to_date` status. Compare `lastSyncAt` against your @@ -22,6 +23,7 @@ class BlobStorageSyncStatus(enum.StrEnum): """ IDLE = "idle" + RUNNING = "running" QUEUED = "queued" UP_TO_DATE = "up_to_date" DISABLED = "disabled" @@ -30,6 +32,7 @@ class BlobStorageSyncStatus(enum.StrEnum): def visit( self, idle: typing.Callable[[], T_Result], + running: typing.Callable[[], T_Result], queued: typing.Callable[[], T_Result], up_to_date: typing.Callable[[], T_Result], disabled: typing.Callable[[], T_Result], @@ -37,6 +40,8 @@ def visit( ) -> T_Result: if self is BlobStorageSyncStatus.IDLE: return idle() + if self is BlobStorageSyncStatus.RUNNING: + return running() if self is BlobStorageSyncStatus.QUEUED: return queued() if self is BlobStorageSyncStatus.UP_TO_DATE: diff --git a/langfuse/api/blob_storage_integrations/types/create_blob_storage_integration_request.py b/langfuse/api/blob_storage_integrations/types/create_blob_storage_integration_request.py index bba3205f8..44d2d2dda 100644 --- a/langfuse/api/blob_storage_integrations/types/create_blob_storage_integration_request.py +++ b/langfuse/api/blob_storage_integrations/types/create_blob_storage_integration_request.py @@ -85,7 +85,7 @@ class CreateBlobStorageIntegrationRequest(UniversalBaseModel): typing.Optional[dt.datetime], FieldMetadata(alias="exportStartDate") ] = pydantic.Field(default=None) """ - Custom start date for exports (required when exportMode is FROM_CUSTOM_DATE) + Custom start date for exports (required when exportMode is FROM_CUSTOM_DATE). Must not be in the future (27 h tolerance for timezone differences). """ compressed: typing.Optional[bool] = pydantic.Field(default=None) diff --git a/langfuse/api/unstable/__init__.py b/langfuse/api/unstable/__init__.py index 827a2ac6b..75b3d705a 100644 --- a/langfuse/api/unstable/__init__.py +++ b/langfuse/api/unstable/__init__.py @@ -21,7 +21,7 @@ UnauthorizedError, UnprocessableContentError, ) - from . import commons, errors, evaluation_rules, evaluators + from . import commons, dashboard_widgets, errors, evaluation_rules, evaluators from .commons import ( ArrayOptionsEvaluationRuleFilter, BooleanEvaluationRuleFilter, @@ -73,6 +73,19 @@ StringObjectEvaluationRuleFilter, StringOptionsEvaluationRuleFilter, ) + from .dashboard_widgets import ( + CreateDashboardWidgetRequest, + DashboardWidget, + DashboardWidgetChartConfig, + DashboardWidgetChartType, + DashboardWidgetDefaultSort, + DashboardWidgetDimension, + DashboardWidgetFilter, + DashboardWidgetMetric, + DashboardWidgetMetricAggregation, + DashboardWidgetSortOrder, + DashboardWidgetView, + ) from .evaluation_rules import ( CodeEvaluationRuleEvaluatorReference, CreateCodeEvaluationRuleRequest, @@ -114,12 +127,23 @@ "ConflictError": ".errors", "CreateCodeEvaluationRuleRequest": ".evaluation_rules", "CreateCodeEvaluatorRequest": ".evaluators", + "CreateDashboardWidgetRequest": ".dashboard_widgets", "CreateEvaluationRuleRequest": ".evaluation_rules", "CreateEvaluatorRequest": ".evaluators", "CreateEvaluatorRequest_Code": ".evaluators", "CreateEvaluatorRequest_LlmAsJudge": ".evaluators", "CreateLlmAsJudgeEvaluationRuleRequest": ".evaluation_rules", "CreateLlmAsJudgeEvaluatorRequest": ".evaluators", + "DashboardWidget": ".dashboard_widgets", + "DashboardWidgetChartConfig": ".dashboard_widgets", + "DashboardWidgetChartType": ".dashboard_widgets", + "DashboardWidgetDefaultSort": ".dashboard_widgets", + "DashboardWidgetDimension": ".dashboard_widgets", + "DashboardWidgetFilter": ".dashboard_widgets", + "DashboardWidgetMetric": ".dashboard_widgets", + "DashboardWidgetMetricAggregation": ".dashboard_widgets", + "DashboardWidgetSortOrder": ".dashboard_widgets", + "DashboardWidgetView": ".dashboard_widgets", "DateTimeEvaluationRuleFilter": ".commons", "DeleteEvaluationRuleResponse": ".evaluation_rules", "DeleteEvaluatorResponse": ".evaluators", @@ -191,6 +215,7 @@ "UnprocessableContentError": ".errors", "UpdateEvaluationRuleRequest": ".evaluation_rules", "commons": ".commons", + "dashboard_widgets": ".dashboard_widgets", "errors": ".errors", "evaluation_rules": ".evaluation_rules", "evaluators": ".evaluators", @@ -236,12 +261,23 @@ def __dir__(): "ConflictError", "CreateCodeEvaluationRuleRequest", "CreateCodeEvaluatorRequest", + "CreateDashboardWidgetRequest", "CreateEvaluationRuleRequest", "CreateEvaluatorRequest", "CreateEvaluatorRequest_Code", "CreateEvaluatorRequest_LlmAsJudge", "CreateLlmAsJudgeEvaluationRuleRequest", "CreateLlmAsJudgeEvaluatorRequest", + "DashboardWidget", + "DashboardWidgetChartConfig", + "DashboardWidgetChartType", + "DashboardWidgetDefaultSort", + "DashboardWidgetDimension", + "DashboardWidgetFilter", + "DashboardWidgetMetric", + "DashboardWidgetMetricAggregation", + "DashboardWidgetSortOrder", + "DashboardWidgetView", "DateTimeEvaluationRuleFilter", "DeleteEvaluationRuleResponse", "DeleteEvaluatorResponse", @@ -313,6 +349,7 @@ def __dir__(): "UnprocessableContentError", "UpdateEvaluationRuleRequest", "commons", + "dashboard_widgets", "errors", "evaluation_rules", "evaluators", diff --git a/langfuse/api/unstable/client.py b/langfuse/api/unstable/client.py index 5c3ac32d7..642b0b985 100644 --- a/langfuse/api/unstable/client.py +++ b/langfuse/api/unstable/client.py @@ -8,6 +8,10 @@ from .raw_client import AsyncRawUnstableClient, RawUnstableClient if typing.TYPE_CHECKING: + from .dashboard_widgets.client import ( + AsyncDashboardWidgetsClient, + DashboardWidgetsClient, + ) from .evaluation_rules.client import ( AsyncEvaluationRulesClient, EvaluationRulesClient, @@ -19,6 +23,7 @@ class UnstableClient: def __init__(self, *, client_wrapper: SyncClientWrapper): self._raw_client = RawUnstableClient(client_wrapper=client_wrapper) self._client_wrapper = client_wrapper + self._dashboard_widgets: typing.Optional[DashboardWidgetsClient] = None self._evaluation_rules: typing.Optional[EvaluationRulesClient] = None self._evaluators: typing.Optional[EvaluatorsClient] = None @@ -33,6 +38,16 @@ def with_raw_response(self) -> RawUnstableClient: """ return self._raw_client + @property + def dashboard_widgets(self): + if self._dashboard_widgets is None: + from .dashboard_widgets.client import DashboardWidgetsClient # noqa: E402 + + self._dashboard_widgets = DashboardWidgetsClient( + client_wrapper=self._client_wrapper + ) + return self._dashboard_widgets + @property def evaluation_rules(self): if self._evaluation_rules is None: @@ -56,6 +71,7 @@ class AsyncUnstableClient: def __init__(self, *, client_wrapper: AsyncClientWrapper): self._raw_client = AsyncRawUnstableClient(client_wrapper=client_wrapper) self._client_wrapper = client_wrapper + self._dashboard_widgets: typing.Optional[AsyncDashboardWidgetsClient] = None self._evaluation_rules: typing.Optional[AsyncEvaluationRulesClient] = None self._evaluators: typing.Optional[AsyncEvaluatorsClient] = None @@ -70,6 +86,16 @@ def with_raw_response(self) -> AsyncRawUnstableClient: """ return self._raw_client + @property + def dashboard_widgets(self): + if self._dashboard_widgets is None: + from .dashboard_widgets.client import AsyncDashboardWidgetsClient # noqa: E402 + + self._dashboard_widgets = AsyncDashboardWidgetsClient( + client_wrapper=self._client_wrapper + ) + return self._dashboard_widgets + @property def evaluation_rules(self): if self._evaluation_rules is None: diff --git a/langfuse/api/unstable/dashboard_widgets/__init__.py b/langfuse/api/unstable/dashboard_widgets/__init__.py new file mode 100644 index 000000000..5e96961c1 --- /dev/null +++ b/langfuse/api/unstable/dashboard_widgets/__init__.py @@ -0,0 +1,76 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .types import ( + CreateDashboardWidgetRequest, + DashboardWidget, + DashboardWidgetChartConfig, + DashboardWidgetChartType, + DashboardWidgetDefaultSort, + DashboardWidgetDimension, + DashboardWidgetFilter, + DashboardWidgetMetric, + DashboardWidgetMetricAggregation, + DashboardWidgetSortOrder, + DashboardWidgetView, + ) +_dynamic_imports: typing.Dict[str, str] = { + "CreateDashboardWidgetRequest": ".types", + "DashboardWidget": ".types", + "DashboardWidgetChartConfig": ".types", + "DashboardWidgetChartType": ".types", + "DashboardWidgetDefaultSort": ".types", + "DashboardWidgetDimension": ".types", + "DashboardWidgetFilter": ".types", + "DashboardWidgetMetric": ".types", + "DashboardWidgetMetricAggregation": ".types", + "DashboardWidgetSortOrder": ".types", + "DashboardWidgetView": ".types", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "CreateDashboardWidgetRequest", + "DashboardWidget", + "DashboardWidgetChartConfig", + "DashboardWidgetChartType", + "DashboardWidgetDefaultSort", + "DashboardWidgetDimension", + "DashboardWidgetFilter", + "DashboardWidgetMetric", + "DashboardWidgetMetricAggregation", + "DashboardWidgetSortOrder", + "DashboardWidgetView", +] diff --git a/langfuse/api/unstable/dashboard_widgets/client.py b/langfuse/api/unstable/dashboard_widgets/client.py new file mode 100644 index 000000000..ff842576b --- /dev/null +++ b/langfuse/api/unstable/dashboard_widgets/client.py @@ -0,0 +1,277 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.request_options import RequestOptions +from .raw_client import AsyncRawDashboardWidgetsClient, RawDashboardWidgetsClient +from .types.dashboard_widget import DashboardWidget +from .types.dashboard_widget_chart_config import DashboardWidgetChartConfig +from .types.dashboard_widget_chart_type import DashboardWidgetChartType +from .types.dashboard_widget_dimension import DashboardWidgetDimension +from .types.dashboard_widget_filter import DashboardWidgetFilter +from .types.dashboard_widget_metric import DashboardWidgetMetric +from .types.dashboard_widget_view import DashboardWidgetView + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class DashboardWidgetsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._raw_client = RawDashboardWidgetsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> RawDashboardWidgetsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + RawDashboardWidgetsClient + """ + return self._raw_client + + def create( + self, + *, + name: str, + description: str, + view: DashboardWidgetView, + dimensions: typing.Sequence[DashboardWidgetDimension], + metrics: typing.Sequence[DashboardWidgetMetric], + filters: typing.Sequence[DashboardWidgetFilter], + chart_type: DashboardWidgetChartType, + chart_config: DashboardWidgetChartConfig, + min_version: typing.Optional[int] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> DashboardWidget: + """ + Create a reusable dashboard widget. + + This endpoint creates the widget. It does not place the widget on a dashboard grid, this has to be done in the UI. + + Supported views are `observations`, `scores-numeric`, and `scores-categorical`. + The legacy `traces` view is not supported by this unstable API, `minVersion` defaults to `2`; values below `2` are rejected. + + Unstable API note: + - This surface may evolve while dashboard/widget APIs are being finalized. + + Parameters + ---------- + name : str + + description : str + + view : DashboardWidgetView + + dimensions : typing.Sequence[DashboardWidgetDimension] + + metrics : typing.Sequence[DashboardWidgetMetric] + + filters : typing.Sequence[DashboardWidgetFilter] + + chart_type : DashboardWidgetChartType + + chart_config : DashboardWidgetChartConfig + + min_version : typing.Optional[int] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + DashboardWidget + + Examples + -------- + from langfuse import LangfuseAPI + from langfuse.unstable.dashboard_widgets import ( + DashboardWidgetChartConfig, + DashboardWidgetChartType, + DashboardWidgetDimension, + DashboardWidgetMetric, + DashboardWidgetMetricAggregation, + DashboardWidgetView, + ) + + client = LangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + client.unstable.dashboard_widgets.create( + name="Total cost by model", + description="Total model cost grouped by provided model name.", + view=DashboardWidgetView.OBSERVATIONS, + dimensions=[ + DashboardWidgetDimension( + field="providedModelName", + ) + ], + metrics=[ + DashboardWidgetMetric( + measure="totalCost", + agg=DashboardWidgetMetricAggregation.SUM, + ) + ], + filters=[], + chart_type=DashboardWidgetChartType.HORIZONTAL_BAR, + chart_config=DashboardWidgetChartConfig( + type=DashboardWidgetChartType.HORIZONTAL_BAR, + row_limit=10, + ), + min_version=2, + ) + """ + _response = self._raw_client.create( + name=name, + description=description, + view=view, + dimensions=dimensions, + metrics=metrics, + filters=filters, + chart_type=chart_type, + chart_config=chart_config, + min_version=min_version, + request_options=request_options, + ) + return _response.data + + +class AsyncDashboardWidgetsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._raw_client = AsyncRawDashboardWidgetsClient(client_wrapper=client_wrapper) + + @property + def with_raw_response(self) -> AsyncRawDashboardWidgetsClient: + """ + Retrieves a raw implementation of this client that returns raw responses. + + Returns + ------- + AsyncRawDashboardWidgetsClient + """ + return self._raw_client + + async def create( + self, + *, + name: str, + description: str, + view: DashboardWidgetView, + dimensions: typing.Sequence[DashboardWidgetDimension], + metrics: typing.Sequence[DashboardWidgetMetric], + filters: typing.Sequence[DashboardWidgetFilter], + chart_type: DashboardWidgetChartType, + chart_config: DashboardWidgetChartConfig, + min_version: typing.Optional[int] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> DashboardWidget: + """ + Create a reusable dashboard widget. + + This endpoint creates the widget. It does not place the widget on a dashboard grid, this has to be done in the UI. + + Supported views are `observations`, `scores-numeric`, and `scores-categorical`. + The legacy `traces` view is not supported by this unstable API, `minVersion` defaults to `2`; values below `2` are rejected. + + Unstable API note: + - This surface may evolve while dashboard/widget APIs are being finalized. + + Parameters + ---------- + name : str + + description : str + + view : DashboardWidgetView + + dimensions : typing.Sequence[DashboardWidgetDimension] + + metrics : typing.Sequence[DashboardWidgetMetric] + + filters : typing.Sequence[DashboardWidgetFilter] + + chart_type : DashboardWidgetChartType + + chart_config : DashboardWidgetChartConfig + + min_version : typing.Optional[int] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + DashboardWidget + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + from langfuse.unstable.dashboard_widgets import ( + DashboardWidgetChartConfig, + DashboardWidgetChartType, + DashboardWidgetDimension, + DashboardWidgetMetric, + DashboardWidgetMetricAggregation, + DashboardWidgetView, + ) + + client = AsyncLangfuseAPI( + x_langfuse_sdk_name="YOUR_X_LANGFUSE_SDK_NAME", + x_langfuse_sdk_version="YOUR_X_LANGFUSE_SDK_VERSION", + x_langfuse_public_key="YOUR_X_LANGFUSE_PUBLIC_KEY", + username="YOUR_USERNAME", + password="YOUR_PASSWORD", + base_url="https://yourhost.com/path/to/api", + ) + + + async def main() -> None: + await client.unstable.dashboard_widgets.create( + name="Total cost by model", + description="Total model cost grouped by provided model name.", + view=DashboardWidgetView.OBSERVATIONS, + dimensions=[ + DashboardWidgetDimension( + field="providedModelName", + ) + ], + metrics=[ + DashboardWidgetMetric( + measure="totalCost", + agg=DashboardWidgetMetricAggregation.SUM, + ) + ], + filters=[], + chart_type=DashboardWidgetChartType.HORIZONTAL_BAR, + chart_config=DashboardWidgetChartConfig( + type=DashboardWidgetChartType.HORIZONTAL_BAR, + row_limit=10, + ), + min_version=2, + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.create( + name=name, + description=description, + view=view, + dimensions=dimensions, + metrics=metrics, + filters=filters, + chart_type=chart_type, + chart_config=chart_config, + min_version=min_version, + request_options=request_options, + ) + return _response.data diff --git a/langfuse/api/unstable/dashboard_widgets/raw_client.py b/langfuse/api/unstable/dashboard_widgets/raw_client.py new file mode 100644 index 000000000..df0bde333 --- /dev/null +++ b/langfuse/api/unstable/dashboard_widgets/raw_client.py @@ -0,0 +1,513 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing +from json.decoder import JSONDecodeError + +from ...commons.errors.access_denied_error import ( + AccessDeniedError as commons_errors_access_denied_error_AccessDeniedError, +) +from ...commons.errors.error import Error +from ...commons.errors.method_not_allowed_error import ( + MethodNotAllowedError as commons_errors_method_not_allowed_error_MethodNotAllowedError, +) +from ...commons.errors.not_found_error import NotFoundError +from ...commons.errors.unauthorized_error import ( + UnauthorizedError as commons_errors_unauthorized_error_UnauthorizedError, +) +from ...core.api_error import ApiError +from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper +from ...core.http_response import AsyncHttpResponse, HttpResponse +from ...core.pydantic_utilities import parse_obj_as +from ...core.request_options import RequestOptions +from ...core.serialization import convert_and_respect_annotation_metadata +from ..errors.errors.access_denied_error import ( + AccessDeniedError as unstable_errors_errors_access_denied_error_AccessDeniedError, +) +from ..errors.errors.bad_request_error import BadRequestError +from ..errors.errors.internal_server_error import InternalServerError +from ..errors.errors.method_not_allowed_error import ( + MethodNotAllowedError as unstable_errors_errors_method_not_allowed_error_MethodNotAllowedError, +) +from ..errors.errors.too_many_requests_error import TooManyRequestsError +from ..errors.errors.unauthorized_error import ( + UnauthorizedError as unstable_errors_errors_unauthorized_error_UnauthorizedError, +) +from ..errors.types.public_api_error import PublicApiError +from .types.dashboard_widget import DashboardWidget +from .types.dashboard_widget_chart_config import DashboardWidgetChartConfig +from .types.dashboard_widget_chart_type import DashboardWidgetChartType +from .types.dashboard_widget_dimension import DashboardWidgetDimension +from .types.dashboard_widget_filter import DashboardWidgetFilter +from .types.dashboard_widget_metric import DashboardWidgetMetric +from .types.dashboard_widget_view import DashboardWidgetView + +# this is used as the default value for optional parameters +OMIT = typing.cast(typing.Any, ...) + + +class RawDashboardWidgetsClient: + def __init__(self, *, client_wrapper: SyncClientWrapper): + self._client_wrapper = client_wrapper + + def create( + self, + *, + name: str, + description: str, + view: DashboardWidgetView, + dimensions: typing.Sequence[DashboardWidgetDimension], + metrics: typing.Sequence[DashboardWidgetMetric], + filters: typing.Sequence[DashboardWidgetFilter], + chart_type: DashboardWidgetChartType, + chart_config: DashboardWidgetChartConfig, + min_version: typing.Optional[int] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[DashboardWidget]: + """ + Create a reusable dashboard widget. + + This endpoint creates the widget. It does not place the widget on a dashboard grid, this has to be done in the UI. + + Supported views are `observations`, `scores-numeric`, and `scores-categorical`. + The legacy `traces` view is not supported by this unstable API, `minVersion` defaults to `2`; values below `2` are rejected. + + Unstable API note: + - This surface may evolve while dashboard/widget APIs are being finalized. + + Parameters + ---------- + name : str + + description : str + + view : DashboardWidgetView + + dimensions : typing.Sequence[DashboardWidgetDimension] + + metrics : typing.Sequence[DashboardWidgetMetric] + + filters : typing.Sequence[DashboardWidgetFilter] + + chart_type : DashboardWidgetChartType + + chart_config : DashboardWidgetChartConfig + + min_version : typing.Optional[int] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[DashboardWidget] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/unstable/dashboard-widgets", + method="POST", + json={ + "name": name, + "description": description, + "view": view, + "dimensions": convert_and_respect_annotation_metadata( + object_=dimensions, + annotation=typing.Sequence[DashboardWidgetDimension], + direction="write", + ), + "metrics": convert_and_respect_annotation_metadata( + object_=metrics, + annotation=typing.Sequence[DashboardWidgetMetric], + direction="write", + ), + "filters": convert_and_respect_annotation_metadata( + object_=filters, + annotation=typing.Sequence[DashboardWidgetFilter], + direction="write", + ), + "chartType": chart_type, + "chartConfig": convert_and_respect_annotation_metadata( + object_=chart_config, + annotation=DashboardWidgetChartConfig, + direction="write", + ), + "minVersion": min_version, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + DashboardWidget, + parse_obj_as( + type_=DashboardWidget, # type: ignore + object_=_response.json(), + ), + ) + return HttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise unstable_errors_errors_unauthorized_error_UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise unstable_errors_errors_access_denied_error_AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise unstable_errors_errors_method_not_allowed_error_MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise commons_errors_unauthorized_error_UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise commons_errors_access_denied_error_AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise commons_errors_method_not_allowed_error_MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) + + +class AsyncRawDashboardWidgetsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def create( + self, + *, + name: str, + description: str, + view: DashboardWidgetView, + dimensions: typing.Sequence[DashboardWidgetDimension], + metrics: typing.Sequence[DashboardWidgetMetric], + filters: typing.Sequence[DashboardWidgetFilter], + chart_type: DashboardWidgetChartType, + chart_config: DashboardWidgetChartConfig, + min_version: typing.Optional[int] = OMIT, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[DashboardWidget]: + """ + Create a reusable dashboard widget. + + This endpoint creates the widget. It does not place the widget on a dashboard grid, this has to be done in the UI. + + Supported views are `observations`, `scores-numeric`, and `scores-categorical`. + The legacy `traces` view is not supported by this unstable API, `minVersion` defaults to `2`; values below `2` are rejected. + + Unstable API note: + - This surface may evolve while dashboard/widget APIs are being finalized. + + Parameters + ---------- + name : str + + description : str + + view : DashboardWidgetView + + dimensions : typing.Sequence[DashboardWidgetDimension] + + metrics : typing.Sequence[DashboardWidgetMetric] + + filters : typing.Sequence[DashboardWidgetFilter] + + chart_type : DashboardWidgetChartType + + chart_config : DashboardWidgetChartConfig + + min_version : typing.Optional[int] + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[DashboardWidget] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/unstable/dashboard-widgets", + method="POST", + json={ + "name": name, + "description": description, + "view": view, + "dimensions": convert_and_respect_annotation_metadata( + object_=dimensions, + annotation=typing.Sequence[DashboardWidgetDimension], + direction="write", + ), + "metrics": convert_and_respect_annotation_metadata( + object_=metrics, + annotation=typing.Sequence[DashboardWidgetMetric], + direction="write", + ), + "filters": convert_and_respect_annotation_metadata( + object_=filters, + annotation=typing.Sequence[DashboardWidgetFilter], + direction="write", + ), + "chartType": chart_type, + "chartConfig": convert_and_respect_annotation_metadata( + object_=chart_config, + annotation=DashboardWidgetChartConfig, + direction="write", + ), + "minVersion": min_version, + }, + request_options=request_options, + omit=OMIT, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + DashboardWidget, + parse_obj_as( + type_=DashboardWidget, # type: ignore + object_=_response.json(), + ), + ) + return AsyncHttpResponse(response=_response, data=_data) + if _response.status_code == 400: + raise BadRequestError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise unstable_errors_errors_unauthorized_error_UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise unstable_errors_errors_access_denied_error_AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise unstable_errors_errors_method_not_allowed_error_MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 429: + raise TooManyRequestsError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 500: + raise InternalServerError( + headers=dict(_response.headers), + body=typing.cast( + PublicApiError, + parse_obj_as( + type_=PublicApiError, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 400: + raise Error( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 401: + raise commons_errors_unauthorized_error_UnauthorizedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 403: + raise commons_errors_access_denied_error_AccessDeniedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 405: + raise commons_errors_method_not_allowed_error_MethodNotAllowedError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + if _response.status_code == 404: + raise NotFoundError( + headers=dict(_response.headers), + body=typing.cast( + typing.Any, + parse_obj_as( + type_=typing.Any, # type: ignore + object_=_response.json(), + ), + ), + ) + _response_json = _response.json() + except JSONDecodeError: + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response.text, + ) + raise ApiError( + status_code=_response.status_code, + headers=dict(_response.headers), + body=_response_json, + ) diff --git a/langfuse/api/unstable/dashboard_widgets/types/__init__.py b/langfuse/api/unstable/dashboard_widgets/types/__init__.py new file mode 100644 index 000000000..7771c98c2 --- /dev/null +++ b/langfuse/api/unstable/dashboard_widgets/types/__init__.py @@ -0,0 +1,74 @@ +# This file was auto-generated by Fern from our API Definition. + +# isort: skip_file + +import typing +from importlib import import_module + +if typing.TYPE_CHECKING: + from .create_dashboard_widget_request import CreateDashboardWidgetRequest + from .dashboard_widget import DashboardWidget + from .dashboard_widget_chart_config import DashboardWidgetChartConfig + from .dashboard_widget_chart_type import DashboardWidgetChartType + from .dashboard_widget_default_sort import DashboardWidgetDefaultSort + from .dashboard_widget_dimension import DashboardWidgetDimension + from .dashboard_widget_filter import DashboardWidgetFilter + from .dashboard_widget_metric import DashboardWidgetMetric + from .dashboard_widget_metric_aggregation import DashboardWidgetMetricAggregation + from .dashboard_widget_sort_order import DashboardWidgetSortOrder + from .dashboard_widget_view import DashboardWidgetView +_dynamic_imports: typing.Dict[str, str] = { + "CreateDashboardWidgetRequest": ".create_dashboard_widget_request", + "DashboardWidget": ".dashboard_widget", + "DashboardWidgetChartConfig": ".dashboard_widget_chart_config", + "DashboardWidgetChartType": ".dashboard_widget_chart_type", + "DashboardWidgetDefaultSort": ".dashboard_widget_default_sort", + "DashboardWidgetDimension": ".dashboard_widget_dimension", + "DashboardWidgetFilter": ".dashboard_widget_filter", + "DashboardWidgetMetric": ".dashboard_widget_metric", + "DashboardWidgetMetricAggregation": ".dashboard_widget_metric_aggregation", + "DashboardWidgetSortOrder": ".dashboard_widget_sort_order", + "DashboardWidgetView": ".dashboard_widget_view", +} + + +def __getattr__(attr_name: str) -> typing.Any: + module_name = _dynamic_imports.get(attr_name) + if module_name is None: + raise AttributeError( + f"No {attr_name} found in _dynamic_imports for module name -> {__name__}" + ) + try: + module = import_module(module_name, __package__) + if module_name == f".{attr_name}": + return module + else: + return getattr(module, attr_name) + except ImportError as e: + raise ImportError( + f"Failed to import {attr_name} from {module_name}: {e}" + ) from e + except AttributeError as e: + raise AttributeError( + f"Failed to get {attr_name} from {module_name}: {e}" + ) from e + + +def __dir__(): + lazy_attrs = list(_dynamic_imports.keys()) + return sorted(lazy_attrs) + + +__all__ = [ + "CreateDashboardWidgetRequest", + "DashboardWidget", + "DashboardWidgetChartConfig", + "DashboardWidgetChartType", + "DashboardWidgetDefaultSort", + "DashboardWidgetDimension", + "DashboardWidgetFilter", + "DashboardWidgetMetric", + "DashboardWidgetMetricAggregation", + "DashboardWidgetSortOrder", + "DashboardWidgetView", +] diff --git a/langfuse/api/unstable/dashboard_widgets/types/create_dashboard_widget_request.py b/langfuse/api/unstable/dashboard_widgets/types/create_dashboard_widget_request.py new file mode 100644 index 000000000..edba543ea --- /dev/null +++ b/langfuse/api/unstable/dashboard_widgets/types/create_dashboard_widget_request.py @@ -0,0 +1,36 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ....core.pydantic_utilities import UniversalBaseModel +from ....core.serialization import FieldMetadata +from .dashboard_widget_chart_config import DashboardWidgetChartConfig +from .dashboard_widget_chart_type import DashboardWidgetChartType +from .dashboard_widget_dimension import DashboardWidgetDimension +from .dashboard_widget_filter import DashboardWidgetFilter +from .dashboard_widget_metric import DashboardWidgetMetric +from .dashboard_widget_view import DashboardWidgetView + + +class CreateDashboardWidgetRequest(UniversalBaseModel): + name: str + description: str + view: DashboardWidgetView + dimensions: typing.List[DashboardWidgetDimension] + metrics: typing.List[DashboardWidgetMetric] + filters: typing.List[DashboardWidgetFilter] + chart_type: typing_extensions.Annotated[ + DashboardWidgetChartType, FieldMetadata(alias="chartType") + ] + chart_config: typing_extensions.Annotated[ + DashboardWidgetChartConfig, FieldMetadata(alias="chartConfig") + ] + min_version: typing_extensions.Annotated[ + typing.Optional[int], FieldMetadata(alias="minVersion") + ] = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/dashboard_widgets/types/dashboard_widget.py b/langfuse/api/unstable/dashboard_widgets/types/dashboard_widget.py new file mode 100644 index 000000000..24826bb52 --- /dev/null +++ b/langfuse/api/unstable/dashboard_widgets/types/dashboard_widget.py @@ -0,0 +1,42 @@ +# This file was auto-generated by Fern from our API Definition. + +import datetime as dt +import typing + +import pydantic +import typing_extensions +from ....core.pydantic_utilities import UniversalBaseModel +from ....core.serialization import FieldMetadata +from .dashboard_widget_chart_config import DashboardWidgetChartConfig +from .dashboard_widget_chart_type import DashboardWidgetChartType +from .dashboard_widget_dimension import DashboardWidgetDimension +from .dashboard_widget_filter import DashboardWidgetFilter +from .dashboard_widget_metric import DashboardWidgetMetric +from .dashboard_widget_view import DashboardWidgetView + + +class DashboardWidget(UniversalBaseModel): + id: str + created_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="createdAt") + ] + updated_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="updatedAt") + ] + name: str + description: str + view: DashboardWidgetView + dimensions: typing.List[DashboardWidgetDimension] + metrics: typing.List[DashboardWidgetMetric] + filters: typing.List[DashboardWidgetFilter] + chart_type: typing_extensions.Annotated[ + DashboardWidgetChartType, FieldMetadata(alias="chartType") + ] + chart_config: typing_extensions.Annotated[ + DashboardWidgetChartConfig, FieldMetadata(alias="chartConfig") + ] + min_version: typing_extensions.Annotated[int, FieldMetadata(alias="minVersion")] + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/dashboard_widgets/types/dashboard_widget_chart_config.py b/langfuse/api/unstable/dashboard_widgets/types/dashboard_widget_chart_config.py new file mode 100644 index 000000000..581a2954b --- /dev/null +++ b/langfuse/api/unstable/dashboard_widgets/types/dashboard_widget_chart_config.py @@ -0,0 +1,33 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +import typing_extensions +from ....core.pydantic_utilities import UniversalBaseModel +from ....core.serialization import FieldMetadata +from .dashboard_widget_chart_type import DashboardWidgetChartType +from .dashboard_widget_default_sort import DashboardWidgetDefaultSort + + +class DashboardWidgetChartConfig(UniversalBaseModel): + """ + Chart-specific widget configuration. + + `type` must match the top-level `chartType`. + `row_limit` applies to total-value charts and pivot tables. + `bins` applies to histograms. + `defaultSort` applies to pivot tables. + """ + + type: DashboardWidgetChartType + row_limit: typing.Optional[int] = None + show_value_labels: typing.Optional[bool] = None + bins: typing.Optional[int] = None + default_sort: typing_extensions.Annotated[ + typing.Optional[DashboardWidgetDefaultSort], FieldMetadata(alias="defaultSort") + ] = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/dashboard_widgets/types/dashboard_widget_chart_type.py b/langfuse/api/unstable/dashboard_widgets/types/dashboard_widget_chart_type.py new file mode 100644 index 000000000..2ab1fe4d0 --- /dev/null +++ b/langfuse/api/unstable/dashboard_widgets/types/dashboard_widget_chart_type.py @@ -0,0 +1,50 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ....core import enum + +T_Result = typing.TypeVar("T_Result") + + +class DashboardWidgetChartType(enum.StrEnum): + LINE_TIME_SERIES = "LINE_TIME_SERIES" + AREA_TIME_SERIES = "AREA_TIME_SERIES" + BAR_TIME_SERIES = "BAR_TIME_SERIES" + HORIZONTAL_BAR = "HORIZONTAL_BAR" + VERTICAL_BAR = "VERTICAL_BAR" + PIE = "PIE" + NUMBER = "NUMBER" + HISTOGRAM = "HISTOGRAM" + PIVOT_TABLE = "PIVOT_TABLE" + + def visit( + self, + line_time_series: typing.Callable[[], T_Result], + area_time_series: typing.Callable[[], T_Result], + bar_time_series: typing.Callable[[], T_Result], + horizontal_bar: typing.Callable[[], T_Result], + vertical_bar: typing.Callable[[], T_Result], + pie: typing.Callable[[], T_Result], + number: typing.Callable[[], T_Result], + histogram: typing.Callable[[], T_Result], + pivot_table: typing.Callable[[], T_Result], + ) -> T_Result: + if self is DashboardWidgetChartType.LINE_TIME_SERIES: + return line_time_series() + if self is DashboardWidgetChartType.AREA_TIME_SERIES: + return area_time_series() + if self is DashboardWidgetChartType.BAR_TIME_SERIES: + return bar_time_series() + if self is DashboardWidgetChartType.HORIZONTAL_BAR: + return horizontal_bar() + if self is DashboardWidgetChartType.VERTICAL_BAR: + return vertical_bar() + if self is DashboardWidgetChartType.PIE: + return pie() + if self is DashboardWidgetChartType.NUMBER: + return number() + if self is DashboardWidgetChartType.HISTOGRAM: + return histogram() + if self is DashboardWidgetChartType.PIVOT_TABLE: + return pivot_table() diff --git a/langfuse/api/unstable/dashboard_widgets/types/dashboard_widget_default_sort.py b/langfuse/api/unstable/dashboard_widgets/types/dashboard_widget_default_sort.py new file mode 100644 index 000000000..ee31c84d5 --- /dev/null +++ b/langfuse/api/unstable/dashboard_widgets/types/dashboard_widget_default_sort.py @@ -0,0 +1,16 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import UniversalBaseModel +from .dashboard_widget_sort_order import DashboardWidgetSortOrder + + +class DashboardWidgetDefaultSort(UniversalBaseModel): + column: str + order: DashboardWidgetSortOrder + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/dashboard_widgets/types/dashboard_widget_dimension.py b/langfuse/api/unstable/dashboard_widgets/types/dashboard_widget_dimension.py new file mode 100644 index 000000000..38bc6ca3a --- /dev/null +++ b/langfuse/api/unstable/dashboard_widgets/types/dashboard_widget_dimension.py @@ -0,0 +1,14 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import UniversalBaseModel + + +class DashboardWidgetDimension(UniversalBaseModel): + field: str + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/dashboard_widgets/types/dashboard_widget_filter.py b/langfuse/api/unstable/dashboard_widgets/types/dashboard_widget_filter.py new file mode 100644 index 000000000..161fa0b1f --- /dev/null +++ b/langfuse/api/unstable/dashboard_widgets/types/dashboard_widget_filter.py @@ -0,0 +1,25 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import UniversalBaseModel + + +class DashboardWidgetFilter(UniversalBaseModel): + """ + A dashboard widget filter in Langfuse filter-state shape. + + Filter shapes depend on `type`, for example string filters use a string `value`, + option filters use a list of strings, and object filters include `key`. + """ + + column: str + operator: str + type: str + value: typing.Optional[typing.Any] = None + key: typing.Optional[str] = None + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/dashboard_widgets/types/dashboard_widget_metric.py b/langfuse/api/unstable/dashboard_widgets/types/dashboard_widget_metric.py new file mode 100644 index 000000000..e26ee2b11 --- /dev/null +++ b/langfuse/api/unstable/dashboard_widgets/types/dashboard_widget_metric.py @@ -0,0 +1,16 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import UniversalBaseModel +from .dashboard_widget_metric_aggregation import DashboardWidgetMetricAggregation + + +class DashboardWidgetMetric(UniversalBaseModel): + measure: str + agg: DashboardWidgetMetricAggregation + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/dashboard_widgets/types/dashboard_widget_metric_aggregation.py b/langfuse/api/unstable/dashboard_widgets/types/dashboard_widget_metric_aggregation.py new file mode 100644 index 000000000..c9de6b278 --- /dev/null +++ b/langfuse/api/unstable/dashboard_widgets/types/dashboard_widget_metric_aggregation.py @@ -0,0 +1,62 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ....core import enum + +T_Result = typing.TypeVar("T_Result") + + +class DashboardWidgetMetricAggregation(enum.StrEnum): + SUM = "sum" + AVG = "avg" + COUNT = "count" + MAX = "max" + MIN = "min" + P50 = "p50" + P75 = "p75" + P90 = "p90" + P95 = "p95" + P99 = "p99" + HISTOGRAM = "histogram" + UNIQ = "uniq" + + def visit( + self, + sum: typing.Callable[[], T_Result], + avg: typing.Callable[[], T_Result], + count: typing.Callable[[], T_Result], + max: typing.Callable[[], T_Result], + min: typing.Callable[[], T_Result], + p50: typing.Callable[[], T_Result], + p75: typing.Callable[[], T_Result], + p90: typing.Callable[[], T_Result], + p95: typing.Callable[[], T_Result], + p99: typing.Callable[[], T_Result], + histogram: typing.Callable[[], T_Result], + uniq: typing.Callable[[], T_Result], + ) -> T_Result: + if self is DashboardWidgetMetricAggregation.SUM: + return sum() + if self is DashboardWidgetMetricAggregation.AVG: + return avg() + if self is DashboardWidgetMetricAggregation.COUNT: + return count() + if self is DashboardWidgetMetricAggregation.MAX: + return max() + if self is DashboardWidgetMetricAggregation.MIN: + return min() + if self is DashboardWidgetMetricAggregation.P50: + return p50() + if self is DashboardWidgetMetricAggregation.P75: + return p75() + if self is DashboardWidgetMetricAggregation.P90: + return p90() + if self is DashboardWidgetMetricAggregation.P95: + return p95() + if self is DashboardWidgetMetricAggregation.P99: + return p99() + if self is DashboardWidgetMetricAggregation.HISTOGRAM: + return histogram() + if self is DashboardWidgetMetricAggregation.UNIQ: + return uniq() diff --git a/langfuse/api/unstable/dashboard_widgets/types/dashboard_widget_sort_order.py b/langfuse/api/unstable/dashboard_widgets/types/dashboard_widget_sort_order.py new file mode 100644 index 000000000..bfdca2177 --- /dev/null +++ b/langfuse/api/unstable/dashboard_widgets/types/dashboard_widget_sort_order.py @@ -0,0 +1,20 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ....core import enum + +T_Result = typing.TypeVar("T_Result") + + +class DashboardWidgetSortOrder(enum.StrEnum): + ASC = "ASC" + DESC = "DESC" + + def visit( + self, asc: typing.Callable[[], T_Result], desc: typing.Callable[[], T_Result] + ) -> T_Result: + if self is DashboardWidgetSortOrder.ASC: + return asc() + if self is DashboardWidgetSortOrder.DESC: + return desc() diff --git a/langfuse/api/unstable/dashboard_widgets/types/dashboard_widget_view.py b/langfuse/api/unstable/dashboard_widgets/types/dashboard_widget_view.py new file mode 100644 index 000000000..461dba8aa --- /dev/null +++ b/langfuse/api/unstable/dashboard_widgets/types/dashboard_widget_view.py @@ -0,0 +1,26 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ....core import enum + +T_Result = typing.TypeVar("T_Result") + + +class DashboardWidgetView(enum.StrEnum): + OBSERVATIONS = "observations" + SCORES_NUMERIC = "scores-numeric" + SCORES_CATEGORICAL = "scores-categorical" + + def visit( + self, + observations: typing.Callable[[], T_Result], + scores_numeric: typing.Callable[[], T_Result], + scores_categorical: typing.Callable[[], T_Result], + ) -> T_Result: + if self is DashboardWidgetView.OBSERVATIONS: + return observations() + if self is DashboardWidgetView.SCORES_NUMERIC: + return scores_numeric() + if self is DashboardWidgetView.SCORES_CATEGORICAL: + return scores_categorical()