From 3839e8b587eb249840217cb90342aafed9519421 Mon Sep 17 00:00:00 2001 From: Buktal <1171971708@qq.com> Date: Thu, 3 Sep 2026 17:33:48 +0800 Subject: [PATCH] fix(core): fail model empty-completion before retry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ModelUtils.applyTimeoutAndRetry's shared path (all model implementations) now detects empty completions—streams with zero chunks or only chunks whose content blocks are not TextBlock/ThinkingBlock/ToolUseBlock—and converts them to ModelException before retryWhen, so the existing retry and fallback chains engage. Detection is unconditional (applies regardless of ExecutionConfig/retry configuration), since empty completion is an upstream transport anomaly that should always surface as an error. Detection sits after timeout (a timed-out request bypasses the check and goes straight to error). Three regression tests: zero-chunk stream, all-empty-content chunks, and retry-then-success (confirming the error triggers retry and a subsequent non-empty response completes successfully). Fixes #2962 --- .../io/agentscope/core/model/ModelUtils.java | 104 +++++++- .../agentscope/core/agent/ReActAgentTest.java | 49 ++++ .../core/model/ModelTimeoutRetryTest.java | 237 ++++++++++++++++++ .../dashscope/DashScopeChatModelTest.java | 113 +++++---- ...ScopeNonStreamingBlockingBehaviorTest.java | 7 +- .../model/openai/OpenAIChatModelTest.java | 12 +- 6 files changed, 474 insertions(+), 48 deletions(-) diff --git a/agentscope-core/src/main/java/io/agentscope/core/model/ModelUtils.java b/agentscope-core/src/main/java/io/agentscope/core/model/ModelUtils.java index c8f0cf3cd8..3dd9b26237 100644 --- a/agentscope-core/src/main/java/io/agentscope/core/model/ModelUtils.java +++ b/agentscope-core/src/main/java/io/agentscope/core/model/ModelUtils.java @@ -16,10 +16,14 @@ package io.agentscope.core.model; import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Predicate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; import reactor.util.retry.Retry; /** @@ -43,6 +47,17 @@ private ModelUtils() { * configuration in GenerateOptions. Both timeout and retry are optional and only applied if * configured. * + *

Empty-completion detection: A stream that completes without any content-bearing + * {@code ChatResponse} (no chunk carries a non-null, non-empty content list) is converted to + * a {@code ModelException} after the timeout check and before retry. Contentless chunks are + * withheld until the first content-bearing chunk arrives (then released in order), so an + * empty completion surfaces the error as the first downstream signal and switchOnFirst-based + * fallback models engage. The check is deliberately type-agnostic: any content block counts, + * so multimodal completions carrying only image/video/audio blocks are not misclassified as + * empty. This detection is unconditional (applies regardless of retry configuration), since + * an empty completion is an upstream transport anomaly that should always surface as an + * error. + * *

Timeout Behavior: *