fix: anitize empty text content blocks sfix: resolve issue #11906 #11929
Open
xiaoyin1993 wants to merge 2 commits into
Open
fix: anitize empty text content blocks sfix: resolve issue #11906 #11929xiaoyin1993 wants to merge 2 commits into
xiaoyin1993 wants to merge 2 commits into
Conversation
13 tasks
Collaborator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What does this PR do?
Fixes a class of
HTTP 400: "messages: text content blocks must be non-empty"errors that occur when Hermes talks to an OpenAI-compatible endpoint that proxies to Anthropic (e.g.claude-code-router,one-api's Anthropic channel, any reverse proxy that translates OpenAI/v1/chat/completionsto Anthropic/v1/messages).Several code paths were producing assistant messages with
content: ""(empty string) instead of the OpenAI-standardcontent: null. When such a message is translated to Anthropic format, the empty string becomes an empty{"type": "text", "text": ""}block, which Anthropic rejects.The fix takes a defense-in-depth approach:
_build_api_kwargsnow sanitizes residual empty content on thechat_completionspath, covering edge cases from session load, context compression, prefill messages, etc.Related Issue
Fixes #
Type of Change
Changes Made
All changes are in
run_agent.py:New helpers (module-level):
_needs_empty_text_sanitization(messages)— read-only pre-scan, so normal traffic pays zero cost._sanitize_empty_text_blocks(messages)— in-place cleanup: empty-string or whitespace-onlycontentonassistant + tool_calls→None(OpenAI-standard); other roles → single-space placeholder; list content filters empty text blocks with the same fallback rules.Source fix —
_build_assistant_message(~line 7041): when the model returns no text content, storeNoneinstead of"". Matches OpenAI's spec forassistant + tool_callsturns and prevents the empty string from entering conversation history in the first place.Source fix — KV-cache whitespace normalization (~line 8878): the existing
.strip()loop silently turned" "into"", actively creating the problem it was supposed to prevent. Whitespace-only content now maps toNone(forassistant + tool_calls) or" "(other roles).Source fix — Codex Responses placeholder (~line 3915): the required "following item" after a reasoning-only turn uses
" "instead of""for consistency. This path does not go through_build_api_kwargs, so the source fix is the only defense.Exit-level safety net —
_build_api_kwargschat_completions path (~line 6787): runs_needs_empty_text_sanitizationfirst; only when empty content is detected does it shallow-copy messages and sanitize. Preserves the existingkwargs["messages"] is messagessemantics for normal traffic.How to Test
Reproduction (before the fix):
Run any OpenAI→Anthropic proxy locally, e.g.
claude-code-routeronhttp://localhost:3001/v1.Configure Hermes:
Start a conversation that triggers a tool call. As soon as the assistant returns a message with only
tool_calls(no text), the next turn fails with:Verification (after the fix):
Same setup, same conversation — the tool-calling loop now completes without 400.
Unit-level check — the helper produces the correct output for all cases:
Regression check —
_build_api_kwargs(messages)returnskwargs["messages"] is messagesfor conversations without empty content (no unnecessary copy).Checklist
Code
fix(scope):,feat(scope):, etc.)pytest tests/ -qand all tests passDocumentation & Housekeeping
docs/, docstrings) — N/A (internal helpers, no public API change)cli-config.yaml.exampleif I added/changed config keys — N/A (no new config)CONTRIBUTING.mdorAGENTS.mdif I changed architecture or workflows — N/AScreenshots / Logs
Before:
After: the tool-calling loop completes normally; no 400 response from the proxy.