mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-01 12:02:05 +00:00
fix(image-routing): unblock message queue on OpenRouter 'no endpoints' image 404 (#53901)
The agent's image-rejection fallback strips images and retries text-only when a provider rejects image content, which is what lets the gateway drain its queued messages. The fallback only fires on a hardcoded phrase list, and the OpenRouter wording — HTTP 404 'No endpoints found that support image input' — was missing. For OpenRouter-routed non-vision models the fallback never fired, the retry loop re-sent the same rejected request until exhaustion, and every subsequent message (including plain text) stayed queued behind the stuck turn. Add the phrase to _IMAGE_REJECTION_PHRASES (the 404 already passes the 4xx gate). Add a positive test and a guard test so the sibling OpenRouter 'no endpoints ... data policy / guardrail' 404s do NOT get their images stripped. Fixes #21160. Reported by @liu14goal14-ux; PR #21198 by @ygd58.
This commit is contained in:
parent
a94f657a50
commit
1a570dae00
2 changed files with 28 additions and 0 deletions
|
|
@ -2259,6 +2259,15 @@ def run_conversation(
|
|||
# "unknown variant `image_url`, expected `text`".
|
||||
"unknown variant `image_url`, expected `text`",
|
||||
"unknown variant image_url, expected text",
|
||||
# OpenRouter routes a request to upstream endpoints and,
|
||||
# when none of the candidate endpoints for the model accept
|
||||
# image input, returns HTTP 404 "No endpoints found that
|
||||
# support image input". Without this phrase the agent never
|
||||
# strips the images, the retry loop re-sends the same
|
||||
# rejected request until exhaustion, and the gateway leaves
|
||||
# every subsequent message queued behind the stuck turn —
|
||||
# the P1 in issue #21160. The 404 passes the 4xx gate below.
|
||||
"no endpoints found that support image input",
|
||||
)
|
||||
_err_lower = _err_body.lower()
|
||||
_looks_like_image_rejection = any(
|
||||
|
|
|
|||
|
|
@ -195,6 +195,7 @@ class TestImageRejectionPhraseIsolation:
|
|||
"does not support vision",
|
||||
"model does not support image",
|
||||
"image_url'. expected",
|
||||
"no endpoints found that support image input",
|
||||
)
|
||||
|
||||
def _matches(self, body: str) -> bool:
|
||||
|
|
@ -244,10 +245,28 @@ class TestImageRejectionPhraseIsolation:
|
|||
# match the agent cascaded into compression / context-too-large
|
||||
# recovery instead of just stripping the images.
|
||||
"Invalid 'input[56].content[1].image_url'. Expected a valid URL, but got a value with an invalid format.",
|
||||
# OpenRouter 404 when no upstream endpoint for the model accepts
|
||||
# image input — issue #21160. The exact wording from the report.
|
||||
"HTTP 404: No endpoints found that support image input",
|
||||
]
|
||||
for body in bodies:
|
||||
assert self._matches(body) is True, f"false negative on: {body}"
|
||||
|
||||
def test_openrouter_data_policy_no_endpoints_does_not_trip(self):
|
||||
"""OpenRouter has several 'no endpoints ...' 404 bodies. Only the
|
||||
image-input one is an image rejection — the guardrail / data-policy
|
||||
variants (agent/error_classifier.py) are about routing restrictions,
|
||||
not vision, and must route to their own handler, not get their images
|
||||
stripped.
|
||||
"""
|
||||
bodies = [
|
||||
"No endpoints available matching your guardrail restrictions",
|
||||
"No endpoints available matching your data policy",
|
||||
"No endpoints found matching your data policy",
|
||||
]
|
||||
for body in bodies:
|
||||
assert self._matches(body) is False, f"false positive on: {body}"
|
||||
|
||||
def test_codex_data_url_rejection_does_not_false_match_other_url_errors(self):
|
||||
"""The narrow 'image_url'. expected' phrase (keyed on the
|
||||
field-path apostrophe used in the Codex Responses error format)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue