mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-23 16:36:23 +00:00
fix(compression): don't recommend a threshold the small-context floor will ignore
The auxiliary-compression feasibility warning computes its compression.threshold suggestion as aux_context / main_context, independently of ContextCompressor._effective_threshold_percent()'s raise-only small-context floor. For main windows under 512K the floor raises any configured value below 75% back up, so a suggestion like 'threshold: 0.40' is silently ignored and the same warning returns every session. Derive the suggestion's viability through the compressor's own floor logic: offer the 'lower the threshold' option only when the floored value still fits the auxiliary model's context; otherwise recommend only a larger compression model and explain the floor, so the guidance is always actionable. Tests: the updated auto-correct test pins the floored branch (no threshold suggestion, floor explained); two new tests pin the surviving suggestion at/above the floor on a small window and below 75% on a 512K+ window where no floor applies. The updated test fails against the previous code. Fixes #67422 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
295e20358c
commit
dbc71fb6e4
2 changed files with 88 additions and 9 deletions
|
|
@ -504,6 +504,20 @@ def check_compression_model_feasibility(agent: Any) -> None:
|
|||
new_threshold / main_ctx
|
||||
)
|
||||
safe_pct = int((aux_context / main_ctx) * 100) if main_ctx else 50
|
||||
# The "lower the threshold" suggestion must survive the
|
||||
# compressor's raise-only small-context floor (#67422): for main
|
||||
# windows under 512K, _effective_threshold_percent() raises any
|
||||
# configured value below 75% back up, so recommending e.g. 0.40
|
||||
# would be silently ignored and this warning would reappear every
|
||||
# session. Only offer the option when the floored value still
|
||||
# fits the auxiliary model's context.
|
||||
from agent.context_compressor import ContextCompressor as _CC
|
||||
|
||||
threshold_suggestion_viable = (
|
||||
not main_ctx
|
||||
or _CC._effective_threshold_percent(main_ctx, safe_pct / 100) * main_ctx
|
||||
<= aux_context
|
||||
)
|
||||
# Build human-readable "model (provider)" labels for both
|
||||
# the main model and the compression model so users can
|
||||
# tell at a glance which provider each side is actually
|
||||
|
|
@ -537,15 +551,30 @@ def check_compression_model_feasibility(agent: Any) -> None:
|
|||
f"{old_threshold:,} tokens. "
|
||||
f"Auto-lowered this session's threshold to "
|
||||
f"{new_threshold:,} tokens so compression can run.\n"
|
||||
f" To make this permanent, edit config.yaml — either:\n"
|
||||
f" 1. Use a larger compression model:\n"
|
||||
f" auxiliary:\n"
|
||||
f" compression:\n"
|
||||
f" model: <model-with-{old_threshold:,}+-context>\n"
|
||||
f" 2. Lower the compression threshold:\n"
|
||||
f" compression:\n"
|
||||
f" threshold: 0.{safe_pct:02d}"
|
||||
)
|
||||
if threshold_suggestion_viable:
|
||||
msg += (
|
||||
f" To make this permanent, edit config.yaml — either:\n"
|
||||
f" 1. Use a larger compression model:\n"
|
||||
f" auxiliary:\n"
|
||||
f" compression:\n"
|
||||
f" model: <model-with-{old_threshold:,}+-context>\n"
|
||||
f" 2. Lower the compression threshold:\n"
|
||||
f" compression:\n"
|
||||
f" threshold: 0.{safe_pct:02d}"
|
||||
)
|
||||
else:
|
||||
msg += (
|
||||
f" To make this permanent, use a larger compression "
|
||||
f"model in config.yaml:\n"
|
||||
f" auxiliary:\n"
|
||||
f" compression:\n"
|
||||
f" model: <model-with-{old_threshold:,}+-context>\n"
|
||||
f" (Lowering compression.threshold cannot help here — "
|
||||
f"{_main_label}'s {main_ctx:,}-token window is under "
|
||||
f"512K, where Hermes floors the compression trigger at "
|
||||
f"75% and raises any lower configured value back up.)"
|
||||
)
|
||||
agent._compression_warning = msg
|
||||
agent._emit_status(msg)
|
||||
logger.warning(
|
||||
|
|
|
|||
|
|
@ -95,7 +95,12 @@ def test_auto_corrects_threshold_when_aux_context_below_threshold(mock_get_clien
|
|||
assert "config.yaml" in messages[0]
|
||||
assert "auxiliary:" in messages[0]
|
||||
assert "compression:" in messages[0]
|
||||
assert "threshold:" in messages[0]
|
||||
# 200K main is under the 512K small-context limit and 80K/200K = 40% sits
|
||||
# below the 75% floor — a `threshold:` suggestion would be raised back to
|
||||
# 75% and ignored (#67422), so the message must not offer one and must
|
||||
# explain the floor instead.
|
||||
assert "threshold:" not in messages[0]
|
||||
assert "75%" in messages[0]
|
||||
# Warning stored for gateway replay
|
||||
assert agent._compression_warning is not None
|
||||
# Threshold on the live compressor was actually lowered to aux_context.
|
||||
|
|
@ -514,3 +519,48 @@ def test_run_conversation_clears_warning_after_replay(mock_get_client, mock_ctx_
|
|||
agent._compression_warning = None
|
||||
|
||||
assert len(callback_events) == 0
|
||||
|
||||
|
||||
# ── #67422: threshold suggestion must survive the small-context floor ────────
|
||||
|
||||
|
||||
@patch("agent.model_metadata.get_model_context_length", return_value=80_000)
|
||||
@patch("agent.auxiliary_client.get_text_auxiliary_client")
|
||||
def test_threshold_suggestion_kept_when_at_or_above_floor(mock_get_client, mock_ctx_len):
|
||||
"""Small-context main, but aux/main = 80% >= the 75% floor — the
|
||||
`threshold:` suggestion is still viable and must be offered."""
|
||||
agent = _make_agent(main_context=100_000, threshold_percent=0.85)
|
||||
# threshold = 85,000 — aux has 80,000 (above 64K floor, below threshold)
|
||||
mock_client = MagicMock()
|
||||
mock_client.base_url = "https://openrouter.ai/api/v1"
|
||||
mock_client.api_key = "sk-aux"
|
||||
mock_get_client.return_value = (mock_client, "google/gemini-3-flash-preview")
|
||||
|
||||
messages = []
|
||||
agent._emit_status = lambda msg: messages.append(msg)
|
||||
|
||||
agent._check_compression_model_feasibility()
|
||||
|
||||
assert len(messages) == 1
|
||||
assert "threshold: 0.80" in messages[0]
|
||||
|
||||
|
||||
@patch("agent.model_metadata.get_model_context_length", return_value=300_000)
|
||||
@patch("agent.auxiliary_client.get_text_auxiliary_client")
|
||||
def test_threshold_suggestion_kept_for_large_context_main(mock_get_client, mock_ctx_len):
|
||||
"""Main window >= 512K has no floor — any suggestion is honored, so the
|
||||
`threshold:` option stays even below 75%."""
|
||||
agent = _make_agent(main_context=1_000_000, threshold_percent=0.50)
|
||||
# threshold = 500,000 — aux has 300,000
|
||||
mock_client = MagicMock()
|
||||
mock_client.base_url = "https://openrouter.ai/api/v1"
|
||||
mock_client.api_key = "sk-aux"
|
||||
mock_get_client.return_value = (mock_client, "google/gemini-3-flash-preview")
|
||||
|
||||
messages = []
|
||||
agent._emit_status = lambda msg: messages.append(msg)
|
||||
|
||||
agent._check_compression_model_feasibility()
|
||||
|
||||
assert len(messages) == 1
|
||||
assert "threshold: 0.30" in messages[0]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue