mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-11-06 05:17:15 +00:00
The route identity read a provider entry's context pin only for a task that names the entry. A task with `provider: custom` and its own `base_url` returned early, but the probe still reads the pin of the entry at that URL, so lowering only that pin (900K to 80K) left a warm agent's trigger on the old window. The identity now carries the pin for the route's own URL too. Reported-by: greptile-apps[bot]
128 lines
6.5 KiB
Python
128 lines
6.5 KiB
Python
"""When the compression feasibility probe runs: after a main-runtime change, once a request first reaches
|
|
the smallest summariser window, and again when a warm agent meets a compression route other than the one
|
|
it probed. The probe itself is ``check_compression_model_feasibility`` in ``agent.conversation_compression``."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import Any
|
|
|
|
logger = logging.getLogger("agent.conversation_compression")
|
|
|
|
|
|
def _probe(agent: Any) -> None:
|
|
from agent.conversation_compression import check_compression_model_feasibility
|
|
check_compression_model_feasibility(agent)
|
|
|
|
|
|
def _mark_checked(agent: Any, route: tuple) -> None:
|
|
"""Latch the probe as done for *route*, the compression route read before the probe ran: an edit that
|
|
lands during the probe is then a moved route on the next request, not a measured one."""
|
|
agent._compression_feasibility_checked = True
|
|
agent._compression_feasibility_route = route
|
|
|
|
|
|
def compression_route_identity() -> tuple:
|
|
"""The configured compression route a probe measures: the task's provider, model, URL and context
|
|
length, the URL of a ``providers:`` / ``custom_providers:`` entry the task names, and the context pin
|
|
a provider entry sets for the compression model at the route's URL. A config this
|
|
cannot read yields ``("unreadable",)``: the main request carries on and the probe reports the problem."""
|
|
try:
|
|
return _route_identity()
|
|
except Exception:
|
|
logger.debug("Compression route unreadable for the feasibility latch", exc_info=True)
|
|
return ("unreadable",)
|
|
|
|
|
|
def _route_identity() -> tuple:
|
|
from agent.auxiliary_task_config import _get_auxiliary_task_config
|
|
cfg = _get_auxiliary_task_config("compression")
|
|
route = tuple(str(cfg.get(k) or "") for k in ("provider", "model", "base_url", "context_length"))
|
|
provider = route[0].strip()
|
|
from hermes_cli.config import get_custom_provider_context_length
|
|
if route[2]:
|
|
url, model, named = route[2], route[1], ()
|
|
elif not provider or provider in ("auto", "main", "custom"):
|
|
return route
|
|
else:
|
|
from hermes_cli.runtime_provider_custom import _get_named_custom_provider
|
|
entry = _get_named_custom_provider(provider, resolve_credentials=False) or {}
|
|
url, model = str(entry.get("base_url") or ""), route[1] or str(entry.get("model") or "")
|
|
named = (url, str(entry.get("model") or ""))
|
|
# The context pin the probe reads for this model at this URL (a matching entry's per-model, else entry-level).
|
|
pin = get_custom_provider_context_length(model=model, base_url=url) if url and model else None
|
|
return route + named + (str(pin or ""),)
|
|
|
|
|
|
def _drop_stale_ceiling(agent: Any) -> None:
|
|
"""A warm agent met a compression route other than the one it probed: the ceiling it installed and
|
|
any failure cooldown belong to the old summariser, so the trigger returns to the main window and the
|
|
cooldown is cleared (in memory and in the session row) before the new probe."""
|
|
agent._compression_feasibility_checked = False
|
|
compressor = agent.context_compressor
|
|
clear_cooldown = getattr(compressor, "_clear_compression_failure_cooldown", None)
|
|
if callable(clear_cooldown):
|
|
clear_cooldown()
|
|
if getattr(compressor, "_aux_context_ceiling", None) is not None:
|
|
compressor._aux_context_ceiling = None
|
|
compressor.update_model(compressor.model, compressor.context_length, base_url=compressor.base_url,
|
|
api_key=compressor.api_key, provider=compressor.provider,
|
|
api_mode=compressor.api_mode)
|
|
|
|
|
|
def revalidate_compression_feasibility(agent: Any) -> None:
|
|
"""Re-run the aux feasibility probe after the main runtime changed (model switch, fallback activation,
|
|
primary restore). ``update_model()`` already voided the previous ceiling; probing now clamps the trigger
|
|
before the first compaction on the new window rather than after it (#114707). A probe failure leaves the
|
|
latch unset so the lazy probe at the next compaction re-raises hard rejections."""
|
|
agent._compression_feasibility_checked = False
|
|
if not getattr(agent, "context_compressor", None):
|
|
return
|
|
route = compression_route_identity()
|
|
try:
|
|
_probe(agent)
|
|
except Exception as exc:
|
|
logger.debug("Compression feasibility re-check deferred to the next compaction: %s", exc, exc_info=True)
|
|
return
|
|
_mark_checked(agent, route)
|
|
|
|
|
|
def ensure_compression_feasibility_checked(agent: Any, estimated_tokens: int) -> None:
|
|
"""Run the deferred aux feasibility probe once a request first reaches ``MINIMUM_CONTEXT_LENGTH`` — the
|
|
smallest window any summariser may have — so an aux clamp lands before the first compaction fires on the
|
|
main-window threshold instead of after it (#114707). Below that size no summariser can be too small, so
|
|
short sessions keep the probe-free cold start (#28957). A probe failure leaves the latch unset for the
|
|
lazy probe in ``compress_context`` to re-raise hard rejections. A warm agent whose compression route moved
|
|
since its last probe (side tasks follow the profile's config.yaml) probes again."""
|
|
if not getattr(agent, "context_compressor", None):
|
|
return
|
|
route = compression_route_identity()
|
|
if getattr(agent, "_compression_feasibility_checked", False):
|
|
probed = getattr(agent, "_compression_feasibility_route", None)
|
|
if probed is None or route == probed:
|
|
return
|
|
_drop_stale_ceiling(agent)
|
|
from agent.model_metadata import MINIMUM_CONTEXT_LENGTH
|
|
if int(estimated_tokens or 0) < MINIMUM_CONTEXT_LENGTH:
|
|
return
|
|
try:
|
|
_probe(agent)
|
|
except Exception as exc:
|
|
logger.debug("Compression feasibility probe deferred to the first compaction: %s", exc, exc_info=True)
|
|
return
|
|
_mark_checked(agent, route)
|
|
|
|
|
|
def lazy_compression_feasibility_check(agent: Any) -> None:
|
|
"""The probe ``compress_context`` runs when nothing probed yet, or the route moved since the probe
|
|
that recorded one; a hard rejection propagates to the caller. A latch with no recorded route (set
|
|
before route tracking, or by a caller marking the agent checked) is trusted as it always was."""
|
|
route = compression_route_identity()
|
|
if getattr(agent, "_compression_feasibility_checked", False):
|
|
probed = getattr(agent, "_compression_feasibility_route", None)
|
|
if probed is None or route == probed:
|
|
return
|
|
_drop_stale_ceiling(agent)
|
|
_probe(agent)
|
|
_mark_checked(agent, route)
|