Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
2c1173c
Record loaded model packages for HTTP serving
leeclemnet Sep 15, 2026
a50c13a
Return resolved model metadata in HTTP inference responses
leeclemnet Sep 15, 2026
718ceab
Attach resolved metadata in model adapters
leeclemnet Sep 15, 2026
7378a44
Keep inference-models flag gating at model selection
leeclemnet Sep 15, 2026
9bcbc61
Support the pinned inference-models release without metadata
leeclemnet Sep 16, 2026
163c5c7
Address resolved metadata review findings
leeclemnet Sep 16, 2026
6c091d2
Expose resolved metadata through the base model and API schemas
leeclemnet Sep 17, 2026
da5b561
Simplify resolved model response field documentation
leeclemnet Sep 17, 2026
5388631
Patch metadata test flags on the active base model module
leeclemnet Sep 17, 2026
904cfef
Align resolved model metadata with response conventions
leeclemnet Sep 17, 2026
4e408c2
Preserve null response fields and report legacy model identity
leeclemnet Sep 17, 2026
01f0d92
Remove unreachable flag-off adapter test cases
leeclemnet Sep 17, 2026
f8c8c6e
Report legacy model identity with inference models enabled
leeclemnet Sep 17, 2026
ee46009
Report resolved model metadata for legacy classification and wrapped …
PawelPeczek-Roboflow Sep 18, 2026
9fbafd0
Bump dependencies
PawelPeczek-Roboflow Sep 18, 2026
23df2d7
Move inference-models changelog entries under 0.37.2
PawelPeczek-Roboflow Sep 18, 2026
c07dc14
Pin inference-models 0.37.2 in the inference requirements
PawelPeczek-Roboflow Sep 18, 2026
7d69dfa
Merge branch 'main' into feat/http-resolved-model-metadata
PawelPeczek-Roboflow Sep 18, 2026
523bb93
Merge branch 'main' into feat/http-resolved-model-metadata
PawelPeczek-Roboflow Sep 18, 2026
013ec33
Make linters happy
PawelPeczek-Roboflow Sep 18, 2026
e19135a
Merge branch 'main' into feat/http-resolved-model-metadata
PawelPeczek-Roboflow Sep 18, 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
5 changes: 3 additions & 2 deletions inference/core/entities/responses/action_recognition.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
from typing import List

from pydantic import BaseModel, Field
from pydantic import Field

from inference.core.entities.responses.inference import InferenceResponse
from inference.core.workflows.core_steps.models.roboflow.action_recognition.entities import ( # noqa: F401
ActionRecognitionPrediction,
)


class ActionRecognitionInferenceResponse(BaseModel):
class ActionRecognitionInferenceResponse(InferenceResponse):
"""Classified ranges covering one clip.

Frame indices count from the first frame of the submitted clip, so a
Expand Down
6 changes: 5 additions & 1 deletion inference/core/entities/responses/inference.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
InferenceResponse,
InferenceResponseImage,
InstanceSegmentationInferenceResponse,
ResolvedModel,
WithVisualizationResponse,
)
from inference.core.workflows.core_steps.common.segmentation_entities import ( # noqa: F401
Expand Down Expand Up @@ -232,6 +233,7 @@ class InstanceSegmentationInferenceResponseDC:
frame_id: object = None
time: object = None
visualization: object = None
resolved_model: Optional[ResolvedModel] = None
# Internal stream-pipeline fast path: lets workflow execution carry a
# response future through Model.infer_from_request without blocking the
# inference thread. `_is_response_dc_to_dict` intentionally ignores it.
Expand Down Expand Up @@ -283,6 +285,8 @@ def _is_response_dc_to_dict(r: InstanceSegmentationInferenceResponseDC) -> dict:
d["time"] = r.time
if r.visualization is not None:
d["visualization"] = r.visualization
if r.resolved_model is not None:
d["resolved_model"] = r.resolved_model.model_dump()
return d


Expand Down Expand Up @@ -362,7 +366,7 @@ class FaceDetectionPrediction(ObjectDetectionPrediction):
landmarks: Union[List[Point], List[Point3D]]


class DepthEstimationResponse(BaseModel):
class DepthEstimationResponse(InferenceResponse):
"""Response for depth estimation inference.

