mirror of
https://github.com/usestrix/strix.git
synced 2026-08-21 18:52:47 +02:00
fix(context): skip compaction when the window can't fit a summary request
When instructions, the reserved summary output, and any prior checkpoint already consume the model's context window, the summary-input budget is zero. Previously a doomed summary request was still submitted (and rejected). Skip summarization in that case and log, rather than issue a request guaranteed to overflow.
This commit is contained in:
+11
-5
@@ -297,13 +297,19 @@ async def maybe_compact(
|
||||
|
||||
split = _select_split(model, items, context.keep_tokens)
|
||||
head, recent = items[:split], items[split:]
|
||||
if not head:
|
||||
previous = _previous_summary(head)
|
||||
input_budget = _summary_input_budget(model, previous)
|
||||
if not head or input_budget <= 0:
|
||||
# Nothing to summarise, or the window can't even fit the summary
|
||||
# instructions plus the reserved output allowance — any request would
|
||||
# be rejected, so skip rather than submit a doomed call.
|
||||
if head:
|
||||
logger.warning(
|
||||
"skipping compaction for %s: no room to summarise within its context window", model
|
||||
)
|
||||
return False
|
||||
|
||||
previous = _previous_summary(head)
|
||||
serialized_head = _fit_to_tokens(
|
||||
model, _serialize_items(head), _summary_input_budget(model, previous)
|
||||
)
|
||||
serialized_head = _fit_to_tokens(model, _serialize_items(head), input_budget)
|
||||
summary = await _summarize(
|
||||
model,
|
||||
_build_summary_prompt(serialized_head, previous),
|
||||
|
||||
@@ -139,11 +139,11 @@ async def test_maybe_compact_noop_when_within_budget(monkeypatch: pytest.MonkeyP
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_maybe_compact_rewrites_and_keeps_pairs(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
_patch_budget(monkeypatch, keep_tokens=30, window=50)
|
||||
_patch_budget(monkeypatch, keep_tokens=30, window=4_000)
|
||||
_patch_summary(monkeypatch, "SUMMARY BODY")
|
||||
session = FakeSession(_turns(12))
|
||||
|
||||
assert await compaction.maybe_compact(session, model="m") is True
|
||||
assert await compaction.maybe_compact(session, model="m", force=True) is True
|
||||
|
||||
items = await session.get_items()
|
||||
assert items[0]["role"] == "user"
|
||||
@@ -155,7 +155,8 @@ async def test_maybe_compact_rewrites_and_keeps_pairs(monkeypatch: pytest.Monkey
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_maybe_compact_updates_previous_summary(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
_patch_budget(monkeypatch, keep_tokens=30, window=50)
|
||||
# Window large enough to leave real room for the summary instructions.
|
||||
_patch_budget(monkeypatch, keep_tokens=30, window=4_000)
|
||||
captured: dict[str, str] = {}
|
||||
|
||||
async def fake_acompletion(**kwargs: Any) -> Any:
|
||||
@@ -167,7 +168,7 @@ async def test_maybe_compact_updates_previous_summary(monkeypatch: pytest.Monkey
|
||||
prior = compaction._checkpoint_item("OLD SUMMARY TEXT")
|
||||
session = FakeSession([prior, *_turns(12)])
|
||||
|
||||
assert await compaction.maybe_compact(session, model="m") is True
|
||||
assert await compaction.maybe_compact(session, model="m", force=True) is True
|
||||
assert "OLD SUMMARY TEXT" in captured["prompt"]
|
||||
|
||||
|
||||
@@ -227,7 +228,7 @@ async def test_summary_request_fits_when_room_is_below_old_floor(
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_maybe_compact_skips_when_summary_fails(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
_patch_budget(monkeypatch, keep_tokens=30, window=50)
|
||||
_patch_budget(monkeypatch, keep_tokens=30, window=4_000)
|
||||
|
||||
async def fake_acompletion(**_kwargs: Any) -> Any:
|
||||
raise RuntimeError("boom")
|
||||
@@ -236,5 +237,28 @@ async def test_maybe_compact_skips_when_summary_fails(monkeypatch: pytest.Monkey
|
||||
session = FakeSession(_turns(12))
|
||||
before = await session.get_items()
|
||||
|
||||
assert await compaction.maybe_compact(session, model="m") is False
|
||||
assert await compaction.maybe_compact(session, model="m", force=True) is False
|
||||
assert await session.get_items() == before
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_maybe_compact_skips_when_no_room_to_summarise(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
# A window too small to fit the summary instructions plus the reserved
|
||||
# output leaves no room for any head, so no (doomed) summary is attempted.
|
||||
_patch_budget(monkeypatch, keep_tokens=30, window=200)
|
||||
called = False
|
||||
|
||||
async def fake_acompletion(**_kwargs: Any) -> Any:
|
||||
nonlocal called
|
||||
called = True
|
||||
return SimpleNamespace(choices=[SimpleNamespace(message=SimpleNamespace(content="S"))])
|
||||
|
||||
monkeypatch.setattr("strix.llm.compaction.litellm.acompletion", fake_acompletion)
|
||||
session = FakeSession(_turns(12))
|
||||
before = await session.get_items()
|
||||
|
||||
assert await compaction.maybe_compact(session, model="m", force=True) is False
|
||||
assert called is False
|
||||
assert await session.get_items() == before
|
||||
|
||||
Reference in New Issue
Block a user