Files
devplacepy/tests/unit/services/devii/agentic/compaction.py
T

155 lines
5.0 KiB
Python
Raw Normal View History

# retoor <retoor@molodetz.nl>
import json
from devplacepy.services.devii.agentic.compaction import (
compact_messages,
find_compaction_split,
is_context_length_error,
)
from devplacepy.services.devii.errors import LLMError
from tests.conftest import run_async
def _error(status, body):
return LLMError("Model endpoint returned error", status=status, body=body)
def test_openrouter_style_message_detected():
body = json.dumps(
{
"error": {
"message": (
"This endpoint's maximum context length is 131072 tokens. "
"However, you requested about 403355 tokens (349784 of text "
"input, 53571 of tool input). Please reduce the length of "
"either one, or use the context-compression plugin."
),
"code": 400,
"metadata": {"provider_name": None},
}
}
)
assert is_context_length_error(_error(400, body)) is True
def test_openai_style_code_detected():
body = json.dumps(
{
"error": {
"message": "This model's maximum context length is 128000 tokens.",
"type": "invalid_request_error",
"param": None,
"code": "context_length_exceeded",
}
}
)
assert is_context_length_error(_error(400, body)) is True
def test_unrelated_400_not_detected():
body = json.dumps({"error": {"message": "Invalid API key.", "code": 400}})
assert is_context_length_error(_error(400, body)) is False
def test_non_400_status_not_detected_even_with_matching_text():
body = json.dumps(
{"error": {"message": "maximum context length is 131072 tokens"}}
)
assert is_context_length_error(_error(429, body)) is False
def test_malformed_body_falls_back_to_phrase_match():
truncated = "maximum context length is 131072 tokens, please reduce the length"
assert is_context_length_error(_error(400, truncated)) is True
def test_malformed_body_with_no_match_is_false():
assert is_context_length_error(_error(400, "not valid json at all")) is False
class _StubLlm:
def __init__(self, summary="a concise summary of the earlier turns"):
self.summary = summary
self.calls = 0
async def summarize(self, prompt):
self.calls += 1
return self.summary
def _tool_call_message(name="run_tool"):
return {
"role": "assistant",
"content": "",
"tool_calls": [
{"id": "c1", "function": {"name": name, "arguments": "{}"}}
],
}
def _tool_result_message(content="result"):
return {"role": "tool", "tool_call_id": "c1", "name": "run_tool", "content": content}
def _long_tool_heavy_conversation(rounds=20):
messages = [
{"role": "system", "content": "sys"},
{"role": "user", "content": "start the long task"},
]
for i in range(rounds):
messages.append(_tool_call_message())
messages.append(_tool_result_message(f"result {i}" * 200))
return messages
def test_find_compaction_split_prefers_a_user_message():
messages = [
{"role": "system", "content": "sys"},
{"role": "user", "content": "first"},
{"role": "assistant", "content": "reply"},
{"role": "user", "content": "second"},
{"role": "assistant", "content": "reply2"},
{"role": "user", "content": "third"},
{"role": "assistant", "content": "reply3"},
]
split = find_compaction_split(messages, keep_tail=2)
assert messages[split]["role"] == "user"
def test_find_compaction_split_falls_back_to_a_non_tool_boundary_without_a_recent_user_message():
messages = _long_tool_heavy_conversation(rounds=20)
split = find_compaction_split(messages, keep_tail=4)
assert split > 1
assert messages[split].get("role") != "tool"
def test_find_compaction_split_never_lands_inside_a_tool_result_run():
messages = _long_tool_heavy_conversation(rounds=30)
for keep_tail in (2, 3, 4, 5, 8, 10, 15):
split = find_compaction_split(messages, keep_tail)
assert messages[split].get("role") != "tool", (
f"keep_tail={keep_tail} split at a tool message, orphaning its tool_calls"
)
def test_compact_messages_shrinks_a_tool_heavy_conversation_with_no_recent_user_message():
messages = _long_tool_heavy_conversation(rounds=20)
original_len = len(messages)
llm = _StubLlm()
compacted = run_async(compact_messages(llm, messages, keep_tail=4))
assert llm.calls == 1
assert len(compacted) < original_len
assert compacted[0]["role"] == "system"
assert "[compacted earlier turns]" in compacted[1]["content"]
assert compacted[-1] == messages[-1]
def test_compact_messages_tail_never_starts_with_a_dangling_tool_result():
messages = _long_tool_heavy_conversation(rounds=25)
llm = _StubLlm()
compacted = run_async(compact_messages(llm, messages, keep_tail=6))
tail = compacted[2:]
assert tail
assert tail[0].get("role") != "tool"