Attributes:
Expand Down
5 changes: 3 additions & 2 deletions inference/core/entities/responses/ocr.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
from typing import List, Optional

from pydantic import BaseModel, Field
from pydantic import Field

from inference.core.entities.responses.inference import (
InferenceResponse,
InferenceResponseImage,
ObjectDetectionPrediction,
)


class OCRInferenceResponse(BaseModel):
class OCRInferenceResponse(InferenceResponse):
"""
OCR Inference response.

Expand Down
8 changes: 5 additions & 3 deletions inference/core/entities/responses/sam.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from typing import Any, List, Union

from pydantic import BaseModel, Field
from pydantic import Field

from inference.core.entities.responses.inference import InferenceResponse

class SamEmbeddingResponse(BaseModel):

class SamEmbeddingResponse(InferenceResponse):
"""SAM embedding response.

Attributes:
Expand All @@ -20,7 +22,7 @@ class SamEmbeddingResponse(BaseModel):
)


class SamSegmentationResponse(BaseModel):
class SamSegmentationResponse(InferenceResponse):
"""SAM segmentation response.

Attributes:
Expand Down
7 changes: 4 additions & 3 deletions inference/core/entities/responses/sam2.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
from typing import List

from pydantic import BaseModel, Field
from pydantic import Field

from inference.core.entities.responses.inference import InferenceResponse
from inference.core.workflows.core_steps.common.segmentation_entities import ( # noqa: F401
Sam2SegmentationPrediction,
)


class Sam2EmbeddingResponse(BaseModel):
class Sam2EmbeddingResponse(InferenceResponse):
"""SAM embedding response.

Attributes:
Expand All @@ -21,7 +22,7 @@ class Sam2EmbeddingResponse(BaseModel):
)


