fix(context_management): count the system prompt through compact's shared counter

Finishes the main merge's conflict in clear_tool_uses.py. The input_tokens trigger now adds the system prompt through count_system_tokens, the same counter compact uses, while the count reused for cleared_input_tokens stays messages and tools only, so the reported savings are just the cleared tool results
This commit is contained in:
mateo-berri 2026-10-28 08:29:13 -07:00
parent 32b72b2dc1
commit f231abdb81
3 changed files with 21 additions and 18 deletions

View file

@ -31,7 +31,7 @@ from ..constants import (
)
from ..errors import AnthropicContextManagementError
from ..placeholders import build_cleared_tool_result_content
from .compact import system_to_text
from .compact import count_system_tokens
if TYPE_CHECKING:
from litellm.types.llms.openai import ChatCompletionToolParam
@ -199,14 +199,6 @@ def _select_in_batches(candidates: Sequence[str], tokens: Mapping[str, int], cle
return tuple(candidates[:batch_end])
def _with_system(
messages: Sequence[Mapping[str, object]], system: str | Sequence[Mapping[str, object]] | None
) -> Sequence[Mapping[str, object]]:
"""The messages to count: like the API, an ``input_tokens`` trigger counts the system prompt too."""
system_text: Final = system_to_text(system)
return ({"role": "system", "content": system_text}, *messages) if system_text else messages
def _trigger_met(
trigger: dict[str, object],
model: str,
@ -215,7 +207,7 @@ def _trigger_met(
system: str | Sequence[Mapping[str, object]] | None,
counted_tool_uses: int,
) -> tuple[bool, int | None]:
"""Return (trigger_met, input_tokens if counted for reuse)."""
"""Return (trigger_met, message and tool tokens for reuse); like the API, input_tokens adds the system prompt."""
trigger_type: Final = trigger.get("type", "input_tokens")
threshold = trigger.get("value")
@ -226,14 +218,15 @@ def _trigger_met(
if not isinstance(threshold, int):
threshold = DEFAULT_INPUT_TOKENS_TRIGGER
current_tokens: Final = litellm.token_counter(
message_tokens: Final = litellm.token_counter(
model=model,
messages=messages,
tools=cast("list[ChatCompletionToolParam] | None", tools),
)
current_tokens: Final = message_tokens + count_system_tokens(model, system)
verbose_logger.debug("context_management polyfill: current_tokens: %s", current_tokens)
verbose_logger.debug("context_management polyfill: threshold: %s", threshold)
return current_tokens > threshold, current_tokens
return current_tokens > threshold, message_tokens
def _resolve_keep_count(keep: dict[str, object]) -> int:

View file

@ -320,13 +320,15 @@ def _count_effective_tokens(
content: Final = compaction_block.get("content") or ""
if content:
total += litellm.token_counter(model=model, text=content)
system_text: Final = system_to_text(system)
if system_text:
total += litellm.token_counter(model=model, text=system_text)
return total
return total + count_system_tokens(model, system)
def system_to_text(
def count_system_tokens(model: str, system: str | Sequence[Mapping[str, object]] | None) -> int:
system_text: Final = _system_to_text(system)
return litellm.token_counter(model=model, text=system_text) if system_text else 0
def _system_to_text(
system: str | Sequence[Mapping[str, object]] | None,
) -> str:
"""Flatten an Anthropic-style ``system`` value into a single string for

View file

@ -709,10 +709,18 @@ def test_input_tokens_trigger_counts_the_system_prompt(system: str | list[dict[s
_, with_system = apply_clear_tool_uses_20250919(
model=MODEL, messages=deepcopy(messages), tools=None, system=system, edit_spec=edit_spec
)
_, met_without_system = apply_clear_tool_uses_20250919(
model=MODEL,
messages=deepcopy(messages),
tools=None,
system=None,
edit_spec={**edit_spec, "trigger": {"type": "input_tokens", "value": 0}},
)
assert without_system is None
assert with_system is not None
assert met_without_system is not None
assert with_system["cleared_tool_uses"] == 2
assert with_system["cleared_input_tokens"] < history_tokens
assert with_system["cleared_input_tokens"] == met_without_system["cleared_input_tokens"] > 0
@pytest.mark.parametrize(("clear_tool_inputs", "triggers"), [(False, False), (True, True)])