Skip to content

Commit e077836

Browse files
committed
fix: recount tokens after history sanitization
1 parent 643eae7 commit e077836

2 files changed

Lines changed: 41 additions & 2 deletions

File tree

astrbot/core/agent/context/manager.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ async def process(
5656
result = messages
5757
try:
5858
result = self.truncator.fix_messages(messages)
59-
if len(result) != len(messages):
59+
history_was_sanitized = result != messages
60+
if history_was_sanitized:
6061
logger.debug(
6162
f"Removed {len(messages) - len(result)} invalid tool history "
6263
"message(s) before context processing."
@@ -73,7 +74,7 @@ async def process(
7374
# 2. 基于 token 的压缩
7475
if self.config.max_context_tokens > 0:
7576
total_tokens = self.token_counter.count_tokens(
76-
result, trusted_token_usage
77+
result, 0 if history_was_sanitized else trusted_token_usage
7778
)
7879

7980
if self.compressor.should_compress(

tests/agent/test_context_manager.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,44 @@ async def test_trusted_usage_triggers_compression_before_provider_call(self):
577577
mock_compressor.assert_awaited_once_with(messages)
578578
assert result == compressed
579579

580+
@pytest.mark.asyncio
581+
async def test_sanitized_history_does_not_reuse_stale_trusted_usage(self):
582+
config = ContextConfig(max_context_tokens=100, truncate_turns=1)
583+
manager = ContextManager(config)
584+
messages = [
585+
self.create_message("user", "old request"),
586+
Message(
587+
role="assistant",
588+
content="Calling tools",
589+
tool_calls=[
590+
{
591+
"id": "call_1",
592+
"type": "function",
593+
"function": {"name": "first", "arguments": "{}"},
594+
},
595+
{
596+
"id": "call_2",
597+
"type": "function",
598+
"function": {"name": "second", "arguments": "{}"},
599+
},
600+
],
601+
),
602+
Message(role="tool", content="first result", tool_call_id="call_1"),
603+
self.create_message("user", "current request"),
604+
]
605+
sanitized = [messages[0], messages[-1]]
606+
mock_compressor = AsyncMock()
607+
mock_compressor.should_compress = MagicMock(return_value=False)
608+
manager.compressor = mock_compressor
609+
610+
result = await manager.process(messages, trusted_token_usage=83)
611+
612+
first_check = mock_compressor.should_compress.call_args_list[0]
613+
expected_tokens = manager.token_counter.count_tokens(sanitized)
614+
assert first_check.args == (sanitized, expected_tokens, 100)
615+
mock_compressor.assert_not_awaited()
616+
assert result == sanitized
617+
580618
@pytest.mark.asyncio
581619
async def test_token_compression_with_zero_max_tokens(self):
582620
"""Test that compression is skipped when max_context_tokens is 0."""

0 commit comments

Comments
 (0)