class Sam2SegmentationResponse(BaseModel):
class Sam2SegmentationResponse(InferenceResponse):
predictions: List[Sam2SegmentationPrediction] = Field()
time: float = Field(
description="The time in seconds it took to produce the segmentation including preprocessing"
Expand Down
6 changes: 4 additions & 2 deletions inference/core/entities/responses/sam3.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

from pydantic import BaseModel, Field

from inference.core.entities.responses.inference import InferenceResponse

class Sam3EmbeddingResponse(BaseModel):

class Sam3EmbeddingResponse(InferenceResponse):
image_id: str = Field(description="Image id embeddings are cached to")
time: float = Field(
description="The time in seconds it took to produce the embeddings including preprocessing"
Expand Down Expand Up @@ -33,7 +35,7 @@ class Sam3PromptResult(BaseModel):
predictions: List[Sam3SegmentationPrediction] = Field()


class Sam3SegmentationResponse(BaseModel):
class Sam3SegmentationResponse(InferenceResponse):
prompt_results: List[Sam3PromptResult] = Field()
time: float = Field(
description="The time in seconds it took to produce the segmentation including preprocessing"
Expand Down
1 change: 1 addition & 0 deletions inference/core/interfaces/http/http_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -4256,6 +4256,7 @@ def _infer_depth_estimation(
depth_data["normalized_depth"]
)
return DepthEstimationResponse(
resolved_model=getattr(response, "resolved_model", None),
normalized_depth=serialized_depth,
depth_map_format=inference_request.depth_map_format,
image=depth_data["image"].base64_image,
Expand Down
32 changes: 31 additions & 1 deletion inference/core/models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@

from inference.core import logger
from inference.core.entities.requests.inference import InferenceRequest
from inference.core.entities.responses.inference import InferenceResponse
from inference.core.entities.responses.inference import (
InferenceResponse,
InstanceSegmentationInferenceResponseDC,
ResolvedModel,
)
from inference.core.env import USE_INFERENCE_MODELS
from inference.core.models.types import PreprocessReturnMetadata
from inference.core.telemetry import set_span_attribute, start_span
Expand Down Expand Up @@ -106,6 +110,31 @@ class Model(BaseInference):
clear_cache(): Clears any cache if necessary.
"""

@property
def resolved_model(self) -> Any:
model = getattr(self, "_model", None)
if USE_INFERENCE_MODELS and model is not None:
return getattr(model, "resolved_model", None)
model_id = getattr(self, "endpoint", None) or getattr(self, "model_id", None)
return ResolvedModel(model_id=model_id) if model_id is not None else None

def _attach_resolved_model_metadata(self, responses: Any) -> None:
metadata = self.resolved_model
if metadata is None:
return
resolved_model = ResolvedModel(
model_id=metadata.model_id,
model_package_id=metadata.model_package_id,
backend=metadata.backend,
quantization=metadata.quantization,
)
responses = responses if isinstance(responses, list) else [responses]
for response in responses:
if isinstance(
response, (InferenceResponse, InstanceSegmentationInferenceResponseDC)
):
response.resolved_model = resolved_model

def log(self, m):
"""Prints the given message.

Expand Down Expand Up @@ -181,6 +210,7 @@ def infer_from_request(
if not isinstance(request.image, list) and len(responses) > 0:
responses = responses[0]

self._attach_resolved_model_metadata(responses)
return responses

def make_response(
Expand Down
1 change: 1 addition & 0 deletions inference/core/models/classification_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,7 @@ def infer_from_request(
if not isinstance(request.image, list):
responses = responses[0]

self._attach_resolved_model_metadata(responses)
return responses

def make_response(
Expand Down
5 changes: 4 additions & 1 deletion inference/core/models/inference_models_adapters.py
Original file line number Diff line number Diff line change
Expand Up @@ -1508,6 +1508,7 @@ def infer_from_request(
if not isinstance(request.image, list):
responses = responses[0]

self._attach_resolved_model_metadata(responses)
return responses


Expand Down Expand Up @@ -2040,12 +2041,14 @@ def infer_from_request(
stride=max(1.0, source_fps / window.sample_fps),
)
timeline.sort(key=lambda entry: (entry.start_frame_idx, entry.class_id))
return ActionRecognitionInferenceResponse(
response = ActionRecognitionInferenceResponse(
timeline=timeline,
source_fps=source_fps,
frame_count=frame_count,
windows_classified=windows_classified,
)
self._attach_resolved_model_metadata(response)
return response

def preprocess(self, *args, **kwargs):
raise NotImplementedError(
Expand Down
1 change: 1 addition & 0 deletions inference/core/workflows/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
installs the compat finder and rebinds this module in ``sys.modules`` to the
canonical package so the two names share one object.
"""

from inference._workflows_compat import alias_legacy_root as _alias_legacy_root

_alias_legacy_root(__name__)
1 change: 1 addition & 0 deletions inference/enterprise/workflows/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
but it installs the compat finder so child imports of ``enterprise_blocks``
resolve to canonical modules.
"""

from inference._workflows_compat import install as _install

_install()
1 change: 1 addition & 0 deletions inference/models/clip/clip_inference_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ def infer_from_request(
data = infer_func(**request.dict())
response = make_response_func(data)
response.time = perf_counter() - t1
self._attach_resolved_model_metadata(response)
return response

def make_response(self, embeddings, *args, **kwargs) -> InferenceResponse:
Expand Down
1 change: 1 addition & 0 deletions inference/models/clip/clip_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ def infer_from_request(
data = infer_func(**request.dict())
response = make_response_func(data)
response.time = perf_counter() - t1
self._attach_resolved_model_metadata(response)
return response

def make_response(self, embeddings, *args, **kwargs) -> InferenceResponse:
Expand Down
5 changes: 4 additions & 1 deletion inference/models/doctr/doctr_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,11 @@ def infer_from_request(
for image in request.image:
request_copy.image = image
response.append(self.single_request(request=request_copy))
self._attach_resolved_model_metadata(response)
return response
return self.single_request(request)
response = self.single_request(request)
self._attach_resolved_model_metadata(response)
return response

def single_request(self, request: DoctrOCRInferenceRequest) -> OCRInferenceResponse:
t1 = perf_counter()
Expand Down
8 changes: 5 additions & 3 deletions inference/models/doctr/doctr_model_inference_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,14 @@ def infer_from_request(
) -> Union[OCRInferenceResponse, List]:
if type(request.image) is list:
response = []
request_copy = copy.copy(request)
request_copy = copy(request)
for image in request.image:
request_copy.image = image
response.append(self.single_request(request=request_copy))
return response
return self.single_request(request)
else:
response = self.single_request(request)
self._attach_resolved_model_metadata(response)
return response

def single_request(self, request: DoctrOCRInferenceRequest) -> OCRInferenceResponse:
t1 = perf_counter()
Expand Down
5 changes: 4 additions & 1 deletion inference/models/easy_ocr/easy_ocr.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,11 @@ def infer_from_request(
for image in request.image:
request_copy.image = image
response.append(self.single_request(request=request_copy))
self._attach_resolved_model_metadata(response)
return response
return self.single_request(request)
response = self.single_request(request)
self._attach_resolved_model_metadata(response)
return response

def single_request(self, request: EasyOCRInferenceRequest) -> OCRInferenceResponse:
t1 = perf_counter()
Expand Down
6 changes: 4 additions & 2 deletions inference/models/easy_ocr/easy_ocr_inference_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,10 @@ def infer_from_request(
for image in request.image:
request_copy.image = image
response.append(self.single_request(request=request_copy))
return response
return self.single_request(request)
else:
response = self.single_request(request)
self._attach_resolved_model_metadata(response)
return response

def single_request(self, request: EasyOCRInferenceRequest) -> OCRInferenceResponse:
t1 = perf_counter()
Expand Down
1 change: 1 addition & 0 deletions inference/models/grounding_dino/grounding_dino.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ def infer_from_request(
Perform inference based on the details provided in the request, and return the associated responses.
"""
result = self.infer(**request.dict())
self._attach_resolved_model_metadata(result)
return result

@usage_collector("model")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ def infer_from_request(
Perform inference based on the details provided in the request, and return the associated responses.
"""
result = self.infer(**request.dict())
self._attach_resolved_model_metadata(result)
return result

@usage_collector("model")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,7 @@ def infer_from_request(
data = infer_func(**request.dict())
response = make_response_func(data)
response.time = perf_counter() - t1
self._attach_resolved_model_metadata(response)
return response

def make_response(self, embeddings, *args, **kwargs) -> InferenceResponse:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ def infer_from_request(
data = infer_func(**request.dict())
response = make_response_func(data)
response.time = perf_counter() - t1
self._attach_resolved_model_metadata(response)
return response

def make_response(self, embeddings, *args, **kwargs) -> InferenceResponse:
Expand Down
9 changes: 7 additions & 2 deletions inference/models/sam/segment_anything.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,16 +141,20 @@ def infer_from_request(self, request: SamInferenceRequest):
embedding, _ = self.embed_image(**request.dict())
inference_time = perf_counter() - t1
if request.format == "json":
return SamEmbeddingResponse(
response = SamEmbeddingResponse(
embeddings=embedding.tolist(), time=inference_time
)
self._attach_resolved_model_metadata(response)
return response
elif request.format == "binary":
binary_vector = BytesIO()
np.save(binary_vector, embedding)
binary_vector.seek(0)
return SamEmbeddingResponse(
response = SamEmbeddingResponse(
embeddings=binary_vector.getvalue(), time=inference_time
)
self._attach_resolved_model_metadata(response)
return response
elif isinstance(request, SamSegmentationRequest):
masks, low_res_masks = self.segment_image(**request.dict())
if request.format == "json":
Expand All @@ -174,6 +178,7 @@ def infer_from_request(self, request: SamInferenceRequest):
low_res_masks=[m.tolist() for m in low_res_masks],
time=perf_counter() - t1,
)
self._attach_resolved_model_metadata(response)
return response

def preproc_image(self, image: InferenceRequestImage):
Expand Down
Loading
Loading