fix(vision): honor supports_vision_tool_messages=False in tool-result media gates
A ProviderProfile that declares supports_vision_tool_messages=False accepts images in user messages but rejects list-type tool-result content with 400 (xiaomi/MiMo "text is not set"). supports_vision=True alone used to flip _supports_media_in_tool_results to True, and a vision-capable capability lookup could re-open _should_use_native_vision_fast_path — so the native multimodal envelope landed in a role:tool message and 400'd every turn. Both gates now go through one _profile_rejects_tool_media() veto. Refs #89981 (cherry picked from commit daed88f940a6a475f12bb435498e48181da60f4d, trimmed)
This commit is contained in:
@@ -90,6 +90,30 @@ class TestSupportsMediaInToolResults:
|
||||
assert _supports_media_in_tool_results("", "anything") is False
|
||||
assert _supports_media_in_tool_results(None, "anything") is False # type: ignore[arg-type]
|
||||
|
||||
def test_profile_tool_message_veto_overrides_supports_vision(self):
|
||||
"""supports_vision_tool_messages=False is a hard veto even when the
|
||||
profile declares supports_vision=True (xiaomi/MiMo 400s on list-type
|
||||
tool-result content, #89981)."""
|
||||
assert _supports_media_in_tool_results("xiaomi", "mimo-v2.5") is False
|
||||
|
||||
def test_profile_veto_applies_even_when_vision_capable_lookup_agrees(self):
|
||||
"""A capability source marking the model vision-capable must not
|
||||
re-open the native fast path for a provider that rejects it."""
|
||||
from tools.vision_tools import _should_use_native_vision_fast_path
|
||||
from agent.auxiliary_client import set_runtime_main, clear_runtime_main
|
||||
from agent import image_routing
|
||||
|
||||
set_runtime_main("xiaomi", "mimo-v2.5")
|
||||
try:
|
||||
with patch.object(
|
||||
image_routing, "decide_image_input_mode", return_value="native"
|
||||
), patch.object(
|
||||
image_routing, "_lookup_supports_vision", return_value=True
|
||||
):
|
||||
assert _should_use_native_vision_fast_path() is False
|
||||
finally:
|
||||
clear_runtime_main()
|
||||
|
||||
|
||||
# ─── _build_native_vision_tool_result ────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -1077,6 +1077,22 @@ def _resize_image_for_vision(image_path: Path, mime_type: Optional[str] = None,
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _profile_rejects_tool_media(provider: str) -> bool:
|
||||
"""Hard veto: the provider's ``ProviderProfile`` declares
|
||||
``supports_vision_tool_messages=False`` — images are accepted in user
|
||||
messages but list-type tool-result content is rejected with 400
|
||||
(xiaomi/MiMo "text is not set"). ``supports_vision`` alone must not
|
||||
override this, or the multimodal tool-result envelope 400s every turn
|
||||
and the image never enters context (#89981).
|
||||
"""
|
||||
try:
|
||||
from providers import get_provider_profile
|
||||
profile = get_provider_profile(str(provider or "").strip().lower())
|
||||
return profile is not None and profile.supports_vision_tool_messages is False
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def _supports_media_in_tool_results(provider: str, model: str) -> bool:
|
||||
"""Whether the given provider+model combination accepts image content
|
||||
inside a tool-result message.
|
||||
@@ -1100,7 +1116,7 @@ def _supports_media_in_tool_results(provider: str, model: str) -> bool:
|
||||
if not isinstance(provider, str):
|
||||
return False
|
||||
p = provider.strip().lower()
|
||||
if not p:
|
||||
if not p or _profile_rejects_tool_media(p):
|
||||
return False
|
||||
|
||||
# Aggregators that route to multiple vendors — assume support since
|
||||
@@ -1170,6 +1186,11 @@ def _should_use_native_vision_fast_path() -> bool:
|
||||
cfg = load_config()
|
||||
if decide_image_input_mode(provider, model, cfg) != "native":
|
||||
return False
|
||||
# The profile veto applies ahead of the capability lookup too: a
|
||||
# model marked vision-capable by models.dev / custom_providers must
|
||||
# not re-open the multimodal-envelope route the profile rejects.
|
||||
if _profile_rejects_tool_media(provider):
|
||||
return False
|
||||
return (
|
||||
_supports_media_in_tool_results(provider, model)
|
||||
or _lookup_supports_vision(provider, model, cfg) is True
|
||||
|
||||
Reference in New Issue
Block a user