Skip to content

fix(bedrock): add Claude Sonnet/Opus/Haiku 5 to Bedrock context-length table - #66167

Closed
Polyhistor wants to merge 3 commits into
NousResearch:mainfrom
Polyhistor:fix/bedrock-sonnet5-context-length
Closed

fix(bedrock): add Claude Sonnet/Opus/Haiku 5 to Bedrock context-length table#66167
Polyhistor wants to merge 3 commits into
NousResearch:mainfrom
Polyhistor:fix/bedrock-sonnet5-context-length

Conversation

@Polyhistor

Copy link
Copy Markdown
Contributor

Problem

au.anthropic.claude-sonnet-5 (and the opus-5/haiku-5 siblings) have no entry in BEDROCK_CONTEXT_LENGTHS in agent/bedrock_adapter.py. Since Bedrock's ListFoundationModels API doesn't expose context window sizes, get_bedrock_context_length() falls back to BEDROCK_DEFAULT_CONTEXT_LENGTH (128,000) for any unmatched model — silently under-reporting the real window.

AWS documents a 200,000 token default context window for Claude Sonnet 5 on Bedrock (same as the Sonnet 4.5/Opus 4 family already in the table), with a 1M beta available via header — not the 128K fallback.

Impact

ContextCompressor applies a small-context floor: any model resolved under 512K is treated as 'small' and its compaction threshold is raised to 75% of window (to avoid thrashing on tight windows). With the wrong 128K window this floor fires at 96,000 tokens; at the correct 200K window it fires at 150,000 tokens — about 36% less usable headroom before Hermes force-compacts the conversation. On long agentic sessions (e.g. heavy read_file/grep/web_search tool output) this causes materially more frequent compaction than intended.

Fix

Add anthropic.claude-opus-5, anthropic.claude-sonnet-5, anthropic.claude-haiku-5 → 200,000 to BEDROCK_CONTEXT_LENGTHS. Minimal, additive, no behavior change for existing entries.

Verification

>>> from agent.bedrock_adapter import get_bedrock_context_length
>>> get_bedrock_context_length('au.anthropic.claude-sonnet-5')
200000   # was 128000 before this change

Also confirmed downstream in ContextCompressor: with this fix (or an equivalent model.context_length: 200000 config override), threshold_tokens for this model correctly resolves to 150,000 instead of 96,000.

…h table

au.anthropic.claude-sonnet-5 (and opus-5/haiku-5) had no entry in
BEDROCK_CONTEXT_LENGTHS, so get_bedrock_context_length() silently fell
back to BEDROCK_DEFAULT_CONTEXT_LENGTH (128_000) instead of the actual
200_000 token window AWS documents for this model family on Bedrock.

Impact: ContextCompressor's small-context floor treats any resolved
window under 512K as 'small' and raises the compaction threshold to
75% of window. With the wrong 128K window that floor fires at 96,000
tokens instead of 150,000 tokens at the correct 200K window -- roughly
36% less usable headroom before Hermes force-compacts the conversation,
causing more frequent/premature compaction on long agentic sessions.

Verified locally:
  get_bedrock_context_length('au.anthropic.claude-sonnet-5') -> 200000
  (was 128000 before this change)
@alt-glitch alt-glitch added type/bug Something isn't working comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint provider/bedrock AWS Bedrock (boto3, IAM) P3 Low — cosmetic, nice to have sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades labels Jul 17, 2026
@alt-glitch

Copy link
Copy Markdown
Collaborator

This was generated by AI during triage.

Related: #24059 corrects Claude 4.x 1M Bedrock entries; this independently adds the 200K Claude 5 identifiers. Not a duplicate.

@tonydwb tonydwb left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

Verdict: Approved

Context length table update for Claude 5 family on Bedrock. Simple safe addition. No security concerns.


Reviewed by Hermes Agent

