From eedf55dd6a00ac4d0b1c62604e82af3e69199868 Mon Sep 17 00:00:00 2001 From: JiriStipek <567776@mail.muni.cz> Date: Sat, 14 Mar 2026 17:28:11 +0100 Subject: [PATCH 01/11] feat: add f32 or f16 --- rationai/resources/models.py | 52 ++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/rationai/resources/models.py b/rationai/resources/models.py index 47be222..70dd91c 100644 --- a/rationai/resources/models.py +++ b/rationai/resources/models.py @@ -1,3 +1,5 @@ +from typing import Any, Literal + import lz4.frame import numpy as np from httpx import USE_CLIENT_DEFAULT @@ -64,6 +66,31 @@ def segment_image( lz4.frame.decompress(response.content), dtype=np.float16 ).reshape(-1, h, w) + def embed_image( + self, + model: str, + image: Image | NDArray[np.uint8], + output_dtype: Literal["float16", "float32"] = "float32", + timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, + ) -> NDArray[np.floating[Any]]: + """Compute an embedding vector for an image using the specified model. + + Args: + model: The name of the model to use for embedding. + image: The image to embed. It must be uint8 RGB image. + output_dtype: Output numpy dtype for embeddings ("float16" or "float32"). + timeout: Optional timeout for the request. + + Returns: + NDArray[np.floating[Any]]: The embedding vector as a 1-D numpy array. + """ + data = image.tobytes() + compressed_data = lz4.frame.compress(data) + response = self._post(model, data=compressed_data, timeout=timeout) + response.raise_for_status() + np_dtype = np.float16 if output_dtype == "float16" else np.float32 + return np.array(response.json(), dtype=np_dtype) + class AsyncModels(AsyncAPIResource): async def classify_image( @@ -119,3 +146,28 @@ async def segment_image( return np.frombuffer( lz4.frame.decompress(response.content), dtype=np.float16 ).reshape(-1, h, w) + + async def embed_image( + self, + model: str, + image: Image | NDArray[np.uint8], + output_dtype: Literal["float16", "float32"] = "float32", + timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, + ) -> NDArray[np.floating[Any]]: + """Compute an embedding vector for an image using the specified model. + + Args: + model: The name of the model to use for embedding. + image: The image to embed. It must be uint8 RGB image. + output_dtype: Output numpy dtype for embeddings ("float16" or "float32"). + timeout: Optional timeout for the request. + + Returns: + NDArray[np.floating[Any]]: The embedding vector as a 1-D numpy array. + """ + data = image.tobytes() + compressed_data = lz4.frame.compress(data) + response = await self._post(model, data=compressed_data, timeout=timeout) + response.raise_for_status() + np_dtype = np.float16 if output_dtype == "float16" else np.float32 + return np.array(response.json(), dtype=np_dtype) From 75c10c3ea832ade0ddd36264c150ae3d9e225537 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20=C5=A0t=C3=ADpek?= <91186480+Jurgee@users.noreply.github.com> Date: Sat, 14 Mar 2026 17:33:41 +0100 Subject: [PATCH 02/11] Update rationai/resources/models.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- rationai/resources/models.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rationai/resources/models.py b/rationai/resources/models.py index 70dd91c..b6e3cc5 100644 --- a/rationai/resources/models.py +++ b/rationai/resources/models.py @@ -88,8 +88,9 @@ def embed_image( compressed_data = lz4.frame.compress(data) response = self._post(model, data=compressed_data, timeout=timeout) response.raise_for_status() - np_dtype = np.float16 if output_dtype == "float16" else np.float32 - return np.array(response.json(), dtype=np_dtype) + if output_dtype not in ("float16", "float32"): + raise ValueError('output_dtype must be one of "float16" or "float32"') + return np.array(response.json(), dtype=np.dtype(output_dtype)) class AsyncModels(AsyncAPIResource): From 1bc4eeaac7dc0ed5f5112eae7f88d6120127a84c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ji=C5=99=C3=AD=20=C5=A0t=C3=ADpek?= <91186480+Jurgee@users.noreply.github.com> Date: Sat, 14 Mar 2026 17:33:50 +0100 Subject: [PATCH 03/11] Update rationai/resources/models.py Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- rationai/resources/models.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/rationai/resources/models.py b/rationai/resources/models.py index b6e3cc5..f888d74 100644 --- a/rationai/resources/models.py +++ b/rationai/resources/models.py @@ -170,5 +170,6 @@ async def embed_image( compressed_data = lz4.frame.compress(data) response = await self._post(model, data=compressed_data, timeout=timeout) response.raise_for_status() - np_dtype = np.float16 if output_dtype == "float16" else np.float32 - return np.array(response.json(), dtype=np_dtype) + if output_dtype not in ("float16", "float32"): + raise ValueError('output_dtype must be one of "float16" or "float32"') + return np.array(response.json(), dtype=np.dtype(output_dtype)) From 6b1e7479a5414d664ac21ebfb6f9755c19a5bfb4 Mon Sep 17 00:00:00 2001 From: JiriStipek <567776@mail.muni.cz> Date: Sun, 15 Mar 2026 19:28:29 +0100 Subject: [PATCH 04/11] fix: usage of dtype --- rationai/resources/models.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/rationai/resources/models.py b/rationai/resources/models.py index 70dd91c..f023cfd 100644 --- a/rationai/resources/models.py +++ b/rationai/resources/models.py @@ -1,11 +1,11 @@ -from typing import Any, Literal +from typing import Any import lz4.frame import numpy as np from httpx import USE_CLIENT_DEFAULT from httpx._client import UseClientDefault from httpx._types import TimeoutTypes -from numpy.typing import NDArray +from numpy.typing import DTypeLike, NDArray from PIL.Image import Image from rationai._resource import APIResource, AsyncAPIResource @@ -70,7 +70,7 @@ def embed_image( self, model: str, image: Image | NDArray[np.uint8], - output_dtype: Literal["float16", "float32"] = "float32", + output_dtype: DTypeLike = np.float32, timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, ) -> NDArray[np.floating[Any]]: """Compute an embedding vector for an image using the specified model. @@ -78,7 +78,7 @@ def embed_image( Args: model: The name of the model to use for embedding. image: The image to embed. It must be uint8 RGB image. - output_dtype: Output numpy dtype for embeddings ("float16" or "float32"). + output_dtype: Output numpy dtype for embeddings (e.g. np.float16, np.float32). timeout: Optional timeout for the request. Returns: @@ -88,8 +88,7 @@ def embed_image( compressed_data = lz4.frame.compress(data) response = self._post(model, data=compressed_data, timeout=timeout) response.raise_for_status() - np_dtype = np.float16 if output_dtype == "float16" else np.float32 - return np.array(response.json(), dtype=np_dtype) + return np.array(response.json(), dtype=output_dtype) class AsyncModels(AsyncAPIResource): @@ -151,7 +150,7 @@ async def embed_image( self, model: str, image: Image | NDArray[np.uint8], - output_dtype: Literal["float16", "float32"] = "float32", + output_dtype: DTypeLike = np.float32, timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, ) -> NDArray[np.floating[Any]]: """Compute an embedding vector for an image using the specified model. @@ -159,7 +158,7 @@ async def embed_image( Args: model: The name of the model to use for embedding. image: The image to embed. It must be uint8 RGB image. - output_dtype: Output numpy dtype for embeddings ("float16" or "float32"). + output_dtype: Output numpy dtype for embeddings (e.g. np.float16, np.float32). timeout: Optional timeout for the request. Returns: @@ -169,5 +168,4 @@ async def embed_image( compressed_data = lz4.frame.compress(data) response = await self._post(model, data=compressed_data, timeout=timeout) response.raise_for_status() - np_dtype = np.float16 if output_dtype == "float16" else np.float32 - return np.array(response.json(), dtype=np_dtype) + return np.array(response.json(), dtype=output_dtype) From 6c043a882da3b1b4857172663161954e00b2635a Mon Sep 17 00:00:00 2001 From: JiriStipek <567776@mail.muni.cz> Date: Wed, 18 Mar 2026 11:22:18 +0100 Subject: [PATCH 05/11] fix: switch embed_image to binary lz4 serialization with dtype negotiation --- rationai/resources/models.py | 41 +++++++++++++++++++++++++++--------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/rationai/resources/models.py b/rationai/resources/models.py index f023cfd..68b9726 100644 --- a/rationai/resources/models.py +++ b/rationai/resources/models.py @@ -1,8 +1,8 @@ -from typing import Any +from typing import Any, cast import lz4.frame import numpy as np -from httpx import USE_CLIENT_DEFAULT +from httpx import USE_CLIENT_DEFAULT, Response from httpx._client import UseClientDefault from httpx._types import TimeoutTypes from numpy.typing import DTypeLike, NDArray @@ -11,6 +11,17 @@ from rationai._resource import APIResource, AsyncAPIResource +def _parse_embedding_response( + response: Response, + output_dtype: DTypeLike, +) -> NDArray[np.floating[Any]]: + payload = lz4.frame.decompress(response.content) + return cast( + "NDArray[np.floating[Any]]", + np.frombuffer(payload, dtype=np.dtype(output_dtype)), + ) + + class Models(APIResource): def classify_image( self, @@ -84,11 +95,16 @@ def embed_image( Returns: NDArray[np.floating[Any]]: The embedding vector as a 1-D numpy array. """ - data = image.tobytes() - compressed_data = lz4.frame.compress(data) - response = self._post(model, data=compressed_data, timeout=timeout) + image_array = np.asarray(image, dtype=np.uint8) + compressed_data = lz4.frame.compress(image_array.tobytes()) + response = self._post( + model, + data=compressed_data, + headers={"x-output-dtype": np.dtype(output_dtype).name}, + timeout=timeout, + ) response.raise_for_status() - return np.array(response.json(), dtype=output_dtype) + return _parse_embedding_response(response, output_dtype) class AsyncModels(AsyncAPIResource): @@ -164,8 +180,13 @@ async def embed_image( Returns: NDArray[np.floating[Any]]: The embedding vector as a 1-D numpy array. """ - data = image.tobytes() - compressed_data = lz4.frame.compress(data) - response = await self._post(model, data=compressed_data, timeout=timeout) + image_array = np.asarray(image, dtype=np.uint8) + compressed_data = lz4.frame.compress(image_array.tobytes()) + response = await self._post( + model, + data=compressed_data, + headers={"x-output-dtype": np.dtype(output_dtype).name}, + timeout=timeout, + ) response.raise_for_status() - return np.array(response.json(), dtype=output_dtype) + return _parse_embedding_response(response, output_dtype) From 0ecebd64c7f1727d3e8979a3d2c443e9ea830dc8 Mon Sep 17 00:00:00 2001 From: JiriStipek <567776@mail.muni.cz> Date: Wed, 18 Mar 2026 12:21:21 +0100 Subject: [PATCH 06/11] fix: correct type annotation --- rationai/resources/models.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/rationai/resources/models.py b/rationai/resources/models.py index 68b9726..3edbc88 100644 --- a/rationai/resources/models.py +++ b/rationai/resources/models.py @@ -11,15 +11,12 @@ from rationai._resource import APIResource, AsyncAPIResource -def _parse_embedding_response( +def _parse_embedding_response[DType: np.generic]( response: Response, - output_dtype: DTypeLike, -) -> NDArray[np.floating[Any]]: + output_dtype: type[DType], +) -> NDArray[DType]: payload = lz4.frame.decompress(response.content) - return cast( - "NDArray[np.floating[Any]]", - np.frombuffer(payload, dtype=np.dtype(output_dtype)), - ) + return cast("NDArray[DType]", np.frombuffer(payload, dtype=np.dtype(output_dtype))) class Models(APIResource): @@ -104,7 +101,10 @@ def embed_image( timeout=timeout, ) response.raise_for_status() - return _parse_embedding_response(response, output_dtype) + return cast( + "NDArray[np.floating[Any]]", + _parse_embedding_response(response, np.dtype(output_dtype).type), + ) class AsyncModels(AsyncAPIResource): @@ -189,4 +189,7 @@ async def embed_image( timeout=timeout, ) response.raise_for_status() - return _parse_embedding_response(response, output_dtype) + return cast( + "NDArray[np.floating[Any]]", + _parse_embedding_response(response, np.dtype(output_dtype).type), + ) From 616d50663b816805e7b0b0e9ebee4b86769265fa Mon Sep 17 00:00:00 2001 From: JiriStipek <567776@mail.muni.cz> Date: Wed, 18 Mar 2026 20:34:47 +0100 Subject: [PATCH 07/11] fix --- rationai/resources/models.py | 82 +++++++++++++++++++++--------------- 1 file changed, 47 insertions(+), 35 deletions(-) diff --git a/rationai/resources/models.py b/rationai/resources/models.py index 3edbc88..dab9248 100644 --- a/rationai/resources/models.py +++ b/rationai/resources/models.py @@ -1,24 +1,16 @@ -from typing import Any, cast +from typing import cast import lz4.frame import numpy as np -from httpx import USE_CLIENT_DEFAULT, Response +from httpx import USE_CLIENT_DEFAULT from httpx._client import UseClientDefault from httpx._types import TimeoutTypes -from numpy.typing import DTypeLike, NDArray +from numpy.typing import NDArray from PIL.Image import Image from rationai._resource import APIResource, AsyncAPIResource -def _parse_embedding_response[DType: np.generic]( - response: Response, - output_dtype: type[DType], -) -> NDArray[DType]: - payload = lz4.frame.decompress(response.content) - return cast("NDArray[DType]", np.frombuffer(payload, dtype=np.dtype(output_dtype))) - - class Models(APIResource): def classify_image( self, @@ -37,8 +29,8 @@ def classify_image( (float | dict[str, float]): The classification result as a single float (for binary classification) or probabilities for each class. """ - data = image.tobytes() - compressed_data = lz4.frame.compress(data) + image_array = np.asarray(image, dtype=np.uint8) + compressed_data = lz4.frame.compress(image_array.tobytes()) response = self._post(model, data=compressed_data, timeout=timeout) response.raise_for_status() return response.json() @@ -65,8 +57,8 @@ def segment_image( else: h, w = image.shape[:2] - data = image.tobytes() - compressed_data = lz4.frame.compress(data) + image_array = np.asarray(image, dtype=np.uint8) + compressed_data = lz4.frame.compress(image_array.tobytes()) response = self._post(model, data=compressed_data, timeout=timeout) response.raise_for_status() @@ -74,13 +66,13 @@ def segment_image( lz4.frame.decompress(response.content), dtype=np.float16 ).reshape(-1, h, w) - def embed_image( + def embed_image[DType: np.generic]( self, model: str, image: Image | NDArray[np.uint8], - output_dtype: DTypeLike = np.float32, + output_dtype: type[DType] = np.float32, timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, - ) -> NDArray[np.floating[Any]]: + ) -> NDArray[DType]: """Compute an embedding vector for an image using the specified model. Args: @@ -90,7 +82,7 @@ def embed_image( timeout: Optional timeout for the request. Returns: - NDArray[np.floating[Any]]: The embedding vector as a 1-D numpy array. + NDArray[DType]: The embedding vector as a 1-D numpy array. """ image_array = np.asarray(image, dtype=np.uint8) compressed_data = lz4.frame.compress(image_array.tobytes()) @@ -101,10 +93,19 @@ def embed_image( timeout=timeout, ) response.raise_for_status() - return cast( - "NDArray[np.floating[Any]]", - _parse_embedding_response(response, np.dtype(output_dtype).type), + + payload = lz4.frame.decompress(response.content) + embedding = np.frombuffer(payload, dtype=np.dtype(output_dtype)) + + response_shape = response.headers.get("x-output-shape") or response.headers.get( + "x-array-shape" ) + if response_shape: + shape = tuple(int(d) for d in response_shape.split(",") if d) + if shape: + embedding = embedding.reshape(shape) + + return cast("NDArray[DType]", embedding) class AsyncModels(AsyncAPIResource): @@ -125,8 +126,8 @@ async def classify_image( (float | dict[str, float]): The classification result as a single float (for binary classification) or probabilities for each class. """ - data = image.tobytes() - compressed_data = lz4.frame.compress(data) + image_array = np.asarray(image, dtype=np.uint8) + compressed_data = lz4.frame.compress(image_array.tobytes()) response = await self._post(model, data=compressed_data, timeout=timeout) response.raise_for_status() return response.json() @@ -153,8 +154,8 @@ async def segment_image( else: h, w = image.shape[:2] - data = image.tobytes() - compressed_data = lz4.frame.compress(data) + image_array = np.asarray(image, dtype=np.uint8) + compressed_data = lz4.frame.compress(image_array.tobytes()) response = await self._post(model, data=compressed_data, timeout=timeout) response.raise_for_status() @@ -162,13 +163,13 @@ async def segment_image( lz4.frame.decompress(response.content), dtype=np.float16 ).reshape(-1, h, w) - async def embed_image( + async def embed_image[DType: np.generic]( self, model: str, image: Image | NDArray[np.uint8], - output_dtype: DTypeLike = np.float32, + output_dtype: type[DType] = np.float32, timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, - ) -> NDArray[np.floating[Any]]: + ) -> NDArray[DType]: """Compute an embedding vector for an image using the specified model. Args: @@ -178,18 +179,29 @@ async def embed_image( timeout: Optional timeout for the request. Returns: - NDArray[np.floating[Any]]: The embedding vector as a 1-D numpy array. + NDArray[DType]: The embedding vector as a 1-D numpy array. """ image_array = np.asarray(image, dtype=np.uint8) compressed_data = lz4.frame.compress(image_array.tobytes()) response = await self._post( model, data=compressed_data, - headers={"x-output-dtype": np.dtype(output_dtype).name}, + headers={ + "x-output-dtype": np.dtype(output_dtype).name + }, # send output dtype hint to server timeout=timeout, ) response.raise_for_status() - return cast( - "NDArray[np.floating[Any]]", - _parse_embedding_response(response, np.dtype(output_dtype).type), - ) + + payload = lz4.frame.decompress(response.content) + embedding = np.frombuffer(payload, dtype=np.dtype(output_dtype)) + + response_shape = response.headers.get("x-output-shape") or response.headers.get( + "x-array-shape" + ) # check for shape hint in response headers and reshape if present + if response_shape: + shape = tuple(int(d) for d in response_shape.split(",") if d) + if shape: + embedding = embedding.reshape(shape) + + return cast("NDArray[DType]", embedding) From 14ec21702f0f89b41f255544b5e1753b48053766 Mon Sep 17 00:00:00 2001 From: JiriStipek <567776@mail.muni.cz> Date: Wed, 18 Mar 2026 20:38:57 +0100 Subject: [PATCH 08/11] lint fix --- rationai/resources/models.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/rationai/resources/models.py b/rationai/resources/models.py index dab9248..deac5a2 100644 --- a/rationai/resources/models.py +++ b/rationai/resources/models.py @@ -70,7 +70,7 @@ def embed_image[DType: np.generic]( self, model: str, image: Image | NDArray[np.uint8], - output_dtype: type[DType] = np.float32, + output_dtype: type[DType] | None = None, timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, ) -> NDArray[DType]: """Compute an embedding vector for an image using the specified model. @@ -84,6 +84,9 @@ def embed_image[DType: np.generic]( Returns: NDArray[DType]: The embedding vector as a 1-D numpy array. """ + if output_dtype is None: + output_dtype = cast("type[DType]", np.float32) + image_array = np.asarray(image, dtype=np.uint8) compressed_data = lz4.frame.compress(image_array.tobytes()) response = self._post( @@ -167,7 +170,7 @@ async def embed_image[DType: np.generic]( self, model: str, image: Image | NDArray[np.uint8], - output_dtype: type[DType] = np.float32, + output_dtype: type[DType] | None = None, timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, ) -> NDArray[DType]: """Compute an embedding vector for an image using the specified model. @@ -181,6 +184,9 @@ async def embed_image[DType: np.generic]( Returns: NDArray[DType]: The embedding vector as a 1-D numpy array. """ + if output_dtype is None: + output_dtype = cast("type[DType]", np.float32) + image_array = np.asarray(image, dtype=np.uint8) compressed_data = lz4.frame.compress(image_array.tobytes()) response = await self._post( From 195a9c323f18610c5c76cc0cfbb705f03d8894a5 Mon Sep 17 00:00:00 2001 From: JiriStipek <567776@mail.muni.cz> Date: Thu, 19 Mar 2026 15:54:21 +0100 Subject: [PATCH 09/11] fix: review comments --- rationai/resources/models.py | 52 +++++++++++------------------------- 1 file changed, 15 insertions(+), 37 deletions(-) diff --git a/rationai/resources/models.py b/rationai/resources/models.py index deac5a2..f011f8e 100644 --- a/rationai/resources/models.py +++ b/rationai/resources/models.py @@ -29,8 +29,7 @@ def classify_image( (float | dict[str, float]): The classification result as a single float (for binary classification) or probabilities for each class. """ - image_array = np.asarray(image, dtype=np.uint8) - compressed_data = lz4.frame.compress(image_array.tobytes()) + compressed_data = lz4.frame.compress(image.tobytes()) response = self._post(model, data=compressed_data, timeout=timeout) response.raise_for_status() return response.json() @@ -57,8 +56,7 @@ def segment_image( else: h, w = image.shape[:2] - image_array = np.asarray(image, dtype=np.uint8) - compressed_data = lz4.frame.compress(image_array.tobytes()) + compressed_data = lz4.frame.compress(image.tobytes()) response = self._post(model, data=compressed_data, timeout=timeout) response.raise_for_status() @@ -70,7 +68,7 @@ def embed_image[DType: np.generic]( self, model: str, image: Image | NDArray[np.uint8], - output_dtype: type[DType] | None = None, + output_dtype: type[DType] = np.float32, # type: ignore[assignment] timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, ) -> NDArray[DType]: """Compute an embedding vector for an image using the specified model. @@ -84,11 +82,7 @@ def embed_image[DType: np.generic]( Returns: NDArray[DType]: The embedding vector as a 1-D numpy array. """ - if output_dtype is None: - output_dtype = cast("type[DType]", np.float32) - - image_array = np.asarray(image, dtype=np.uint8) - compressed_data = lz4.frame.compress(image_array.tobytes()) + compressed_data = lz4.frame.compress(image.tobytes()) response = self._post( model, data=compressed_data, @@ -98,15 +92,11 @@ def embed_image[DType: np.generic]( response.raise_for_status() payload = lz4.frame.decompress(response.content) - embedding = np.frombuffer(payload, dtype=np.dtype(output_dtype)) + embedding = np.frombuffer(payload, dtype=output_dtype) - response_shape = response.headers.get("x-output-shape") or response.headers.get( - "x-array-shape" - ) + response_shape = response.headers.get("x-output-shape") if response_shape: - shape = tuple(int(d) for d in response_shape.split(",") if d) - if shape: - embedding = embedding.reshape(shape) + embedding = embedding.reshape(eval(response_shape)) return cast("NDArray[DType]", embedding) @@ -129,8 +119,7 @@ async def classify_image( (float | dict[str, float]): The classification result as a single float (for binary classification) or probabilities for each class. """ - image_array = np.asarray(image, dtype=np.uint8) - compressed_data = lz4.frame.compress(image_array.tobytes()) + compressed_data = lz4.frame.compress(image.tobytes()) response = await self._post(model, data=compressed_data, timeout=timeout) response.raise_for_status() return response.json() @@ -157,8 +146,7 @@ async def segment_image( else: h, w = image.shape[:2] - image_array = np.asarray(image, dtype=np.uint8) - compressed_data = lz4.frame.compress(image_array.tobytes()) + compressed_data = lz4.frame.compress(image.tobytes()) response = await self._post(model, data=compressed_data, timeout=timeout) response.raise_for_status() @@ -170,7 +158,7 @@ async def embed_image[DType: np.generic]( self, model: str, image: Image | NDArray[np.uint8], - output_dtype: type[DType] | None = None, + output_dtype: type[DType] = np.float32, # type: ignore[assignment] timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT, ) -> NDArray[DType]: """Compute an embedding vector for an image using the specified model. @@ -184,30 +172,20 @@ async def embed_image[DType: np.generic]( Returns: NDArray[DType]: The embedding vector as a 1-D numpy array. """ - if output_dtype is None: - output_dtype = cast("type[DType]", np.float32) - - image_array = np.asarray(image, dtype=np.uint8) - compressed_data = lz4.frame.compress(image_array.tobytes()) + compressed_data = lz4.frame.compress(image.tobytes()) response = await self._post( model, data=compressed_data, - headers={ - "x-output-dtype": np.dtype(output_dtype).name - }, # send output dtype hint to server + headers={"x-output-dtype": np.dtype(output_dtype).name}, timeout=timeout, ) response.raise_for_status() payload = lz4.frame.decompress(response.content) - embedding = np.frombuffer(payload, dtype=np.dtype(output_dtype)) + embedding = np.frombuffer(payload, dtype=output_dtype) - response_shape = response.headers.get("x-output-shape") or response.headers.get( - "x-array-shape" - ) # check for shape hint in response headers and reshape if present + response_shape = response.headers.get("x-output-shape") if response_shape: - shape = tuple(int(d) for d in response_shape.split(",") if d) - if shape: - embedding = embedding.reshape(shape) + embedding = embedding.reshape(eval(response_shape)) return cast("NDArray[DType]", embedding) From 3a75c55dae8bfab1cd0a696ffb9298ccb0bb4fad Mon Sep 17 00:00:00 2001 From: JiriStipek <567776@mail.muni.cz> Date: Thu, 19 Mar 2026 21:18:40 +0100 Subject: [PATCH 10/11] fix: remove improper condition --- rationai/resources/models.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/rationai/resources/models.py b/rationai/resources/models.py index f011f8e..a9d219e 100644 --- a/rationai/resources/models.py +++ b/rationai/resources/models.py @@ -94,9 +94,8 @@ def embed_image[DType: np.generic]( payload = lz4.frame.decompress(response.content) embedding = np.frombuffer(payload, dtype=output_dtype) - response_shape = response.headers.get("x-output-shape") - if response_shape: - embedding = embedding.reshape(eval(response_shape)) + response_shape = response.headers["x-output-shape"] + embedding = embedding.reshape(eval(response_shape)) return cast("NDArray[DType]", embedding) @@ -184,8 +183,7 @@ async def embed_image[DType: np.generic]( payload = lz4.frame.decompress(response.content) embedding = np.frombuffer(payload, dtype=output_dtype) - response_shape = response.headers.get("x-output-shape") - if response_shape: - embedding = embedding.reshape(eval(response_shape)) + response_shape = response.headers["x-output-shape"] + embedding = embedding.reshape(eval(response_shape)) return cast("NDArray[DType]", embedding) From 05f0e67f1dd7bf25e52675fbebc8bbdba52d7561 Mon Sep 17 00:00:00 2001 From: JiriStipek <567776@mail.muni.cz> Date: Thu, 19 Mar 2026 22:19:24 +0100 Subject: [PATCH 11/11] fix: function description --- rationai/resources/models.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/rationai/resources/models.py b/rationai/resources/models.py index a9d219e..3a297a7 100644 --- a/rationai/resources/models.py +++ b/rationai/resources/models.py @@ -80,7 +80,8 @@ def embed_image[DType: np.generic]( timeout: Optional timeout for the request. Returns: - NDArray[DType]: The embedding vector as a 1-D numpy array. + NDArray[DType]: The embedding array reshaped according to + the `x-output-shape` response header. """ compressed_data = lz4.frame.compress(image.tobytes()) response = self._post( @@ -169,7 +170,8 @@ async def embed_image[DType: np.generic]( timeout: Optional timeout for the request. Returns: - NDArray[DType]: The embedding vector as a 1-D numpy array. + NDArray[DType]: The embedding array reshaped according to + the `x-output-shape` response header. """ compressed_data = lz4.frame.compress(image.tobytes()) response = await self._post(