mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-25 05:52:34 +00:00
fix(agent): catch ChatGPT-account Codex data-URL rejection so images are stripped instead of cascading to compression (#23602)
When the user's main provider is openai-codex on the ChatGPT-account backend (https://chatgpt.com/backend-api/codex), sending a native image attachment encodes it as data:image/...base64,... in the input_image field. The OpenAI Responses API on the public endpoint accepts that, but the ChatGPT-account variant rejects it with HTTP 400: Invalid 'input[N].content[K].image_url'. Expected a valid URL, but got a value with an invalid format. Hermes' image-rejection phrase list didn't include this wording, so the error escaped the strip-and-retry branch and fell through to the generic recovery path: model fallback → context-too-large → compression cascade → auxiliary OpenRouter 402 spam (issue #23570). Add a NARROW phrase keyed on the field-path apostrophe used by the Codex Responses error format: "image_url'. expected". This matches the actual error format without false-tripping on generic 'Expected a valid URL' errors from unrelated tools (webhooks, redirect_uri, etc.). Once matched, the existing branch strips images from history, sets _vision_supported= False for the session, and retries text-only. Refs #23570 (1 of 3 image-replay improvements; persistence rewrite to store image PATHS instead of inlined base64 is a separate follow-up)
This commit is contained in:
parent
3e7145e0bb
commit
7026af4e23
2 changed files with 39 additions and 0 deletions
15
run_agent.py
15
run_agent.py
|
|
@ -13117,6 +13117,21 @@ class AIAgent:
|
||||||
"does not support multimodal",
|
"does not support multimodal",
|
||||||
"does not support vision",
|
"does not support vision",
|
||||||
"model does not support image",
|
"model does not support image",
|
||||||
|
# ChatGPT-account Codex backend
|
||||||
|
# (https://chatgpt.com/backend-api/codex) rejects
|
||||||
|
# data:image/...base64 URLs in input_image fields
|
||||||
|
# with HTTP 400 "Invalid 'input[N].content[K].image_url'.
|
||||||
|
# Expected a valid URL, but got a value with an
|
||||||
|
# invalid format." The OpenAI Responses API on the
|
||||||
|
# public endpoint accepts data URLs, but the
|
||||||
|
# ChatGPT-account variant does not. Without this
|
||||||
|
# phrase the agent cascaded into compression /
|
||||||
|
# context-too-large recovery instead of just
|
||||||
|
# stripping the images. Match is narrow on
|
||||||
|
# purpose — keyed on the field-path apostrophe so
|
||||||
|
# we don't false-trip on other URL validation
|
||||||
|
# errors. (issue #23570)
|
||||||
|
"image_url'. expected",
|
||||||
)
|
)
|
||||||
_err_lower = _err_body.lower()
|
_err_lower = _err_body.lower()
|
||||||
_looks_like_image_rejection = any(
|
_looks_like_image_rejection = any(
|
||||||
|
|
|
||||||
|
|
@ -194,6 +194,7 @@ class TestImageRejectionPhraseIsolation:
|
||||||
"does not support multimodal",
|
"does not support multimodal",
|
||||||
"does not support vision",
|
"does not support vision",
|
||||||
"model does not support image",
|
"model does not support image",
|
||||||
|
"image_url'. expected",
|
||||||
)
|
)
|
||||||
|
|
||||||
def _matches(self, body: str) -> bool:
|
def _matches(self, body: str) -> bool:
|
||||||
|
|
@ -238,6 +239,29 @@ class TestImageRejectionPhraseIsolation:
|
||||||
"This model does not support images",
|
"This model does not support images",
|
||||||
"vision is not supported on this endpoint",
|
"vision is not supported on this endpoint",
|
||||||
"model does not support image input",
|
"model does not support image input",
|
||||||
|
# ChatGPT-account Codex backend (issue #23570) — rejects
|
||||||
|
# data:image/...base64 URLs in input_image fields. Without this
|
||||||
|
# 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.",
|
||||||
]
|
]
|
||||||
for body in bodies:
|
for body in bodies:
|
||||||
assert self._matches(body) is True, f"false negative on: {body}"
|
assert self._matches(body) is True, f"false negative 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)
|
||||||
|
must NOT trip on URL validation errors that aren't about
|
||||||
|
image_url specifically. See issue #23570 for the original error.
|
||||||
|
"""
|
||||||
|
bodies = [
|
||||||
|
# Generic URL validation errors — should NOT trip
|
||||||
|
"Invalid webhook_url. Must be a valid URL.",
|
||||||
|
"Expected a valid URL but got an empty string.",
|
||||||
|
"redirect_uri does not look like a valid URL.",
|
||||||
|
# An image_url error worded differently — also should not trip
|
||||||
|
# the narrow phrase (a separate phrase would be needed)
|
||||||
|
"image_url field cannot be empty",
|
||||||
|
]
|
||||||
|
for body in bodies:
|
||||||
|
assert self._matches(body) is False, f"false positive on: {body}"
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue