fix(context_management): run edits on the history drop_params sends, so count_tokens and /v1/messages clear alike

This commit is contained in:
mateo-berri 2026-10-28 11:17:24 -07:00
parent 272e9cfea2
commit b089560973
2 changed files with 58 additions and 2 deletions

View file

@ -8,6 +8,7 @@ from litellm._logging import verbose_logger
from litellm.litellm_core_utils.asyncify import asyncify
from litellm.types.llms.anthropic import AppliedEdit
from ..convertible_blocks import drop_non_string_tool_result_texts
from .constants import CLEAR_TOOL_USES_EDIT_TYPE, COMPACT_EDIT_TYPE
from .editors import (
CheckedEdit,
@ -103,7 +104,7 @@ async def apply_context_management(
Every ``clear_tool_uses_20250919`` edit is validated before any editor runs, so a malformed one raises its 400
before a ``compact_20260112`` summary call is paid for, or runs without its malformed fields under
``drop_invalid_edits``.
``drop_invalid_edits``, which also drops non-string ``tool_result`` text first, as the provider never receives it.
The dispatcher is async so async editors (``compact_20260112``) can
``await`` the configured summarization model. Sync editors run in a
@ -119,7 +120,7 @@ async def apply_context_management(
raise first_error
well_formed_edits: Final = tuple(check.edit for check in checked)
current_messages = messages
current_messages = list(drop_non_string_tool_result_texts(messages, drop_invalid_edits))
current_system = system
aggregated_applied: Final[list[AppliedEdit]] = []
aggregated_compaction_block = None

View file

@ -5,6 +5,7 @@ Unit tests for the context_management polyfill dispatcher.
from litellm.llms.anthropic.messages.context_management import (
apply_context_management,
)
from litellm.llms.anthropic.messages.convertible_blocks import drop_non_string_tool_result_texts
MODEL = "xai/grok-4"
@ -159,3 +160,57 @@ async def test_sync_editor_counts_tokens_off_the_event_loop():
assert result.messages == messages
assert_loop_stayed_free(took, lags)
def _history_with_null_text_beside_a_short_first_result():
return [
{"role": "user", "content": "Hi"},
{
"role": "assistant",
"content": [{"type": "tool_use", "id": "t0", "name": "f", "input": {}}],
},
{
"role": "user",
"content": [
{
"type": "tool_result",
"tool_use_id": "t0",
"content": [{"type": "text", "text": "ok"}, {"type": "text", "text": None}],
}
],
},
*_history_with_two_tool_pairs()[1:],
]
async def test_drop_invalid_edits_clears_as_if_malformed_tool_result_text_was_never_sent():
spec = {
"edits": [
{
"type": "clear_tool_uses_20250919",
"trigger": {"type": "tool_uses", "value": 2},
"keep": {"type": "tool_uses", "value": 1},
}
]
}
history = _history_with_null_text_beside_a_short_first_result()
from_raw = await apply_context_management(
model=MODEL,
messages=history,
tools=None,
system=None,
context_management_spec=spec,
drop_invalid_edits=True,
)
from_sent = await apply_context_management(
model=MODEL,
messages=list(drop_non_string_tool_result_texts(history, True)),
tools=None,
system=None,
context_management_spec=spec,
drop_invalid_edits=True,
)
assert from_raw.applied_edits == from_sent.applied_edits
assert from_raw.messages == from_sent.messages