Skip to content

fix(chat): sanitize empty assistant content before requests - #31175

Closed
kenanjun001 wants to merge 1 commit into
NousResearch:mainfrom
kenanjun001:fix-empty-assistant-chat-completions
Closed

kenanjun001 wants to merge 1 commit into
NousResearch:mainfrom
kenanjun001:fix-empty-assistant-chat-completions

Conversation

@kenanjun001

Copy link
Copy Markdown

Summary

Sanitize assistant messages with empty content before sending Chat Completions requests.

Strict Anthropic-compatible providers such as AWS Bedrock Anthropic reject assistant messages with empty content.

During tool / skill execution, Hermes can produce:

{
  "role": "assistant",
  "content": ""
}

This causes HTTP 400:

messages: text content blocks must be non-empty

Fix

Normalize empty assistant content before sending requests.

@kenanjun001

Copy link
Copy Markdown
Author

For anyone investigating this issue:

I was able to reproduce it consistently with skill_view calls against an Anthropic-compatible backend (AWS Bedrock Anthropic via a custom OpenAI-compatible provider).

The root cause was an assistant message with empty content being sent in the request history:

{
  "role": "assistant",
  "content": ""
}

After applying the patch in this PR, the same workflow completed successfully without triggering:

messages: text content blocks must be non-empty

@kenanjun001

Copy link
Copy Markdown
Author

Additional reproduction details:

From the user perspective, the issue appears as repeated provider failures after a skill invocation.

Example:

📚 skill_view: "hermes-weixin-ilink"

⚠️ The model provider failed after retries. I kept raw provider details out of chat; check gateway logs for diagnostics.

⚠️ The model provider failed after retries. I kept raw provider details out of chat; check gateway logs for diagnostics.

⚠️ The model provider failed after retries. I kept raw provider details out of chat; check gateway logs for diagnostics.

The visible error is misleading because the actual failure happens earlier when Hermes replays conversation history containing:

{
  "role": "assistant",
  "content": ""
}

On strict Anthropic-compatible providers (AWS Bedrock Anthropic in my case), this results in a request validation failure:

messages: text content blocks must be non-empty

The provider error is then retried multiple times, which is why users observe several identical failure messages instead of the underlying validation problem.

@alt-glitch alt-glitch added type/bug Something isn't working P2 Medium — degraded but workaround exists comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint provider/anthropic Anthropic native Messages API labels May 23, 2026
@teknium1

Copy link
Copy Markdown
Collaborator

Thanks for isolating the strict-provider replay failure. The current code still constructs a pure tool-call assistant turn with content: "" in agent/chat_completion_helpers.py:1077-1112, and agent/transports/chat_completions.py:209-210 returns a clean history unchanged, so the underlying issue remains relevant.

Problems

  • The submitted hunk predates the current copy-on-write transport path. On HEAD, empty content must itself trigger needs_sanitize; otherwise the identity fast path bypasses the repair (agent/transports/chat_completions.py:180-210).
  • The diff also replaces empty assistant messages without tool calls with fabricated text. Scope the wire-only repair to assistant turns carrying non-empty tool_calls; the reported failing history is that pure-tool-call shape.
  • The PR changes one production file only and adds no regression coverage.

Suggested changes

  • Salvage this at the current transport boundary with copy-on-write behavior, producing content: null only for blank/missing assistant content plus non-empty tool calls.
  • Add transport tests covering empty, whitespace, and missing content while preserving substantive content and no-tool-call assistant turns.

Automated hermes-sweeper review.

@teknium1 teknium1 added sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit labels Jul 13, 2026
@teknium1

Copy link
Copy Markdown
Collaborator

Resolved on main via PR #73071. Empty assistant content is now repaired before it ever reaches the transport: repair_empty_non_final_messages runs inside sanitize_api_messages (the pre-send chokepoint upstream of convert_messages), substituting a non-whitespace placeholder that satisfies Anthropic/Bedrock's non-empty text-block validation — the exact rejection you reported. The Anthropic and Bedrock adapters additionally carry their own _safe_text block-level coercion for list-content shapes. Your PR was the EARLIEST in this cluster (May 23) to propose fixing this at a shared boundary — first-finder credit is yours. Thanks @kenanjun001. Commit 725c7ba.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P2 Medium — degraded but workaround exists provider/anthropic Anthropic native Messages API sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants