fix(openrouter): accept only PNG and JPEG images for TypeSafe Jev decision models

This commit is contained in:
mtrefilek 2026-10-29 05:23:54 -05:00
parent 5d1a081334
commit 029d3c4a7e
4 changed files with 49 additions and 2 deletions

View file

@ -612,7 +612,7 @@ def _has_media_type_in(media_types: frozenset[str], image: DecisionsImage) -> bo
def _checked_image_types(call: DecisionsCall) -> DecisionsCall:
media_types: Final = call.provider_config.image_media_types
media_types: Final = call.provider_config.image_media_types_for(call.model)
if media_types is None:
return call
found: Final = (image_media_type(image) for image in input_images(call.ir_request.input))

View file

@ -649,6 +649,9 @@ class BaseDecisionsConfig(ABC):
) -> bool:
return self.supports_images
def image_media_types_for(self, model: str) -> frozenset[str] | None:
return self.image_media_types
def supports_assets(self, headers: Mapping[str, str]) -> bool:
return False

View file

@ -3,6 +3,8 @@ from typing import Final
from litellm.llms.base_llm.decisions.transformation import BaseDecisionsConfig
from litellm.types.decisions import DecisionsIRResponse
_TYPESAFE_IMAGE_MEDIA_TYPES: Final = frozenset({"image/png", "image/jpeg", "image/jpg"})
class OpenRouterDecisionsConfig(BaseDecisionsConfig):
path = "/alpha/decisions"
@ -16,6 +18,11 @@ class OpenRouterDecisionsConfig(BaseDecisionsConfig):
def get_default_api_base(self) -> str | None:
return "https://openrouter.ai/api"
def image_media_types_for(self, model: str) -> frozenset[str] | None:
if model.removeprefix("~").startswith("typesafe/"):
return _TYPESAFE_IMAGE_MEDIA_TYPES
return self.image_media_types
def provider_reported_cost(self, response: DecisionsIRResponse) -> float | None:
cost: Final = response.usage.extra.get("cost") if response.usage is not None else None
if not isinstance(cost, (int, float)):

View file

@ -4065,7 +4065,7 @@ async def test_hosted_vllm_decisions_sends_input_images_to_systemone(
_STATE_IMAGE_PROVIDERS: Final = (
("openrouter/typesafe/jev-1.14", "https://openrouter.ai/api/alpha/decisions", "typesafe/jev-1.14"),
("openrouter/cloudflare/clef", "https://openrouter.ai/api/alpha/decisions", "cloudflare/clef"),
("perplexity/pplx-decider-v1.2", "https://api.perplexity.ai/v1/decisions", "pplx-decider-v1.2"),
)
_STATE_IMAGE_PROVIDER_IDS: Final = ("openrouter", "perplexity")
@ -4157,6 +4157,43 @@ def test_a_gif_to_a_state_image_provider_is_rejected_before_http(
assert not route.called
@pytest.mark.parametrize("model", ("openrouter/typesafe/jev-1.14", "openrouter/~typesafe/jev-latest"))
def test_openrouter_jev_rejects_webp_before_http(
model: str, respx_mock: respx.MockRouter, monkeypatch: pytest.MonkeyPatch
) -> None:
monkeypatch.setattr(litellm, "drop_params", False)
route: Final = respx_mock.post("https://openrouter.ai/api/alpha/decisions").respond(json=_RESPONSE)
with pytest.raises(litellm.BadRequestError, match=re.escape("got image/webp")):
litellm.systemone(
model=model,
state=_SCREEN_TEXT,
questions=_SYSTEMONE_PREDICATE,
images=["data:image/webp;base64,BB=="],
api_key="k",
)
assert not route.called
def test_openrouter_jev_drops_webp_and_keeps_png_under_drop_params(respx_mock: respx.MockRouter) -> None:
route: Final = respx_mock.post("https://openrouter.ai/api/alpha/decisions").respond(json=_RESPONSE)
litellm.systemone(
model="openrouter/typesafe/jev-1.14",
state=_SCREEN_TEXT,
questions=_SYSTEMONE_PREDICATE,
images=["data:image/webp;base64,BB==", "data:image/png;base64,AA=="],
api_key="k",
drop_params=True,
)
assert json.loads(route.calls.last.request.content)["state"] == [
_SCREEN_TEXT,
{"type": "image_url", "image_url": {"url": "data:image/png;base64,AA=="}},
]
def test_hosted_vllm_systemone_forwards_images(
monkeypatch: pytest.MonkeyPatch,
respx_mock: respx.MockRouter,