diff --git a/inference/core/version.py b/inference/core/version.py index e91e20458e..436e6dde31 100644 --- a/inference/core/version.py +++ b/inference/core/version.py @@ -1,4 +1,4 @@ -__version__ = "1.6.2" +__version__ = "1.6.2-post1" if __name__ == "__main__": diff --git a/requirements/requirements.workflows.txt b/requirements/requirements.workflows.txt index 28b3ebbee9..0952d46fa7 100644 --- a/requirements/requirements.workflows.txt +++ b/requirements/requirements.workflows.txt @@ -1,2 +1,2 @@ # keep in sync with workflows/pyproject.toml version -roboflow-workflows[enterprise]==0.1.2 +roboflow-workflows[enterprise]==0.1.2.post1 diff --git a/workflows/CHANGELOG.md b/workflows/CHANGELOG.md index 61913de8ed..51b7875efd 100644 --- a/workflows/CHANGELOG.md +++ b/workflows/CHANGELOG.md @@ -2,6 +2,14 @@ ## Unreleased +### Added + +- SpaceXAI block (`spacexai@v3`): `grok-4.7` model option with `low`/`medium`/`high`/`xhigh` reasoning effort. Detection reuses the Grok 4.5/4.6 prompt. + +### Fixed + +- SpaceXAI object detection encodes PNG at compression level 9. OpenCV's default compression put large frames over xAI's 25MB upload limit. + --- ## `0.1.2` diff --git a/workflows/pyproject.toml b/workflows/pyproject.toml index 52a2dad475..3ee330e190 100644 --- a/workflows/pyproject.toml +++ b/workflows/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "roboflow-workflows" -version = "0.1.2" +version = "0.1.2-post1" description = "Roboflow Workflows execution engine and block library" readme = "README.md" license = {file = "LICENSE"} diff --git a/workflows/roboflow_workflows/core_steps/models/foundation/spacexai/v3.py b/workflows/roboflow_workflows/core_steps/models/foundation/spacexai/v3.py index 4e8389c29f..518e504db7 100644 --- a/workflows/roboflow_workflows/core_steps/models/foundation/spacexai/v3.py +++ b/workflows/roboflow_workflows/core_steps/models/foundation/spacexai/v3.py @@ -74,7 +74,13 @@ XAI_BASE_URL = "https://api.x.ai/v1" # grok-4.5 `xhigh` excluded: xAI silently downgrades it to `high`. +# grok-4.7 was not re-benchmarked; it reuses the 4.5/4.6 detection prompt. GROK_MODELS = [ + { + "id": "grok-4.7", + "name": "Grok 4.7", + "reasoning_levels": ["low", "medium", "high", "xhigh"], + }, { "id": "grok-4.6", "name": "Grok 4.6", @@ -149,6 +155,7 @@ Images for object detection are sent at original resolution as lossless PNG with `detail: "high"`, matching the vlm-exam benchmark setup for Grok 4.5/4.6. +Grok 4.7 uses the same prompt and image handling. ## Version Differences @@ -278,7 +285,7 @@ class BlockManifest(WorkflowBlockManifest): ] = Field( default="grok-4.6", description="Model to be used", - examples=["grok-4.6", "grok-4.5", "$inputs.grok_model"], + examples=["grok-4.7", "grok-4.6", "grok-4.5", "$inputs.grok_model"], json_schema_extra={ "values_metadata": MODEL_VERSION_METADATA, }, @@ -293,9 +300,10 @@ class BlockManifest(WorkflowBlockManifest): description=( "Optional reasoning effort passed to xAI as " '`reasoning: {"effort": ...}`. Grok models default to "high" and ' - 'cannot disable reasoning. "xhigh" is only supported by grok-4.6. ' - "For requests with a direct xAI key, the request is retried " - "without reasoning when the model rejects the parameter." + 'cannot disable reasoning. "xhigh" is not supported by grok-4.5. ' + "With a direct xAI key, if the model rejects the `reasoning` " + "parameter the request is retried without it, so the model falls " + "back to its default effort." ), examples=["low", "high"], ) @@ -563,7 +571,15 @@ def encode_image_for_task( def _encode_image_to_png_bytes(image: np.ndarray) -> bytes: - _, encoded_image = cv2.imencode(".png", image) + # OpenCV's default PNG compression (level 1) puts the large drone frames + # in the detection benchmark over xAI's 25MB upload limit, so those + # requests 400. Level 9 is still lossless and stays under the limit, + # matching the benchmark's PIL encoder. + _, encoded_image = cv2.imencode( + ".png", + image, + [cv2.IMWRITE_PNG_COMPRESSION, 9], + ) return encoded_image.tobytes() diff --git a/workflows/tests/unit_tests/core_steps/common/test_reasoning_contract.py b/workflows/tests/unit_tests/core_steps/common/test_reasoning_contract.py index 6f32662fdd..59ef54500a 100644 --- a/workflows/tests/unit_tests/core_steps/common/test_reasoning_contract.py +++ b/workflows/tests/unit_tests/core_steps/common/test_reasoning_contract.py @@ -13,6 +13,7 @@ from roboflow_workflows.core_steps.models.foundation.openai import v6 as openai_v6 from roboflow_workflows.core_steps.models.foundation.qwen_vlm import v3 as qwen_vlm_v3 from roboflow_workflows.core_steps.models.foundation.spacexai import v2 as spacexai_v2 +from roboflow_workflows.core_steps.models.foundation.spacexai import v3 as spacexai_v3 from roboflow_workflows.core_steps.models.foundation.zai_vlm import v1 as zai_vlm_v1 _LEVELS = {"model-a": ["low", "high"], "model-b": []} @@ -71,6 +72,12 @@ def test_validate_reasoning_level_rejects_model_without_levels(): spacexai_v2.MODEL_VERSION_METADATA, None, ), + "spacexai@v3": ( + spacexai_v3.MODEL_REASONING_LEVELS, + spacexai_v3.REASONING_EFFORT_VALUES, + spacexai_v3.MODEL_VERSION_METADATA, + None, + ), "meta_vlm@v2": ( meta_vlm_v2.MODEL_REASONING_LEVELS, meta_vlm_v2.REASONING_EFFORT_OPTIONS, @@ -141,6 +148,7 @@ def test_block_default_level_is_valid_for_every_model(block): "open_ai@v6": openai_v6.BlockManifest, "google_gemini@v5": gemini_v5.BlockManifest, "spacexai@v2": spacexai_v2.BlockManifest, + "spacexai@v3": spacexai_v3.BlockManifest, "meta_vlm@v2": meta_vlm_v2.BlockManifest, "qwen_vlm@v3": qwen_vlm_v3.BlockManifest, } @@ -154,7 +162,7 @@ def _manifest(block_type: str, **overrides): "task_type": "unconstrained", "prompt": "describe", } - if block_type == "spacexai@v2": + if block_type in {"spacexai@v2", "spacexai@v3"}: spec["api_key"] = "$inputs.xai_api_key" spec.update(overrides) return _MANIFESTS[block_type].model_validate(spec) @@ -166,6 +174,7 @@ def _manifest(block_type: str, **overrides): ("open_ai@v6", {"model_version": "$inputs.model", "reasoning_effort": "xhigh"}), ("open_ai@v6", {"model_version": "gpt-4o"}), ("qwen_vlm@v3", {"backend": "native"}), + ("spacexai@v3", {"model_version": "grok-4.7", "reasoning_effort": "xhigh"}), ], ) def test_manifest_accepts_edges_rejects_cannot_cover(block_type, overrides): @@ -194,6 +203,7 @@ def test_manifest_accepts_edges_rejects_cannot_cover(block_type, overrides): {"model_version": "gemini-2.5-pro", "thinking_level": "low"}, ), ("spacexai@v2", {"model_version": "grok-4.5", "reasoning_effort": "xhigh"}), + ("spacexai@v3", {"model_version": "grok-4.5", "reasoning_effort": "xhigh"}), ( "meta_vlm@v2", {"model_version": "Muse Glimmer", "reasoning_effort": "minimal"}, diff --git a/workflows/tests/unit_tests/core_steps/models/foundation/test_spacexai_v3.py b/workflows/tests/unit_tests/core_steps/models/foundation/test_spacexai_v3.py index 40e5bbd3fa..4d47cb1ea2 100644 --- a/workflows/tests/unit_tests/core_steps/models/foundation/test_spacexai_v3.py +++ b/workflows/tests/unit_tests/core_steps/models/foundation/test_spacexai_v3.py @@ -8,11 +8,13 @@ from unittest.mock import MagicMock, patch +import cv2 import numpy as np import pytest from roboflow_workflows.core_steps.models.foundation.spacexai.v3 import ( BlockManifest, SpaceXAIBlockV3, + _encode_image_to_png_bytes, ) from roboflow_workflows.execution_engine.entities.base import WorkflowImageData @@ -174,3 +176,12 @@ def test_run_reports_error_status_for_undecodable_detection_output(mock_promptin assert result[0]["predictions"] is None assert result[0]["error_status"] is True + + +def test_detection_png_uses_compression_that_fits_xai_upload_limit(): + image = np.random.default_rng(0).integers(0, 256, (128, 128, 3), dtype=np.uint8) + + encoded = _encode_image_to_png_bytes(image) + _, compressed = cv2.imencode(".png", image, [cv2.IMWRITE_PNG_COMPRESSION, 9]) + + assert encoded == compressed.tobytes() diff --git a/workflows/uv.lock b/workflows/uv.lock index b5e1a8fae3..782a22631d 100644 --- a/workflows/uv.lock +++ b/workflows/uv.lock @@ -2430,6 +2430,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/36/54/0169bc772ec491108b62f644f8ecf1fe5d8ae5ebafde2ee2142210166903/pillow-12.3.0-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:04f01d28a6aaff387bf842a13be313df23ba0597a44f1a976c9feb3c6ff4711a", size = 7231786, upload-time = "2026-07-01T11:56:35.046Z" }, ] +[[package]] +name = "pkgconfig" +version = "1.6.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/52/fd/0adde075cd3bfecd557bc7d757e00e231d34d8a6edb4c8d1642759254c21/pkgconfig-1.6.0.tar.gz", hash = "sha256:4a5a6631ce937fafac457104a40d558785a658bbdca5c49b6295bc3fd651907f", size = 5691, upload-time = "2026-03-06T11:26:01.194Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/75/6f/f7ec07fba48f07c555cc4099481df644fbbc12067879072c17ac229f6556/pkgconfig-1.6.0-py3-none-any.whl", hash = "sha256:98e71754855e9563838d952a160eb577edabb57782e49853edb5381927e6bea1", size = 7086, upload-time = "2026-03-06T11:26:07.688Z" }, +] + [[package]] name = "platformdirs" version = "4.11.8" @@ -3060,6 +3069,7 @@ version = "2.2.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "cffi" }, + { name = "pkgconfig" }, ] sdist = { url = "https://files.pythonhosted.org/packages/7b/88/f73dae807ec68b228fba72507105e3ba80a561dc0bade0004ce24fd118fc/pyvips-2.2.3.tar.gz", hash = "sha256:43bceced0db492654c93008246a58a508e0373ae1621116b87b322f2ac72212f", size = 56626, upload-time = "2024-04-28T11:19:58.158Z" } @@ -3401,7 +3411,7 @@ wheels = [ [[package]] name = "roboflow-workflows" -version = "0.1.2" +version = "0.1.2.post1" source = { editable = "." } dependencies = [ { name = "anthropic" },