mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-10-20 05:15:31 +00:00
fix(bedrock): add Fable + Claude 4.6/4.7/4.8 1M entries to context table, drop stale cached values
BEDROCK_CONTEXT_LENGTHS was missing entries for current 1M-context Claude models, and the resolution path in get_model_context_length() short-circuits to that table (step 1b) before DEFAULT_CONTEXT_LENGTHS is ever consulted, so the catalog's correct values could never apply on Bedrock: - claude-fable-5 (no entry at all) fell through to BEDROCK_DEFAULT_CONTEXT_LENGTH and reported 128K for a 1M model. - opus-4-7 / opus-4-8 substring-matched the generic 'anthropic.claude-opus-4' key and reported 200K. - opus-4-6 / sonnet-4-6 had explicit 200K entries predating their 1M windows. The practical symptom: the agent compresses context prematurely (at ~128K or ~200K of a 1M window) on every Bedrock-hosted current Claude model. Fixing the table alone is not enough for existing installs: a previously persisted 128K/200K value in the context-length cache wins at step 1 and masks the corrected table forever. Step 1 now reconciles Bedrock-context cache hits against the static table (the table is authoritative for Bedrock — there is no live probe to reconcile against), invalidating stale entries so existing users converge to the right window without manual cache surgery. Tests cover the new table entries (incl. inference-profile and versioned ID forms), the 128K-default regression for Fable, the stale-cache invalidation path, and that pre-4.6 models keep their 200K entries.
This commit is contained in:
parent
3b86af90ed
commit
77ba81f75f
4 changed files with 93 additions and 8 deletions
|
|
@ -1352,12 +1352,16 @@ BEDROCK_CONTEXT_LENGTHS: Dict[str, int] = {
|
|||
# Anthropic Claude models on Bedrock.
|
||||
# Context windows per Anthropic's official models comparison
|
||||
# (https://platform.claude.com/docs/en/about-claude/models/overview).
|
||||
# Sonnet 5 / Opus 4.8 / 4.7 / 4.6 / Sonnet 4.6 have 1M generally
|
||||
# Fable / Sonnet 5 / Opus 4.8 / 4.7 / 4.6 / Sonnet 4.6 have 1M generally
|
||||
# available (no beta header required as of April 2026). Sonnet 4.5 and
|
||||
# Sonnet 4 had their `context-1m-2025-08-07` beta retired on
|
||||
# April 30, 2026, so they are standard 200K; Haiku 4.5 is 200K.
|
||||
# These 1M entries must match agent/model_metadata.py
|
||||
# DEFAULT_CONTEXT_LENGTHS or the agent compresses context prematurely.
|
||||
# Keys are matched by longest-substring, so the versioned 4-6/4-7/4-8
|
||||
# entries win over the generic "anthropic.claude-opus-4" fallback.
|
||||
"anthropic.claude-fable-5": 1_000_000,
|
||||
"anthropic.claude-fable": 1_000_000,
|
||||
"anthropic.claude-sonnet-5": 1_000_000,
|
||||
"anthropic.claude-opus-4-8": 1_000_000,
|
||||
"anthropic.claude-opus-4-7": 1_000_000,
|
||||
|
|
|
|||
|
|
@ -2175,6 +2175,12 @@ def get_model_context_length(
|
|||
if endpoint_context is not None:
|
||||
return endpoint_context
|
||||
|
||||
is_bedrock_context = provider == "bedrock" or (
|
||||
base_url
|
||||
and base_url_hostname(base_url).startswith("bedrock-runtime.")
|
||||
and base_url_host_matches(base_url, "amazonaws.com")
|
||||
)
|
||||
|
||||
# 1. Check persistent cache (model+provider)
|
||||
# LM Studio is excluded — its loaded context length is transient (the
|
||||
# user can reload the model with a different context_length at any time
|
||||
|
|
@ -2243,6 +2249,26 @@ def get_model_context_length(
|
|||
model, base_url,
|
||||
)
|
||||
# Fall through; step 5b reconciles and overwrites if portal responds.
|
||||
# Invalidate stale Bedrock entries seeded before the Claude 4.6+
|
||||
# long-context table was corrected to 1M.
|
||||
elif is_bedrock_context:
|
||||
try:
|
||||
from agent.bedrock_adapter import get_bedrock_context_length
|
||||
bedrock_ctx = get_bedrock_context_length(model)
|
||||
if cached != bedrock_ctx:
|
||||
logger.info(
|
||||
"Dropping stale Bedrock cache entry %s@%s -> %s; "
|
||||
"using static Bedrock table value %s",
|
||||
model,
|
||||
base_url,
|
||||
f"{cached:,}",
|
||||
f"{bedrock_ctx:,}",
|
||||
)
|
||||
_invalidate_cached_context_length(model, base_url)
|
||||
return bedrock_ctx
|
||||
except ImportError:
|
||||
pass
|
||||
return cached
|
||||
else:
|
||||
if is_local_endpoint(base_url):
|
||||
return _reconcile_local_cached_context_length(
|
||||
|
|
@ -2253,17 +2279,13 @@ def get_model_context_length(
|
|||
# 1b. AWS Bedrock — use static context length table.
|
||||
# Bedrock's ListFoundationModels API doesn't expose context window sizes,
|
||||
# so we maintain a curated table in bedrock_adapter.py that reflects
|
||||
# AWS-imposed limits (e.g. 200K for Claude models vs 1M on the native
|
||||
# Anthropic API). This must run BEFORE the custom-endpoint probe at
|
||||
# Bedrock-hosted model limits (e.g. older Claude 4 at 200K; Claude
|
||||
# Opus/Sonnet 4.6+ at 1M). This must run BEFORE the custom-endpoint probe at
|
||||
# step 2 — bedrock-runtime.<region>.amazonaws.com is not in
|
||||
# _URL_TO_PROVIDER, so it would otherwise be treated as a custom endpoint,
|
||||
# fail the /models probe (Bedrock doesn't expose that shape), and fall
|
||||
# back to the 128K default before reaching the original step 4b branch.
|
||||
if provider == "bedrock" or (
|
||||
base_url
|
||||
and base_url_hostname(base_url).startswith("bedrock-runtime.")
|
||||
and base_url_host_matches(base_url, "amazonaws.com")
|
||||
):
|
||||
if is_bedrock_context:
|
||||
try:
|
||||
from agent.bedrock_adapter import get_bedrock_context_length
|
||||
return get_bedrock_context_length(model)
|
||||
|
|
|
|||
|
|
@ -1181,6 +1181,22 @@ class TestBedrockContextLength:
|
|||
# Opus 4.6 has 1M context generally available (no beta header required).
|
||||
assert get_bedrock_context_length("anthropic.claude-opus-4-6-20250514-v1:0") == 1_000_000
|
||||
|
||||
def test_claude_fable_5(self):
|
||||
from agent.bedrock_adapter import get_bedrock_context_length
|
||||
# Fable is a 1M-context model. DEFAULT_CONTEXT_LENGTHS already maps
|
||||
# claude-fable-5 -> 1M, but the Bedrock resolution path short-circuits
|
||||
# to this table before consulting it, so without entries here every
|
||||
# Fable inference profile fell through to
|
||||
# BEDROCK_DEFAULT_CONTEXT_LENGTH (128K).
|
||||
assert get_bedrock_context_length("us.anthropic.claude-fable-5") == 1_000_000
|
||||
assert get_bedrock_context_length("global.anthropic.claude-fable-5") == 1_000_000
|
||||
assert get_bedrock_context_length("anthropic.claude-fable-5-v1:0") == 1_000_000
|
||||
|
||||
def test_claude_opus_4_base_stays_200k(self):
|
||||
from agent.bedrock_adapter import get_bedrock_context_length
|
||||
# The original Opus 4 (no minor version) keeps the 200K window.
|
||||
assert get_bedrock_context_length("anthropic.claude-opus-4-20250514-v1:0") == 200_000
|
||||
|
||||
def test_claude_sonnet_versioned(self):
|
||||
from agent.bedrock_adapter import get_bedrock_context_length
|
||||
# Sonnet 4.6 has 1M context generally available (no beta header required).
|
||||
|
|
|
|||
|
|
@ -1117,6 +1117,49 @@ class TestBedrockContextResolution:
|
|||
assert ctx == 200000
|
||||
mock_fetch.assert_not_called()
|
||||
|
||||
@patch("agent.model_metadata.fetch_endpoint_model_metadata")
|
||||
def test_bedrock_claude_4_6_resolves_to_1m_before_probe(self, mock_fetch):
|
||||
"""Claude 4.6 Bedrock IDs resolve to the 1M table entry."""
|
||||
ctx = get_model_context_length(
|
||||
"us.anthropic.claude-sonnet-4-6",
|
||||
provider="bedrock",
|
||||
base_url="https://bedrock-runtime.us-east-2.amazonaws.com",
|
||||
)
|
||||
assert ctx == 1_000_000
|
||||
mock_fetch.assert_not_called()
|
||||
|
||||
@patch("agent.model_metadata.fetch_endpoint_model_metadata")
|
||||
def test_bedrock_claude_fable_resolves_to_1m_not_128k_default(self, mock_fetch):
|
||||
"""Fable on Bedrock must hit its own table entry, not the 128K default.
|
||||
|
||||
DEFAULT_CONTEXT_LENGTHS maps claude-fable-5 -> 1M, but the Bedrock
|
||||
branch at step 1b returns get_bedrock_context_length() before that
|
||||
catalog is ever consulted — so a missing BEDROCK_CONTEXT_LENGTHS
|
||||
entry silently reported 128K for a 1M model.
|
||||
"""
|
||||
ctx = get_model_context_length(
|
||||
"global.anthropic.claude-fable-5",
|
||||
provider="bedrock",
|
||||
base_url="https://bedrock-runtime.us-east-2.amazonaws.com",
|
||||
)
|
||||
assert ctx == 1_000_000
|
||||
mock_fetch.assert_not_called()
|
||||
|
||||
@patch("agent.model_metadata.fetch_endpoint_model_metadata")
|
||||
def test_bedrock_claude_4_6_ignores_stale_200k_cache(self, mock_fetch, tmp_path):
|
||||
"""Old 200K Bedrock cache entries must not mask the 1M table entry."""
|
||||
cache_file = tmp_path / "context_length_cache.yaml"
|
||||
base_url = "https://bedrock-runtime.us-east-2.amazonaws.com"
|
||||
with patch("agent.model_metadata._get_context_cache_path", return_value=cache_file):
|
||||
save_context_length("us.anthropic.claude-sonnet-4-6", base_url, 200_000)
|
||||
ctx = get_model_context_length(
|
||||
"us.anthropic.claude-sonnet-4-6",
|
||||
provider="bedrock",
|
||||
base_url=base_url,
|
||||
)
|
||||
assert ctx == 1_000_000
|
||||
mock_fetch.assert_not_called()
|
||||
|
||||
@patch("agent.model_metadata.fetch_endpoint_model_metadata")
|
||||
def test_bedrock_url_without_provider_hint(self, mock_fetch):
|
||||
"""bedrock-runtime host infers Bedrock even when provider is omitted."""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue