Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
52ecc8d
Cache explicit package selections independently and validate serving …
leeclemnet Sep 15, 2026
896b061
Add explicit HTTP model package selection
leeclemnet Sep 15, 2026
337bf44
Preserve positional SDK configuration arguments
leeclemnet Sep 15, 2026
30ec16d
Document library rollout requirement for package selection
leeclemnet Sep 15, 2026
eba5f49
Exercise package cache with typed inference requests
leeclemnet Sep 15, 2026
f6babbb
Require the updated inference-models release for package selection
leeclemnet Sep 15, 2026
bf21ee7
Run package selection tests with the pinned inference-models wheel
leeclemnet Sep 16, 2026
a374ec8
Keep package handles server-owned and correct selection errors
leeclemnet Sep 16, 2026
25589d0
Simplify HTTP model selection request and response types
leeclemnet Sep 16, 2026
99169be
Document package selection through the generated OpenAPI schema
leeclemnet Sep 17, 2026
33ccdb7
Align package cache fixtures with base model metadata
leeclemnet Sep 17, 2026
e3e0451
Shorten model selection field descriptions
leeclemnet Sep 17, 2026
28e64c5
Support action recognition package selection and independent manageme…
leeclemnet Sep 17, 2026
cd57a84
Remove redundant model selection gates
leeclemnet Sep 17, 2026
640dd97
Document model package selection exceptions
leeclemnet Sep 17, 2026
dd146f4
Use confirmed handles for selected unloads and move cache identity po…
leeclemnet Sep 17, 2026
e152452
Check selector schema contracts instead of description text
leeclemnet Sep 17, 2026
e256ff7
Match CI import formatting and document cache HMAC alert
leeclemnet Sep 18, 2026
8b390b2
Fix package selection validation, SDK video requests, and telemetry
leeclemnet Sep 28, 2026
c47a853
Merge origin/main into feat/http-model-package-selection
leeclemnet Sep 28, 2026
544fec8
Merge branch 'main' into feat/http-model-package-selection
leeclemnet Sep 29, 2026
edefb50
Share loaded models by resolved package identity
leeclemnet Sep 29, 2026
f3009ee
Merge branch 'main' into feat/http-model-package-selection
leeclemnet Sep 29, 2026
91ee525
Fix workflow tests for shared package caching
leeclemnet Sep 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions inference/core/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
MODEL_LOAD_TIME_HEADER = "X-Model-Load-Time"
MODEL_LOAD_DETAILS_HEADER = "X-Model-Load-Details"
MODEL_ID_HEADER = "X-Model-Id"
MODEL_SELECTION_HEADER = "X-Roboflow-Model-Selection"
WORKFLOW_ID_HEADER = "X-Workflow-Id"
WORKSPACE_ID_HEADER = "X-Workspace-Id"
TRACE_ID_HEADER = "X-Trace-Id"
5 changes: 3 additions & 2 deletions inference/core/entities/requests/action_recognition.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from pydantic import BaseModel, Field

from inference.core.entities.requests.inference import BaseRequest
from inference.core.entities.requests.inference import InferenceRequest


class InferenceRequestVideo(BaseModel):
Expand Down Expand Up @@ -32,7 +32,7 @@ class InferenceRequestVideo(BaseModel):
)


class ActionRecognitionInferenceRequest(BaseRequest):
class ActionRecognitionInferenceRequest(InferenceRequest):
"""Request for action recognition over a video clip.

Attributes:
Expand All @@ -44,6 +44,7 @@ class ActionRecognitionInferenceRequest(BaseRequest):
"""

model_id: str = Field(
default=...,
examples=["workspace/action-recognition-1"],
description="The model to classify with",
)
Expand Down
38 changes: 37 additions & 1 deletion inference/core/entities/requests/inference.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
from typing import Any, ClassVar, List, Literal, Optional, Union
from uuid import uuid4

from pydantic import BaseModel, ConfigDict, Field, field_validator, validator
from pydantic import (
BaseModel,
ConfigDict,
Field,
field_validator,
model_validator,
validator,
)
from pydantic.json_schema import SkipJsonSchema

from inference.core import logger
from inference.core.entities.common import ApiKey, ModelID, ModelType
from inference.core.entities.requests.model_selection import (
model_selection_kwargs,
validate_model_selection,
)
from inference_sdk.http.entities import Confidence


Expand Down Expand Up @@ -53,6 +64,23 @@ class InferenceRequest(BaseRequest):

model_id: Optional[str] = ModelID
model_type: Optional[str] = ModelType
model_package_id: Optional[str] = Field(
default=None,
min_length=1,
description="Exact model package ID.",
)
backend: Optional[str] = Field(
default=None,
min_length=1,
description="Required package backend, such as trt or onnx.",
)
quantization: Optional[str] = Field(
default=None,
min_length=1,
description="Required package quantization, such as fp16 or fp32.",
)

validate_model_selection = model_validator(mode="after")(validate_model_selection)


class InferenceRequestImage(BaseModel):
Expand Down Expand Up @@ -134,6 +162,14 @@ class DepthEstimationRequest(InferenceRequest):
"lossy for geometric use).",
)

