Skip to content
Merged
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
8 changes: 0 additions & 8 deletions baseten/client/inferenceapi/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,13 @@
AsyncRunRemoteInput,
AsyncRunRemoteOutput,
CancelAsyncRequestOutput,
Code,
ErrorCode,
ErrorResponse,
GetAsyncQueueStatusOutput,
InferenceRetryConfig,
PredictInput,
PredictOutput,
RunRemoteInput,
RunRemoteOutput,
Status,
WebhookStatus,
)

__all__ = [
Expand All @@ -42,8 +38,6 @@
"AsyncRunRemoteInput",
"AsyncRunRemoteOutput",
"CancelAsyncRequestOutput",
"Code",
"ErrorCode",
"ErrorResponse",
"GetAsyncQueueStatusOutput",
"InferenceRetryConfig",
Expand All @@ -53,6 +47,4 @@
"ResponseErrorResponse",
"RunRemoteInput",
"RunRemoteOutput",
"Status",
"WebhookStatus",
]
93 changes: 45 additions & 48 deletions baseten/client/inferenceapi/_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@

from __future__ import annotations

from enum import StrEnum
from typing import Annotated, Any
from typing import Annotated, Any, Literal

from pydantic import AnyUrl, BaseModel, Field, RootModel

Expand All @@ -27,59 +26,44 @@ class InferenceRetryConfig(BaseModel):
] = 5000


class Status(StrEnum):
QUEUED = "QUEUED"
IN_PROGRESS = "IN_PROGRESS"
SUCCEEDED = "SUCCEEDED"
FAILED = "FAILED"
EXPIRED = "EXPIRED"
CANCELED = "CANCELED"
WEBHOOK_FAILED = "WEBHOOK_FAILED"


class WebhookStatus(StrEnum):
PENDING = "PENDING"
SUCCEEDED = "SUCCEEDED"
FAILED = "FAILED"
CANCELED = "CANCELED"
NO_WEBHOOK_PROVIDED = "NO_WEBHOOK_PROVIDED"


class Code(StrEnum):
MODEL_PREDICT_ERROR = "MODEL_PREDICT_ERROR"
MODEL_PREDICT_TIMEOUT = "MODEL_PREDICT_TIMEOUT"
MODEL_NOT_READY = "MODEL_NOT_READY"
MODEL_DOES_NOT_EXIST = "MODEL_DOES_NOT_EXIST"
MODEL_UNAVAILABLE = "MODEL_UNAVAILABLE"
MODEL_INVALID_INPUT = "MODEL_INVALID_INPUT"
ASYNC_REQUEST_NOT_SUPPORTED = "ASYNC_REQUEST_NOT_SUPPORTED"
INTERNAL_SERVER_ERROR = "INTERNAL_SERVER_ERROR"


class AsyncRequestError(BaseModel):

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.

I've been wondering if it would be better to create a custom OpenStrEnum class that handles unknown values well, but would allow using it's members for a call sites instead of now having str literals everywhere.

But it turns out in terms of assert_never(status)-exhaustive handling - forcing to add a branch of the unknown values that are the plain str case, your Literal approach is better.

But there is a way to combine them, by still defining constants that match the literals, e.g.

Status = Literal["ACTIVE", "PAUSED"] | str

class Statuses:
    ACTIVE: Final = "ACTIVE"
    PAUSED: Final = "PAUSED"

and then you can reference at call sites Statuses.ACTIVE instead of literals. The issue with the str base case is that it would not check typos at call sites, you could write "ACTVIE" without noticing.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Makes sense, just not sure it's worth doubling the generated surface area for enumerates that aren't even used in our code. But we can add later if needed.

We have this same problem in JS and Go SDK, such is the nature of allowing any string. Same for Stainless-generated SDKs (like OpenAI, Anthropic, etc).

Another option is that we could try to limit this fix only to enumerates that are returned and leave input types closed, but we can't guarantee they will stay as only returned and it seemed like overkill. We will have this same pattern in the high-level SDK wrt enumerates, so this is also a concern there.

code: Annotated[Code, Field(description="The type of error that occurred.")]
code: Annotated[
Literal[
"MODEL_PREDICT_ERROR",
"MODEL_PREDICT_TIMEOUT",
"MODEL_NOT_READY",
"MODEL_DOES_NOT_EXIST",
"MODEL_UNAVAILABLE",
"MODEL_INVALID_INPUT",
"ASYNC_REQUEST_NOT_SUPPORTED",
"INTERNAL_SERVER_ERROR",
]
| str,
Field(description="The type of error that occurred."),
]
message: Annotated[str, Field(description="Details of the error.")]


class ErrorCode(StrEnum):
timeout = "timeout"
client_error = "client_error"
model_unavailable = "model_unavailable"
model_not_ready = "model_not_ready"
model_does_not_exist = "model_does_not_exist"
rate_limited = "rate_limited"
payload_too_large = "payload_too_large"
unauthorized = "unauthorized"
application_error = "application_error"
internal_baseten_error = "internal_baseten_error"


class ErrorResponse(BaseModel):
error: Annotated[str | None, Field(description="Human-readable error message.")] = (
None
)
error_code: Annotated[
ErrorCode | None, Field(description="Machine-readable error code.")
Literal[
"timeout",
"client_error",
"model_unavailable",
"model_not_ready",
"model_does_not_exist",
"rate_limited",
"payload_too_large",
"unauthorized",
"application_error",
"internal_baseten_error",
]
| str
| None,
Field(description="Machine-readable error code."),
] = None
detail: Annotated[
str | None, Field(description="Additional error details, if available.")
Expand Down Expand Up @@ -182,9 +166,22 @@ class AsyncRequestStatusResponse(BaseModel):
deployment_id: Annotated[
str, Field(description="The ID of the deployment that executed the request.")
]
status: Annotated[Status, Field(description="The status of the async request.")]
status: Annotated[
Literal[
"QUEUED",
"IN_PROGRESS",
"SUCCEEDED",
"FAILED",
"EXPIRED",
"CANCELED",
"WEBHOOK_FAILED",
]
| str,
Field(description="The status of the async request."),
]
webhook_status: Annotated[
WebhookStatus,
Literal["PENDING", "SUCCEEDED", "FAILED", "CANCELED", "NO_WEBHOOK_PROVIDED"]
| str,
Field(
description="The status of sending the prediction result to the provided webhook."
),
Expand Down
Loading
Loading