From c75e8b1bbafce3ee4ae491999be3d8bbf8232208 Mon Sep 17 00:00:00 2001 From: "logic.wu0" <605524858@qq.com> Date: Tue, 14 Jul 2026 10:14:17 +0800 Subject: [PATCH] fix(tool): mark ToolResultBlock.error() results with state=ERROR ToolResultBlock.error() left state unset (defaulting to RUNNING) and produced an "Error: " text prefix. determineToolResultState() only recognizes a non-RUNNING state or an "[ERROR]" text prefix, so failed tool calls (MCP timeouts, tool-not-found, execution errors, ...) fell through to SUCCESS and the agent never saw the failure. Set state=ERROR in error() so failures are classified by structured state, independent of the output text. Fixes all error() call sites at once; the output text is unchanged. Closes #2157 --- .../agentscope/core/message/ToolResultBlock.java | 8 ++++++-- .../io/agentscope/core/tool/ToolExecutorTest.java | 15 +++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/agentscope-core/src/main/java/io/agentscope/core/message/ToolResultBlock.java b/agentscope-core/src/main/java/io/agentscope/core/message/ToolResultBlock.java index 3f64596f22..80eb5751fd 100644 --- a/agentscope-core/src/main/java/io/agentscope/core/message/ToolResultBlock.java +++ b/agentscope-core/src/main/java/io/agentscope/core/message/ToolResultBlock.java @@ -199,15 +199,19 @@ public static ToolResultBlock text(String text) { /** * Create an error result (for tool method return values). * + *
The result carries {@link ToolResultState#ERROR} so that failures are + * recognized by state, independent of the output text prefix. + * * @param errorMessage Error message - * @return ToolResultBlock with error output + * @return ToolResultBlock with error output and {@code state = ERROR} */ public static ToolResultBlock error(String errorMessage) { return new ToolResultBlock( null, null, List.of(TextBlock.builder().text("Error: " + errorMessage).build()), - null); + null, + ToolResultState.ERROR); } /** diff --git a/agentscope-core/src/test/java/io/agentscope/core/tool/ToolExecutorTest.java b/agentscope-core/src/test/java/io/agentscope/core/tool/ToolExecutorTest.java index b123c3ac90..9ef4cc9a33 100644 --- a/agentscope-core/src/test/java/io/agentscope/core/tool/ToolExecutorTest.java +++ b/agentscope-core/src/test/java/io/agentscope/core/tool/ToolExecutorTest.java @@ -22,6 +22,7 @@ import io.agentscope.core.message.ContentBlock; import io.agentscope.core.message.TextBlock; import io.agentscope.core.message.ToolResultBlock; +import io.agentscope.core.message.ToolResultState; import io.agentscope.core.message.ToolUseBlock; import io.agentscope.core.tool.test.SampleTools; import io.agentscope.core.tool.test.ToolTestUtils; @@ -124,6 +125,20 @@ void shouldReturnErrorWhenToolThrows() { "Error: Tool execution failed: Tool error: test failure", content, "Error message should be wrapped by executor"); + assertEquals( + ToolResultState.ERROR, + responses.get(0).getState(), + "A failed tool call must be reported with state=ERROR, not SUCCESS"); + } + + @Test + @DisplayName("ToolResultBlock.error() carries state=ERROR (issue #2157)") + void errorResultCarriesErrorState() { + // Regression guard: previously error() left state=null (defaulting to + // RUNNING) and relied on an "[ERROR]" text prefix it never produced, so + // determineToolResultState() misclassified failures as SUCCESS. + ToolResultBlock result = ToolResultBlock.error("boom"); + assertEquals(ToolResultState.ERROR, result.getState()); } @Test