Bedrock Converse rejects text content blocks that are empty OR
whitespace-only (ValidationException: "text content blocks must
contain non-whitespace text"). The prior fix attempt substituted a
single space (" ") for missing content -- but a lone space IS
whitespace, so it was rejected by the exact same validation rule it
was meant to satisfy. This caused a deterministic, unrecoverable
retry-loop failure once any blank/whitespace assistant, tool, or user
turn entered history (most commonly via context-compaction rewriting
a turn to a blank string).

Adds _safe_text()/_EMPTY_TEXT_PLACEHOLDER ("(empty)") and applies it
everywhere a blank text block could reach the wire: user/assistant
content conversion, tool results, the assistant-empty-turn fallback,
and the first/last-message user-alternation padding. System-prompt
blocks are the one exception: blank parts are dropped entirely rather
than placeholder-filled, since a system prompt block should never
carry meaningless placeholder text.

Adds tests/agent/test_bedrock_empty_text_blocks.py (11 tests, was
already present uncommitted -- codifies the exact failing history
from issue NousResearch#9486 and asserts no blank block ever reaches Bedrock).

Verified against the actual failed request dump from this session
(27-message payload) -- replaying it through the fixed converter now
produces zero blank/whitespace-only blocks.

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for identifying the missing Claude 5 Bedrock context entries. The premise is present on current main: agent/model_metadata.py:2259-2266 routes Bedrock models to get_bedrock_context_length(), while agent/bedrock_adapter.py:1307-1350 has no Claude 5 keys and returns the 128K default for unmatched IDs.

Problems

  • The placeholder change will fail current tests: tests/agent/test_bedrock_adapter.py:1310-1323 asserts that None, empty, and whitespace-only content produce a literal space, while this PR changes those outputs to (empty) without updating the assertions.
  • The new _safe_text() is not applied to supported string entries in list content. agent/bedrock_adapter.py:511-512 still emits {"text": part} directly, so [" "] can still send a whitespace-only block to Bedrock.

Suggested changes

  • Update the existing placeholder assertions and route list string items through _safe_text(); add a list-string whitespace regression case.

Automated hermes-sweeper review.

Comment thread agent/bedrock_adapter.py
# Bedrock's Converse API rejects any text content block whose text is empty
# OR whitespace-only (ValidationException: "text content blocks must contain
# non-whitespace text"). A lone space is whitespace and is rejected too — the
# placeholder MUST itself be non-whitespace. Ref: issue #9486.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please update tests/agent/test_bedrock_adapter.py:1310-1323 in this PR: those existing tests still assert the old literal-space placeholder and will fail after this constant changes the converter output.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in bbb233e:

Updated tests/agent/test_bedrock_adapter.py::TestEmptyTextBlockFix — the placeholder assertions were stale (still expecting the old literal-space behavior); now assert _EMPTY_TEXT_PLACEHOLDER (non-whitespace) instead.
Fixed _convert_content_to_converse(): plain-string items inside a content list (e.g. [" "]) were emitted as {"text": part}" directly, bypassing _safe_text(). Now routed through _safe_text() like every other path. Added a regression test (test_whitespace_only_list_string_item_gets_placeholder`) plus a happy-path counterpart.
Full adapter test suite: 135 passed, 10 skipped (pre-existing, unrelated botocore-version-gated skips).

…text, update stale placeholder assertions

Addresses hermes-sweeper review on PR NousResearch#66167:

1. _convert_content_to_converse() still emitted {"text": part} directly
   for plain-string items inside a content list (as opposed to
   {"type": "text"} dicts), bypassing _safe_text() entirely. A
   whitespace-only string item (e.g. ["   "]) could still reach Bedrock
   as a blank block. Now routed through _safe_text().

2. tests/agent/test_bedrock_adapter.py::TestEmptyTextBlockFix asserted
   the pre-fix behavior (whitespace -> literal space " "), contradicting
   the new _safe_text()/_EMPTY_TEXT_PLACEHOLDER behavior added in
   4618095. Updated assertions to expect the non-whitespace placeholder,
   plus added a regression test for the list-string-item case above.
@Polyhistor

Copy link
Copy Markdown
Contributor Author

Addressed in bbb233e:

  1. Updated tests/agent/test_bedrock_adapter.py::TestEmptyTextBlockFix — the placeholder assertions were stale (still expecting the old literal-space behavior); now assert _EMPTY_TEXT_PLACEHOLDER (non-whitespace) instead.
  2. Fixed _convert_content_to_converse(): plain-string items inside a content list (e.g. [" "]) were emitted as {"text": part}" directly, bypassing _safe_text(). Now routed through _safe_text() like every other path. Added a regression test (test_whitespace_only_list_string_item_gets_placeholder`) plus a happy-path counterpart.

Full adapter test suite: 135 passed, 10 skipped (pre-existing, unrelated botocore-version-gated skips).

@teknium1 teknium1 added the sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform label Jul 18, 2026
teknium1 pushed a commit that referenced this pull request Jul 20, 2026
…text, update stale placeholder assertions

Addresses hermes-sweeper review on PR #66167:

1. _convert_content_to_converse() still emitted {"text": part} directly
   for plain-string items inside a content list (as opposed to
   {"type": "text"} dicts), bypassing _safe_text() entirely. A
   whitespace-only string item (e.g. ["   "]) could still reach Bedrock
   as a blank block. Now routed through _safe_text().

2. tests/agent/test_bedrock_adapter.py::TestEmptyTextBlockFix asserted
   the pre-fix behavior (whitespace -> literal space " "), contradicting
   the new _safe_text()/_EMPTY_TEXT_PLACEHOLDER behavior added in
   4618095. Updated assertions to expect the non-whitespace placeholder,
   plus added a regression test for the list-string-item case above.
@teknium1

Copy link
Copy Markdown
Contributor

Merged the fix half via PR #67978 — both of your _safe_text commits were cherry-picked onto current main with your authorship preserved (rebase merge), including the review follow-up routing list-string items through the guard. Nice catch that the lone-space placeholder was itself whitespace.

The context-table half of this PR was superseded by #67977: current main already carried anthropic.claude-sonnet-5 at 1M (from #67932), so the 200K row would have been a regression, and the opus-5/haiku-5 rows were speculative (those model IDs don't exist on Bedrock yet — happy to add them when AWS publishes the model cards). Thanks!

#67978

randlee pushed a commit to randlee/hermes-agent that referenced this pull request Aug 11, 2026
…text, update stale placeholder assertions

Addresses hermes-sweeper review on PR NousResearch#66167:

1. _convert_content_to_converse() still emitted {"text": part} directly
   for plain-string items inside a content list (as opposed to
   {"type": "text"} dicts), bypassing _safe_text() entirely. A
   whitespace-only string item (e.g. ["   "]) could still reach Bedrock
   as a blank block. Now routed through _safe_text().

2. tests/agent/test_bedrock_adapter.py::TestEmptyTextBlockFix asserted
   the pre-fix behavior (whitespace -> literal space " "), contradicting
   the new _safe_text()/_EMPTY_TEXT_PLACEHOLDER behavior added in
   4618095. Updated assertions to expect the non-whitespace placeholder,
   plus added a regression test for the list-string-item case above.
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 P3 Low — cosmetic, nice to have provider/bedrock AWS Bedrock (boto3, IAM) sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform 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.

4 participants