mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-20 15:33:54 +00:00
fix(context): count fallback compactions as ineffective
This commit is contained in:
parent
55624e10b9
commit
5ce827cac9
2 changed files with 79 additions and 4 deletions
|
|
@ -1186,13 +1186,23 @@ class ContextCompressor(ContextEngine):
|
|||
self.last_total_tokens = usage.get("total_tokens", self.last_prompt_tokens + self.last_completion_tokens)
|
||||
if self.last_prompt_tokens > 0:
|
||||
self.last_real_prompt_tokens = self.last_prompt_tokens
|
||||
fallback_compaction = (
|
||||
self._verify_compaction_cleared_threshold
|
||||
and self._last_summary_fallback_used
|
||||
)
|
||||
if self.last_prompt_tokens < self.threshold_tokens:
|
||||
if self.awaiting_real_usage_after_compression and self.last_compression_rough_tokens > 0:
|
||||
self.last_rough_tokens_when_real_prompt_fit = self.last_compression_rough_tokens
|
||||
# Any real provider reading below the trigger proves the prompt
|
||||
# fits again. Clear the episode latch even when this response was
|
||||
# not the one immediately following compaction.
|
||||
self._ineffective_compression_count = 0
|
||||
# fits again, UNLESS the just-finished compaction had to fall
|
||||
# back to the deterministic placeholder summary. That path
|
||||
# does shrink the prompt, but it is still a degraded compaction
|
||||
# attempt and must count toward the anti-thrashing strike limit.
|
||||
# Otherwise repeated empty-summary failures can rotate through
|
||||
# fallback markers forever while each smaller prompt reading
|
||||
# resets the counter back to zero (#63008 / R1).
|
||||
if not fallback_compaction:
|
||||
self._ineffective_compression_count = 0
|
||||
else:
|
||||
self.last_rough_tokens_when_real_prompt_fit = 0
|
||||
|
||||
|
|
@ -1213,7 +1223,17 @@ class ContextCompressor(ContextEngine):
|
|||
# Keying on real usage compares like with like and fires exactly once
|
||||
# per compaction.
|
||||
if self._verify_compaction_cleared_threshold:
|
||||
if self.last_prompt_tokens >= self.threshold_tokens:
|
||||
if fallback_compaction:
|
||||
self._ineffective_compression_count += 1
|
||||
if not self.quiet_mode:
|
||||
logger.warning(
|
||||
"Compaction completed with a deterministic fallback "
|
||||
"summary. Counting this as a degraded attempt to "
|
||||
"avoid repeated fallback-only compaction loops. "
|
||||
"ineffective_compression_count=%d",
|
||||
self._ineffective_compression_count,
|
||||
)
|
||||
elif self.last_prompt_tokens >= self.threshold_tokens:
|
||||
self._ineffective_compression_count += 1
|
||||
if not self.quiet_mode:
|
||||
logger.warning(
|
||||
|
|
|
|||
|
|
@ -65,6 +65,61 @@ class TestUpdateFromResponse:
|
|||
compressor.update_from_response({})
|
||||
assert compressor.last_prompt_tokens == 0
|
||||
|
||||
def test_fallback_compaction_counts_as_ineffective_even_when_prompt_fits(self, compressor):
|
||||
compressor.threshold_tokens = 85_000
|
||||
compressor.awaiting_real_usage_after_compression = True
|
||||
compressor.last_compression_rough_tokens = 90_000
|
||||
compressor._verify_compaction_cleared_threshold = True
|
||||
compressor._last_summary_fallback_used = True
|
||||
|
||||
compressor.update_from_response({
|
||||
"prompt_tokens": 5_000,
|
||||
"completion_tokens": 100,
|
||||
"total_tokens": 5_100,
|
||||
})
|
||||
|
||||
assert compressor.last_real_prompt_tokens == 5_000
|
||||
assert compressor.last_rough_tokens_when_real_prompt_fit == 90_000
|
||||
assert compressor._ineffective_compression_count == 1
|
||||
assert compressor._verify_compaction_cleared_threshold is False
|
||||
assert compressor.awaiting_real_usage_after_compression is False
|
||||
|
||||
def test_successful_compaction_fit_still_clears_prior_ineffective_count(self, compressor):
|
||||
compressor.threshold_tokens = 85_000
|
||||
compressor.awaiting_real_usage_after_compression = True
|
||||
compressor.last_compression_rough_tokens = 90_000
|
||||
compressor._verify_compaction_cleared_threshold = True
|
||||
compressor._last_summary_fallback_used = False
|
||||
compressor._ineffective_compression_count = 1
|
||||
|
||||
compressor.update_from_response({
|
||||
"prompt_tokens": 5_000,
|
||||
"completion_tokens": 100,
|
||||
"total_tokens": 5_100,
|
||||
})
|
||||
|
||||
assert compressor._ineffective_compression_count == 0
|
||||
|
||||
def test_second_fallback_strike_blocks_future_compression(self, compressor):
|
||||
compressor.threshold_tokens = 85_000
|
||||
compressor.last_prompt_tokens = 90_000
|
||||
assert compressor.should_compress() is True
|
||||
|
||||
for expected in (1, 2):
|
||||
compressor.awaiting_real_usage_after_compression = True
|
||||
compressor.last_compression_rough_tokens = 90_000
|
||||
compressor._verify_compaction_cleared_threshold = True
|
||||
compressor._last_summary_fallback_used = True
|
||||
compressor.update_from_response({
|
||||
"prompt_tokens": 5_000,
|
||||
"completion_tokens": 100,
|
||||
"total_tokens": 5_100,
|
||||
})
|
||||
assert compressor._ineffective_compression_count == expected
|
||||
compressor.last_prompt_tokens = 90_000
|
||||
|
||||
assert compressor.should_compress() is False
|
||||
|
||||
|
||||
class TestPreflightDeferral:
|
||||
def test_defers_when_recent_real_usage_fit_and_rough_growth_is_small(self, compressor):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue