From 5f77efed0595cc49ca4633b6a6785bf1563fc1ea Mon Sep 17 00:00:00 2001 From: langfuse-bot Date: Thu, 12 Nov 2026 14:32:18 +0000 Subject: [PATCH] feat(api): update API spec from langfuse/langfuse 4496c72 --- langfuse/api/__init__.py | 3 + .../api/blob_storage_integrations/__init__.py | 3 + .../api/blob_storage_integrations/client.py | 29 +- .../blob_storage_integrations/raw_client.py | 29 +- .../types/__init__.py | 3 + .../blob_storage_integration_auth_type.py | 28 + .../blob_storage_integration_response.py | 25 + ...create_blob_storage_integration_request.py | 19 +- langfuse/api/commons/types/model.py | 6 +- .../types/evaluation_rule_filter.py | 2 +- .../types/null_evaluation_rule_filter.py | 4 +- langfuse/api/models/client.py | 16 +- langfuse/api/models/raw_client.py | 16 +- .../api/models/types/create_model_request.py | 4 +- .../api/models/types/model_tokenizer_id.py | 2 +- langfuse/api/observations/client.py | 50 +- langfuse/api/observations/raw_client.py | 50 +- .../observations/types/observations_v2meta.py | 5 + .../types/observations_v2response.py | 4 +- langfuse/api/prompt_version/client.py | 30 +- langfuse/api/prompt_version/raw_client.py | 18 +- langfuse/api/unstable/__init__.py | 33 + .../types/dashboard_widget_filter.py | 2 +- langfuse/api/unstable/topics/__init__.py | 33 + langfuse/api/unstable/topics/client.py | 669 +++++- langfuse/api/unstable/topics/raw_client.py | 1905 ++++++++++++++++- .../api/unstable/topics/types/__init__.py | 33 + langfuse/api/unstable/topics/types/topic.py | 7 + .../unstable/topics/types/topic_assignment.py | 50 + .../topics/types/topic_assignment_list.py | 19 + .../topics/types/topic_assignment_status.py | 22 + .../topics/types/topic_assignment_topic.py | 22 + .../api/unstable/topics/types/topic_facet.py | 9 +- .../topics/types/topic_facet_counts.py | 3 + .../topics/types/topic_result_list_meta.py | 22 + .../topics/types/topic_results_order_by.py | 22 + .../api/unstable/topics/types/topic_rule.py | 41 + .../unstable/topics/types/topic_rule_list.py | 17 + .../unstable/topics/types/topic_summary.py | 40 + .../topics/types/topic_summary_list.py | 19 + .../topics/types/topic_summary_state.py | 26 + .../topics/types/topic_with_status.py | 2 +- 42 files changed, 3154 insertions(+), 188 deletions(-) create mode 100644 langfuse/api/blob_storage_integrations/types/blob_storage_integration_auth_type.py create mode 100644 langfuse/api/unstable/topics/types/topic_assignment.py create mode 100644 langfuse/api/unstable/topics/types/topic_assignment_list.py create mode 100644 langfuse/api/unstable/topics/types/topic_assignment_status.py create mode 100644 langfuse/api/unstable/topics/types/topic_assignment_topic.py create mode 100644 langfuse/api/unstable/topics/types/topic_result_list_meta.py create mode 100644 langfuse/api/unstable/topics/types/topic_results_order_by.py create mode 100644 langfuse/api/unstable/topics/types/topic_rule.py create mode 100644 langfuse/api/unstable/topics/types/topic_rule_list.py create mode 100644 langfuse/api/unstable/topics/types/topic_summary.py create mode 100644 langfuse/api/unstable/topics/types/topic_summary_list.py create mode 100644 langfuse/api/unstable/topics/types/topic_summary_state.py diff --git a/langfuse/api/__init__.py b/langfuse/api/__init__.py index 64f5a67be..9b661339f 100644 --- a/langfuse/api/__init__.py +++ b/langfuse/api/__init__.py @@ -58,6 +58,7 @@ BlobStorageExportFrequency, BlobStorageExportMode, BlobStorageExportSource, + BlobStorageIntegrationAuthType, BlobStorageIntegrationDeletionResponse, BlobStorageIntegrationFileType, BlobStorageIntegrationFileTypeResponse, @@ -424,6 +425,7 @@ "BlobStorageExportFrequency": ".blob_storage_integrations", "BlobStorageExportMode": ".blob_storage_integrations", "BlobStorageExportSource": ".blob_storage_integrations", + "BlobStorageIntegrationAuthType": ".blob_storage_integrations", "BlobStorageIntegrationDeletionResponse": ".blob_storage_integrations", "BlobStorageIntegrationFileType": ".blob_storage_integrations", "BlobStorageIntegrationFileTypeResponse": ".blob_storage_integrations", @@ -813,6 +815,7 @@ def __dir__(): "BlobStorageExportFrequency", "BlobStorageExportMode", "BlobStorageExportSource", + "BlobStorageIntegrationAuthType", "BlobStorageIntegrationDeletionResponse", "BlobStorageIntegrationFileType", "BlobStorageIntegrationFileTypeResponse", diff --git a/langfuse/api/blob_storage_integrations/__init__.py b/langfuse/api/blob_storage_integrations/__init__.py index a14abaab5..de6c00866 100644 --- a/langfuse/api/blob_storage_integrations/__init__.py +++ b/langfuse/api/blob_storage_integrations/__init__.py @@ -11,6 +11,7 @@ BlobStorageExportFrequency, BlobStorageExportMode, BlobStorageExportSource, + BlobStorageIntegrationAuthType, BlobStorageIntegrationDeletionResponse, BlobStorageIntegrationFileType, BlobStorageIntegrationFileTypeResponse, @@ -26,6 +27,7 @@ "BlobStorageExportFrequency": ".types", "BlobStorageExportMode": ".types", "BlobStorageExportSource": ".types", + "BlobStorageIntegrationAuthType": ".types", "BlobStorageIntegrationDeletionResponse": ".types", "BlobStorageIntegrationFileType": ".types", "BlobStorageIntegrationFileTypeResponse": ".types", @@ -70,6 +72,7 @@ def __dir__(): "BlobStorageExportFrequency", "BlobStorageExportMode", "BlobStorageExportSource", + "BlobStorageIntegrationAuthType", "BlobStorageIntegrationDeletionResponse", "BlobStorageIntegrationFileType", "BlobStorageIntegrationFileTypeResponse", diff --git a/langfuse/api/blob_storage_integrations/client.py b/langfuse/api/blob_storage_integrations/client.py index 242eeb2e8..dfa358d90 100644 --- a/langfuse/api/blob_storage_integrations/client.py +++ b/langfuse/api/blob_storage_integrations/client.py @@ -13,6 +13,7 @@ from .types.blob_storage_export_frequency import BlobStorageExportFrequency from .types.blob_storage_export_mode import BlobStorageExportMode from .types.blob_storage_export_source import BlobStorageExportSource +from .types.blob_storage_integration_auth_type import BlobStorageIntegrationAuthType from .types.blob_storage_integration_deletion_response import ( BlobStorageIntegrationDeletionResponse, ) @@ -92,6 +93,8 @@ def upsert_blob_storage_integration( file_type: BlobStorageIntegrationFileType, export_mode: BlobStorageExportMode, endpoint: typing.Optional[str] = OMIT, + auth_type: typing.Optional[BlobStorageIntegrationAuthType] = OMIT, + role_arn: typing.Optional[str] = OMIT, access_key_id: typing.Optional[str] = OMIT, secret_access_key: typing.Optional[str] = OMIT, prefix: typing.Optional[str] = OMIT, @@ -134,8 +137,16 @@ def upsert_blob_storage_integration( endpoint : typing.Optional[str] Custom endpoint URL (required for S3_COMPATIBLE type). Ignored for GOOGLE_CLOUD_STORAGE. + auth_type : typing.Optional[BlobStorageIntegrationAuthType] + Authentication method. `ROLE_ARN` requires type `S3`, a concrete AWS region (not `auto`), and `roleArn`; `accessKeyId` and `secretAccessKey` must then be omitted, and `endpoint` and `forcePathStyle` are ignored. When omitted, `ROLE_ARN` applies to type `S3` if the request sends `roleArn` or the integration already uses `ROLE_ARN`, unless the request sends `accessKeyId` or `secretAccessKey`; otherwise `ACCESS_KEYS` applies. + + The external ID is generated on the first save that uses `ROLE_ARN` and stays the same afterwards. That save stores the integration with `enabled: false`, because the trust policy cannot reference the new ID yet. Add the trust policy for `crossAccountRoleArn` with the returned `externalId` to your role, then update the integration with `enabled: true`. + + role_arn : typing.Optional[str] + ARN of the IAM role in your AWS account that Langfuse assumes when `authType` is `ROLE_ARN`, e.g. `arn:aws:iam::123456789012:role/langfuse-export`. When omitted on update, the stored role ARN is kept. + access_key_id : typing.Optional[str] - Access key ID for authentication. Not used for GOOGLE_CLOUD_STORAGE. + Access key ID for authentication. Not used for GOOGLE_CLOUD_STORAGE or when `authType` is `ROLE_ARN`. secret_access_key : typing.Optional[str] Secret access key for authentication (will be encrypted when stored). When omitted on update, the stored secret is kept, unless the type changes between GOOGLE_CLOUD_STORAGE and another type. @@ -211,6 +222,8 @@ def upsert_blob_storage_integration( file_type=file_type, export_mode=export_mode, endpoint=endpoint, + auth_type=auth_type, + role_arn=role_arn, access_key_id=access_key_id, secret_access_key=secret_access_key, prefix=prefix, @@ -371,6 +384,8 @@ async def upsert_blob_storage_integration( file_type: BlobStorageIntegrationFileType, export_mode: BlobStorageExportMode, endpoint: typing.Optional[str] = OMIT, + auth_type: typing.Optional[BlobStorageIntegrationAuthType] = OMIT, + role_arn: typing.Optional[str] = OMIT, access_key_id: typing.Optional[str] = OMIT, secret_access_key: typing.Optional[str] = OMIT, prefix: typing.Optional[str] = OMIT, @@ -413,8 +428,16 @@ async def upsert_blob_storage_integration( endpoint : typing.Optional[str] Custom endpoint URL (required for S3_COMPATIBLE type). Ignored for GOOGLE_CLOUD_STORAGE. + auth_type : typing.Optional[BlobStorageIntegrationAuthType] + Authentication method. `ROLE_ARN` requires type `S3`, a concrete AWS region (not `auto`), and `roleArn`; `accessKeyId` and `secretAccessKey` must then be omitted, and `endpoint` and `forcePathStyle` are ignored. When omitted, `ROLE_ARN` applies to type `S3` if the request sends `roleArn` or the integration already uses `ROLE_ARN`, unless the request sends `accessKeyId` or `secretAccessKey`; otherwise `ACCESS_KEYS` applies. + + The external ID is generated on the first save that uses `ROLE_ARN` and stays the same afterwards. That save stores the integration with `enabled: false`, because the trust policy cannot reference the new ID yet. Add the trust policy for `crossAccountRoleArn` with the returned `externalId` to your role, then update the integration with `enabled: true`. + + role_arn : typing.Optional[str] + ARN of the IAM role in your AWS account that Langfuse assumes when `authType` is `ROLE_ARN`, e.g. `arn:aws:iam::123456789012:role/langfuse-export`. When omitted on update, the stored role ARN is kept. + access_key_id : typing.Optional[str] - Access key ID for authentication. Not used for GOOGLE_CLOUD_STORAGE. + Access key ID for authentication. Not used for GOOGLE_CLOUD_STORAGE or when `authType` is `ROLE_ARN`. secret_access_key : typing.Optional[str] Secret access key for authentication (will be encrypted when stored). When omitted on update, the stored secret is kept, unless the type changes between GOOGLE_CLOUD_STORAGE and another type. @@ -498,6 +521,8 @@ async def main() -> None: file_type=file_type, export_mode=export_mode, endpoint=endpoint, + auth_type=auth_type, + role_arn=role_arn, access_key_id=access_key_id, secret_access_key=secret_access_key, prefix=prefix, diff --git a/langfuse/api/blob_storage_integrations/raw_client.py b/langfuse/api/blob_storage_integrations/raw_client.py index 5e7fcd6f9..27d70ba4f 100644 --- a/langfuse/api/blob_storage_integrations/raw_client.py +++ b/langfuse/api/blob_storage_integrations/raw_client.py @@ -19,6 +19,7 @@ from .types.blob_storage_export_frequency import BlobStorageExportFrequency from .types.blob_storage_export_mode import BlobStorageExportMode from .types.blob_storage_export_source import BlobStorageExportSource +from .types.blob_storage_integration_auth_type import BlobStorageIntegrationAuthType from .types.blob_storage_integration_deletion_response import ( BlobStorageIntegrationDeletionResponse, ) @@ -149,6 +150,8 @@ def upsert_blob_storage_integration( file_type: BlobStorageIntegrationFileType, export_mode: BlobStorageExportMode, endpoint: typing.Optional[str] = OMIT, + auth_type: typing.Optional[BlobStorageIntegrationAuthType] = OMIT, + role_arn: typing.Optional[str] = OMIT, access_key_id: typing.Optional[str] = OMIT, secret_access_key: typing.Optional[str] = OMIT, prefix: typing.Optional[str] = OMIT, @@ -191,8 +194,16 @@ def upsert_blob_storage_integration( endpoint : typing.Optional[str] Custom endpoint URL (required for S3_COMPATIBLE type). Ignored for GOOGLE_CLOUD_STORAGE. + auth_type : typing.Optional[BlobStorageIntegrationAuthType] + Authentication method. `ROLE_ARN` requires type `S3`, a concrete AWS region (not `auto`), and `roleArn`; `accessKeyId` and `secretAccessKey` must then be omitted, and `endpoint` and `forcePathStyle` are ignored. When omitted, `ROLE_ARN` applies to type `S3` if the request sends `roleArn` or the integration already uses `ROLE_ARN`, unless the request sends `accessKeyId` or `secretAccessKey`; otherwise `ACCESS_KEYS` applies. + + The external ID is generated on the first save that uses `ROLE_ARN` and stays the same afterwards. That save stores the integration with `enabled: false`, because the trust policy cannot reference the new ID yet. Add the trust policy for `crossAccountRoleArn` with the returned `externalId` to your role, then update the integration with `enabled: true`. + + role_arn : typing.Optional[str] + ARN of the IAM role in your AWS account that Langfuse assumes when `authType` is `ROLE_ARN`, e.g. `arn:aws:iam::123456789012:role/langfuse-export`. When omitted on update, the stored role ARN is kept. + access_key_id : typing.Optional[str] - Access key ID for authentication. Not used for GOOGLE_CLOUD_STORAGE. + Access key ID for authentication. Not used for GOOGLE_CLOUD_STORAGE or when `authType` is `ROLE_ARN`. secret_access_key : typing.Optional[str] Secret access key for authentication (will be encrypted when stored). When omitted on update, the stored secret is kept, unless the type changes between GOOGLE_CLOUD_STORAGE and another type. @@ -236,6 +247,8 @@ def upsert_blob_storage_integration( "bucketName": bucket_name, "endpoint": endpoint, "region": region, + "authType": auth_type, + "roleArn": role_arn, "accessKeyId": access_key_id, "secretAccessKey": secret_access_key, "prefix": prefix, @@ -646,6 +659,8 @@ async def upsert_blob_storage_integration( file_type: BlobStorageIntegrationFileType, export_mode: BlobStorageExportMode, endpoint: typing.Optional[str] = OMIT, + auth_type: typing.Optional[BlobStorageIntegrationAuthType] = OMIT, + role_arn: typing.Optional[str] = OMIT, access_key_id: typing.Optional[str] = OMIT, secret_access_key: typing.Optional[str] = OMIT, prefix: typing.Optional[str] = OMIT, @@ -688,8 +703,16 @@ async def upsert_blob_storage_integration( endpoint : typing.Optional[str] Custom endpoint URL (required for S3_COMPATIBLE type). Ignored for GOOGLE_CLOUD_STORAGE. + auth_type : typing.Optional[BlobStorageIntegrationAuthType] + Authentication method. `ROLE_ARN` requires type `S3`, a concrete AWS region (not `auto`), and `roleArn`; `accessKeyId` and `secretAccessKey` must then be omitted, and `endpoint` and `forcePathStyle` are ignored. When omitted, `ROLE_ARN` applies to type `S3` if the request sends `roleArn` or the integration already uses `ROLE_ARN`, unless the request sends `accessKeyId` or `secretAccessKey`; otherwise `ACCESS_KEYS` applies. + + The external ID is generated on the first save that uses `ROLE_ARN` and stays the same afterwards. That save stores the integration with `enabled: false`, because the trust policy cannot reference the new ID yet. Add the trust policy for `crossAccountRoleArn` with the returned `externalId` to your role, then update the integration with `enabled: true`. + + role_arn : typing.Optional[str] + ARN of the IAM role in your AWS account that Langfuse assumes when `authType` is `ROLE_ARN`, e.g. `arn:aws:iam::123456789012:role/langfuse-export`. When omitted on update, the stored role ARN is kept. + access_key_id : typing.Optional[str] - Access key ID for authentication. Not used for GOOGLE_CLOUD_STORAGE. + Access key ID for authentication. Not used for GOOGLE_CLOUD_STORAGE or when `authType` is `ROLE_ARN`. secret_access_key : typing.Optional[str] Secret access key for authentication (will be encrypted when stored). When omitted on update, the stored secret is kept, unless the type changes between GOOGLE_CLOUD_STORAGE and another type. @@ -733,6 +756,8 @@ async def upsert_blob_storage_integration( "bucketName": bucket_name, "endpoint": endpoint, "region": region, + "authType": auth_type, + "roleArn": role_arn, "accessKeyId": access_key_id, "secretAccessKey": secret_access_key, "prefix": prefix, diff --git a/langfuse/api/blob_storage_integrations/types/__init__.py b/langfuse/api/blob_storage_integrations/types/__init__.py index 0da7964dd..3e6c5557d 100644 --- a/langfuse/api/blob_storage_integrations/types/__init__.py +++ b/langfuse/api/blob_storage_integrations/types/__init__.py @@ -10,6 +10,7 @@ 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_auth_type import BlobStorageIntegrationAuthType from .blob_storage_integration_deletion_response import ( BlobStorageIntegrationDeletionResponse, ) @@ -32,6 +33,7 @@ "BlobStorageExportFrequency": ".blob_storage_export_frequency", "BlobStorageExportMode": ".blob_storage_export_mode", "BlobStorageExportSource": ".blob_storage_export_source", + "BlobStorageIntegrationAuthType": ".blob_storage_integration_auth_type", "BlobStorageIntegrationDeletionResponse": ".blob_storage_integration_deletion_response", "BlobStorageIntegrationFileType": ".blob_storage_integration_file_type", "BlobStorageIntegrationFileTypeResponse": ".blob_storage_integration_file_type_response", @@ -76,6 +78,7 @@ def __dir__(): "BlobStorageExportFrequency", "BlobStorageExportMode", "BlobStorageExportSource", + "BlobStorageIntegrationAuthType", "BlobStorageIntegrationDeletionResponse", "BlobStorageIntegrationFileType", "BlobStorageIntegrationFileTypeResponse", diff --git a/langfuse/api/blob_storage_integrations/types/blob_storage_integration_auth_type.py b/langfuse/api/blob_storage_integrations/types/blob_storage_integration_auth_type.py new file mode 100644 index 000000000..69192770c --- /dev/null +++ b/langfuse/api/blob_storage_integrations/types/blob_storage_integration_auth_type.py @@ -0,0 +1,28 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ...core import enum + +T_Result = typing.TypeVar("T_Result") + + +class BlobStorageIntegrationAuthType(enum.StrEnum): + """ + How Langfuse authenticates to the bucket. + - `ACCESS_KEYS`: stored access key ID and secret access key. + - `ROLE_ARN`: Langfuse assumes an IAM role in your AWS account (Amazon S3 only). Langfuse's cross-account role (`crossAccountRoleArn`) assumes your role with the integration's `externalId`, using a session limited to writing under the configured prefix. Requires an Enterprise plan and a deployment that configures a cross-account role; `crossAccountRoleArn` is not null when both apply. + """ + + ACCESS_KEYS = "ACCESS_KEYS" + ROLE_ARN = "ROLE_ARN" + + def visit( + self, + access_keys: typing.Callable[[], T_Result], + role_arn: typing.Callable[[], T_Result], + ) -> T_Result: + if self is BlobStorageIntegrationAuthType.ACCESS_KEYS: + return access_keys() + if self is BlobStorageIntegrationAuthType.ROLE_ARN: + return role_arn() 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 343a03d58..3d018c685 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,6 +11,7 @@ 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_auth_type import BlobStorageIntegrationAuthType from .blob_storage_integration_file_type_response import ( BlobStorageIntegrationFileTypeResponse, ) @@ -24,9 +25,33 @@ class BlobStorageIntegrationResponse(UniversalBaseModel): bucket_name: typing_extensions.Annotated[str, FieldMetadata(alias="bucketName")] endpoint: typing.Optional[str] = None region: str + auth_type: typing_extensions.Annotated[ + BlobStorageIntegrationAuthType, FieldMetadata(alias="authType") + ] access_key_id: typing_extensions.Annotated[ typing.Optional[str], FieldMetadata(alias="accessKeyId") ] = None + role_arn: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="roleArn") + ] = pydantic.Field(default=None) + """ + IAM role Langfuse assumes. May be set while `authType` is `ACCESS_KEYS` if the integration used `ROLE_ARN` before. + """ + + external_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="externalId") + ] = pydantic.Field(default=None) + """ + External ID Langfuse passes when assuming `roleArn`. Require it in the role's trust policy with an `sts:ExternalId` condition. Null until the integration first uses `ROLE_ARN`. + """ + + cross_account_role_arn: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="crossAccountRoleArn") + ] = pydantic.Field(default=None) + """ + Langfuse role that must be trusted by `roleArn`. Null when the deployment does not support IAM role auth or the organization is not on an Enterprise plan. + """ + prefix: str export_frequency: typing_extensions.Annotated[ BlobStorageExportFrequency, FieldMetadata(alias="exportFrequency") 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 65fc84e5c..4fa78928c 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 @@ -11,6 +11,7 @@ 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_auth_type import BlobStorageIntegrationAuthType from .blob_storage_integration_file_type import BlobStorageIntegrationFileType from .blob_storage_integration_type import BlobStorageIntegrationType @@ -41,11 +42,27 @@ class CreateBlobStorageIntegrationRequest(UniversalBaseModel): Storage region used by S3-compatible clients (AWS, GCS, Cloudflare R2, MinIO, Azure location IDs such as eastus, OCI). Leading and trailing whitespace is removed. The remaining value must be 1-63 letters, numbers, or hyphens, and cannot start or end with a hyphen. Examples: us-east-1, europe-west1, eastus, auto. Not used by GOOGLE_CLOUD_STORAGE; pass auto. """ + auth_type: typing_extensions.Annotated[ + typing.Optional[BlobStorageIntegrationAuthType], FieldMetadata(alias="authType") + ] = pydantic.Field(default=None) + """ + Authentication method. `ROLE_ARN` requires type `S3`, a concrete AWS region (not `auto`), and `roleArn`; `accessKeyId` and `secretAccessKey` must then be omitted, and `endpoint` and `forcePathStyle` are ignored. When omitted, `ROLE_ARN` applies to type `S3` if the request sends `roleArn` or the integration already uses `ROLE_ARN`, unless the request sends `accessKeyId` or `secretAccessKey`; otherwise `ACCESS_KEYS` applies. + + The external ID is generated on the first save that uses `ROLE_ARN` and stays the same afterwards. That save stores the integration with `enabled: false`, because the trust policy cannot reference the new ID yet. Add the trust policy for `crossAccountRoleArn` with the returned `externalId` to your role, then update the integration with `enabled: true`. + """ + + role_arn: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="roleArn") + ] = pydantic.Field(default=None) + """ + ARN of the IAM role in your AWS account that Langfuse assumes when `authType` is `ROLE_ARN`, e.g. `arn:aws:iam::123456789012:role/langfuse-export`. When omitted on update, the stored role ARN is kept. + """ + access_key_id: typing_extensions.Annotated[ typing.Optional[str], FieldMetadata(alias="accessKeyId") ] = pydantic.Field(default=None) """ - Access key ID for authentication. Not used for GOOGLE_CLOUD_STORAGE. + Access key ID for authentication. Not used for GOOGLE_CLOUD_STORAGE or when `authType` is `ROLE_ARN`. """ secret_access_key: typing_extensions.Annotated[ diff --git a/langfuse/api/commons/types/model.py b/langfuse/api/commons/types/model.py index 3c7b7a182..b516ecee0 100644 --- a/langfuse/api/commons/types/model.py +++ b/langfuse/api/commons/types/model.py @@ -14,7 +14,7 @@ class Model(UniversalBaseModel): """ - Model definition used for transforming usage into USD cost and/or tokenization. + Model definition used for calculating USD cost from usage submitted with observations. Models can have either simple flat pricing or tiered pricing: - Flat pricing: Single price per usage type (legacy, but still supported) @@ -77,14 +77,14 @@ class Model(UniversalBaseModel): typing.Optional[str], FieldMetadata(alias="tokenizerId") ] = pydantic.Field(default=None) """ - Optional. Tokenizer to be applied to observations which match to this model. See docs for more details. + Legacy tokenizer identifier retained for API compatibility. Ingestion no longer uses it to infer observation usage. """ tokenizer_config: typing_extensions.Annotated[ typing.Any, FieldMetadata(alias="tokenizerConfig") ] = pydantic.Field() """ - Optional. Configuration for the selected tokenizer. Needs to be JSON. See docs for more details. + Legacy tokenizer configuration retained for API compatibility. Ingestion no longer uses it to infer observation usage. """ is_langfuse_managed: typing_extensions.Annotated[ diff --git a/langfuse/api/evaluation_commons/types/evaluation_rule_filter.py b/langfuse/api/evaluation_commons/types/evaluation_rule_filter.py index 160026a20..5e38561c7 100644 --- a/langfuse/api/evaluation_commons/types/evaluation_rule_filter.py +++ b/langfuse/api/evaluation_commons/types/evaluation_rule_filter.py @@ -647,7 +647,7 @@ class EvaluationRuleFilter_Null(UniversalBaseModel): type: typing.Literal["null"] = "null" column: str operator: EvaluationRuleNullFilterOperator - value: typing.Literal[""] = "" + value: typing.Optional[typing.Literal[""]] = None model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( extra="allow", frozen=True diff --git a/langfuse/api/evaluation_commons/types/null_evaluation_rule_filter.py b/langfuse/api/evaluation_commons/types/null_evaluation_rule_filter.py index 17343d87b..8fafb3ca5 100644 --- a/langfuse/api/evaluation_commons/types/null_evaluation_rule_filter.py +++ b/langfuse/api/evaluation_commons/types/null_evaluation_rule_filter.py @@ -14,9 +14,9 @@ class NullEvaluationRuleFilter(UniversalBaseModel): """ operator: EvaluationRuleNullFilterOperator - value: typing.Literal[""] = pydantic.Field(default="") + value: typing.Optional[typing.Literal[""]] = pydantic.Field(default=None) """ - Required empty-string placeholder used by the runtime filter contract. + Optional. Defaults to an empty string. """ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( diff --git a/langfuse/api/models/client.py b/langfuse/api/models/client.py index 05b245e5b..51c7d6165 100644 --- a/langfuse/api/models/client.py +++ b/langfuse/api/models/client.py @@ -93,10 +93,10 @@ def create( which will automatically create a single default tier named "Standard". tokenizer_id : typing.Optional[ModelTokenizerId] - Optional. Tokenizer to be applied to observations which match to this model. See docs for more details. + Legacy tokenizer identifier retained for API compatibility. Ingestion no longer uses it to infer observation usage. tokenizer_config : typing.Optional[typing.Any] - Optional. Configuration for the selected tokenizer. Needs to be JSON. See docs for more details. + Legacy tokenizer configuration retained for API compatibility. Ingestion no longer uses it to infer observation usage. request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -204,10 +204,10 @@ def upsert( which will automatically create a single default tier named "Standard". tokenizer_id : typing.Optional[ModelTokenizerId] - Optional. Tokenizer to be applied to observations which match to this model. See docs for more details. + Legacy tokenizer identifier retained for API compatibility. Ingestion no longer uses it to infer observation usage. tokenizer_config : typing.Optional[typing.Any] - Optional. Configuration for the selected tokenizer. Needs to be JSON. See docs for more details. + Legacy tokenizer configuration retained for API compatibility. Ingestion no longer uses it to infer observation usage. request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -446,10 +446,10 @@ async def create( which will automatically create a single default tier named "Standard". tokenizer_id : typing.Optional[ModelTokenizerId] - Optional. Tokenizer to be applied to observations which match to this model. See docs for more details. + Legacy tokenizer identifier retained for API compatibility. Ingestion no longer uses it to infer observation usage. tokenizer_config : typing.Optional[typing.Any] - Optional. Configuration for the selected tokenizer. Needs to be JSON. See docs for more details. + Legacy tokenizer configuration retained for API compatibility. Ingestion no longer uses it to infer observation usage. request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -565,10 +565,10 @@ async def upsert( which will automatically create a single default tier named "Standard". tokenizer_id : typing.Optional[ModelTokenizerId] - Optional. Tokenizer to be applied to observations which match to this model. See docs for more details. + Legacy tokenizer identifier retained for API compatibility. Ingestion no longer uses it to infer observation usage. tokenizer_config : typing.Optional[typing.Any] - Optional. Configuration for the selected tokenizer. Needs to be JSON. See docs for more details. + Legacy tokenizer configuration retained for API compatibility. Ingestion no longer uses it to infer observation usage. request_options : typing.Optional[RequestOptions] Request-specific configuration. diff --git a/langfuse/api/models/raw_client.py b/langfuse/api/models/raw_client.py index 4d645dcab..734a2d282 100644 --- a/langfuse/api/models/raw_client.py +++ b/langfuse/api/models/raw_client.py @@ -92,10 +92,10 @@ def create( which will automatically create a single default tier named "Standard". tokenizer_id : typing.Optional[ModelTokenizerId] - Optional. Tokenizer to be applied to observations which match to this model. See docs for more details. + Legacy tokenizer identifier retained for API compatibility. Ingestion no longer uses it to infer observation usage. tokenizer_config : typing.Optional[typing.Any] - Optional. Configuration for the selected tokenizer. Needs to be JSON. See docs for more details. + Legacy tokenizer configuration retained for API compatibility. Ingestion no longer uses it to infer observation usage. request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -269,10 +269,10 @@ def upsert( which will automatically create a single default tier named "Standard". tokenizer_id : typing.Optional[ModelTokenizerId] - Optional. Tokenizer to be applied to observations which match to this model. See docs for more details. + Legacy tokenizer identifier retained for API compatibility. Ingestion no longer uses it to infer observation usage. tokenizer_config : typing.Optional[typing.Any] - Optional. Configuration for the selected tokenizer. Needs to be JSON. See docs for more details. + Legacy tokenizer configuration retained for API compatibility. Ingestion no longer uses it to infer observation usage. request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -753,10 +753,10 @@ async def create( which will automatically create a single default tier named "Standard". tokenizer_id : typing.Optional[ModelTokenizerId] - Optional. Tokenizer to be applied to observations which match to this model. See docs for more details. + Legacy tokenizer identifier retained for API compatibility. Ingestion no longer uses it to infer observation usage. tokenizer_config : typing.Optional[typing.Any] - Optional. Configuration for the selected tokenizer. Needs to be JSON. See docs for more details. + Legacy tokenizer configuration retained for API compatibility. Ingestion no longer uses it to infer observation usage. request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -930,10 +930,10 @@ async def upsert( which will automatically create a single default tier named "Standard". tokenizer_id : typing.Optional[ModelTokenizerId] - Optional. Tokenizer to be applied to observations which match to this model. See docs for more details. + Legacy tokenizer identifier retained for API compatibility. Ingestion no longer uses it to infer observation usage. tokenizer_config : typing.Optional[typing.Any] - Optional. Configuration for the selected tokenizer. Needs to be JSON. See docs for more details. + Legacy tokenizer configuration retained for API compatibility. Ingestion no longer uses it to infer observation usage. request_options : typing.Optional[RequestOptions] Request-specific configuration. diff --git a/langfuse/api/models/types/create_model_request.py b/langfuse/api/models/types/create_model_request.py index 458a5290a..8f082069b 100644 --- a/langfuse/api/models/types/create_model_request.py +++ b/langfuse/api/models/types/create_model_request.py @@ -89,14 +89,14 @@ class CreateModelRequest(UniversalBaseModel): typing.Optional[ModelTokenizerId], FieldMetadata(alias="tokenizerId") ] = pydantic.Field(default=None) """ - Optional. Tokenizer to be applied to observations which match to this model. See docs for more details. + Legacy tokenizer identifier retained for API compatibility. Ingestion no longer uses it to infer observation usage. """ tokenizer_config: typing_extensions.Annotated[ typing.Optional[typing.Any], FieldMetadata(alias="tokenizerConfig") ] = pydantic.Field(default=None) """ - Optional. Configuration for the selected tokenizer. Needs to be JSON. See docs for more details. + Legacy tokenizer configuration retained for API compatibility. Ingestion no longer uses it to infer observation usage. """ model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( diff --git a/langfuse/api/models/types/model_tokenizer_id.py b/langfuse/api/models/types/model_tokenizer_id.py index 83a9b234d..882a2c59c 100644 --- a/langfuse/api/models/types/model_tokenizer_id.py +++ b/langfuse/api/models/types/model_tokenizer_id.py @@ -9,7 +9,7 @@ class ModelTokenizerId(enum.StrEnum): """ - Tokenizer supported by Langfuse for model usage inference. + Legacy tokenizer identifier retained for API compatibility. Ingestion no longer uses it to infer observation usage. """ OPENAI = "openai" diff --git a/langfuse/api/observations/client.py b/langfuse/api/observations/client.py index 5e717adb6..90672665c 100644 --- a/langfuse/api/observations/client.py +++ b/langfuse/api/observations/client.py @@ -56,7 +56,10 @@ def get_many( ## Cursor-based Pagination This endpoint uses cursor-based pagination for efficient traversal of large datasets. The cursor is returned in the response metadata and should be passed in subsequent requests - to retrieve the next page of results. + to retrieve the next page of results. The `limit` parameter is a maximum: a page may contain + fewer items than the requested limit, including zero items, while `meta.cursor` is present. + Continue requesting pages until `meta.cursor` is absent. + Requests without a lower start-time bound may search only from January 1, 2020. ## Field Selection Use the `fields` parameter to control which observation fields are returned: @@ -75,7 +78,7 @@ def get_many( ## Filters Multiple filtering options are available via query parameters or the structured `filter` parameter. - When using the `filter` parameter, it takes precedence over individual query parameter filters. + A condition in the `filter` parameter replaces the query parameter for the same field. Query parameters for other fields still apply. A `startTime` condition replaces both `fromStartTime` and `toStartTime`. Parameters ---------- @@ -92,7 +95,7 @@ def get_many( Example: "key1,key2" limit : typing.Optional[int] - Number of items to return per page. Maximum 1000, default 50. + Maximum number of items to return per page. A page may contain fewer items, including zero items, while meta.cursor is present. Maximum 1000, default 50. cursor : typing.Optional[str] Base64-encoded cursor for pagination. Use the cursor from the previous response to get the next page. @@ -139,28 +142,26 @@ def get_many( Optional filter to only include observations with a certain version. filter : typing.Optional[str] - JSON string containing an array of filter conditions. When provided, this takes precedence over query parameter filters (userId, name, type, level, environment, fromStartTime, ...). + JSON string containing an array of filter conditions. A condition on the same field as a query parameter filter (userId, name, type, level, environment, fromStartTime, ...) replaces that parameter. Query parameters for other fields still apply. A `startTime` condition replaces both `fromStartTime` and `toStartTime`. ## Filter Structure Each filter condition has the following structure: ```json [ { - "type": string, // Required. One of: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" + "type": string, // Required. One of: "datetime", "string", "number", "stringOptions", "arrayOptions", "stringObject", "boolean", "null" "column": string, // Required. Column to filter on (see available columns below) "operator": string, // Required. Operator based on type: // - datetime: ">", "<", ">=", "<=" - // - string: "=", "contains", "does not contain", "starts with", "ends with", "matches" - // - stringOptions: "any of", "none of" - // - categoryOptions: "any of", "none of" + // - string: "=", "contains", "does not contain", "starts with", "ends with", "is not empty", "matches" (input/output only) + // - stringOptions: "any of", "none of" (works on any string column except input/output) // - arrayOptions: "any of", "none of", "all of" // - number: "=", ">", "<", ">=", "<=" // - stringObject: "=", "contains", "does not contain", "starts with", "ends with", "matches", "is set", "is not set" - // - numberObject: "=", ">", "<", ">=", "<=" // - boolean: "=", "<>" // - null: "is null", "is not null" - "value": any, // Required (except for null type). Value to compare against. Type depends on filter type - "key": string // Required only for stringObject, numberObject, and categoryOptions types when filtering on nested fields like metadata + "value": any, // Required. Type depends on filter type: ISO 8601 string for datetime, JSON number for number (not a numeric string), array of strings for stringOptions/arrayOptions, boolean for boolean, and "" (empty string) for null + "key": string // Required only for stringObject (the metadata key to filter on) } ] ``` @@ -210,7 +211,7 @@ def get_many( ### Structured Data - `input` (string) - Observation input. Supports accelerated indexed literal search with the `matches` operator. - `output` (string) - Observation output. Supports accelerated indexed literal search with the `matches` operator. - - `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. + - `metadata` (stringObject) - Metadata key-value pairs. Use the `key` parameter to filter on a specific metadata key. Metadata values are stored as strings, so match numbers and booleans by their string form with `=` (for example `"value": "true"` or `"value": "42"`); numeric comparisons such as `>` are not supported on metadata. The `matches` operator is only supported for `input`, `output`, and stringObject `metadata` filters. It performs indexed literal search with token-boundary pruning using the events table text indexes. Case sensitivity differs by target: `input` and `output` matches are case-insensitive, while metadata value matches are case-sensitive. Unlike SQL `LIKE`, `%` and `_` are treated as literal characters. Use `contains` for legacy substring semantics where the API allows it. Any v2 `input` or `output` filter must be accompanied by at least one `=` or `matches` filter on `input` or `output`; standalone `contains`, `starts with`, `ends with`, and `does not contain` filters on these columns are rejected. @@ -342,7 +343,10 @@ async def get_many( ## Cursor-based Pagination This endpoint uses cursor-based pagination for efficient traversal of large datasets. The cursor is returned in the response metadata and should be passed in subsequent requests - to retrieve the next page of results. + to retrieve the next page of results. The `limit` parameter is a maximum: a page may contain + fewer items than the requested limit, including zero items, while `meta.cursor` is present. + Continue requesting pages until `meta.cursor` is absent. + Requests without a lower start-time bound may search only from January 1, 2020. ## Field Selection Use the `fields` parameter to control which observation fields are returned: @@ -361,7 +365,7 @@ async def get_many( ## Filters Multiple filtering options are available via query parameters or the structured `filter` parameter. - When using the `filter` parameter, it takes precedence over individual query parameter filters. + A condition in the `filter` parameter replaces the query parameter for the same field. Query parameters for other fields still apply. A `startTime` condition replaces both `fromStartTime` and `toStartTime`. Parameters ---------- @@ -378,7 +382,7 @@ async def get_many( Example: "key1,key2" limit : typing.Optional[int] - Number of items to return per page. Maximum 1000, default 50. + Maximum number of items to return per page. A page may contain fewer items, including zero items, while meta.cursor is present. Maximum 1000, default 50. cursor : typing.Optional[str] Base64-encoded cursor for pagination. Use the cursor from the previous response to get the next page. @@ -425,28 +429,26 @@ async def get_many( Optional filter to only include observations with a certain version. filter : typing.Optional[str] - JSON string containing an array of filter conditions. When provided, this takes precedence over query parameter filters (userId, name, type, level, environment, fromStartTime, ...). + JSON string containing an array of filter conditions. A condition on the same field as a query parameter filter (userId, name, type, level, environment, fromStartTime, ...) replaces that parameter. Query parameters for other fields still apply. A `startTime` condition replaces both `fromStartTime` and `toStartTime`. ## Filter Structure Each filter condition has the following structure: ```json [ { - "type": string, // Required. One of: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" + "type": string, // Required. One of: "datetime", "string", "number", "stringOptions", "arrayOptions", "stringObject", "boolean", "null" "column": string, // Required. Column to filter on (see available columns below) "operator": string, // Required. Operator based on type: // - datetime: ">", "<", ">=", "<=" - // - string: "=", "contains", "does not contain", "starts with", "ends with", "matches" - // - stringOptions: "any of", "none of" - // - categoryOptions: "any of", "none of" + // - string: "=", "contains", "does not contain", "starts with", "ends with", "is not empty", "matches" (input/output only) + // - stringOptions: "any of", "none of" (works on any string column except input/output) // - arrayOptions: "any of", "none of", "all of" // - number: "=", ">", "<", ">=", "<=" // - stringObject: "=", "contains", "does not contain", "starts with", "ends with", "matches", "is set", "is not set" - // - numberObject: "=", ">", "<", ">=", "<=" // - boolean: "=", "<>" // - null: "is null", "is not null" - "value": any, // Required (except for null type). Value to compare against. Type depends on filter type - "key": string // Required only for stringObject, numberObject, and categoryOptions types when filtering on nested fields like metadata + "value": any, // Required. Type depends on filter type: ISO 8601 string for datetime, JSON number for number (not a numeric string), array of strings for stringOptions/arrayOptions, boolean for boolean, and "" (empty string) for null + "key": string // Required only for stringObject (the metadata key to filter on) } ] ``` @@ -496,7 +498,7 @@ async def get_many( ### Structured Data - `input` (string) - Observation input. Supports accelerated indexed literal search with the `matches` operator. - `output` (string) - Observation output. Supports accelerated indexed literal search with the `matches` operator. - - `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. + - `metadata` (stringObject) - Metadata key-value pairs. Use the `key` parameter to filter on a specific metadata key. Metadata values are stored as strings, so match numbers and booleans by their string form with `=` (for example `"value": "true"` or `"value": "42"`); numeric comparisons such as `>` are not supported on metadata. The `matches` operator is only supported for `input`, `output`, and stringObject `metadata` filters. It performs indexed literal search with token-boundary pruning using the events table text indexes. Case sensitivity differs by target: `input` and `output` matches are case-insensitive, while metadata value matches are case-sensitive. Unlike SQL `LIKE`, `%` and `_` are treated as literal characters. Use `contains` for legacy substring semantics where the API allows it. Any v2 `input` or `output` filter must be accompanied by at least one `=` or `matches` filter on `input` or `output`; standalone `contains`, `starts with`, `ends with`, and `does not contain` filters on these columns are rejected. diff --git a/langfuse/api/observations/raw_client.py b/langfuse/api/observations/raw_client.py index 84dba4891..43f9da27c 100644 --- a/langfuse/api/observations/raw_client.py +++ b/langfuse/api/observations/raw_client.py @@ -54,7 +54,10 @@ def get_many( ## Cursor-based Pagination This endpoint uses cursor-based pagination for efficient traversal of large datasets. The cursor is returned in the response metadata and should be passed in subsequent requests - to retrieve the next page of results. + to retrieve the next page of results. The `limit` parameter is a maximum: a page may contain + fewer items than the requested limit, including zero items, while `meta.cursor` is present. + Continue requesting pages until `meta.cursor` is absent. + Requests without a lower start-time bound may search only from January 1, 2020. ## Field Selection Use the `fields` parameter to control which observation fields are returned: @@ -73,7 +76,7 @@ def get_many( ## Filters Multiple filtering options are available via query parameters or the structured `filter` parameter. - When using the `filter` parameter, it takes precedence over individual query parameter filters. + A condition in the `filter` parameter replaces the query parameter for the same field. Query parameters for other fields still apply. A `startTime` condition replaces both `fromStartTime` and `toStartTime`. Parameters ---------- @@ -90,7 +93,7 @@ def get_many( Example: "key1,key2" limit : typing.Optional[int] - Number of items to return per page. Maximum 1000, default 50. + Maximum number of items to return per page. A page may contain fewer items, including zero items, while meta.cursor is present. Maximum 1000, default 50. cursor : typing.Optional[str] Base64-encoded cursor for pagination. Use the cursor from the previous response to get the next page. @@ -137,28 +140,26 @@ def get_many( Optional filter to only include observations with a certain version. filter : typing.Optional[str] - JSON string containing an array of filter conditions. When provided, this takes precedence over query parameter filters (userId, name, type, level, environment, fromStartTime, ...). + JSON string containing an array of filter conditions. A condition on the same field as a query parameter filter (userId, name, type, level, environment, fromStartTime, ...) replaces that parameter. Query parameters for other fields still apply. A `startTime` condition replaces both `fromStartTime` and `toStartTime`. ## Filter Structure Each filter condition has the following structure: ```json [ { - "type": string, // Required. One of: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" + "type": string, // Required. One of: "datetime", "string", "number", "stringOptions", "arrayOptions", "stringObject", "boolean", "null" "column": string, // Required. Column to filter on (see available columns below) "operator": string, // Required. Operator based on type: // - datetime: ">", "<", ">=", "<=" - // - string: "=", "contains", "does not contain", "starts with", "ends with", "matches" - // - stringOptions: "any of", "none of" - // - categoryOptions: "any of", "none of" + // - string: "=", "contains", "does not contain", "starts with", "ends with", "is not empty", "matches" (input/output only) + // - stringOptions: "any of", "none of" (works on any string column except input/output) // - arrayOptions: "any of", "none of", "all of" // - number: "=", ">", "<", ">=", "<=" // - stringObject: "=", "contains", "does not contain", "starts with", "ends with", "matches", "is set", "is not set" - // - numberObject: "=", ">", "<", ">=", "<=" // - boolean: "=", "<>" // - null: "is null", "is not null" - "value": any, // Required (except for null type). Value to compare against. Type depends on filter type - "key": string // Required only for stringObject, numberObject, and categoryOptions types when filtering on nested fields like metadata + "value": any, // Required. Type depends on filter type: ISO 8601 string for datetime, JSON number for number (not a numeric string), array of strings for stringOptions/arrayOptions, boolean for boolean, and "" (empty string) for null + "key": string // Required only for stringObject (the metadata key to filter on) } ] ``` @@ -208,7 +209,7 @@ def get_many( ### Structured Data - `input` (string) - Observation input. Supports accelerated indexed literal search with the `matches` operator. - `output` (string) - Observation output. Supports accelerated indexed literal search with the `matches` operator. - - `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. + - `metadata` (stringObject) - Metadata key-value pairs. Use the `key` parameter to filter on a specific metadata key. Metadata values are stored as strings, so match numbers and booleans by their string form with `=` (for example `"value": "true"` or `"value": "42"`); numeric comparisons such as `>` are not supported on metadata. The `matches` operator is only supported for `input`, `output`, and stringObject `metadata` filters. It performs indexed literal search with token-boundary pruning using the events table text indexes. Case sensitivity differs by target: `input` and `output` matches are case-insensitive, while metadata value matches are case-sensitive. Unlike SQL `LIKE`, `%` and `_` are treated as literal characters. Use `contains` for legacy substring semantics where the API allows it. Any v2 `input` or `output` filter must be accompanied by at least one `=` or `matches` filter on `input` or `output`; standalone `contains`, `starts with`, `ends with`, and `does not contain` filters on these columns are rejected. @@ -399,7 +400,10 @@ async def get_many( ## Cursor-based Pagination This endpoint uses cursor-based pagination for efficient traversal of large datasets. The cursor is returned in the response metadata and should be passed in subsequent requests - to retrieve the next page of results. + to retrieve the next page of results. The `limit` parameter is a maximum: a page may contain + fewer items than the requested limit, including zero items, while `meta.cursor` is present. + Continue requesting pages until `meta.cursor` is absent. + Requests without a lower start-time bound may search only from January 1, 2020. ## Field Selection Use the `fields` parameter to control which observation fields are returned: @@ -418,7 +422,7 @@ async def get_many( ## Filters Multiple filtering options are available via query parameters or the structured `filter` parameter. - When using the `filter` parameter, it takes precedence over individual query parameter filters. + A condition in the `filter` parameter replaces the query parameter for the same field. Query parameters for other fields still apply. A `startTime` condition replaces both `fromStartTime` and `toStartTime`. Parameters ---------- @@ -435,7 +439,7 @@ async def get_many( Example: "key1,key2" limit : typing.Optional[int] - Number of items to return per page. Maximum 1000, default 50. + Maximum number of items to return per page. A page may contain fewer items, including zero items, while meta.cursor is present. Maximum 1000, default 50. cursor : typing.Optional[str] Base64-encoded cursor for pagination. Use the cursor from the previous response to get the next page. @@ -482,28 +486,26 @@ async def get_many( Optional filter to only include observations with a certain version. filter : typing.Optional[str] - JSON string containing an array of filter conditions. When provided, this takes precedence over query parameter filters (userId, name, type, level, environment, fromStartTime, ...). + JSON string containing an array of filter conditions. A condition on the same field as a query parameter filter (userId, name, type, level, environment, fromStartTime, ...) replaces that parameter. Query parameters for other fields still apply. A `startTime` condition replaces both `fromStartTime` and `toStartTime`. ## Filter Structure Each filter condition has the following structure: ```json [ { - "type": string, // Required. One of: "datetime", "string", "number", "stringOptions", "categoryOptions", "arrayOptions", "stringObject", "numberObject", "boolean", "null" + "type": string, // Required. One of: "datetime", "string", "number", "stringOptions", "arrayOptions", "stringObject", "boolean", "null" "column": string, // Required. Column to filter on (see available columns below) "operator": string, // Required. Operator based on type: // - datetime: ">", "<", ">=", "<=" - // - string: "=", "contains", "does not contain", "starts with", "ends with", "matches" - // - stringOptions: "any of", "none of" - // - categoryOptions: "any of", "none of" + // - string: "=", "contains", "does not contain", "starts with", "ends with", "is not empty", "matches" (input/output only) + // - stringOptions: "any of", "none of" (works on any string column except input/output) // - arrayOptions: "any of", "none of", "all of" // - number: "=", ">", "<", ">=", "<=" // - stringObject: "=", "contains", "does not contain", "starts with", "ends with", "matches", "is set", "is not set" - // - numberObject: "=", ">", "<", ">=", "<=" // - boolean: "=", "<>" // - null: "is null", "is not null" - "value": any, // Required (except for null type). Value to compare against. Type depends on filter type - "key": string // Required only for stringObject, numberObject, and categoryOptions types when filtering on nested fields like metadata + "value": any, // Required. Type depends on filter type: ISO 8601 string for datetime, JSON number for number (not a numeric string), array of strings for stringOptions/arrayOptions, boolean for boolean, and "" (empty string) for null + "key": string // Required only for stringObject (the metadata key to filter on) } ] ``` @@ -553,7 +555,7 @@ async def get_many( ### Structured Data - `input` (string) - Observation input. Supports accelerated indexed literal search with the `matches` operator. - `output` (string) - Observation output. Supports accelerated indexed literal search with the `matches` operator. - - `metadata` (stringObject/numberObject/categoryOptions) - Metadata key-value pairs. Use `key` parameter to filter on specific metadata keys. + - `metadata` (stringObject) - Metadata key-value pairs. Use the `key` parameter to filter on a specific metadata key. Metadata values are stored as strings, so match numbers and booleans by their string form with `=` (for example `"value": "true"` or `"value": "42"`); numeric comparisons such as `>` are not supported on metadata. The `matches` operator is only supported for `input`, `output`, and stringObject `metadata` filters. It performs indexed literal search with token-boundary pruning using the events table text indexes. Case sensitivity differs by target: `input` and `output` matches are case-insensitive, while metadata value matches are case-sensitive. Unlike SQL `LIKE`, `%` and `_` are treated as literal characters. Use `contains` for legacy substring semantics where the API allows it. Any v2 `input` or `output` filter must be accompanied by at least one `=` or `matches` filter on `input` or `output`; standalone `contains`, `starts with`, `ends with`, and `does not contain` filters on these columns are rejected. diff --git a/langfuse/api/observations/types/observations_v2meta.py b/langfuse/api/observations/types/observations_v2meta.py index 46d9f42aa..2b34f8664 100644 --- a/langfuse/api/observations/types/observations_v2meta.py +++ b/langfuse/api/observations/types/observations_v2meta.py @@ -11,6 +11,11 @@ class ObservationsV2Meta(CursorMetaResponse): Metadata for cursor-based pagination. """ + comment: typing.Optional[str] = pydantic.Field(default=None) + """ + Present when an empty page has a cursor; continue requesting pages until the cursor is absent. + """ + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( extra="allow", frozen=True ) diff --git a/langfuse/api/observations/types/observations_v2response.py b/langfuse/api/observations/types/observations_v2response.py index 0ee1fb8bd..d702b59f7 100644 --- a/langfuse/api/observations/types/observations_v2response.py +++ b/langfuse/api/observations/types/observations_v2response.py @@ -13,7 +13,9 @@ class ObservationsV2Response(UniversalBaseModel): Response containing observations with field-group-based filtering and cursor-based pagination. The `data` array contains observation objects with only the requested field groups included. - Use the `cursor` in `meta` to retrieve the next page of results. + Use the `cursor` in `meta` to retrieve the next page of results. The array may contain fewer + than the requested `limit` items, or be empty, while a cursor is present. Continue until the + cursor is absent. """ data: typing.List[ObservationV2] = pydantic.Field() diff --git a/langfuse/api/prompt_version/client.py b/langfuse/api/prompt_version/client.py index f212447a4..70dbe828e 100644 --- a/langfuse/api/prompt_version/client.py +++ b/langfuse/api/prompt_version/client.py @@ -31,7 +31,8 @@ def update( name: str, version: int, *, - new_labels: typing.Sequence[str], + new_labels: typing.Optional[typing.Sequence[str]] = OMIT, + remove_labels: typing.Optional[typing.Sequence[str]] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> Prompt: """ @@ -46,9 +47,12 @@ def update( version : int Version of the prompt to update - new_labels : typing.Sequence[str] + new_labels : typing.Optional[typing.Sequence[str]] New labels for the prompt version. Labels are unique across versions. The "latest" label is reserved and managed by Langfuse. + remove_labels : typing.Optional[typing.Sequence[str]] + Labels to remove from the prompt version. The "latest" label is reserved and managed by Langfuse. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -71,11 +75,14 @@ def update( client.prompt_version.update( name="name", version=1, - new_labels=["newLabels", "newLabels"], ) """ _response = self._raw_client.update( - name, version, new_labels=new_labels, request_options=request_options + name, + version, + new_labels=new_labels, + remove_labels=remove_labels, + request_options=request_options, ) return _response.data @@ -100,7 +107,8 @@ async def update( name: str, version: int, *, - new_labels: typing.Sequence[str], + new_labels: typing.Optional[typing.Sequence[str]] = OMIT, + remove_labels: typing.Optional[typing.Sequence[str]] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> Prompt: """ @@ -115,9 +123,12 @@ async def update( version : int Version of the prompt to update - new_labels : typing.Sequence[str] + new_labels : typing.Optional[typing.Sequence[str]] New labels for the prompt version. Labels are unique across versions. The "latest" label is reserved and managed by Langfuse. + remove_labels : typing.Optional[typing.Sequence[str]] + Labels to remove from the prompt version. The "latest" label is reserved and managed by Langfuse. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -145,13 +156,16 @@ async def main() -> None: await client.prompt_version.update( name="name", version=1, - new_labels=["newLabels", "newLabels"], ) asyncio.run(main()) """ _response = await self._raw_client.update( - name, version, new_labels=new_labels, request_options=request_options + name, + version, + new_labels=new_labels, + remove_labels=remove_labels, + request_options=request_options, ) return _response.data diff --git a/langfuse/api/prompt_version/raw_client.py b/langfuse/api/prompt_version/raw_client.py index 7d1e8d3f2..5dc373450 100644 --- a/langfuse/api/prompt_version/raw_client.py +++ b/langfuse/api/prompt_version/raw_client.py @@ -29,7 +29,8 @@ def update( name: str, version: int, *, - new_labels: typing.Sequence[str], + new_labels: typing.Optional[typing.Sequence[str]] = OMIT, + remove_labels: typing.Optional[typing.Sequence[str]] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> HttpResponse[Prompt]: """ @@ -44,9 +45,12 @@ def update( version : int Version of the prompt to update - new_labels : typing.Sequence[str] + new_labels : typing.Optional[typing.Sequence[str]] New labels for the prompt version. Labels are unique across versions. The "latest" label is reserved and managed by Langfuse. + remove_labels : typing.Optional[typing.Sequence[str]] + Labels to remove from the prompt version. The "latest" label is reserved and managed by Langfuse. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -59,6 +63,7 @@ def update( method="PATCH", json={ "newLabels": new_labels, + "removeLabels": remove_labels, }, request_options=request_options, omit=OMIT, @@ -151,7 +156,8 @@ async def update( name: str, version: int, *, - new_labels: typing.Sequence[str], + new_labels: typing.Optional[typing.Sequence[str]] = OMIT, + remove_labels: typing.Optional[typing.Sequence[str]] = OMIT, request_options: typing.Optional[RequestOptions] = None, ) -> AsyncHttpResponse[Prompt]: """ @@ -166,9 +172,12 @@ async def update( version : int Version of the prompt to update - new_labels : typing.Sequence[str] + new_labels : typing.Optional[typing.Sequence[str]] New labels for the prompt version. Labels are unique across versions. The "latest" label is reserved and managed by Langfuse. + remove_labels : typing.Optional[typing.Sequence[str]] + Labels to remove from the prompt version. The "latest" label is reserved and managed by Langfuse. + request_options : typing.Optional[RequestOptions] Request-specific configuration. @@ -181,6 +190,7 @@ async def update( method="PATCH", json={ "newLabels": new_labels, + "removeLabels": remove_labels, }, request_options=request_options, omit=OMIT, diff --git a/langfuse/api/unstable/__init__.py b/langfuse/api/unstable/__init__.py index 39fafb6f8..4cc87ca60 100644 --- a/langfuse/api/unstable/__init__.py +++ b/langfuse/api/unstable/__init__.py @@ -61,13 +61,24 @@ ) from .topics import ( Topic, + TopicAssignment, + TopicAssignmentList, + TopicAssignmentStatus, + TopicAssignmentTopic, TopicFacet, TopicFacetCounts, TopicFacetList, TopicFacetPublishedRun, TopicFacetRef, TopicList, + TopicResultListMeta, + TopicResultsOrderBy, + TopicRule, + TopicRuleList, TopicStatusCounts, + TopicSummary, + TopicSummaryList, + TopicSummaryState, TopicWindow, TopicWithStatus, TopicsOrderBy, @@ -115,13 +126,24 @@ "PublicApiValidationIssue": ".errors", "TooManyRequestsError": ".errors", "Topic": ".topics", + "TopicAssignment": ".topics", + "TopicAssignmentList": ".topics", + "TopicAssignmentStatus": ".topics", + "TopicAssignmentTopic": ".topics", "TopicFacet": ".topics", "TopicFacetCounts": ".topics", "TopicFacetList": ".topics", "TopicFacetPublishedRun": ".topics", "TopicFacetRef": ".topics", "TopicList": ".topics", + "TopicResultListMeta": ".topics", + "TopicResultsOrderBy": ".topics", + "TopicRule": ".topics", + "TopicRuleList": ".topics", "TopicStatusCounts": ".topics", + "TopicSummary": ".topics", + "TopicSummaryList": ".topics", + "TopicSummaryState": ".topics", "TopicWindow": ".topics", "TopicWithStatus": ".topics", "TopicsOrderBy": ".topics", @@ -207,13 +229,24 @@ def __dir__(): "PublicApiValidationIssue", "TooManyRequestsError", "Topic", + "TopicAssignment", + "TopicAssignmentList", + "TopicAssignmentStatus", + "TopicAssignmentTopic", "TopicFacet", "TopicFacetCounts", "TopicFacetList", "TopicFacetPublishedRun", "TopicFacetRef", "TopicList", + "TopicResultListMeta", + "TopicResultsOrderBy", + "TopicRule", + "TopicRuleList", "TopicStatusCounts", + "TopicSummary", + "TopicSummaryList", + "TopicSummaryState", "TopicWindow", "TopicWithStatus", "TopicsOrderBy", diff --git a/langfuse/api/unstable/dashboard_widgets/types/dashboard_widget_filter.py b/langfuse/api/unstable/dashboard_widgets/types/dashboard_widget_filter.py index df623c2fa..f52016865 100644 --- a/langfuse/api/unstable/dashboard_widgets/types/dashboard_widget_filter.py +++ b/langfuse/api/unstable/dashboard_widgets/types/dashboard_widget_filter.py @@ -17,7 +17,7 @@ class DashboardWidgetFilter(UniversalBaseModel): | `number` | number | `=`, `>`, `<`, `>=`, `<=` | | `datetime` | ISO datetime string | `>`, `<`, `>=`, `<=` | | `boolean` | boolean | `=`, `<>` | - | `null` | `""` | `is null`, `is not null` | + | `null` | omit, or `""` | `is null`, `is not null` | | `stringOptions` | list of strings | `any of`, `none of` | | `arrayOptions` | list of strings | `any of`, `none of`, `all of` | | `categoryOptions` | list of strings (requires `key`) | `any of`, `none of` | diff --git a/langfuse/api/unstable/topics/__init__.py b/langfuse/api/unstable/topics/__init__.py index baa5ee5e9..178162448 100644 --- a/langfuse/api/unstable/topics/__init__.py +++ b/langfuse/api/unstable/topics/__init__.py @@ -8,26 +8,48 @@ if typing.TYPE_CHECKING: from .types import ( Topic, + TopicAssignment, + TopicAssignmentList, + TopicAssignmentStatus, + TopicAssignmentTopic, TopicFacet, TopicFacetCounts, TopicFacetList, TopicFacetPublishedRun, TopicFacetRef, TopicList, + TopicResultListMeta, + TopicResultsOrderBy, + TopicRule, + TopicRuleList, TopicStatusCounts, + TopicSummary, + TopicSummaryList, + TopicSummaryState, TopicWindow, TopicWithStatus, TopicsOrderBy, ) _dynamic_imports: typing.Dict[str, str] = { "Topic": ".types", + "TopicAssignment": ".types", + "TopicAssignmentList": ".types", + "TopicAssignmentStatus": ".types", + "TopicAssignmentTopic": ".types", "TopicFacet": ".types", "TopicFacetCounts": ".types", "TopicFacetList": ".types", "TopicFacetPublishedRun": ".types", "TopicFacetRef": ".types", "TopicList": ".types", + "TopicResultListMeta": ".types", + "TopicResultsOrderBy": ".types", + "TopicRule": ".types", + "TopicRuleList": ".types", "TopicStatusCounts": ".types", + "TopicSummary": ".types", + "TopicSummaryList": ".types", + "TopicSummaryState": ".types", "TopicWindow": ".types", "TopicWithStatus": ".types", "TopicsOrderBy": ".types", @@ -63,13 +85,24 @@ def __dir__(): __all__ = [ "Topic", + "TopicAssignment", + "TopicAssignmentList", + "TopicAssignmentStatus", + "TopicAssignmentTopic", "TopicFacet", "TopicFacetCounts", "TopicFacetList", "TopicFacetPublishedRun", "TopicFacetRef", "TopicList", + "TopicResultListMeta", + "TopicResultsOrderBy", + "TopicRule", + "TopicRuleList", "TopicStatusCounts", + "TopicSummary", + "TopicSummaryList", + "TopicSummaryState", "TopicWindow", "TopicWithStatus", "TopicsOrderBy", diff --git a/langfuse/api/unstable/topics/client.py b/langfuse/api/unstable/topics/client.py index 9ca28537a..9b67da8fe 100644 --- a/langfuse/api/unstable/topics/client.py +++ b/langfuse/api/unstable/topics/client.py @@ -6,9 +6,14 @@ from ...core.client_wrapper import AsyncClientWrapper, SyncClientWrapper from ...core.request_options import RequestOptions from .raw_client import AsyncRawTopicsClient, RawTopicsClient +from .types.topic_assignment_list import TopicAssignmentList from .types.topic_facet import TopicFacet from .types.topic_facet_list import TopicFacetList from .types.topic_list import TopicList +from .types.topic_results_order_by import TopicResultsOrderBy +from .types.topic_rule import TopicRule +from .types.topic_rule_list import TopicRuleList +from .types.topic_summary_list import TopicSummaryList from .types.topic_with_status import TopicWithStatus from .types.topics_order_by import TopicsOrderBy @@ -39,9 +44,9 @@ def list_facets( """ Unstable: Topics is in closed beta. The shape may change without notice. - List the project's Topics facets. Only active facets (attached to the - project's Topics rule, or all facets if the project has no rule) are - returned unless `filter` contains an `active` condition. + List the project's Topics facets. Only active facets (run by a Topics + rule, or all facets if the project has no rule) are returned unless + `filter` contains an `active` condition. `filter` is a JSON array of filter conditions combined with AND. String matching is case-insensitive. Columns: `id` (stringOptions), @@ -139,7 +144,7 @@ def list( """ Unstable: Topics is in closed beta. The shape may change without notice. - List the topics of each active facet's current published map, with + List the topics of each active facet's live map, with the number of traces currently assigned to each topic in the window. Retired topics are not listed. `facets` holds per-facet trace counts by topic status. @@ -149,9 +154,9 @@ def list( `filter` is a JSON array of filter conditions combined with AND. String matching is case-insensitive. Columns: `facetId` - (stringOptions), `facetName` (string, stringOptions), `id` - (stringOptions), `name` (string, stringOptions). Facet conditions also - restrict `facets`. + (stringOptions), `facetName` (string, stringOptions), `ruleId` + (stringOptions), `id` (stringOptions), `name` (string, stringOptions). + Facet and rule conditions also restrict `facets`. Parameters ---------- @@ -216,9 +221,8 @@ def get( """ Unstable: Topics is in closed beta. The shape may change without notice. - Get one topic by its stable id. Topics on the current map are always - found. A retired topic is found while traces in the window are still - assigned to it, with `inCurrentMap: false`. + Get one topic of a facet's current map by its stable id. Retired + topics are not found. Parameters ---------- @@ -261,6 +265,302 @@ def get( ) return _response.data + def list_rules( + self, + *, + filter: typing.Optional[str] = None, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> TopicRuleList: + """ + Unstable: Topics is in closed beta. The shape may change without notice. + + List the project's Topics rules. A rule selects traces with an + observation filter and a sampling rate, runs the facets in `facetIds` + and keeps its own live topic map. + + `filter` is a JSON array of filter conditions combined with AND. + String matching is case-insensitive. Columns: `id` (stringOptions), + `name` (string, stringOptions). + + Parameters + ---------- + filter : typing.Optional[str] + JSON array of filter conditions, for example `[{"type":"string","column":"name","operator":"contains","value":"support"}]`. + + page : typing.Optional[int] + 1-based page number. Defaults to `1`. + + limit : typing.Optional[int] + Maximum number of items per page. Defaults to `50`, at most `100`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TopicRuleList + + Examples + -------- + from langfuse import LangfuseAPI + + 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.topics.list_rules() + """ + _response = self._raw_client.list_rules( + filter=filter, page=page, limit=limit, request_options=request_options + ) + return _response.data + + def get_rule( + self, rule_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> TopicRule: + """ + Unstable: Topics is in closed beta. The shape may change without notice. + + Get one Topics rule by id. + + Parameters + ---------- + rule_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TopicRule + + Examples + -------- + from langfuse import LangfuseAPI + + 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.topics.get_rule( + rule_id="ruleId", + ) + """ + _response = self._raw_client.get_rule(rule_id, request_options=request_options) + return _response.data + + def list_summaries( + self, + *, + from_timestamp: typing.Optional[dt.datetime] = None, + to_timestamp: typing.Optional[dt.datetime] = None, + filter: typing.Optional[str] = None, + truncate_summaries: typing.Optional[bool] = None, + order_by: typing.Optional[TopicResultsOrderBy] = None, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> TopicSummaryList: + """ + Unstable: Topics is in closed beta. The shape may change without notice. + + List the current Topics summary per trace and facet: what the trace + is about from the facet's perspective. Summaries are shared by all + rules. A trace that was re-summarized lists only its latest summary. + + The window applies to the trace start time. It defaults to the last + 7 days and can span at most 30 days, starting no earlier than the + plan's data access window. Without `fromTimestamp` and `toTimestamp`, + a `traceId` `any of` filter reads the listed traces at their own start + time within the last 93 days. Rows are ordered by trace start time. + + Each request scans a bounded number of candidate rows. A page shorter + than `limit` (even an empty one) with a `meta.cursor` does not + indicate completion; continue with the cursor until it is null. + + Only active facets (attached to a Topics rule, or all facets if the + project has no rule) are read. Traces Topics never processed are not + listed. + + `filter` is a JSON array of filter conditions combined with AND, with + at most 100 `traceId` values. Columns: `traceId`, `facetId`, `facetName`, `ruleId`, `environment` + (stringOptions), `timestamp`, `processedAt` (datetime), `state`, + `topicId`, `topicName` (stringOptions), `assignmentStatus` + (stringOptions, `any of` only). An unknown `topicName` matches nothing under `any of` + and is ignored under `none of`. + + `state` values: `complete` (summarized), `not_applicable` (the facet + does not apply to the trace), `insufficient_input` (too little input + to summarize). `assignmentStatus` values: `assigned`, `outlier` (no + topic fits), `awaiting_map` (summarized, not yet assigned). + + Common questions: + - Summaries of specific traces: `traceId` `any of` the trace ids. + - Summaries of a topic's traces: `topicId` or `topicName`. + - Traces without a topic yet: `assignmentStatus` `any of` `["awaiting_map", "outlier"]`. + + Parameters + ---------- + from_timestamp : typing.Optional[dt.datetime] + Start of the window (trace start time, inclusive). Defaults to 7 days before `toTimestamp`. + + to_timestamp : typing.Optional[dt.datetime] + End of the window (trace start time, exclusive). Defaults to now. + + filter : typing.Optional[str] + JSON array of filter conditions, for example `[{"type":"stringOptions","column":"traceId","operator":"any of","value":[""]}]`. + + truncate_summaries : typing.Optional[bool] + Whether to truncate summaries to 500 characters. Defaults to `false`. + + order_by : typing.Optional[TopicResultsOrderBy] + Defaults to `timestamp.desc`. + + limit : typing.Optional[int] + Maximum number of rows per page. Defaults to `50`, at most `100`. + + cursor : typing.Optional[str] + The `meta.cursor` of the previous page. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TopicSummaryList + + Examples + -------- + from langfuse import LangfuseAPI + + 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.topics.list_summaries() + """ + _response = self._raw_client.list_summaries( + from_timestamp=from_timestamp, + to_timestamp=to_timestamp, + filter=filter, + truncate_summaries=truncate_summaries, + order_by=order_by, + limit=limit, + cursor=cursor, + request_options=request_options, + ) + return _response.data + + def list_assignments( + self, + *, + from_timestamp: typing.Optional[dt.datetime] = None, + to_timestamp: typing.Optional[dt.datetime] = None, + filter: typing.Optional[str] = None, + order_by: typing.Optional[TopicResultsOrderBy] = None, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> TopicAssignmentList: + """ + Unstable: Topics is in closed beta. The shape may change without notice. + + List the current topic per trace, facet and rule on each rule's live + map: `assigned` with its topic, or `outlier` when no topic fits. + Summarized traces that are not assigned yet are not listed; find + them through `GET /topic-summaries` with `assignmentStatus`. + + The window applies to the trace start time. It defaults to the last + 7 days and can span at most 30 days, starting no earlier than the + plan's data access window. Without `fromTimestamp` and `toTimestamp`, + a `traceId` `any of` filter reads the listed traces at their own start + time within the last 93 days. Rows are ordered by trace start time. + + Each request scans a bounded number of candidate rows. A page shorter + than `limit` (even an empty one) with a `meta.cursor` does not + indicate completion; continue with the cursor until it is null. + + Only active facets (attached to a Topics rule, or all facets if the + project has no rule) are read. Traces Topics never processed are not + listed. + + `filter` is a JSON array of filter conditions combined with AND, with + at most 100 `traceId` values. Columns: `traceId`, `facetId`, `facetName`, `ruleId`, `environment`, + `topicId`, `topicName` (stringOptions), `status` (stringOptions, + `any of` only: `assigned`, `outlier`), `timestamp` (datetime). An + unknown `topicName` matches nothing under `any of` and is ignored + under `none of`. + + Common questions: + - Topics of specific traces: `traceId` `any of` the trace ids. + - Traces in a topic: `topicId` or `topicName`. + + Parameters + ---------- + from_timestamp : typing.Optional[dt.datetime] + Start of the window (trace start time, inclusive). Defaults to 7 days before `toTimestamp`. + + to_timestamp : typing.Optional[dt.datetime] + End of the window (trace start time, exclusive). Defaults to now. + + filter : typing.Optional[str] + JSON array of filter conditions, for example `[{"type":"stringOptions","column":"topicName","operator":"any of","value":["Refunds"]}]`. + + order_by : typing.Optional[TopicResultsOrderBy] + Defaults to `timestamp.desc`. + + limit : typing.Optional[int] + Maximum number of rows per page. Defaults to `50`, at most `100`. + + cursor : typing.Optional[str] + The `meta.cursor` of the previous page. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TopicAssignmentList + + Examples + -------- + from langfuse import LangfuseAPI + + 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.topics.list_assignments() + """ + _response = self._raw_client.list_assignments( + from_timestamp=from_timestamp, + to_timestamp=to_timestamp, + filter=filter, + order_by=order_by, + limit=limit, + cursor=cursor, + request_options=request_options, + ) + return _response.data + class AsyncTopicsClient: def __init__(self, *, client_wrapper: AsyncClientWrapper): @@ -288,9 +588,9 @@ async def list_facets( """ Unstable: Topics is in closed beta. The shape may change without notice. - List the project's Topics facets. Only active facets (attached to the - project's Topics rule, or all facets if the project has no rule) are - returned unless `filter` contains an `active` condition. + List the project's Topics facets. Only active facets (run by a Topics + rule, or all facets if the project has no rule) are returned unless + `filter` contains an `active` condition. `filter` is a JSON array of filter conditions combined with AND. String matching is case-insensitive. Columns: `id` (stringOptions), @@ -404,7 +704,7 @@ async def list( """ Unstable: Topics is in closed beta. The shape may change without notice. - List the topics of each active facet's current published map, with + List the topics of each active facet's live map, with the number of traces currently assigned to each topic in the window. Retired topics are not listed. `facets` holds per-facet trace counts by topic status. @@ -414,9 +714,9 @@ async def list( `filter` is a JSON array of filter conditions combined with AND. String matching is case-insensitive. Columns: `facetId` - (stringOptions), `facetName` (string, stringOptions), `id` - (stringOptions), `name` (string, stringOptions). Facet conditions also - restrict `facets`. + (stringOptions), `facetName` (string, stringOptions), `ruleId` + (stringOptions), `id` (stringOptions), `name` (string, stringOptions). + Facet and rule conditions also restrict `facets`. Parameters ---------- @@ -489,9 +789,8 @@ async def get( """ Unstable: Topics is in closed beta. The shape may change without notice. - Get one topic by its stable id. Topics on the current map are always - found. A retired topic is found while traces in the window are still - assigned to it, with `inCurrentMap: false`. + Get one topic of a facet's current map by its stable id. Retired + topics are not found. Parameters ---------- @@ -541,3 +840,333 @@ async def main() -> None: request_options=request_options, ) return _response.data + + async def list_rules( + self, + *, + filter: typing.Optional[str] = None, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> TopicRuleList: + """ + Unstable: Topics is in closed beta. The shape may change without notice. + + List the project's Topics rules. A rule selects traces with an + observation filter and a sampling rate, runs the facets in `facetIds` + and keeps its own live topic map. + + `filter` is a JSON array of filter conditions combined with AND. + String matching is case-insensitive. Columns: `id` (stringOptions), + `name` (string, stringOptions). + + Parameters + ---------- + filter : typing.Optional[str] + JSON array of filter conditions, for example `[{"type":"string","column":"name","operator":"contains","value":"support"}]`. + + page : typing.Optional[int] + 1-based page number. Defaults to `1`. + + limit : typing.Optional[int] + Maximum number of items per page. Defaults to `50`, at most `100`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TopicRuleList + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + 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.topics.list_rules() + + + asyncio.run(main()) + """ + _response = await self._raw_client.list_rules( + filter=filter, page=page, limit=limit, request_options=request_options + ) + return _response.data + + async def get_rule( + self, rule_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> TopicRule: + """ + Unstable: Topics is in closed beta. The shape may change without notice. + + Get one Topics rule by id. + + Parameters + ---------- + rule_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TopicRule + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + 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.topics.get_rule( + rule_id="ruleId", + ) + + + asyncio.run(main()) + """ + _response = await self._raw_client.get_rule( + rule_id, request_options=request_options + ) + return _response.data + + async def list_summaries( + self, + *, + from_timestamp: typing.Optional[dt.datetime] = None, + to_timestamp: typing.Optional[dt.datetime] = None, + filter: typing.Optional[str] = None, + truncate_summaries: typing.Optional[bool] = None, + order_by: typing.Optional[TopicResultsOrderBy] = None, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> TopicSummaryList: + """ + Unstable: Topics is in closed beta. The shape may change without notice. + + List the current Topics summary per trace and facet: what the trace + is about from the facet's perspective. Summaries are shared by all + rules. A trace that was re-summarized lists only its latest summary. + + The window applies to the trace start time. It defaults to the last + 7 days and can span at most 30 days, starting no earlier than the + plan's data access window. Without `fromTimestamp` and `toTimestamp`, + a `traceId` `any of` filter reads the listed traces at their own start + time within the last 93 days. Rows are ordered by trace start time. + + Each request scans a bounded number of candidate rows. A page shorter + than `limit` (even an empty one) with a `meta.cursor` does not + indicate completion; continue with the cursor until it is null. + + Only active facets (attached to a Topics rule, or all facets if the + project has no rule) are read. Traces Topics never processed are not + listed. + + `filter` is a JSON array of filter conditions combined with AND, with + at most 100 `traceId` values. Columns: `traceId`, `facetId`, `facetName`, `ruleId`, `environment` + (stringOptions), `timestamp`, `processedAt` (datetime), `state`, + `topicId`, `topicName` (stringOptions), `assignmentStatus` + (stringOptions, `any of` only). An unknown `topicName` matches nothing under `any of` + and is ignored under `none of`. + + `state` values: `complete` (summarized), `not_applicable` (the facet + does not apply to the trace), `insufficient_input` (too little input + to summarize). `assignmentStatus` values: `assigned`, `outlier` (no + topic fits), `awaiting_map` (summarized, not yet assigned). + + Common questions: + - Summaries of specific traces: `traceId` `any of` the trace ids. + - Summaries of a topic's traces: `topicId` or `topicName`. + - Traces without a topic yet: `assignmentStatus` `any of` `["awaiting_map", "outlier"]`. + + Parameters + ---------- + from_timestamp : typing.Optional[dt.datetime] + Start of the window (trace start time, inclusive). Defaults to 7 days before `toTimestamp`. + + to_timestamp : typing.Optional[dt.datetime] + End of the window (trace start time, exclusive). Defaults to now. + + filter : typing.Optional[str] + JSON array of filter conditions, for example `[{"type":"stringOptions","column":"traceId","operator":"any of","value":[""]}]`. + + truncate_summaries : typing.Optional[bool] + Whether to truncate summaries to 500 characters. Defaults to `false`. + + order_by : typing.Optional[TopicResultsOrderBy] + Defaults to `timestamp.desc`. + + limit : typing.Optional[int] + Maximum number of rows per page. Defaults to `50`, at most `100`. + + cursor : typing.Optional[str] + The `meta.cursor` of the previous page. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TopicSummaryList + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + 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.topics.list_summaries() + + + asyncio.run(main()) + """ + _response = await self._raw_client.list_summaries( + from_timestamp=from_timestamp, + to_timestamp=to_timestamp, + filter=filter, + truncate_summaries=truncate_summaries, + order_by=order_by, + limit=limit, + cursor=cursor, + request_options=request_options, + ) + return _response.data + + async def list_assignments( + self, + *, + from_timestamp: typing.Optional[dt.datetime] = None, + to_timestamp: typing.Optional[dt.datetime] = None, + filter: typing.Optional[str] = None, + order_by: typing.Optional[TopicResultsOrderBy] = None, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> TopicAssignmentList: + """ + Unstable: Topics is in closed beta. The shape may change without notice. + + List the current topic per trace, facet and rule on each rule's live + map: `assigned` with its topic, or `outlier` when no topic fits. + Summarized traces that are not assigned yet are not listed; find + them through `GET /topic-summaries` with `assignmentStatus`. + + The window applies to the trace start time. It defaults to the last + 7 days and can span at most 30 days, starting no earlier than the + plan's data access window. Without `fromTimestamp` and `toTimestamp`, + a `traceId` `any of` filter reads the listed traces at their own start + time within the last 93 days. Rows are ordered by trace start time. + + Each request scans a bounded number of candidate rows. A page shorter + than `limit` (even an empty one) with a `meta.cursor` does not + indicate completion; continue with the cursor until it is null. + + Only active facets (attached to a Topics rule, or all facets if the + project has no rule) are read. Traces Topics never processed are not + listed. + + `filter` is a JSON array of filter conditions combined with AND, with + at most 100 `traceId` values. Columns: `traceId`, `facetId`, `facetName`, `ruleId`, `environment`, + `topicId`, `topicName` (stringOptions), `status` (stringOptions, + `any of` only: `assigned`, `outlier`), `timestamp` (datetime). An + unknown `topicName` matches nothing under `any of` and is ignored + under `none of`. + + Common questions: + - Topics of specific traces: `traceId` `any of` the trace ids. + - Traces in a topic: `topicId` or `topicName`. + + Parameters + ---------- + from_timestamp : typing.Optional[dt.datetime] + Start of the window (trace start time, inclusive). Defaults to 7 days before `toTimestamp`. + + to_timestamp : typing.Optional[dt.datetime] + End of the window (trace start time, exclusive). Defaults to now. + + filter : typing.Optional[str] + JSON array of filter conditions, for example `[{"type":"stringOptions","column":"topicName","operator":"any of","value":["Refunds"]}]`. + + order_by : typing.Optional[TopicResultsOrderBy] + Defaults to `timestamp.desc`. + + limit : typing.Optional[int] + Maximum number of rows per page. Defaults to `50`, at most `100`. + + cursor : typing.Optional[str] + The `meta.cursor` of the previous page. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + TopicAssignmentList + + Examples + -------- + import asyncio + + from langfuse import AsyncLangfuseAPI + + 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.topics.list_assignments() + + + asyncio.run(main()) + """ + _response = await self._raw_client.list_assignments( + from_timestamp=from_timestamp, + to_timestamp=to_timestamp, + filter=filter, + order_by=order_by, + limit=limit, + cursor=cursor, + request_options=request_options, + ) + return _response.data diff --git a/langfuse/api/unstable/topics/raw_client.py b/langfuse/api/unstable/topics/raw_client.py index 2a28528a4..d51377b26 100644 --- a/langfuse/api/unstable/topics/raw_client.py +++ b/langfuse/api/unstable/topics/raw_client.py @@ -40,9 +40,14 @@ UnauthorizedError as unstable_errors_errors_unauthorized_error_UnauthorizedError, ) from ..errors.types.public_api_error import PublicApiError +from .types.topic_assignment_list import TopicAssignmentList from .types.topic_facet import TopicFacet from .types.topic_facet_list import TopicFacetList from .types.topic_list import TopicList +from .types.topic_results_order_by import TopicResultsOrderBy +from .types.topic_rule import TopicRule +from .types.topic_rule_list import TopicRuleList +from .types.topic_summary_list import TopicSummaryList from .types.topic_with_status import TopicWithStatus from .types.topics_order_by import TopicsOrderBy @@ -62,9 +67,9 @@ def list_facets( """ Unstable: Topics is in closed beta. The shape may change without notice. - List the project's Topics facets. Only active facets (attached to the - project's Topics rule, or all facets if the project has no rule) are - returned unless `filter` contains an `active` condition. + List the project's Topics facets. Only active facets (run by a Topics + rule, or all facets if the project has no rule) are returned unless + `filter` contains an `active` condition. `filter` is a JSON array of filter conditions combined with AND. String matching is case-insensitive. Columns: `id` (stringOptions), @@ -447,7 +452,7 @@ def list( """ Unstable: Topics is in closed beta. The shape may change without notice. - List the topics of each active facet's current published map, with + List the topics of each active facet's live map, with the number of traces currently assigned to each topic in the window. Retired topics are not listed. `facets` holds per-facet trace counts by topic status. @@ -457,9 +462,9 @@ def list( `filter` is a JSON array of filter conditions combined with AND. String matching is case-insensitive. Columns: `facetId` - (stringOptions), `facetName` (string, stringOptions), `id` - (stringOptions), `name` (string, stringOptions). Facet conditions also - restrict `facets`. + (stringOptions), `facetName` (string, stringOptions), `ruleId` + (stringOptions), `id` (stringOptions), `name` (string, stringOptions). + Facet and rule conditions also restrict `facets`. Parameters ---------- @@ -671,9 +676,8 @@ def get( """ Unstable: Topics is in closed beta. The shape may change without notice. - Get one topic by its stable id. Topics on the current map are always - found. A retired topic is found while traces in the window are still - assigned to it, with `inCurrentMap: false`. + Get one topic of a facet's current map by its stable id. Retired + topics are not found. Parameters ---------- @@ -860,35 +864,1719 @@ def get( body=_response_json, ) + def list_rules( + self, + *, + filter: typing.Optional[str] = None, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[TopicRuleList]: + """ + Unstable: Topics is in closed beta. The shape may change without notice. + + List the project's Topics rules. A rule selects traces with an + observation filter and a sampling rate, runs the facets in `facetIds` + and keeps its own live topic map. + + `filter` is a JSON array of filter conditions combined with AND. + String matching is case-insensitive. Columns: `id` (stringOptions), + `name` (string, stringOptions). + + Parameters + ---------- + filter : typing.Optional[str] + JSON array of filter conditions, for example `[{"type":"string","column":"name","operator":"contains","value":"support"}]`. + + page : typing.Optional[int] + 1-based page number. Defaults to `1`. + + limit : typing.Optional[int] + Maximum number of items per page. Defaults to `50`, at most `100`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[TopicRuleList] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/unstable/topic-rules", + method="GET", + params={ + "filter": filter, + "page": page, + "limit": limit, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + TopicRuleList, + parse_obj_as( + type_=TopicRuleList, # 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 == 404: + raise unstable_errors_errors_not_found_error_NotFoundError( + 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 commons_errors_not_found_error_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, + ) + + def get_rule( + self, rule_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> HttpResponse[TopicRule]: + """ + Unstable: Topics is in closed beta. The shape may change without notice. + + Get one Topics rule by id. + + Parameters + ---------- + rule_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[TopicRule] + """ + _response = self._client_wrapper.httpx_client.request( + f"api/public/unstable/topic-rules/{jsonable_encoder(rule_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + TopicRule, + parse_obj_as( + type_=TopicRule, # 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 == 404: + raise unstable_errors_errors_not_found_error_NotFoundError( + 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 commons_errors_not_found_error_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, + ) + + def list_summaries( + self, + *, + from_timestamp: typing.Optional[dt.datetime] = None, + to_timestamp: typing.Optional[dt.datetime] = None, + filter: typing.Optional[str] = None, + truncate_summaries: typing.Optional[bool] = None, + order_by: typing.Optional[TopicResultsOrderBy] = None, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[TopicSummaryList]: + """ + Unstable: Topics is in closed beta. The shape may change without notice. + + List the current Topics summary per trace and facet: what the trace + is about from the facet's perspective. Summaries are shared by all + rules. A trace that was re-summarized lists only its latest summary. + + The window applies to the trace start time. It defaults to the last + 7 days and can span at most 30 days, starting no earlier than the + plan's data access window. Without `fromTimestamp` and `toTimestamp`, + a `traceId` `any of` filter reads the listed traces at their own start + time within the last 93 days. Rows are ordered by trace start time. + + Each request scans a bounded number of candidate rows. A page shorter + than `limit` (even an empty one) with a `meta.cursor` does not + indicate completion; continue with the cursor until it is null. + + Only active facets (attached to a Topics rule, or all facets if the + project has no rule) are read. Traces Topics never processed are not + listed. + + `filter` is a JSON array of filter conditions combined with AND, with + at most 100 `traceId` values. Columns: `traceId`, `facetId`, `facetName`, `ruleId`, `environment` + (stringOptions), `timestamp`, `processedAt` (datetime), `state`, + `topicId`, `topicName` (stringOptions), `assignmentStatus` + (stringOptions, `any of` only). An unknown `topicName` matches nothing under `any of` + and is ignored under `none of`. + + `state` values: `complete` (summarized), `not_applicable` (the facet + does not apply to the trace), `insufficient_input` (too little input + to summarize). `assignmentStatus` values: `assigned`, `outlier` (no + topic fits), `awaiting_map` (summarized, not yet assigned). + + Common questions: + - Summaries of specific traces: `traceId` `any of` the trace ids. + - Summaries of a topic's traces: `topicId` or `topicName`. + - Traces without a topic yet: `assignmentStatus` `any of` `["awaiting_map", "outlier"]`. + + Parameters + ---------- + from_timestamp : typing.Optional[dt.datetime] + Start of the window (trace start time, inclusive). Defaults to 7 days before `toTimestamp`. + + to_timestamp : typing.Optional[dt.datetime] + End of the window (trace start time, exclusive). Defaults to now. + + filter : typing.Optional[str] + JSON array of filter conditions, for example `[{"type":"stringOptions","column":"traceId","operator":"any of","value":[""]}]`. + + truncate_summaries : typing.Optional[bool] + Whether to truncate summaries to 500 characters. Defaults to `false`. + + order_by : typing.Optional[TopicResultsOrderBy] + Defaults to `timestamp.desc`. + + limit : typing.Optional[int] + Maximum number of rows per page. Defaults to `50`, at most `100`. + + cursor : typing.Optional[str] + The `meta.cursor` of the previous page. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[TopicSummaryList] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/unstable/topic-summaries", + method="GET", + params={ + "fromTimestamp": serialize_datetime(from_timestamp) + if from_timestamp is not None + else None, + "toTimestamp": serialize_datetime(to_timestamp) + if to_timestamp is not None + else None, + "filter": filter, + "truncateSummaries": truncate_summaries, + "orderBy": order_by, + "limit": limit, + "cursor": cursor, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + TopicSummaryList, + parse_obj_as( + type_=TopicSummaryList, # 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 == 404: + raise unstable_errors_errors_not_found_error_NotFoundError( + 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 commons_errors_not_found_error_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, + ) + + def list_assignments( + self, + *, + from_timestamp: typing.Optional[dt.datetime] = None, + to_timestamp: typing.Optional[dt.datetime] = None, + filter: typing.Optional[str] = None, + order_by: typing.Optional[TopicResultsOrderBy] = None, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> HttpResponse[TopicAssignmentList]: + """ + Unstable: Topics is in closed beta. The shape may change without notice. + + List the current topic per trace, facet and rule on each rule's live + map: `assigned` with its topic, or `outlier` when no topic fits. + Summarized traces that are not assigned yet are not listed; find + them through `GET /topic-summaries` with `assignmentStatus`. + + The window applies to the trace start time. It defaults to the last + 7 days and can span at most 30 days, starting no earlier than the + plan's data access window. Without `fromTimestamp` and `toTimestamp`, + a `traceId` `any of` filter reads the listed traces at their own start + time within the last 93 days. Rows are ordered by trace start time. + + Each request scans a bounded number of candidate rows. A page shorter + than `limit` (even an empty one) with a `meta.cursor` does not + indicate completion; continue with the cursor until it is null. + + Only active facets (attached to a Topics rule, or all facets if the + project has no rule) are read. Traces Topics never processed are not + listed. + + `filter` is a JSON array of filter conditions combined with AND, with + at most 100 `traceId` values. Columns: `traceId`, `facetId`, `facetName`, `ruleId`, `environment`, + `topicId`, `topicName` (stringOptions), `status` (stringOptions, + `any of` only: `assigned`, `outlier`), `timestamp` (datetime). An + unknown `topicName` matches nothing under `any of` and is ignored + under `none of`. + + Common questions: + - Topics of specific traces: `traceId` `any of` the trace ids. + - Traces in a topic: `topicId` or `topicName`. + + Parameters + ---------- + from_timestamp : typing.Optional[dt.datetime] + Start of the window (trace start time, inclusive). Defaults to 7 days before `toTimestamp`. + + to_timestamp : typing.Optional[dt.datetime] + End of the window (trace start time, exclusive). Defaults to now. + + filter : typing.Optional[str] + JSON array of filter conditions, for example `[{"type":"stringOptions","column":"topicName","operator":"any of","value":["Refunds"]}]`. + + order_by : typing.Optional[TopicResultsOrderBy] + Defaults to `timestamp.desc`. + + limit : typing.Optional[int] + Maximum number of rows per page. Defaults to `50`, at most `100`. + + cursor : typing.Optional[str] + The `meta.cursor` of the previous page. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + HttpResponse[TopicAssignmentList] + """ + _response = self._client_wrapper.httpx_client.request( + "api/public/unstable/topic-assignments", + method="GET", + params={ + "fromTimestamp": serialize_datetime(from_timestamp) + if from_timestamp is not None + else None, + "toTimestamp": serialize_datetime(to_timestamp) + if to_timestamp is not None + else None, + "filter": filter, + "orderBy": order_by, + "limit": limit, + "cursor": cursor, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + TopicAssignmentList, + parse_obj_as( + type_=TopicAssignmentList, # 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 == 404: + raise unstable_errors_errors_not_found_error_NotFoundError( + 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 commons_errors_not_found_error_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 AsyncRawTopicsClient: + def __init__(self, *, client_wrapper: AsyncClientWrapper): + self._client_wrapper = client_wrapper + + async def list_facets( + self, + *, + filter: typing.Optional[str] = None, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[TopicFacetList]: + """ + Unstable: Topics is in closed beta. The shape may change without notice. + + List the project's Topics facets. Only active facets (run by a Topics + rule, or all facets if the project has no rule) are returned unless + `filter` contains an `active` condition. + + `filter` is a JSON array of filter conditions combined with AND. + String matching is case-insensitive. Columns: `id` (stringOptions), + `name` (string, stringOptions), `description` (string), `isBuiltIn` + (boolean), `active` (boolean). + + Parameters + ---------- + filter : typing.Optional[str] + JSON array of filter conditions, for example `[{"type":"string","column":"name","operator":"contains","value":"issue"}]`. + + page : typing.Optional[int] + 1-based page number. Defaults to `1`. + + limit : typing.Optional[int] + Maximum number of items per page. Defaults to `50`, at most `100`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[TopicFacetList] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/unstable/topic-facets", + method="GET", + params={ + "filter": filter, + "page": page, + "limit": limit, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + TopicFacetList, + parse_obj_as( + type_=TopicFacetList, # 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 == 404: + raise unstable_errors_errors_not_found_error_NotFoundError( + 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 commons_errors_not_found_error_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, + ) + + async def get_facet( + self, facet_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[TopicFacet]: + """ + Unstable: Topics is in closed beta. The shape may change without notice. + + Get one Topics facet by id, including inactive facets. + + Parameters + ---------- + facet_id : str + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[TopicFacet] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/unstable/topic-facets/{jsonable_encoder(facet_id)}", + method="GET", + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + TopicFacet, + parse_obj_as( + type_=TopicFacet, # 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 == 404: + raise unstable_errors_errors_not_found_error_NotFoundError( + 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 commons_errors_not_found_error_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, + ) + + async def list( + self, + *, + from_timestamp: typing.Optional[dt.datetime] = None, + to_timestamp: typing.Optional[dt.datetime] = None, + filter: typing.Optional[str] = None, + order_by: typing.Optional[TopicsOrderBy] = None, + page: typing.Optional[int] = None, + limit: typing.Optional[int] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[TopicList]: + """ + Unstable: Topics is in closed beta. The shape may change without notice. + + List the topics of each active facet's live map, with + the number of traces currently assigned to each topic in the window. + Retired topics are not listed. `facets` holds per-facet trace counts + by topic status. + + The window applies to the trace start time. It defaults to the last + 7 days and can span at most 93 days. + + `filter` is a JSON array of filter conditions combined with AND. + String matching is case-insensitive. Columns: `facetId` + (stringOptions), `facetName` (string, stringOptions), `ruleId` + (stringOptions), `id` (stringOptions), `name` (string, stringOptions). + Facet and rule conditions also restrict `facets`. + + Parameters + ---------- + from_timestamp : typing.Optional[dt.datetime] + Start of the window (trace start time, inclusive). Defaults to 7 days before `toTimestamp`. + + to_timestamp : typing.Optional[dt.datetime] + End of the window (trace start time, exclusive). Defaults to now. + + filter : typing.Optional[str] + JSON array of filter conditions, for example `[{"type":"stringOptions","column":"facetName","operator":"any of","value":["Issues"]}]`. + + order_by : typing.Optional[TopicsOrderBy] + Defaults to `traceCount.desc`. + + page : typing.Optional[int] + 1-based page number. Defaults to `1`. + + limit : typing.Optional[int] + Maximum number of items per page. Defaults to `50`, at most `100`. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[TopicList] + """ + _response = await self._client_wrapper.httpx_client.request( + "api/public/unstable/topics", + method="GET", + params={ + "fromTimestamp": serialize_datetime(from_timestamp) + if from_timestamp is not None + else None, + "toTimestamp": serialize_datetime(to_timestamp) + if to_timestamp is not None + else None, + "filter": filter, + "orderBy": order_by, + "page": page, + "limit": limit, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + TopicList, + parse_obj_as( + type_=TopicList, # 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 == 404: + raise unstable_errors_errors_not_found_error_NotFoundError( + 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 commons_errors_not_found_error_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, + ) + + async def get( + self, + topic_id: str, + *, + from_timestamp: typing.Optional[dt.datetime] = None, + to_timestamp: typing.Optional[dt.datetime] = None, + request_options: typing.Optional[RequestOptions] = None, + ) -> AsyncHttpResponse[TopicWithStatus]: + """ + Unstable: Topics is in closed beta. The shape may change without notice. -class AsyncRawTopicsClient: - def __init__(self, *, client_wrapper: AsyncClientWrapper): - self._client_wrapper = client_wrapper + Get one topic of a facet's current map by its stable id. Retired + topics are not found. - async def list_facets( + Parameters + ---------- + topic_id : str + + from_timestamp : typing.Optional[dt.datetime] + Start of the window used for `traceCount`. Defaults to 7 days before `toTimestamp`. + + to_timestamp : typing.Optional[dt.datetime] + End of the window used for `traceCount`. Defaults to now. + + request_options : typing.Optional[RequestOptions] + Request-specific configuration. + + Returns + ------- + AsyncHttpResponse[TopicWithStatus] + """ + _response = await self._client_wrapper.httpx_client.request( + f"api/public/unstable/topics/{jsonable_encoder(topic_id)}", + method="GET", + params={ + "fromTimestamp": serialize_datetime(from_timestamp) + if from_timestamp is not None + else None, + "toTimestamp": serialize_datetime(to_timestamp) + if to_timestamp is not None + else None, + }, + request_options=request_options, + ) + try: + if 200 <= _response.status_code < 300: + _data = typing.cast( + TopicWithStatus, + parse_obj_as( + type_=TopicWithStatus, # 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 == 404: + raise unstable_errors_errors_not_found_error_NotFoundError( + 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 commons_errors_not_found_error_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, + ) + + async def list_rules( self, *, filter: typing.Optional[str] = None, page: typing.Optional[int] = None, limit: typing.Optional[int] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> AsyncHttpResponse[TopicFacetList]: + ) -> AsyncHttpResponse[TopicRuleList]: """ Unstable: Topics is in closed beta. The shape may change without notice. - List the project's Topics facets. Only active facets (attached to the - project's Topics rule, or all facets if the project has no rule) are - returned unless `filter` contains an `active` condition. + List the project's Topics rules. A rule selects traces with an + observation filter and a sampling rate, runs the facets in `facetIds` + and keeps its own live topic map. `filter` is a JSON array of filter conditions combined with AND. String matching is case-insensitive. Columns: `id` (stringOptions), - `name` (string, stringOptions), `description` (string), `isBuiltIn` - (boolean), `active` (boolean). + `name` (string, stringOptions). Parameters ---------- filter : typing.Optional[str] - JSON array of filter conditions, for example `[{"type":"string","column":"name","operator":"contains","value":"issue"}]`. + JSON array of filter conditions, for example `[{"type":"string","column":"name","operator":"contains","value":"support"}]`. page : typing.Optional[int] 1-based page number. Defaults to `1`. @@ -901,10 +2589,10 @@ async def list_facets( Returns ------- - AsyncHttpResponse[TopicFacetList] + AsyncHttpResponse[TopicRuleList] """ _response = await self._client_wrapper.httpx_client.request( - "api/public/unstable/topic-facets", + "api/public/unstable/topic-rules", method="GET", params={ "filter": filter, @@ -916,9 +2604,9 @@ async def list_facets( try: if 200 <= _response.status_code < 300: _data = typing.cast( - TopicFacetList, + TopicRuleList, parse_obj_as( - type_=TopicFacetList, # type: ignore + type_=TopicRuleList, # type: ignore object_=_response.json(), ), ) @@ -1068,36 +2756,36 @@ async def list_facets( body=_response_json, ) - async def get_facet( - self, facet_id: str, *, request_options: typing.Optional[RequestOptions] = None - ) -> AsyncHttpResponse[TopicFacet]: + async def get_rule( + self, rule_id: str, *, request_options: typing.Optional[RequestOptions] = None + ) -> AsyncHttpResponse[TopicRule]: """ Unstable: Topics is in closed beta. The shape may change without notice. - Get one Topics facet by id, including inactive facets. + Get one Topics rule by id. Parameters ---------- - facet_id : str + rule_id : str request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[TopicFacet] + AsyncHttpResponse[TopicRule] """ _response = await self._client_wrapper.httpx_client.request( - f"api/public/unstable/topic-facets/{jsonable_encoder(facet_id)}", + f"api/public/unstable/topic-rules/{jsonable_encoder(rule_id)}", method="GET", request_options=request_options, ) try: if 200 <= _response.status_code < 300: _data = typing.cast( - TopicFacet, + TopicRule, parse_obj_as( - type_=TopicFacet, # type: ignore + type_=TopicRule, # type: ignore object_=_response.json(), ), ) @@ -1247,33 +2935,55 @@ async def get_facet( body=_response_json, ) - async def list( + async def list_summaries( self, *, from_timestamp: typing.Optional[dt.datetime] = None, to_timestamp: typing.Optional[dt.datetime] = None, filter: typing.Optional[str] = None, - order_by: typing.Optional[TopicsOrderBy] = None, - page: typing.Optional[int] = None, + truncate_summaries: typing.Optional[bool] = None, + order_by: typing.Optional[TopicResultsOrderBy] = None, limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> AsyncHttpResponse[TopicList]: + ) -> AsyncHttpResponse[TopicSummaryList]: """ Unstable: Topics is in closed beta. The shape may change without notice. - List the topics of each active facet's current published map, with - the number of traces currently assigned to each topic in the window. - Retired topics are not listed. `facets` holds per-facet trace counts - by topic status. + List the current Topics summary per trace and facet: what the trace + is about from the facet's perspective. Summaries are shared by all + rules. A trace that was re-summarized lists only its latest summary. The window applies to the trace start time. It defaults to the last - 7 days and can span at most 93 days. - - `filter` is a JSON array of filter conditions combined with AND. - String matching is case-insensitive. Columns: `facetId` - (stringOptions), `facetName` (string, stringOptions), `id` - (stringOptions), `name` (string, stringOptions). Facet conditions also - restrict `facets`. + 7 days and can span at most 30 days, starting no earlier than the + plan's data access window. Without `fromTimestamp` and `toTimestamp`, + a `traceId` `any of` filter reads the listed traces at their own start + time within the last 93 days. Rows are ordered by trace start time. + + Each request scans a bounded number of candidate rows. A page shorter + than `limit` (even an empty one) with a `meta.cursor` does not + indicate completion; continue with the cursor until it is null. + + Only active facets (attached to a Topics rule, or all facets if the + project has no rule) are read. Traces Topics never processed are not + listed. + + `filter` is a JSON array of filter conditions combined with AND, with + at most 100 `traceId` values. Columns: `traceId`, `facetId`, `facetName`, `ruleId`, `environment` + (stringOptions), `timestamp`, `processedAt` (datetime), `state`, + `topicId`, `topicName` (stringOptions), `assignmentStatus` + (stringOptions, `any of` only). An unknown `topicName` matches nothing under `any of` + and is ignored under `none of`. + + `state` values: `complete` (summarized), `not_applicable` (the facet + does not apply to the trace), `insufficient_input` (too little input + to summarize). `assignmentStatus` values: `assigned`, `outlier` (no + topic fits), `awaiting_map` (summarized, not yet assigned). + + Common questions: + - Summaries of specific traces: `traceId` `any of` the trace ids. + - Summaries of a topic's traces: `topicId` or `topicName`. + - Traces without a topic yet: `assignmentStatus` `any of` `["awaiting_map", "outlier"]`. Parameters ---------- @@ -1284,26 +2994,29 @@ async def list( End of the window (trace start time, exclusive). Defaults to now. filter : typing.Optional[str] - JSON array of filter conditions, for example `[{"type":"stringOptions","column":"facetName","operator":"any of","value":["Issues"]}]`. + JSON array of filter conditions, for example `[{"type":"stringOptions","column":"traceId","operator":"any of","value":[""]}]`. - order_by : typing.Optional[TopicsOrderBy] - Defaults to `traceCount.desc`. + truncate_summaries : typing.Optional[bool] + Whether to truncate summaries to 500 characters. Defaults to `false`. - page : typing.Optional[int] - 1-based page number. Defaults to `1`. + order_by : typing.Optional[TopicResultsOrderBy] + Defaults to `timestamp.desc`. limit : typing.Optional[int] - Maximum number of items per page. Defaults to `50`, at most `100`. + Maximum number of rows per page. Defaults to `50`, at most `100`. + + cursor : typing.Optional[str] + The `meta.cursor` of the previous page. request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[TopicList] + AsyncHttpResponse[TopicSummaryList] """ _response = await self._client_wrapper.httpx_client.request( - "api/public/unstable/topics", + "api/public/unstable/topic-summaries", method="GET", params={ "fromTimestamp": serialize_datetime(from_timestamp) @@ -1313,18 +3026,19 @@ async def list( if to_timestamp is not None else None, "filter": filter, + "truncateSummaries": truncate_summaries, "orderBy": order_by, - "page": page, "limit": limit, + "cursor": cursor, }, request_options=request_options, ) try: if 200 <= _response.status_code < 300: _data = typing.cast( - TopicList, + TopicSummaryList, parse_obj_as( - type_=TopicList, # type: ignore + type_=TopicSummaryList, # type: ignore object_=_response.json(), ), ) @@ -1474,40 +3188,79 @@ async def list( body=_response_json, ) - async def get( + async def list_assignments( self, - topic_id: str, *, from_timestamp: typing.Optional[dt.datetime] = None, to_timestamp: typing.Optional[dt.datetime] = None, + filter: typing.Optional[str] = None, + order_by: typing.Optional[TopicResultsOrderBy] = None, + limit: typing.Optional[int] = None, + cursor: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None, - ) -> AsyncHttpResponse[TopicWithStatus]: + ) -> AsyncHttpResponse[TopicAssignmentList]: """ Unstable: Topics is in closed beta. The shape may change without notice. - Get one topic by its stable id. Topics on the current map are always - found. A retired topic is found while traces in the window are still - assigned to it, with `inCurrentMap: false`. + List the current topic per trace, facet and rule on each rule's live + map: `assigned` with its topic, or `outlier` when no topic fits. + Summarized traces that are not assigned yet are not listed; find + them through `GET /topic-summaries` with `assignmentStatus`. + + The window applies to the trace start time. It defaults to the last + 7 days and can span at most 30 days, starting no earlier than the + plan's data access window. Without `fromTimestamp` and `toTimestamp`, + a `traceId` `any of` filter reads the listed traces at their own start + time within the last 93 days. Rows are ordered by trace start time. + + Each request scans a bounded number of candidate rows. A page shorter + than `limit` (even an empty one) with a `meta.cursor` does not + indicate completion; continue with the cursor until it is null. + + Only active facets (attached to a Topics rule, or all facets if the + project has no rule) are read. Traces Topics never processed are not + listed. + + `filter` is a JSON array of filter conditions combined with AND, with + at most 100 `traceId` values. Columns: `traceId`, `facetId`, `facetName`, `ruleId`, `environment`, + `topicId`, `topicName` (stringOptions), `status` (stringOptions, + `any of` only: `assigned`, `outlier`), `timestamp` (datetime). An + unknown `topicName` matches nothing under `any of` and is ignored + under `none of`. + + Common questions: + - Topics of specific traces: `traceId` `any of` the trace ids. + - Traces in a topic: `topicId` or `topicName`. Parameters ---------- - topic_id : str - from_timestamp : typing.Optional[dt.datetime] - Start of the window used for `traceCount`. Defaults to 7 days before `toTimestamp`. + Start of the window (trace start time, inclusive). Defaults to 7 days before `toTimestamp`. to_timestamp : typing.Optional[dt.datetime] - End of the window used for `traceCount`. Defaults to now. + End of the window (trace start time, exclusive). Defaults to now. + + filter : typing.Optional[str] + JSON array of filter conditions, for example `[{"type":"stringOptions","column":"topicName","operator":"any of","value":["Refunds"]}]`. + + order_by : typing.Optional[TopicResultsOrderBy] + Defaults to `timestamp.desc`. + + limit : typing.Optional[int] + Maximum number of rows per page. Defaults to `50`, at most `100`. + + cursor : typing.Optional[str] + The `meta.cursor` of the previous page. request_options : typing.Optional[RequestOptions] Request-specific configuration. Returns ------- - AsyncHttpResponse[TopicWithStatus] + AsyncHttpResponse[TopicAssignmentList] """ _response = await self._client_wrapper.httpx_client.request( - f"api/public/unstable/topics/{jsonable_encoder(topic_id)}", + "api/public/unstable/topic-assignments", method="GET", params={ "fromTimestamp": serialize_datetime(from_timestamp) @@ -1516,15 +3269,19 @@ async def get( "toTimestamp": serialize_datetime(to_timestamp) if to_timestamp is not None else None, + "filter": filter, + "orderBy": order_by, + "limit": limit, + "cursor": cursor, }, request_options=request_options, ) try: if 200 <= _response.status_code < 300: _data = typing.cast( - TopicWithStatus, + TopicAssignmentList, parse_obj_as( - type_=TopicWithStatus, # type: ignore + type_=TopicAssignmentList, # type: ignore object_=_response.json(), ), ) diff --git a/langfuse/api/unstable/topics/types/__init__.py b/langfuse/api/unstable/topics/types/__init__.py index 4e6e56333..1e389eefa 100644 --- a/langfuse/api/unstable/topics/types/__init__.py +++ b/langfuse/api/unstable/topics/types/__init__.py @@ -7,25 +7,47 @@ if typing.TYPE_CHECKING: from .topic import Topic + from .topic_assignment import TopicAssignment + from .topic_assignment_list import TopicAssignmentList + from .topic_assignment_status import TopicAssignmentStatus + from .topic_assignment_topic import TopicAssignmentTopic from .topic_facet import TopicFacet from .topic_facet_counts import TopicFacetCounts from .topic_facet_list import TopicFacetList from .topic_facet_published_run import TopicFacetPublishedRun from .topic_facet_ref import TopicFacetRef from .topic_list import TopicList + from .topic_result_list_meta import TopicResultListMeta + from .topic_results_order_by import TopicResultsOrderBy + from .topic_rule import TopicRule + from .topic_rule_list import TopicRuleList from .topic_status_counts import TopicStatusCounts + from .topic_summary import TopicSummary + from .topic_summary_list import TopicSummaryList + from .topic_summary_state import TopicSummaryState from .topic_window import TopicWindow from .topic_with_status import TopicWithStatus from .topics_order_by import TopicsOrderBy _dynamic_imports: typing.Dict[str, str] = { "Topic": ".topic", + "TopicAssignment": ".topic_assignment", + "TopicAssignmentList": ".topic_assignment_list", + "TopicAssignmentStatus": ".topic_assignment_status", + "TopicAssignmentTopic": ".topic_assignment_topic", "TopicFacet": ".topic_facet", "TopicFacetCounts": ".topic_facet_counts", "TopicFacetList": ".topic_facet_list", "TopicFacetPublishedRun": ".topic_facet_published_run", "TopicFacetRef": ".topic_facet_ref", "TopicList": ".topic_list", + "TopicResultListMeta": ".topic_result_list_meta", + "TopicResultsOrderBy": ".topic_results_order_by", + "TopicRule": ".topic_rule", + "TopicRuleList": ".topic_rule_list", "TopicStatusCounts": ".topic_status_counts", + "TopicSummary": ".topic_summary", + "TopicSummaryList": ".topic_summary_list", + "TopicSummaryState": ".topic_summary_state", "TopicWindow": ".topic_window", "TopicWithStatus": ".topic_with_status", "TopicsOrderBy": ".topics_order_by", @@ -61,13 +83,24 @@ def __dir__(): __all__ = [ "Topic", + "TopicAssignment", + "TopicAssignmentList", + "TopicAssignmentStatus", + "TopicAssignmentTopic", "TopicFacet", "TopicFacetCounts", "TopicFacetList", "TopicFacetPublishedRun", "TopicFacetRef", "TopicList", + "TopicResultListMeta", + "TopicResultsOrderBy", + "TopicRule", + "TopicRuleList", "TopicStatusCounts", + "TopicSummary", + "TopicSummaryList", + "TopicSummaryState", "TopicWindow", "TopicWithStatus", "TopicsOrderBy", diff --git a/langfuse/api/unstable/topics/types/topic.py b/langfuse/api/unstable/topics/types/topic.py index e32938810..c1bd49195 100644 --- a/langfuse/api/unstable/topics/types/topic.py +++ b/langfuse/api/unstable/topics/types/topic.py @@ -25,6 +25,13 @@ class Topic(UniversalBaseModel): name: str description: str facet: TopicFacetRef + rule_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="ruleId") + ] = pydantic.Field(default=None) + """ + The rule whose live map has the topic. Null when the project has no rule. + """ + run_id: typing_extensions.Annotated[str, FieldMetadata(alias="runId")] = ( pydantic.Field() ) diff --git a/langfuse/api/unstable/topics/types/topic_assignment.py b/langfuse/api/unstable/topics/types/topic_assignment.py new file mode 100644 index 000000000..b600e36ac --- /dev/null +++ b/langfuse/api/unstable/topics/types/topic_assignment.py @@ -0,0 +1,50 @@ +# 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 .topic_assignment_status import TopicAssignmentStatus +from .topic_assignment_topic import TopicAssignmentTopic + + +class TopicAssignment(UniversalBaseModel): + trace_id: typing_extensions.Annotated[str, FieldMetadata(alias="traceId")] + timestamp: dt.datetime = pydantic.Field() + """ + Trace start time. + """ + + facet_id: typing_extensions.Annotated[str, FieldMetadata(alias="facetId")] + facet_name: typing_extensions.Annotated[str, FieldMetadata(alias="facetName")] + facet_version: typing_extensions.Annotated[int, FieldMetadata(alias="facetVersion")] + rule_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="ruleId") + ] = pydantic.Field(default=None) + """ + The rule whose map assigned the trace. Null when the project has no rule. + """ + + status: TopicAssignmentStatus + topic: typing.Optional[TopicAssignmentTopic] = pydantic.Field(default=None) + """ + Set when `status` is `assigned`. + """ + + run_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="runId") + ] = pydantic.Field(default=None) + """ + The run that assigned the trace. Null for assignments made as traces arrive. + """ + + assigned_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="assignedAt") + ] + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/topics/types/topic_assignment_list.py b/langfuse/api/unstable/topics/types/topic_assignment_list.py new file mode 100644 index 000000000..d5be6c11f --- /dev/null +++ b/langfuse/api/unstable/topics/types/topic_assignment_list.py @@ -0,0 +1,19 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import UniversalBaseModel +from .topic_assignment import TopicAssignment +from .topic_result_list_meta import TopicResultListMeta +from .topic_window import TopicWindow + + +class TopicAssignmentList(UniversalBaseModel): + window: TopicWindow + data: typing.List[TopicAssignment] + meta: TopicResultListMeta + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/topics/types/topic_assignment_status.py b/langfuse/api/unstable/topics/types/topic_assignment_status.py new file mode 100644 index 000000000..bee431ae3 --- /dev/null +++ b/langfuse/api/unstable/topics/types/topic_assignment_status.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ....core import enum + +T_Result = typing.TypeVar("T_Result") + + +class TopicAssignmentStatus(enum.StrEnum): + ASSIGNED = "assigned" + OUTLIER = "outlier" + + def visit( + self, + assigned: typing.Callable[[], T_Result], + outlier: typing.Callable[[], T_Result], + ) -> T_Result: + if self is TopicAssignmentStatus.ASSIGNED: + return assigned() + if self is TopicAssignmentStatus.OUTLIER: + return outlier() diff --git a/langfuse/api/unstable/topics/types/topic_assignment_topic.py b/langfuse/api/unstable/topics/types/topic_assignment_topic.py new file mode 100644 index 000000000..7bb0f54f4 --- /dev/null +++ b/langfuse/api/unstable/topics/types/topic_assignment_topic.py @@ -0,0 +1,22 @@ +# 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 + + +class TopicAssignmentTopic(UniversalBaseModel): + id: str = pydantic.Field() + """ + Stable topic id. + """ + + version_id: typing_extensions.Annotated[str, FieldMetadata(alias="versionId")] + name: str + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/topics/types/topic_facet.py b/langfuse/api/unstable/topics/types/topic_facet.py index 2f3673abf..166373b5e 100644 --- a/langfuse/api/unstable/topics/types/topic_facet.py +++ b/langfuse/api/unstable/topics/types/topic_facet.py @@ -16,7 +16,14 @@ class TopicFacet(UniversalBaseModel): is_built_in: typing_extensions.Annotated[bool, FieldMetadata(alias="isBuiltIn")] active: bool = pydantic.Field() """ - Whether the facet is attached to the project's Topics rule. True for all facets if the project has no rule. + Whether a Topics rule runs the facet. True for all facets if the project has no rule. + """ + + rule_ids: typing_extensions.Annotated[ + typing.List[str], FieldMetadata(alias="ruleIds") + ] = pydantic.Field() + """ + The rules that run the facet. """ version: int = pydantic.Field() diff --git a/langfuse/api/unstable/topics/types/topic_facet_counts.py b/langfuse/api/unstable/topics/types/topic_facet_counts.py index 0bc83e2cf..4932663b6 100644 --- a/langfuse/api/unstable/topics/types/topic_facet_counts.py +++ b/langfuse/api/unstable/topics/types/topic_facet_counts.py @@ -12,6 +12,9 @@ class TopicFacetCounts(UniversalBaseModel): facet: TopicFacetRef + rule_id: typing_extensions.Annotated[ + typing.Optional[str], FieldMetadata(alias="ruleId") + ] = None published_run_id: typing_extensions.Annotated[ typing.Optional[str], FieldMetadata(alias="publishedRunId") ] = None diff --git a/langfuse/api/unstable/topics/types/topic_result_list_meta.py b/langfuse/api/unstable/topics/types/topic_result_list_meta.py new file mode 100644 index 000000000..c5cdf08de --- /dev/null +++ b/langfuse/api/unstable/topics/types/topic_result_list_meta.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import UniversalBaseModel + + +class TopicResultListMeta(UniversalBaseModel): + cursor: typing.Optional[str] = pydantic.Field(default=None) + """ + Pass as `cursor` to fetch the next page. Null on the last page. + """ + + comment: typing.Optional[str] = pydantic.Field(default=None) + """ + Set when the page is shorter than `limit` but more rows may follow. + """ + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/topics/types/topic_results_order_by.py b/langfuse/api/unstable/topics/types/topic_results_order_by.py new file mode 100644 index 000000000..4df738d50 --- /dev/null +++ b/langfuse/api/unstable/topics/types/topic_results_order_by.py @@ -0,0 +1,22 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +from ....core import enum + +T_Result = typing.TypeVar("T_Result") + + +class TopicResultsOrderBy(enum.StrEnum): + TIMESTAMP_DESC = "timestamp.desc" + TIMESTAMP_ASC = "timestamp.asc" + + def visit( + self, + timestamp_desc: typing.Callable[[], T_Result], + timestamp_asc: typing.Callable[[], T_Result], + ) -> T_Result: + if self is TopicResultsOrderBy.TIMESTAMP_DESC: + return timestamp_desc() + if self is TopicResultsOrderBy.TIMESTAMP_ASC: + return timestamp_asc() diff --git a/langfuse/api/unstable/topics/types/topic_rule.py b/langfuse/api/unstable/topics/types/topic_rule.py new file mode 100644 index 000000000..a0109241e --- /dev/null +++ b/langfuse/api/unstable/topics/types/topic_rule.py @@ -0,0 +1,41 @@ +# 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 ....evaluation_commons.types.evaluation_rule_read_filter import ( + EvaluationRuleReadFilter, +) + + +class TopicRule(UniversalBaseModel): + id: str + name: str + filter: typing.List[EvaluationRuleReadFilter] = pydantic.Field() + """ + Observation filter that selects the rule's traces, returned verbatim. + """ + + sampling: float = pydantic.Field() + """ + Fraction of matching traces that are processed, between `0` and `1`. + """ + + facet_ids: typing_extensions.Annotated[ + typing.List[str], FieldMetadata(alias="facetIds") + ] = pydantic.Field() + """ + Facets the rule runs. + """ + + updated_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="updatedAt") + ] + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/topics/types/topic_rule_list.py b/langfuse/api/unstable/topics/types/topic_rule_list.py new file mode 100644 index 000000000..e9b0b7f6c --- /dev/null +++ b/langfuse/api/unstable/topics/types/topic_rule_list.py @@ -0,0 +1,17 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import UniversalBaseModel +from ....utils.pagination.types.meta_response import MetaResponse +from .topic_rule import TopicRule + + +class TopicRuleList(UniversalBaseModel): + data: typing.List[TopicRule] + meta: MetaResponse + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/topics/types/topic_summary.py b/langfuse/api/unstable/topics/types/topic_summary.py new file mode 100644 index 000000000..f4bdbdc2a --- /dev/null +++ b/langfuse/api/unstable/topics/types/topic_summary.py @@ -0,0 +1,40 @@ +# 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 .topic_summary_state import TopicSummaryState + + +class TopicSummary(UniversalBaseModel): + trace_id: typing_extensions.Annotated[str, FieldMetadata(alias="traceId")] + trace_name: typing_extensions.Annotated[str, FieldMetadata(alias="traceName")] + environment: str + timestamp: dt.datetime = pydantic.Field() + """ + Trace start time. + """ + + facet_id: typing_extensions.Annotated[str, FieldMetadata(alias="facetId")] + facet_name: typing_extensions.Annotated[str, FieldMetadata(alias="facetName")] + facet_version: typing_extensions.Annotated[int, FieldMetadata(alias="facetVersion")] + state: TopicSummaryState + summary: typing.Optional[str] = pydantic.Field(default=None) + """ + Set when `state` is `complete`. + """ + + summary_truncated: typing_extensions.Annotated[ + bool, FieldMetadata(alias="summaryTruncated") + ] + processed_at: typing_extensions.Annotated[ + dt.datetime, FieldMetadata(alias="processedAt") + ] + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/topics/types/topic_summary_list.py b/langfuse/api/unstable/topics/types/topic_summary_list.py new file mode 100644 index 000000000..60a3611dc --- /dev/null +++ b/langfuse/api/unstable/topics/types/topic_summary_list.py @@ -0,0 +1,19 @@ +# This file was auto-generated by Fern from our API Definition. + +import typing + +import pydantic +from ....core.pydantic_utilities import UniversalBaseModel +from .topic_result_list_meta import TopicResultListMeta +from .topic_summary import TopicSummary +from .topic_window import TopicWindow + + +class TopicSummaryList(UniversalBaseModel): + window: TopicWindow + data: typing.List[TopicSummary] + meta: TopicResultListMeta + + model_config: typing.ClassVar[pydantic.ConfigDict] = pydantic.ConfigDict( + extra="allow", frozen=True + ) diff --git a/langfuse/api/unstable/topics/types/topic_summary_state.py b/langfuse/api/unstable/topics/types/topic_summary_state.py new file mode 100644 index 000000000..8b247353b --- /dev/null +++ b/langfuse/api/unstable/topics/types/topic_summary_state.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 TopicSummaryState(enum.StrEnum): + COMPLETE = "complete" + NOT_APPLICABLE = "not_applicable" + INSUFFICIENT_INPUT = "insufficient_input" + + def visit( + self, + complete: typing.Callable[[], T_Result], + not_applicable: typing.Callable[[], T_Result], + insufficient_input: typing.Callable[[], T_Result], + ) -> T_Result: + if self is TopicSummaryState.COMPLETE: + return complete() + if self is TopicSummaryState.NOT_APPLICABLE: + return not_applicable() + if self is TopicSummaryState.INSUFFICIENT_INPUT: + return insufficient_input() diff --git a/langfuse/api/unstable/topics/types/topic_with_status.py b/langfuse/api/unstable/topics/types/topic_with_status.py index ca2749b5a..60fe706a4 100644 --- a/langfuse/api/unstable/topics/types/topic_with_status.py +++ b/langfuse/api/unstable/topics/types/topic_with_status.py @@ -14,7 +14,7 @@ class TopicWithStatus(Topic): bool, FieldMetadata(alias="inCurrentMap") ] = pydantic.Field() """ - False for retired topics that still have assigned traces in the window. + Always true; retired topics are not returned. """ window: TopicWindow