feat(image_routing): accept vision alias for custom provider models

Extend the existing candidate-name resolver in _supports_vision_override
to accept 'vision' as an alias for 'supports_vision' on per-model config,
for both the providers.<name>.models dict and the legacy list-style
custom_providers form.

Per review feedback on #31912: this extends the current resolver rather
than replacing its candidate-name logic. Named custom providers resolve
to the runtime value provider='custom' while the config keeps the
user-declared name under model.provider; that lookup path is preserved.

Adds regression tests covering model.provider=my-vllm with runtime
provider='custom' for both config shapes.
This commit is contained in:
happy5318 2026-07-14 00:31:14 +08:00 committed by Teknium
parent 51083d2edc
commit 6bd02ae1a6
2 changed files with 106 additions and 2 deletions

View file

@ -194,6 +194,10 @@ def _supports_vision_override(
and/or the user-declared name under ``model.provider``; all are
tried. For ``custom:<name>`` syntax, the stripped ``<name>`` is also
tried as a provider key.)
2b. ``custom_providers`` (legacy list form) ``.models.<model>``
Under (2) and (2b), the per-model capability key may be written as
either ``supports_vision`` or the shorter ``vision`` alias; both work.
Returns None when no override is set, so the caller falls through to
models.dev. Returns False explicitly only when the user wrote a
@ -234,7 +238,9 @@ def _supports_vision_override(
models_cfg: Dict[str, Any] = models_raw if isinstance(models_raw, dict) else {}
per_model_raw = models_cfg.get(model)
per_model: Dict[str, Any] = per_model_raw if isinstance(per_model_raw, dict) else {}
coerced = _coerce_capability_bool(per_model.get("supports_vision"))
coerced = _coerce_capability_bool(
per_model.get("supports_vision", per_model.get("vision"))
)
if coerced is not None:
return coerced
@ -258,7 +264,9 @@ def _supports_vision_override(
models_cfg = models_raw if isinstance(models_raw, dict) else {}
per_model_raw = models_cfg.get(model)
per_model = per_model_raw if isinstance(per_model_raw, dict) else {}
coerced = _coerce_capability_bool(per_model.get("supports_vision"))
coerced = _coerce_capability_bool(
per_model.get("supports_vision", per_model.get("vision"))
)
if coerced is not None:
return coerced

View file

@ -874,3 +874,99 @@ class TestFormatCompatibility:
img_path.write_bytes(b'<svg xmlns="http://www.w3.org/2000/svg" width="4" height="4"/>')
url = _file_to_data_url(img_path)
assert url is None
# ─── vision alias for custom providers ──────────────────────────────────────
class TestCustomProviderVisionAlias:
"""`vision: true` should work as an alias for `supports_vision: true`.
Covers both config shapes that host named custom providers:
* the ``providers.<name>.models`` dict, and
* the legacy list-style ``custom_providers`` entries.
Regression for the review of PR #31912: named custom providers resolve
to the runtime value ``provider="custom"`` while the config keeps the
user-declared name under ``model.provider``. The existing candidate-name
resolver must be *extended* to accept the ``vision`` alias, not replaced.
"""
def test_providers_dict_vision_alias_true(self):
cfg = {
"providers": {
"my-vllm": {"models": {"llava-v1.6": {"vision": True}}}
}
}
assert _supports_vision_override(cfg, "my-vllm", "llava-v1.6") is True
def test_providers_dict_vision_alias_false(self):
cfg = {
"providers": {
"my-vllm": {"models": {"llama-3": {"vision": False}}}
}
}
assert _supports_vision_override(cfg, "my-vllm", "llama-3") is False
def test_supports_vision_wins_over_vision_alias(self):
"""When both keys are present, the canonical key takes priority."""
cfg = {
"providers": {
"my-vllm": {
"models": {
"m": {"supports_vision": True, "vision": False}
}
}
}
}
assert _supports_vision_override(cfg, "my-vllm", "m") is True
def test_named_custom_provider_bare_custom_runtime_vision_alias(self):
"""Teknium's requested regression case.
A named custom provider (``model.provider: my-vllm``) is rewritten to
the runtime value ``provider="custom"`` by
``hermes_cli/runtime_provider.py``. The resolver must still match the
``my-vllm`` entry via the ``model.provider`` candidate and honour the
``vision`` alias.
"""
cfg = {
"model": {"provider": "my-vllm"},
"providers": {
"my-vllm": {"models": {"llava-v1.6": {"vision": True}}}
},
}
# Runtime provider is the bare normalized value "custom".
assert _supports_vision_override(cfg, "custom", "llava-v1.6") is True
assert decide_image_input_mode("custom", "llava-v1.6", cfg) == "native"
def test_custom_providers_list_bare_custom_runtime_vision_alias(self):
"""Same regression, but the provider lives in the legacy list form."""
cfg = {
"model": {"provider": "my-vllm"},
"custom_providers": [
{
"name": "my-vllm",
"models": {"llava-v1.6": {"vision": True}},
}
],
}
assert _supports_vision_override(cfg, "custom", "llava-v1.6") is True
assert decide_image_input_mode("custom", "llava-v1.6", cfg) == "native"
def test_custom_providers_list_vision_alias_false(self):
cfg = {
"model": {"provider": "my-vllm"},
"custom_providers": [
{"name": "my-vllm", "models": {"llama-3": {"vision": False}}}
],
}
assert _supports_vision_override(cfg, "custom", "llama-3") is False
def test_vision_alias_none_when_model_absent(self):
cfg = {
"custom_providers": [
{"name": "my-vllm", "models": {"llava": {"vision": True}}}
]
}
assert _supports_vision_override(cfg, "custom:my-vllm", "other") is None