From a59b2f04a66df2f027787c35135e945665a0b608 Mon Sep 17 00:00:00 2001 From: Yufeng He <40085740+he-yufeng@users.noreply.github.com> Date: Thu, 18 Jun 2026 15:29:24 +0800 Subject: [PATCH] fix(truncator): keep an all-system context in the system half _split_system_rest initialized first_non_system to 0, so when every message is a system message the loop never breaks and the split is reversed: the system messages land in the non-system half. truncate_by_halving then treats them as droppable turns and discards half of them (e.g. 4 system messages -> 2). System messages must never be dropped. Default first_non_system to len(messages) so an all-system list stays entirely in the system half; mixed and empty inputs are unchanged. --- astrbot/core/agent/context/truncator.py | 4 +++- tests/agent/test_truncator.py | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/astrbot/core/agent/context/truncator.py b/astrbot/core/agent/context/truncator.py index 9abf574336..e742fb7d18 100644 --- a/astrbot/core/agent/context/truncator.py +++ b/astrbot/core/agent/context/truncator.py @@ -21,7 +21,9 @@ def _split_system_rest( Returns: tuple: (system_messages, non_system_messages) """ - first_non_system = 0 + # Default to the end so an all-system list lands entirely in the system + # half; otherwise the loop never breaks and the split would be reversed. + first_non_system = len(messages) for i, msg in enumerate(messages): if msg.role != "system": first_non_system = i diff --git a/tests/agent/test_truncator.py b/tests/agent/test_truncator.py index 7dac80f9ce..bbcc9db706 100644 --- a/tests/agent/test_truncator.py +++ b/tests/agent/test_truncator.py @@ -376,6 +376,29 @@ def test_all_system_messages(self): if len(result) > 0: assert all(msg.role == "system" for msg in result) + def test_split_system_rest_with_only_system_messages(self): + """All-system input must land entirely in the system half, not the rest.""" + truncator = ContextTruncator() + messages = [ + self.create_message("system", "System 1"), + self.create_message("system", "System 2"), + ] + + system_messages, non_system_messages = truncator._split_system_rest(messages) + + assert len(system_messages) == 2 + assert non_system_messages == [] + + def test_halving_keeps_all_system_messages(self): + """Halving must never drop system messages, even with no turns present.""" + truncator = ContextTruncator() + messages = [self.create_message("system", f"System {i}") for i in range(4)] + + result = truncator.truncate_by_halving(messages) + + assert len(result) == 4 + assert all(msg.role == "system" for msg in result) + # ==================== #6196: 长 tool chain 只有一条 user 消息 ==================== def _build_tool_chain(self, tool_rounds: int = 20) -> list[Message]: