Files
2026-07-13 13:12:33 +08:00

653 lines
19 KiB
Python

"""Tests for context window compaction logic."""
import pytest
from opensquilla.session.compaction import (
CompactionConfig,
CompactionRequest,
build_compaction_config_from_provider,
call_compaction_llm,
compact_context,
estimate_entry_model_replay_tokens,
estimate_entry_replay_tokens,
)
from opensquilla.session.compaction_lifecycle import (
compaction_effect_payload,
compaction_result_payload,
)
def _make_entries(n: int, tokens_each: int = 100) -> list[dict]:
return [
{
"role": "user" if i % 2 == 0 else "assistant",
"content": f"message {i} " + "x" * 50,
"token_count": tokens_each,
}
for i in range(n)
]
def test_compaction_effect_payload_marks_automatic_noop_not_user_visible():
payload = compaction_effect_payload(
status="skipped",
source="automatic",
reason="within_compaction_budget",
)
assert payload == {
"applied": False,
"durability": "none",
"skip_reason": "within_compaction_budget",
"user_visible": False,
}
def test_compaction_effect_payload_surfaces_non_benign_skip_reasons():
for reason in ("coverage_blocked", "empty_summary", "no_safe_turn_boundary"):
payload = compaction_effect_payload(
status="skipped",
source="automatic",
reason=reason,
)
assert payload["applied"] is False
assert payload["durability"] == "none"
assert payload["skip_reason"] == reason
assert payload["user_visible"] is True
def test_compaction_effect_payload_marks_durable_completion_applied():
payload = compaction_effect_payload(status="completed", source="automatic")
assert payload["applied"] is True
assert payload["durability"] == "durable"
assert payload["user_visible"] is True
@pytest.mark.asyncio
async def test_no_compaction_needed_small_context():
entries = _make_entries(5, tokens_each=10) # 50 tokens total
result = await compact_context(
CompactionRequest(
session_id="s1",
entries=entries,
context_window_tokens=10_000, # huge window
)
)
assert result.removed_count == 0
assert result.kept_entries == entries
assert result.summary_source == "skipped"
assert result.skip_reason == "within_compaction_budget"
@pytest.mark.asyncio
async def test_compaction_occurs_when_over_budget():
entries = _make_entries(20, tokens_each=200) # 4000 tokens
result = await compact_context(
CompactionRequest(
session_id="s1",
entries=entries,
context_window_tokens=1600, # tight enough to compact, large enough for the result
config=CompactionConfig(safety_margin=1.0),
)
)
assert result.removed_count > 0
assert result.summary != ""
assert result.chunks_processed >= 1
assert result.summary_source == "fallback"
assert result.tokens_before == 4000
assert result.tokens_after < result.tokens_before
assert result.remaining_budget_tokens >= 0
def _make_tool_heavy_entries(
turns: int = 10, pairs: int = 4, result_chars: int = 2000
) -> list[dict]:
line = "drwxr-xr-x staff 4096 synthetic/file.txt "
result_text = (line * (result_chars // len(line) + 1))[:result_chars]
entries: list[dict] = []
for turn in range(turns):
tool_calls: list[dict] = []
for pair in range(pairs):
tool_id = f"tool-{turn}-{pair}"
tool_calls.append(
{
"type": "tool_use",
"tool_use_id": tool_id,
"name": "exec_shell",
"input": {"command": f"ls batch_{turn}/{pair}"},
}
)
tool_calls.append(
{
"type": "tool_result",
"tool_use_id": tool_id,
"name": "exec_shell",
"result": result_text,
"is_error": False,
}
)
entries.append({"role": "user", "content": f"inspect batch {turn}", "token_count": None})
entries.append(
{
"role": "assistant",
"content": f"inspected batch {turn}",
"token_count": 120,
"tool_calls": tool_calls,
}
)
return entries
@pytest.mark.asyncio
async def test_budget_check_counts_full_tool_call_replay_not_summarized_previews():
entries = _make_tool_heavy_entries()
window = 16_000
summarized = sum(estimate_entry_replay_tokens(e) for e in entries)
model_replay = sum(estimate_entry_model_replay_tokens(e) for e in entries)
# The summarized estimate looks within budget while the model replay overflows.
assert summarized * 1.2 <= window < model_replay
result = await compact_context(
CompactionRequest(
session_id="s1",
entries=entries,
context_window_tokens=window,
config=CompactionConfig(model=None, api_key=""),
)
)
assert result.skip_reason != "within_compaction_budget"
assert result.removed_count > 0
def test_replay_token_estimate_uses_tool_payload_summary_not_raw_arguments():
large_content = "x" * 80_000
entry = {
"role": "assistant",
"content": "wrote file",
"token_count": 1,
"tool_calls": [
{
"type": "tool_use",
"tool_use_id": "write-large",
"name": "write_file",
"input": {"path": "index.html", "content": large_content},
}
],
"reasoning_content": "private reasoning " + ("r" * 20_000),
}
tokens = estimate_entry_replay_tokens(entry)
assert tokens < 500
def test_provider_config_preserves_profile_when_compaction_llm_disabled():
cfg = build_compaction_config_from_provider(
None,
compaction_config=type(
"CompactionSettings",
(),
{
"enabled": False,
"compaction_profile": "coding",
"protected_recent_messages": 6,
},
)(),
)
assert cfg.model is None
assert cfg.api_key == ""
assert cfg.compaction_profile == "coding"
assert cfg.protected_recent_messages == 6
@pytest.mark.asyncio
async def test_compaction_source_is_llm_when_all_chunks_use_llm(monkeypatch):
calls: list[str] = []
async def fake_llm(**kwargs):
calls.append(kwargs["chunk_text"])
return "LLM summary"
monkeypatch.setattr("opensquilla.session.compaction.call_compaction_llm", fake_llm)
entries = _make_entries(12, tokens_each=200)
result = await compact_context(
CompactionRequest(
session_id="s1",
entries=entries,
context_window_tokens=500,
config=CompactionConfig(model="test/model", api_key="test-key"),
)
)
assert calls
assert result.removed_count > 0
assert result.summary_source == "llm"
@pytest.mark.asyncio
async def test_compaction_source_is_mixed_when_llm_partly_falls_back(monkeypatch):
responses = ["LLM summary", None]
async def fake_llm(**kwargs):
return responses.pop(0) if responses else "LLM summary"
monkeypatch.setattr("opensquilla.session.compaction.call_compaction_llm", fake_llm)
entries = _make_entries(12, tokens_each=200)
result = await compact_context(
CompactionRequest(
session_id="s1",
entries=entries,
context_window_tokens=500,
config=CompactionConfig(model="test/model", api_key="test-key"),
)
)
assert result.removed_count > 0
assert result.summary_source == "mixed"
@pytest.mark.asyncio
async def test_compaction_keeps_recent_entries():
entries = _make_entries(20, tokens_each=200)
result = await compact_context(
CompactionRequest(
session_id="s1",
entries=entries,
context_window_tokens=1500,
)
)
# kept entries should be a tail of the original
if result.kept_entries:
last_kept = result.kept_entries[-1]
assert last_kept in entries[-len(result.kept_entries) :]
@pytest.mark.asyncio
async def test_coding_profile_preserves_configured_recent_tail():
entries = _make_entries(30, tokens_each=250)
protected_tail = [
{
"role": "user" if i % 2 == 0 else "assistant",
"content": f"active task message {i}",
"token_count": 5,
}
for i in range(4)
]
entries.extend(protected_tail)
result = await compact_context(
CompactionRequest(
session_id="s1",
entries=entries,
context_window_tokens=1500,
config=CompactionConfig(
safety_margin=1.0,
compaction_profile="coding",
protected_recent_messages=4,
),
)
)
assert result.removed_count > 0
assert result.kept_entries[-4:] == protected_tail
assert result.quality_report["profile"] == "coding"
assert result.quality_report["protected_recent_messages"] == 4
assert result.quality_report["protected_tail_preserved"] is True
assert result.quality_report["fits_context_window"] is True
assert result.quality_report["passes_structural_gate"] is True
assert compaction_result_payload(result)["quality_report"][
"passes_structural_gate"
] is True
@pytest.mark.asyncio
async def test_quality_report_marks_compaction_that_still_exceeds_window():
entries = [
{"role": "user", "content": "old context", "token_count": 10_000},
*[
{
"role": "user" if i % 2 == 0 else "assistant",
"content": f"large protected tail {i}",
"token_count": 500,
}
for i in range(5)
],
]
result = await compact_context(
CompactionRequest(
session_id="s1",
entries=entries,
context_window_tokens=1000,
config=CompactionConfig(
safety_margin=1.0,
protected_recent_messages=5,
),
)
)
assert result.removed_count > 0
assert result.quality_report["fits_context_window"] is False
assert result.quality_report["passes_structural_gate"] is True
assert compaction_result_payload(result)["quality_report"][
"fits_context_window"
] is False
@pytest.mark.asyncio
async def test_protected_tail_retreats_to_tool_boundary():
entries = [
{"role": "user", "content": "old context", "token_count": 300},
{
"role": "assistant",
"content": "[Used tool: read_file]",
"token_count": 5,
},
{
"role": "user",
"content": "[Tool result (toolu_1): file contents]",
"token_count": 5,
},
{"role": "user", "content": "next question", "token_count": 5},
{"role": "assistant", "content": "answer", "token_count": 5},
]
result = await compact_context(
CompactionRequest(
session_id="s1",
entries=entries,
context_window_tokens=100,
config=CompactionConfig(
safety_margin=1.0,
protected_recent_messages=3,
),
)
)
assert result.removed_count > 0
assert result.kept_entries[0]["content"] == "[Used tool: read_file]"
assert result.kept_entries[1]["content"].startswith("[Tool result ")
assert result.quality_report["protected_tail_preserved"] is True
@pytest.mark.asyncio
async def test_protected_tail_retreats_over_multi_result_tool_segment():
entries = [
{"role": "user", "content": "old context", "token_count": 300},
{
"role": "assistant",
"content": "calling tool",
"tool_calls": [{"id": "call_1", "type": "function"}],
"token_count": 5,
},
{
"role": "tool",
"content": "first result",
"tool_call_id": "call_1",
"token_count": 5,
},
{
"role": "tool",
"content": "second result",
"tool_call_id": "call_1",
"token_count": 5,
},
{"role": "user", "content": "next question", "token_count": 5},
{"role": "assistant", "content": "answer", "token_count": 5},
]
result = await compact_context(
CompactionRequest(
session_id="s1",
entries=entries,
context_window_tokens=120,
config=CompactionConfig(
safety_margin=1.0,
protected_recent_messages=3,
),
)
)
assert result.removed_count > 0
assert result.kept_entries[0]["role"] == "assistant"
assert result.kept_entries[1]["content"] == "first result"
assert result.kept_entries[2]["content"] == "second result"
assert result.quality_report["protected_tail_preserved"] is True
@pytest.mark.asyncio
async def test_empty_entries():
result = await compact_context(
CompactionRequest(
session_id="s1",
entries=[],
context_window_tokens=1000,
)
)
assert result.removed_count == 0
assert result.kept_entries == []
assert result.summary == ""
assert result.skip_reason == "no_entries"
@pytest.mark.asyncio
async def test_custom_config():
entries = _make_entries(20, tokens_each=200)
cfg = CompactionConfig(
base_chunk_ratio=0.3,
min_chunk_ratio=0.1,
safety_margin=1.0,
default_parts=3,
identifier_policy="off",
)
result = await compact_context(
CompactionRequest(
session_id="s1",
entries=entries,
context_window_tokens=1000,
config=cfg,
)
)
assert result.removed_count > 0
@pytest.mark.asyncio
async def test_strict_identifier_policy_in_summary():
entries = _make_entries(10, tokens_each=200)
result = await compact_context(
CompactionRequest(
session_id="s1",
entries=entries,
context_window_tokens=500,
config=CompactionConfig(identifier_policy="strict"),
)
)
if result.summary:
assert "identifier" in result.summary.lower() or "IMPORTANT" in result.summary
@pytest.mark.asyncio
async def test_chunks_processed_count():
entries = _make_entries(30, tokens_each=200)
result = await compact_context(
CompactionRequest(
session_id="s1",
entries=entries,
context_window_tokens=500,
)
)
assert result.chunks_processed >= 1
@pytest.mark.asyncio
async def test_call_compaction_llm_adds_openrouter_app_attribution(monkeypatch) -> None:
captured: dict[str, object] = {}
class FakeResponse:
def raise_for_status(self) -> None:
return None
def json(self) -> dict:
return {"choices": [{"message": {"content": "summary"}}]}
class FakeClient:
async def __aenter__(self):
return self
async def __aexit__(self, exc_type, exc, tb) -> bool:
return False
async def post(self, url, *, json, headers):
captured["url"] = url
captured["json"] = json
captured["headers"] = headers
return FakeResponse()
monkeypatch.setattr(
"opensquilla.session.compaction.httpx.AsyncClient",
lambda **kwargs: FakeClient(),
)
result = await call_compaction_llm(
chunk_text="old conversation",
identifier_instruction="",
model="openai/gpt-4o-mini",
api_key="test-key",
base_url="https://openrouter.ai/api/v1",
timeout=10.0,
)
assert result == "summary"
assert captured["url"] == "https://openrouter.ai/api/v1/chat/completions"
assert captured["headers"] == {
"Authorization": "Bearer test-key",
"Content-Type": "application/json",
"HTTP-Referer": "https://opensquilla.ai",
"X-Title": "OpenSquilla",
}
@pytest.mark.asyncio
async def test_call_compaction_llm_adds_tokenrhythm_app_attribution(monkeypatch) -> None:
captured: dict[str, object] = {}
class FakeResponse:
def raise_for_status(self) -> None:
return None
def json(self) -> dict:
return {"choices": [{"message": {"content": "summary"}}]}
class FakeClient:
async def __aenter__(self):
return self
async def __aexit__(self, exc_type, exc, tb) -> bool:
return False
async def post(self, url, *, json, headers):
captured["url"] = url
captured["headers"] = headers
return FakeResponse()
monkeypatch.setattr(
"opensquilla.session.compaction.httpx.AsyncClient",
lambda **kwargs: FakeClient(),
)
result = await call_compaction_llm(
chunk_text="old conversation",
identifier_instruction="",
model="deepseek-v4-flash",
api_key="test-key",
base_url="https://tokenrhythm.studio/v1",
)
assert result == "summary"
assert captured["url"] == "https://tokenrhythm.studio/v1/chat/completions"
headers = captured["headers"]
assert isinstance(headers, dict)
assert headers["HTTP-Referer"] == "https://opensquilla.ai"
assert headers["X-Title"] == "OpenSquilla"
@pytest.mark.asyncio
async def test_call_compaction_llm_timeout_returns_none(monkeypatch) -> None:
class FakeClient:
async def __aenter__(self):
return self
async def __aexit__(self, exc_type, exc, tb) -> bool:
return False
async def post(self, url, *, json, headers):
raise TimeoutError("summary timed out")
monkeypatch.setattr(
"opensquilla.session.compaction.httpx.AsyncClient",
lambda **kwargs: FakeClient(),
)
result = await call_compaction_llm(
chunk_text="old conversation",
identifier_instruction="",
model="openai/gpt-4o-mini",
api_key="test-key",
timeout=0.01,
)
assert result is None
@pytest.mark.asyncio
async def test_custom_instructions_are_user_scoped_and_identifier_policy_stays_system(
monkeypatch,
) -> None:
captured: dict[str, object] = {}
class FakeResponse:
def raise_for_status(self) -> None:
return None
def json(self) -> dict:
return {"choices": [{"message": {"content": "summary"}}]}
class FakeClient:
async def __aenter__(self):
return self
async def __aexit__(self, exc_type, exc, tb) -> bool:
return False
async def post(self, url, *, json, headers):
captured["json"] = json
return FakeResponse()
monkeypatch.setattr(
"opensquilla.session.compaction.httpx.AsyncClient",
lambda **kwargs: FakeClient(),
)
await call_compaction_llm(
chunk_text="old conversation",
identifier_instruction="Preserve exact IDs.",
model="openai/gpt-4o-mini",
api_key="test-key",
base_url="https://openrouter.ai/api/v1",
timeout=10.0,
custom_instructions="Focus on deployment decisions.",
)
messages = captured["json"]["messages"]
assert messages[0]["role"] == "system"
assert "Preserve exact IDs." in messages[0]["content"]
assert "Focus on deployment decisions." not in messages[0]["content"]
assert messages[1]["role"] == "user"
assert "Focus on deployment decisions." in messages[1]["content"]