From 9a3af0ef5930abb5837f5f0e68ed949ff9905a29 Mon Sep 17 00:00:00 2001 From: guslegend <1670547022@qq.com> Date: Mon, 25 May 2026 18:13:56 +0800 Subject: [PATCH 1/4] fix: skip compressed assistant tool calls --- .../io/agentscope/core/util/MessageUtils.java | 19 ++++ .../core/util/MessageUtilsTest.java | 90 +++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 agentscope-core/src/test/java/io/agentscope/core/util/MessageUtilsTest.java diff --git a/agentscope-core/src/main/java/io/agentscope/core/util/MessageUtils.java b/agentscope-core/src/main/java/io/agentscope/core/util/MessageUtils.java index f28c3a627c..b3e0c8962e 100644 --- a/agentscope-core/src/main/java/io/agentscope/core/util/MessageUtils.java +++ b/agentscope-core/src/main/java/io/agentscope/core/util/MessageUtils.java @@ -19,6 +19,7 @@ import io.agentscope.core.message.MsgRole; import io.agentscope.core.message.ToolUseBlock; import java.util.List; +import java.util.Map; import java.util.Objects; /** @@ -30,6 +31,8 @@ */ public final class MessageUtils { + private static final String COMPRESS_META_KEY = "_compress_meta"; + private MessageUtils() { throw new UnsupportedOperationException("Utility class cannot be instantiated"); } @@ -53,6 +56,9 @@ public static List extractRecentToolCalls(List messages, Stri for (int i = messages.size() - 1; i >= 0; i--) { Msg msg = messages.get(i); if (msg.getRole() == MsgRole.ASSISTANT && Objects.equals(msg.getName(), agentName)) { + if (isCompressedMessage(msg)) { + continue; + } List toolCalls = msg.getContentBlocks(ToolUseBlock.class); if (!toolCalls.isEmpty()) { return toolCalls; @@ -63,4 +69,17 @@ public static List extractRecentToolCalls(List messages, Stri return List.of(); } + + private static boolean isCompressedMessage(Msg msg) { + if (msg == null) { + return false; + } + + Map metadata = msg.getMetadata(); + if (metadata == null) { + return false; + } + + return metadata.get(COMPRESS_META_KEY) instanceof Map; + } } diff --git a/agentscope-core/src/test/java/io/agentscope/core/util/MessageUtilsTest.java b/agentscope-core/src/test/java/io/agentscope/core/util/MessageUtilsTest.java new file mode 100644 index 0000000000..51a6390b8d --- /dev/null +++ b/agentscope-core/src/test/java/io/agentscope/core/util/MessageUtilsTest.java @@ -0,0 +1,90 @@ +/* + * Copyright 2024-2026 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.agentscope.core.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import io.agentscope.core.message.Msg; +import io.agentscope.core.message.MsgRole; +import io.agentscope.core.message.TextBlock; +import io.agentscope.core.message.ToolUseBlock; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +@DisplayName("MessageUtils Tests") +class MessageUtilsTest { + + @Test + @DisplayName("Should skip compressed assistant messages when extracting recent tool calls") + void testExtractRecentToolCallsSkipsCompressedAssistantMessages() { + List messages = new ArrayList<>(); + messages.add(createAssistantToolUseMessage("real-tool", "real-call")); + messages.add(createCompressedAssistantToolUseMessage("compressed-tool", "compressed-call")); + + List toolCalls = MessageUtils.extractRecentToolCalls(messages, "assistant"); + + assertEquals(1, toolCalls.size()); + assertEquals("real-tool", toolCalls.get(0).getName()); + assertEquals("real-call", toolCalls.get(0).getId()); + assertFalse(toolCalls.stream().anyMatch(block -> "compressed-call".equals(block.getId()))); + } + + @Test + @DisplayName("Should return empty list when messages are null or empty") + void testExtractRecentToolCallsWithEmptyInput() { + assertTrue(MessageUtils.extractRecentToolCalls(null, "assistant").isEmpty()); + assertTrue(MessageUtils.extractRecentToolCalls(List.of(), "assistant").isEmpty()); + } + + private Msg createAssistantToolUseMessage(String toolName, String callId) { + return Msg.builder() + .role(MsgRole.ASSISTANT) + .name("assistant") + .content( + List.of( + ToolUseBlock.builder() + .name(toolName) + .id(callId) + .input(new HashMap<>()) + .build())) + .build(); + } + + private Msg createCompressedAssistantToolUseMessage(String toolName, String callId) { + Map metadata = new HashMap<>(); + metadata.put("_compress_meta", Map.of("offloaduuid", "uuid-123")); + + return Msg.builder() + .role(MsgRole.ASSISTANT) + .name("assistant") + .content( + List.of( + ToolUseBlock.builder() + .name(toolName) + .id(callId) + .input(new HashMap<>()) + .build(), + TextBlock.builder().text("Compressed summary").build())) + .metadata(metadata) + .build(); + } +} From 45885e633097cfc8d3be3c12cb5c55e42afa009c Mon Sep 17 00:00:00 2001 From: guslegend <1670547022@qq.com> Date: Mon, 25 May 2026 18:48:49 +0800 Subject: [PATCH 2/4] test(core): improve MessageUtils patch coverage --- .../io/agentscope/core/util/MessageUtils.java | 8 -------- .../core/util/MessageUtilsTest.java | 20 +++++++++++++++++++ 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/agentscope-core/src/main/java/io/agentscope/core/util/MessageUtils.java b/agentscope-core/src/main/java/io/agentscope/core/util/MessageUtils.java index b3e0c8962e..2131936ec5 100644 --- a/agentscope-core/src/main/java/io/agentscope/core/util/MessageUtils.java +++ b/agentscope-core/src/main/java/io/agentscope/core/util/MessageUtils.java @@ -71,15 +71,7 @@ public static List extractRecentToolCalls(List messages, Stri } private static boolean isCompressedMessage(Msg msg) { - if (msg == null) { - return false; - } - Map metadata = msg.getMetadata(); - if (metadata == null) { - return false; - } - return metadata.get(COMPRESS_META_KEY) instanceof Map; } } diff --git a/agentscope-core/src/test/java/io/agentscope/core/util/MessageUtilsTest.java b/agentscope-core/src/test/java/io/agentscope/core/util/MessageUtilsTest.java index 51a6390b8d..8dc838675d 100644 --- a/agentscope-core/src/test/java/io/agentscope/core/util/MessageUtilsTest.java +++ b/agentscope-core/src/test/java/io/agentscope/core/util/MessageUtilsTest.java @@ -55,7 +55,26 @@ void testExtractRecentToolCallsWithEmptyInput() { assertTrue(MessageUtils.extractRecentToolCalls(List.of(), "assistant").isEmpty()); } + @Test + @DisplayName("Should not skip assistant messages when _compress_meta is not a map") + void testExtractRecentToolCallsDoesNotSkipAssistantMessagesWithInvalidCompressMetadata() { + Msg message = + createAssistantToolUseMessage( + "real-tool", "real-call", Map.of("_compress_meta", "not-a-map")); + + List toolCalls = + MessageUtils.extractRecentToolCalls(List.of(message), "assistant"); + + assertEquals(1, toolCalls.size()); + assertEquals("real-call", toolCalls.get(0).getId()); + } + private Msg createAssistantToolUseMessage(String toolName, String callId) { + return createAssistantToolUseMessage(toolName, callId, Map.of()); + } + + private Msg createAssistantToolUseMessage( + String toolName, String callId, Map metadata) { return Msg.builder() .role(MsgRole.ASSISTANT) .name("assistant") @@ -66,6 +85,7 @@ private Msg createAssistantToolUseMessage(String toolName, String callId) { .id(callId) .input(new HashMap<>()) .build())) + .metadata(metadata) .build(); } From ae17ebc1dadcb6cbc512fd47c8f30ae953967dd5 Mon Sep 17 00:00:00 2001 From: guslegend <1670547022@qq.com> Date: Wed, 10 Jun 2026 06:52:03 +0800 Subject: [PATCH 3/4] ci: retrigger license check From 6f1b2c369737445f2f5c04a9572a376c9f841fac Mon Sep 17 00:00:00 2001 From: guslegend <1670547022@qq.com> Date: Wed, 10 Jun 2026 15:49:47 +0800 Subject: [PATCH 4/4] fix(core): avoid stale tool calls after compression --- .../io/agentscope/core/util/MessageUtils.java | 26 ++++++++++++------- .../core/util/MessageUtilsTest.java | 23 ++++++++++++++++ 2 files changed, 40 insertions(+), 9 deletions(-) diff --git a/agentscope-core/src/main/java/io/agentscope/core/util/MessageUtils.java b/agentscope-core/src/main/java/io/agentscope/core/util/MessageUtils.java index 2131936ec5..9acc2cff83 100644 --- a/agentscope-core/src/main/java/io/agentscope/core/util/MessageUtils.java +++ b/agentscope-core/src/main/java/io/agentscope/core/util/MessageUtils.java @@ -53,18 +53,26 @@ public static List extractRecentToolCalls(List messages, Stri return List.of(); } + boolean scanningRecentAssistantSegment = false; for (int i = messages.size() - 1; i >= 0; i--) { Msg msg = messages.get(i); - if (msg.getRole() == MsgRole.ASSISTANT && Objects.equals(msg.getName(), agentName)) { - if (isCompressedMessage(msg)) { - continue; + if (msg.getRole() != MsgRole.ASSISTANT || !Objects.equals(msg.getName(), agentName)) { + if (scanningRecentAssistantSegment) { + break; } - List toolCalls = msg.getContentBlocks(ToolUseBlock.class); - if (!toolCalls.isEmpty()) { - return toolCalls; - } - break; + continue; + } + + scanningRecentAssistantSegment = true; + if (isCompressedMessage(msg)) { + continue; + } + + List toolCalls = msg.getContentBlocks(ToolUseBlock.class); + if (!toolCalls.isEmpty()) { + return toolCalls; } + break; } return List.of(); @@ -72,6 +80,6 @@ public static List extractRecentToolCalls(List messages, Stri private static boolean isCompressedMessage(Msg msg) { Map metadata = msg.getMetadata(); - return metadata.get(COMPRESS_META_KEY) instanceof Map; + return metadata != null && metadata.get(COMPRESS_META_KEY) instanceof Map; } } diff --git a/agentscope-core/src/test/java/io/agentscope/core/util/MessageUtilsTest.java b/agentscope-core/src/test/java/io/agentscope/core/util/MessageUtilsTest.java index 8dc838675d..780898da03 100644 --- a/agentscope-core/src/test/java/io/agentscope/core/util/MessageUtilsTest.java +++ b/agentscope-core/src/test/java/io/agentscope/core/util/MessageUtilsTest.java @@ -48,6 +48,21 @@ void testExtractRecentToolCallsSkipsCompressedAssistantMessages() { assertFalse(toolCalls.stream().anyMatch(block -> "compressed-call".equals(block.getId()))); } + @Test + @DisplayName( + "Should return empty list when the latest assistant segment only contains compressed" + + " messages") + void testExtractRecentToolCallsReturnsEmptyWhenLatestAssistantSegmentIsFullyCompressed() { + List messages = new ArrayList<>(); + messages.add(createAssistantToolUseMessage("stale-tool", "stale-call")); + messages.add(createUserMessage("continue")); + messages.add(createCompressedAssistantToolUseMessage("compressed-tool", "compressed-call")); + + List toolCalls = MessageUtils.extractRecentToolCalls(messages, "assistant"); + + assertTrue(toolCalls.isEmpty()); + } + @Test @DisplayName("Should return empty list when messages are null or empty") void testExtractRecentToolCallsWithEmptyInput() { @@ -107,4 +122,12 @@ private Msg createCompressedAssistantToolUseMessage(String toolName, String call .metadata(metadata) .build(); } + + private Msg createUserMessage(String text) { + return Msg.builder() + .role(MsgRole.USER) + .name("user") + .content(List.of(TextBlock.builder().text(text).build())) + .build(); + } }