Skip to content

fix: preserve None content in assistant messages (#11906) - #12470

Closed
Sanjays2402 wants to merge 1 commit into
NousResearch:mainfrom
Sanjays2402:fix/empty-assistant-content-11906
Closed

fix: preserve None content in assistant messages (#11906)#12470
Sanjays2402 wants to merge 1 commit into
NousResearch:mainfrom
Sanjays2402:fix/empty-assistant-content-11906

Conversation

@Sanjays2402

@Sanjays2402 Sanjays2402 commented Apr 19, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

_build_assistant_message() normalized None content to "" via or "". This caused Anthropic-compatible proxies to reject responses containing empty text blocks — the proxy sees {"type": "text", "text": ""} and returns HTTP 400.

Fix: keep None as None instead of coercing to empty string. Only apply surrogate sanitization when content is truthy.

Before: _raw_content = assistant_message.content or ""
After: _raw_content = assistant_message.content + conditional sanitization

Related Issue

Fixes #11906

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)

Changes Made

  • _build_assistant_message(): stop coercing None content to ""; sanitize surrogates only when content is truthy.
  • Updated test_empty_contenttest_none_content_preserved (asserts None not "").
  • Added test_none_content_with_tool_calls for tool-call-only responses.

How to Test

  1. Run Hermes against an Anthropic-compatible proxy that rejects empty text blocks.

  2. Produce a tool-call-only assistant response (no text content).

  3. Before: HTTP 400 from the proxy. After: request succeeds; assistant message carries content=None and tool calls only.

  4. Run unit tests:

    pytest -q -k "none_content"
    

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix:)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix
  • I've run pytest tests/ -q and all tests pass
  • I've added tests for my changes
  • I've tested on my platform: macOS 26.5 (arm64)

Documentation & Housekeeping

  • Documentation updates — N/A
  • cli-config.yaml.example — N/A
  • CONTRIBUTING.md / AGENTS.md — N/A
  • Cross-platform impact considered — pure Python
  • Tool descriptions/schemas — N/A

Screenshots / Logs

N/A — internal message normalization; covered by new unit tests.

Empty content was normalized to "" via `or ""`, causing Anthropic-compatible
proxies to reject responses with empty text blocks. Now None stays None so
tool-call-only messages never emit spurious content.
@vominh1919

Copy link
Copy Markdown
Contributor

LGTM! Clean minimal fix. Good: preserves None, conditional sanitization, clear comments, tests cover tool-call-only case. Minor suggestion: use is not None instead of truthy check for more explicit intent. Ready to merge.

@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 Apr 22, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

Related: PR #12737 addresses the same None-to-empty-string content normalization for Anthropic compatibility. Also see #11929.

@teknium1

Copy link
Copy Markdown
Contributor

Thanks for the focused reproduction and regression coverage. The reported source behavior remains present in current main at agent/chat_completion_helpers.py:979-980, but an overlapping PR received an explicit maintainer decision against this fix direction: #11929 states that chat-completions content is intentionally byte-stable through history and that native Anthropic handling owns empty-content conversion (agent/anthropic_adapter.py:2031-2035).

Problems

  • The proposed history rewrite conflicts with that established design decision.
  • The active implementation has also moved: run_agent.py:5454-5457 is now a forwarder, and commit 4b25619bc4770396faf206429ddc180ad02231a9 placed the builder in agent/chat_completion_helpers.py.

Suggested changes

  • Keep this open for maintainer direction rather than salvaging the patch unchanged; any renewed approach should address the downstream proxy contract without altering persisted chat-completions history.

Automated hermes-sweeper review.

@teknium1 teknium1 added sweeper:risk-caching Sweeper risk: may break/degrade prompt caching or cache-key stability (invariant) 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 12, 2026
@alt-glitch alt-glitch removed the sweeper:risk-caching Sweeper risk: may break/degrade prompt caching or cache-key stability (invariant) label Jul 12, 2026
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.

[Bug]: Assistant messages with empty content cause HTTP 400 on Anthropic-compatible proxies

4 participants