hermes: harden routed vision normalization

This commit is contained in:
jenkins 2026-08-16 07:24:20 -03:00
parent 8f7b57419a
commit ece52b1f2e
3 changed files with 68 additions and 11 deletions

View File

@ -25,7 +25,7 @@ spec:
ai.bstein.dev/execution: Hermes Kanban with durable direct Codex and Claude Code CLI workers
ai.bstein.dev/model-policy: Jetson-assisted AUTO routing, low through xhigh, cross-provider fallback
ai.bstein.dev/placement: rpi5 preferred; Jetson deferred until state storage is available
ai.bstein.dev/config-rev: "20260816-routed-vision-v3"
ai.bstein.dev/config-rev: "20260816-routed-vision-v4"
prometheus.io/scrape: "true"
prometheus.io/path: /metrics
prometheus.io/port: "9010"

View File

@ -226,13 +226,14 @@ def _normalize_input_images(response_input: list[Any]) -> None:
def image_url_value(part: dict[str, Any]) -> tuple[str | None, str | None]:
"""Return a Responses URL from URL-object or base64-source forms."""
raw = part.get("image_url")
if isinstance(raw, str):
return raw, None
candidates = [raw, part.get("source")]
for candidate in candidates:
if not isinstance(candidate, dict):
continue
def search(candidate: Any, depth: int = 0) -> tuple[str | None, str | None]:
if isinstance(candidate, str):
if candidate.startswith(("data:image/", "https://", "http://")):
return candidate, None
return None, None
if not isinstance(candidate, dict) or depth > 4:
return None, None
detail = candidate.get("detail")
url = candidate.get("url")
if isinstance(url, str) and url.strip():
@ -249,8 +250,39 @@ def _normalize_input_images(response_input: list[Any]) -> None:
return f"data:{media_type};base64,{data}", (
detail if isinstance(detail, str) else None
)
for nested in candidate.values():
nested_url, nested_detail = search(nested, depth + 1)
if nested_url:
return nested_url, (
detail if isinstance(detail, str) else nested_detail
)
return None, None
for candidate in (part.get("image_url"), part.get("source"), part):
image_url, detail = search(candidate)
if image_url:
return image_url, detail
return None, None
def image_shape(part: dict[str, Any]) -> str:
"""Describe only structural types when an upstream image is malformed."""
def describe(value: Any, depth: int = 0) -> Any:
if depth > 3:
return type(value).__name__
if isinstance(value, dict):
return {
str(key)[:40]: describe(nested, depth + 1)
for key, nested in list(value.items())[:12]
}
if isinstance(value, list):
return [describe(nested, depth + 1) for nested in value[:4]]
if isinstance(value, str):
return f"str[{len(value)}]"
return type(value).__name__
return json.dumps(describe(part), sort_keys=True, separators=(",", ":"))
for item in response_input:
if not isinstance(item, dict):
continue
@ -268,7 +300,10 @@ def _normalize_input_images(response_input: list[Any]) -> None:
if isinstance(detail, str) and detail and "detail" not in part:
part["detail"] = detail
if not isinstance(image_url, str) or not image_url.strip():
raise ValueError("non-empty Responses image URL required")
raise ValueError(
"non-empty Responses image URL required; shape="
+ image_shape(part)
)
part["type"] = "input_image"
part["image_url"] = image_url
part.pop("source", None)

View File

@ -824,7 +824,26 @@ def test_codex_broker_auth_and_request_contract(tmp_path: Path, monkeypatch):
"type": "input_image",
"image_url": "data:image/png;base64,cHJpdmF0ZQ==",
}
with pytest.raises(ValueError, match="non-empty Responses image URL"):
nested_switchyard_items = [
{
"role": "user",
"content": [
{
"type": "input_image",
"image_url": {
"image": {
"original_url": "data:image/png;base64,cHJpdmF0ZQ=="
}
},
}
],
}
]
nested_image = module._validate_payload(
{"model": "gpt-5.6-terra", "input": nested_switchyard_items}
)["input"][0]["content"][0]
assert nested_image["image_url"] == "data:image/png;base64,cHJpdmF0ZQ=="
with pytest.raises(ValueError, match=r"non-empty Responses image URL.*str\[4\]"):
module._validate_payload(
{
"model": "gpt-5.6-terra",
@ -833,7 +852,10 @@ def test_codex_broker_auth_and_request_contract(tmp_path: Path, monkeypatch):
"type": "message",
"role": "user",
"content": [
{"type": "input_image", "image_url": {"detail": "high"}}
{
"type": "input_image",
"image_url": {"detail": "high"},
}
],
}
],