Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions langfuse/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
BlobStorageExportFrequency,
BlobStorageExportMode,
BlobStorageExportSource,
BlobStorageIntegrationAuthType,
BlobStorageIntegrationDeletionResponse,
BlobStorageIntegrationFileType,
BlobStorageIntegrationFileTypeResponse,
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -813,6 +815,7 @@ def __dir__():
"BlobStorageExportFrequency",
"BlobStorageExportMode",
"BlobStorageExportSource",
"BlobStorageIntegrationAuthType",
"BlobStorageIntegrationDeletionResponse",
"BlobStorageIntegrationFileType",
"BlobStorageIntegrationFileTypeResponse",
Expand Down
3 changes: 3 additions & 0 deletions langfuse/api/blob_storage_integrations/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
BlobStorageExportFrequency,
BlobStorageExportMode,
BlobStorageExportSource,
BlobStorageIntegrationAuthType,
BlobStorageIntegrationDeletionResponse,
BlobStorageIntegrationFileType,
BlobStorageIntegrationFileTypeResponse,
Expand All @@ -26,6 +27,7 @@
"BlobStorageExportFrequency": ".types",
"BlobStorageExportMode": ".types",
"BlobStorageExportSource": ".types",
"BlobStorageIntegrationAuthType": ".types",
"BlobStorageIntegrationDeletionResponse": ".types",
"BlobStorageIntegrationFileType": ".types",
"BlobStorageIntegrationFileTypeResponse": ".types",
Expand Down Expand Up @@ -70,6 +72,7 @@ def __dir__():
"BlobStorageExportFrequency",
"BlobStorageExportMode",
"BlobStorageExportSource",
"BlobStorageIntegrationAuthType",
"BlobStorageIntegrationDeletionResponse",
"BlobStorageIntegrationFileType",
"BlobStorageIntegrationFileTypeResponse",
Expand Down
29 changes: 27 additions & 2 deletions langfuse/api/blob_storage_integrations/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand Down
29 changes: 27 additions & 2 deletions langfuse/api/blob_storage_integrations/raw_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand Down
3 changes: 3 additions & 0 deletions langfuse/api/blob_storage_integrations/types/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand All @@ -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",
Expand Down Expand Up @@ -76,6 +78,7 @@ def __dir__():
"BlobStorageExportFrequency",
"BlobStorageExportMode",
"BlobStorageExportSource",
"BlobStorageIntegrationAuthType",
"BlobStorageIntegrationDeletionResponse",
"BlobStorageIntegrationFileType",
"BlobStorageIntegrationFileTypeResponse",
Expand Down
Original file line number Diff line number Diff line change
@@ -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()
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand All @@ -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")
]
Comment on lines +28 to +30

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Older integration responses fail

If a self-hosted server predates IAM role authentication and does not return authType, the newly required BlobStorageIntegrationResponse.auth_type makes listing integrations or reading an update's response raise ValidationError. These calls worked before this change, even for callers using only access keys. Give the field a backward-compatible default, such as ACCESS_KEYS, so upgrading the SDK does not require upgrading the server.

Prompt To Fix With AI
This is a comment left during a code review.
Path: langfuse/api/blob_storage_integrations/types/blob_storage_integration_response.py
Line: 28-30

Comment:
**Older integration responses fail**

If a self-hosted server predates IAM role authentication and does not return `authType`, the newly required `BlobStorageIntegrationResponse.auth_type` makes listing integrations or reading an update's response raise `ValidationError`. These calls worked before this change, even for callers using only access keys. Give the field a backward-compatible default, such as `ACCESS_KEYS`, so upgrading the SDK does not require upgrading the server.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Comment on lines +28 to +30

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 SDK users on a Langfuse server older than this spec get a pydantic ValidationError when listing or upserting blob storage integrations. The new response field auth_type (alias authType) is required with no default. A server that predates IAM role auth omits authType, so the 2xx response no longer parses. Fix: make every response field this spec adds optional with a default, which covers the 2 sites listed. Do it in the upstream spec so regeneration keeps it, e.g. Optional[BlobStorageIntegrationAuthType] = None. Same pattern at 2 sites (blob_storage_integration_response.py:28, unstable/topics/types/topic_facet.py:22).

Why this was flagged

This triggers when the SDK runs against a self-hosted Langfuse deployment that has not yet picked up ccfda83, so its JSON has no authType. On the base branch BlobStorageIntegrationResponse has no such field, and those responses parse. After the change, blob_storage_integration_response.py:28-30 declares auth_type as Annotated[BlobStorageIntegrationAuthType, FieldMetadata(alias="authType")] with no default. Nothing tolerates the missing key: parse_obj_as in raw_client.py:271-273 (upsert) and in the list path raises a pydantic validation error, so a successful server response turns into an exception. extra="allow" only lets unknown keys through and does nothing for missing ones. The same thing happens with TopicFacet.rule_ids (alias ruleIds, pydantic.Field() with no default) at unstable/topics/types/topic_facet.py:22-24, so facet list calls fail against servers that do not send ruleIds. Every other field this regen adds (role_arn, external_id, rule_id, comment) is Optional with a default None. That shows these two required fields are the outliers.

Verification: Trigger: the SDK talks to a Langfuse server whose blob-storage integration responses predate IAM role auth and so carry no authType key. blob_storage_integration_response.py:28-30 adds auth_type with no default. The base version of the model has no auth_type field, so the same payload parses fine there. The try in raw_client.py only catches JSONDecodeError (line 128).

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")
Expand Down
Loading
Loading