@model_validator(mode="after")
def reject_model_selection(self):
if model_selection_kwargs(self):
raise ValueError(
"Model package selection is not supported for depth estimation requests."
)
return self

@validator("model_id", always=True)
def validate_model_id(cls, value, values):
if value is not None:
Expand Down
22 changes: 22 additions & 0 deletions inference/core/entities/requests/model_selection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from inference.core import env


def validate_model_selection(request):
selectors = model_selection_kwargs(request)
if "model_package_id" in selectors and (
"backend" in selectors or "quantization" in selectors
):
raise ValueError(
"model_package_id cannot be combined with backend or quantization."
)
if selectors and not env.USE_INFERENCE_MODELS:
raise ValueError("Model package selection requires USE_INFERENCE_MODELS=true.")
return request


def model_selection_kwargs(request) -> dict:
return {
name: value
for name in ("model_package_id", "backend", "quantization")
if (value := getattr(request, name, None)) is not None
}
34 changes: 33 additions & 1 deletion inference/core/entities/requests/server_state.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from typing import Optional

from pydantic import BaseModel, ConfigDict
from pydantic import BaseModel, ConfigDict, Field, model_validator

from inference.core.entities.common import ApiKey, ModelID, ModelType
from inference.core.entities.requests.model_selection import validate_model_selection


class AddModelRequest(BaseModel):
Expand All @@ -18,6 +19,21 @@ class AddModelRequest(BaseModel):
model_id: str = ModelID
model_type: Optional[str] = ModelType
api_key: Optional[str] = ApiKey
model_package_id: Optional[str] = Field(
default=None, min_length=1, description="Exact model package ID."
)
backend: Optional[str] = Field(
default=None,
min_length=1,
description="Required package backend, such as trt or onnx.",
)
quantization: Optional[str] = Field(
default=None,
min_length=1,
description="Required package quantization, such as fp16 or fp32.",
)

validate_model_selection = model_validator(mode="after")(validate_model_selection)


class ClearModelRequest(BaseModel):
Expand All @@ -29,3 +45,19 @@ class ClearModelRequest(BaseModel):

model_config = ConfigDict(protected_namespaces=())
model_id: str = ModelID
api_key: Optional[str] = ApiKey
model_package_id: Optional[str] = Field(
default=None, min_length=1, description="Exact model package ID."
)
backend: Optional[str] = Field(
default=None,
min_length=1,
description="Required package backend, such as trt or onnx.",
)
quantization: Optional[str] = Field(
default=None,
min_length=1,
description="Required package quantization, such as fp16 or fp32.",
)

validate_model_selection = model_validator(mode="after")(validate_model_selection)
4 changes: 4 additions & 0 deletions inference/core/entities/responses/server_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,10 @@ def from_model_description(


class ModelsDescriptions(BaseModel):
selected_model_id: Optional[str] = Field(
default=None,
description="Opaque identifier of the loaded entry for an explicit package selection.",
)
models: List[ModelDescriptionEntity] = Field(
description="List of models that are loaded by model manager.",
)
Expand Down
16 changes: 16 additions & 0 deletions inference/core/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,22 @@ class InferenceModelNotFound(Exception):
"""


class ModelPackageSelectionError(Exception):
"""Raised when the requested model package selection cannot be satisfied.

Attributes:
message (str): Optional message describing the error.
"""


class ModelPackageNotFoundError(Exception):
"""Raised when the requested model package is not found.

Attributes:
message (str): Optional message describing the error.
"""


class InvalidMaskDecodeArgument(Exception):
"""Raised when an invalid argument is provided for mask decoding.

Expand Down
16 changes: 16 additions & 0 deletions inference/core/interfaces/http/error_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
ModelArtefactError,
ModelDeploymentNotSupportedError,
ModelManagerLockAcquisitionError,
ModelPackageNotFoundError,
ModelPackageSelectionError,
OnnxProviderNotAvailable,
PayloadTooLargeError,
PaymentRequiredError,
Expand Down Expand Up @@ -499,6 +501,13 @@ def wrapped_route(*args, **kwargs):
status_code=502,
content={"message": "Internal error. Request to Roboflow API failed."},
)
except ModelPackageNotFoundError:
resp = JSONResponse(
status_code=404,
content={"message": "Model or model package not found."},
)
except ModelPackageSelectionError as error:
resp = JSONResponse(status_code=400, content={"message": str(error)})
except InferenceModelNotFound as error:
logger.exception("%s: %s", type(error).__name__, error)
resp = JSONResponse(
Expand Down Expand Up @@ -979,6 +988,13 @@ async def wrapped_route(*args, **kwargs):
status_code=502,
content={"message": "Internal error. Request to Roboflow API failed."},
)
except ModelPackageNotFoundError:
resp = JSONResponse(
status_code=404,
content={"message": "Model or model package not found."},
)
except ModelPackageSelectionError as error:
resp = JSONResponse(status_code=400, content={"message": str(error)})
except InferenceModelNotFound as error:
logger.exception("%s: %s", type(error).__name__, error)
resp = JSONResponse(
Expand Down
Loading
Loading