mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-31 19:16:29 +00:00
fix(gateway): widen compression noise filter to all routine status lines
Post-sweep audit of every compression status emission (conversation_loop,
turn_context, conversation_compression): the buffered overflow/attempt-cap
retry chatter (🗜️ 'Context too large…', 'Compressed X → Y, retrying…',
'Context reduced to…'), the #69332-reworded auto-lower notice
("Auto-lowered this session's threshold…"), the aux-provider-unavailable
notice, and the concurrent-compression skip all leaked past
_TELEGRAM_NOISY_STATUS_RE on chat platforms. Add anchored alternatives for
each; the ', retrying'/'— compressing' anchors keep manual /compress
feedback ('Compressed: 30 → 12 messages') and failure/abort notices
visible per the deliberate carve-outs.
Also extract every routine compression status string into importable
template constants in agent/conversation_compression.py (single source of
truth shared by all emission sites), so tests can iterate the actual
emitted wording instead of hand-copied literals.
This commit is contained in:
parent
a2068c668b
commit
9981242f88
4 changed files with 96 additions and 16 deletions
|
|
@ -71,6 +71,58 @@ def _emit_compaction_done(agent: Any) -> None:
|
|||
logger.debug("status_callback error in compaction completion", exc_info=True)
|
||||
|
||||
|
||||
# ── Routine compression status templates ────────────────────────────────────
|
||||
# Every ROUTINE (non-failure, non-manual-/compress) compression status line the
|
||||
# agent emits lives here so the gateway noise filter and its tests can couple
|
||||
# to the real emitted wording instead of hand-copied literals. These are
|
||||
# suppressed on human-facing chat platforms by _TELEGRAM_NOISY_STATUS_RE
|
||||
# (gateway/run.py) — when rewording ANY of them, update that regex and the
|
||||
# pinned data in tests/gateway/test_telegram_noise_filter.py in the same PR.
|
||||
# Failure notices (⚠ Compression aborted / empty transcript / codex compaction
|
||||
# failed) and manual /compress feedback (manual_compression_feedback.py) are
|
||||
# deliberate carve-outs from silence and must NOT be added here.
|
||||
PRE_API_COMPRESSION_STATUS_TEMPLATE = (
|
||||
"📦 Pre-API compression: ~{tokens:,} tokens "
|
||||
"near the context/output limit. Compacting before the next model call."
|
||||
)
|
||||
PREFLIGHT_COMPRESSION_STATUS_TEMPLATE = (
|
||||
"📦 Preflight compression: ~{tokens:,} tokens "
|
||||
">= {threshold:,} threshold. This may take a moment."
|
||||
)
|
||||
IDLE_COMPACTION_STATUS_TEMPLATE = (
|
||||
"💤 Resumed after {idle_seconds}s idle — compacting "
|
||||
"~{tokens:,} tokens before continuing."
|
||||
)
|
||||
COMPRESSION_RETRY_TOO_LARGE_STATUS_TEMPLATE = (
|
||||
"🗜️ Context too large (~{tokens:,} tokens) — compressing ({attempt}/{cap})..."
|
||||
)
|
||||
COMPRESSION_RETRY_MESSAGES_STATUS_TEMPLATE = (
|
||||
"🗜️ Compressed {before} → {after} messages, retrying..."
|
||||
)
|
||||
COMPRESSION_RETRY_TOKENS_STATUS_TEMPLATE = (
|
||||
"🗜️ Compressed ~{before:,} → ~{after:,} tokens, retrying..."
|
||||
)
|
||||
COMPRESSION_RETRY_CONTEXT_REDUCED_STATUS_TEMPLATE = (
|
||||
"🗜️ Context reduced to {new_ctx:,} tokens (was {old_ctx:,}), retrying..."
|
||||
)
|
||||
|
||||
# Sample-formatted instances of every routine compression status line, for
|
||||
# behavioral tests that iterate the ACTUAL emitted wording (formatted from the
|
||||
# same constants the emission sites use) through the gateway noise filter.
|
||||
ROUTINE_COMPRESSION_STATUS_SAMPLES = (
|
||||
COMPACTION_STATUS,
|
||||
PRE_API_COMPRESSION_STATUS_TEMPLATE.format(tokens=123456),
|
||||
PREFLIGHT_COMPRESSION_STATUS_TEMPLATE.format(tokens=120000, threshold=100000),
|
||||
IDLE_COMPACTION_STATUS_TEMPLATE.format(idle_seconds=3600, tokens=120000),
|
||||
COMPRESSION_RETRY_TOO_LARGE_STATUS_TEMPLATE.format(tokens=250000, attempt=1, cap=3),
|
||||
COMPRESSION_RETRY_MESSAGES_STATUS_TEMPLATE.format(before=30, after=12),
|
||||
COMPRESSION_RETRY_TOKENS_STATUS_TEMPLATE.format(before=250000, after=120000),
|
||||
COMPRESSION_RETRY_CONTEXT_REDUCED_STATUS_TEMPLATE.format(
|
||||
new_ctx=120000, old_ctx=250000
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _builtin_memory_prompt_snapshot(agent: Any) -> Optional[Tuple[str, str]]:
|
||||
"""Return the built-in memory text that can affect a system prompt.
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,14 @@ import uuid
|
|||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from agent.codex_responses_adapter import _summarize_user_message_for_log
|
||||
from agent.conversation_compression import conversation_history_after_compression
|
||||
from agent.conversation_compression import (
|
||||
COMPRESSION_RETRY_CONTEXT_REDUCED_STATUS_TEMPLATE,
|
||||
COMPRESSION_RETRY_MESSAGES_STATUS_TEMPLATE,
|
||||
COMPRESSION_RETRY_TOKENS_STATUS_TEMPLATE,
|
||||
COMPRESSION_RETRY_TOO_LARGE_STATUS_TEMPLATE,
|
||||
PRE_API_COMPRESSION_STATUS_TEMPLATE,
|
||||
conversation_history_after_compression,
|
||||
)
|
||||
from agent.display import KawaiiSpinner
|
||||
from agent.error_classifier import FailoverReason, classify_api_error
|
||||
from agent.iteration_budget import IterationBudget
|
||||
|
|
@ -1279,8 +1286,9 @@ def run_conversation(
|
|||
max_compression_attempts,
|
||||
)
|
||||
agent._emit_status(
|
||||
f"📦 Pre-API compression: ~{request_pressure_tokens:,} tokens "
|
||||
f"near the context/output limit. Compacting before the next model call."
|
||||
PRE_API_COMPRESSION_STATUS_TEMPLATE.format(
|
||||
tokens=request_pressure_tokens
|
||||
)
|
||||
)
|
||||
_last_preflight_pressure = request_pressure_tokens
|
||||
messages, active_system_prompt = agent._compress_context(
|
||||
|
|
@ -3494,8 +3502,9 @@ def run_conversation(
|
|||
)
|
||||
if len(messages) < original_len or old_ctx > _reduced_ctx:
|
||||
agent._buffer_status(
|
||||
f"🗜️ Context reduced to {_reduced_ctx:,} tokens "
|
||||
f"(was {old_ctx:,}), retrying..."
|
||||
COMPRESSION_RETRY_CONTEXT_REDUCED_STATUS_TEMPLATE.format(
|
||||
new_ctx=_reduced_ctx, old_ctx=old_ctx
|
||||
)
|
||||
)
|
||||
time.sleep(2)
|
||||
_retry.restart_with_compressed_messages = True
|
||||
|
|
@ -3756,9 +3765,9 @@ def run_conversation(
|
|||
|
||||
if len(messages) < original_len or (new_tokens > 0 and new_tokens < original_tokens * 0.95):
|
||||
if len(messages) < original_len:
|
||||
agent._buffer_status(f"🗜️ Compressed {original_len} → {len(messages)} messages, retrying...")
|
||||
agent._buffer_status(COMPRESSION_RETRY_MESSAGES_STATUS_TEMPLATE.format(before=original_len, after=len(messages)))
|
||||
else:
|
||||
agent._buffer_status(f"🗜️ Compressed ~{original_tokens:,} → ~{new_tokens:,} tokens, retrying...")
|
||||
agent._buffer_status(COMPRESSION_RETRY_TOKENS_STATUS_TEMPLATE.format(before=original_tokens, after=new_tokens))
|
||||
time.sleep(2) # Brief pause between compression retries
|
||||
_retry.restart_with_compressed_messages = True
|
||||
break
|
||||
|
|
@ -3976,7 +3985,7 @@ def run_conversation(
|
|||
"failed": True,
|
||||
"compression_exhausted": True,
|
||||
}
|
||||
agent._buffer_status(f"🗜️ Context too large (~{approx_tokens:,} tokens) — compressing ({compression_attempts}/{max_compression_attempts})...")
|
||||
agent._buffer_status(COMPRESSION_RETRY_TOO_LARGE_STATUS_TEMPLATE.format(tokens=approx_tokens, attempt=compression_attempts, cap=max_compression_attempts))
|
||||
|
||||
original_len = len(messages)
|
||||
original_tokens = estimate_messages_tokens_rough(messages)
|
||||
|
|
@ -3997,9 +4006,9 @@ def run_conversation(
|
|||
|
||||
if len(messages) < original_len or (new_tokens > 0 and new_tokens < original_tokens * 0.95) or (new_ctx and new_ctx < old_ctx):
|
||||
if len(messages) < original_len:
|
||||
agent._buffer_status(f"🗜️ Compressed {original_len} → {len(messages)} messages, retrying...")
|
||||
agent._buffer_status(COMPRESSION_RETRY_MESSAGES_STATUS_TEMPLATE.format(before=original_len, after=len(messages)))
|
||||
elif new_tokens > 0 and new_tokens < original_tokens * 0.95:
|
||||
agent._buffer_status(f"🗜️ Compressed ~{original_tokens:,} → ~{new_tokens:,} tokens, retrying...")
|
||||
agent._buffer_status(COMPRESSION_RETRY_TOKENS_STATUS_TEMPLATE.format(before=original_tokens, after=new_tokens))
|
||||
time.sleep(2) # Brief pause between compression retries
|
||||
_retry.restart_with_compressed_messages = True
|
||||
break
|
||||
|
|
|
|||
|
|
@ -31,7 +31,11 @@ import uuid
|
|||
from dataclasses import dataclass
|
||||
from typing import Any, Dict, List, Mapping, Optional
|
||||
|
||||
from agent.conversation_compression import conversation_history_after_compression
|
||||
from agent.conversation_compression import (
|
||||
IDLE_COMPACTION_STATUS_TEMPLATE,
|
||||
PREFLIGHT_COMPRESSION_STATUS_TEMPLATE,
|
||||
conversation_history_after_compression,
|
||||
)
|
||||
from agent.iteration_budget import IterationBudget
|
||||
from agent.memory_manager import build_memory_context_block
|
||||
from agent.model_metadata import (
|
||||
|
|
@ -659,8 +663,9 @@ def build_turn_context(
|
|||
agent.session_id or "none",
|
||||
)
|
||||
agent._emit_status(
|
||||
f"💤 Resumed after {int(_idle_gap)}s idle — compacting "
|
||||
f"~{_idle_tokens:,} tokens before continuing."
|
||||
IDLE_COMPACTION_STATUS_TEMPLATE.format(
|
||||
idle_seconds=int(_idle_gap), tokens=_idle_tokens
|
||||
)
|
||||
)
|
||||
_idle_input = messages
|
||||
messages, active_system_prompt = agent._compress_context(
|
||||
|
|
@ -767,9 +772,10 @@ def build_turn_context(
|
|||
f"{_compressor.context_length:,}",
|
||||
)
|
||||
agent._emit_status(
|
||||
f"📦 Preflight compression: ~{_preflight_tokens:,} tokens "
|
||||
f">= {_compressor.threshold_tokens:,} threshold. "
|
||||
"This may take a moment."
|
||||
PREFLIGHT_COMPRESSION_STATUS_TEMPLATE.format(
|
||||
tokens=_preflight_tokens,
|
||||
threshold=_compressor.threshold_tokens,
|
||||
)
|
||||
)
|
||||
# Preflight passes honor the same configured per-turn cap
|
||||
# (compression.max_attempts) as the loop's compression sites;
|
||||
|
|
|
|||
|
|
@ -79,10 +79,23 @@ _TELEGRAM_NOISY_STATUS_RE = re.compile(
|
|||
r"|configured\s+compression\s+model\s+.+\s+failed"
|
||||
r"|no\s+auxiliary\s+llm\s+provider\s+configured"
|
||||
r"|auto-lowered\s+compression\s+threshold"
|
||||
# #69332 reworded the auto-lower notice to "Auto-lowered this session's
|
||||
# threshold to N tokens" — keep both generations covered.
|
||||
r"|auto-lowered\s+(?:this\s+)?session'?s?\s+threshold"
|
||||
r"|configured\s+auxiliary\s+compression\s+provider\s+.+\s+unavailable"
|
||||
r"|skipping\s+concurrent\s+compression"
|
||||
r"|compacting\s+context\s+[—-]\s+summarizing\s+earlier\s+conversation"
|
||||
r"|resumed\s+after\s+\d+s\s+idle\s+[—-]\s+compacting"
|
||||
r"|preflight\s+compression"
|
||||
r"|pre[- ]api\s+compression"
|
||||
# Buffered attempt/overflow retry chatter replayed through _emit_status
|
||||
# when a turn exhausts retries. The ", retrying"/"— compressing" anchors
|
||||
# keep manual /compress feedback ("Compressed: 30 → 12 messages") and
|
||||
# failure notices out of the match.
|
||||
r"|context\s+too\s+large\s+\(~[\d,]+\s+tokens\)\s+[—-]+\s+compressing"
|
||||
r"|compressed\s+\d[\d,]*\s+(?:→|->)\s+\d[\d,]*\s+messages,\s+retrying"
|
||||
r"|compressed\s+~[\d,]+\s+(?:→|->)\s+~[\d,]+\s+tokens,\s+retrying"
|
||||
r"|context\s+reduced\s+to\s+[\d,]+\s+tokens\s+\(was\s+[\d,]+\),\s+retrying"
|
||||
r"|session\s+compressed\s+\d+\s+times"
|
||||
r"|rate\s+limited\.\s+waiting\s+\d"
|
||||
r"|retrying\s+in\s+\d"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue