Final_Assignment_Template / tests /test_compaction.py
yc1838
feat: implement secure Python sandbox with process and Docker backends
f774338
Raw
History Blame Contribute Delete
5.19 kB
"""Tests for `_compact_old_tool_messages`.
The compaction function runs on every model_node turn and is the only thing
keeping older tool outputs from overflowing the context window. The original
implementation head-truncated to 300 chars, which can amputate the exact line
containing the answer. This file tests the summarize-preferred variant: when
an LLM-backed summarizer is supplied, old long tool outputs are replaced by
the summarizer's result; the head-truncation path remains as a fallback for
cases where the summarizer is unavailable or fails.
"""
from __future__ import annotations
from langchain_core.messages import AIMessage, HumanMessage, ToolMessage
from lilith_agent.app import (
_COMPACT_KEEP_RECENT,
_COMPACT_MAX_CHARS,
_COMPACT_SUMMARY_PREFIX,
_compact_old_tool_messages,
)
def _long_tool_msg(name: str, content: str) -> ToolMessage:
return ToolMessage(tool_call_id=f"tc-{name}", name=name, content=content)
def test_short_tool_messages_pass_through_unchanged():
msgs = [
HumanMessage("q"),
_long_tool_msg("web_search", "short result"),
AIMessage("ok"),
]
out = _compact_old_tool_messages(msgs)
assert out[1].content == "short result"
def test_recent_tool_messages_kept_verbatim_even_when_long():
long = "X" * (_COMPACT_MAX_CHARS * 10)
msgs = [_long_tool_msg("web_search", long) for _ in range(_COMPACT_KEEP_RECENT)]
out = _compact_old_tool_messages(msgs)
for m in out:
assert m.content == long
def test_old_long_message_is_summarized_when_summarizer_provided():
long = "X" * (_COMPACT_MAX_CHARS * 5)
msgs = [
_long_tool_msg("web_search", long), # old
*[_long_tool_msg("web_search", "short") for _ in range(_COMPACT_KEEP_RECENT)],
]
def fake_summarizer(tool_name: str, content: str) -> str:
return "SUMMARY_OF_FACTS_42"
out = _compact_old_tool_messages(msgs, summarize_fn=fake_summarizer)
assert "SUMMARY_OF_FACTS_42" in out[0].content
assert out[0].content.startswith(_COMPACT_SUMMARY_PREFIX)
# The old raw payload is gone — summarization replaced it.
assert "X" * 1000 not in out[0].content
def test_summarizer_receives_tool_name_and_full_content():
long = "alpha " * 500
msgs = [
_long_tool_msg("arxiv_search", long),
*[_long_tool_msg("web_search", "short") for _ in range(_COMPACT_KEEP_RECENT)],
]
recorded: dict = {}
def fake_summarizer(tool_name: str, content: str) -> str:
recorded["name"] = tool_name
recorded["len"] = len(content)
return "ok"
_compact_old_tool_messages(msgs, summarize_fn=fake_summarizer)
assert recorded["name"] == "arxiv_search"
assert recorded["len"] == len(long)
def test_summarizer_failure_falls_back_to_head_truncation():
long = "Y" * (_COMPACT_MAX_CHARS * 5)
msgs = [
_long_tool_msg("web_search", long),
*[_long_tool_msg("web_search", "short") for _ in range(_COMPACT_KEEP_RECENT)],
]
def broken_summarizer(tool_name: str, content: str) -> str:
raise RuntimeError("llm offline")
out = _compact_old_tool_messages(msgs, summarize_fn=broken_summarizer)
content = out[0].content
# Fallback marker from the original truncation path
assert "COMPACTED" in content
# First `max_chars` preserved verbatim
assert content.startswith("Y" * _COMPACT_MAX_CHARS)
def test_summarizer_returning_empty_falls_back_to_truncation():
long = "Z" * (_COMPACT_MAX_CHARS * 5)
msgs = [
_long_tool_msg("web_search", long),
*[_long_tool_msg("web_search", "short") for _ in range(_COMPACT_KEEP_RECENT)],
]
def empty_summarizer(tool_name: str, content: str) -> str:
return ""
out = _compact_old_tool_messages(msgs, summarize_fn=empty_summarizer)
assert "COMPACTED" in out[0].content
assert out[0].content.startswith("Z" * _COMPACT_MAX_CHARS)
def test_no_summarizer_uses_truncation_fallback():
"""Backwards-compat: the original (no summarize_fn) path must still truncate."""
long = "W" * (_COMPACT_MAX_CHARS * 5)
msgs = [
_long_tool_msg("web_search", long),
*[_long_tool_msg("web_search", "short") for _ in range(_COMPACT_KEEP_RECENT)],
]
out = _compact_old_tool_messages(msgs)
assert "COMPACTED" in out[0].content
assert out[0].content.startswith("W" * _COMPACT_MAX_CHARS)
def test_already_summarized_message_is_not_resummarized():
"""If a prior pass already produced a `[COMPACTED SUMMARY] …` content,
a second pass must skip it — otherwise we waste a cheap-model call every
turn on the same already-shrunk payload.
"""
prior_summary = _COMPACT_SUMMARY_PREFIX + "already shrunk facts"
msgs = [
_long_tool_msg("web_search", prior_summary),
*[_long_tool_msg("web_search", "short") for _ in range(_COMPACT_KEEP_RECENT)],
]
calls = {"n": 0}
def fake_summarizer(tool_name: str, content: str) -> str:
calls["n"] += 1
return "should not run"
out = _compact_old_tool_messages(msgs, summarize_fn=fake_summarizer)
assert calls["n"] == 0
assert out[0].content == prior_summary