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: *