fix(agent): complete stream when result is empty#1508
Conversation
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Fixes an issue where the agent's event stream would not complete when the underlying model returned empty text content. The fix moves stream completion to the subscription's onComplete callback rather than performing it inline in onNext, ensuring the stream completes reliably.
Changes:
- Replace inline
sink.complete()in theonNexthandler withsink::completeas the explicitonCompletecallback increateEventStream. - Add a streaming test case that verifies
ReActAgent.streamcompletes when the model returns an empty text block.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| agentscope-core/src/main/java/io/agentscope/core/agent/AgentBase.java | Move stream completion from onNext body to the onComplete callback of the subscription. |
| agentscope-core/src/test/java/io/agentscope/core/agent/AgentStreamingTest.java | Add regression test verifying stream completes for empty model responses. |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
AgentScopeJavaBot
left a comment
There was a problem hiding this comment.
🤖 AI Review
This PR fixes a real bug (issue #1503): AgentBase.createEventStream() previously called sink.complete() only inside the onNext callback, so when the upstream Mono<Msg> completed empty (e.g. ReAct reasoning context produced no final message because the model returned empty text), the outer FluxSink was never completed and consumers like stream().blockLast() hung forever. The fix moves sink.complete() to the dedicated onComplete overload of subscribe(onNext, onError, onComplete), which fires for both value-producing and empty Monos. The change is minimal, semantically correct in Reactor (onNext still runs before onComplete for non-empty Monos, so AGENT_RESULT is emitted before completion as before), and covered by a dedicated regression test using a MockModel that returns an empty TextBlock. Behavior change worth flagging: when the model returns empty content, no AGENT_RESULT event is emitted at all (the stream just completes); downstream SSE/AG-UI integrations that rely on a terminal AGENT_RESULT event will now see only completion. This matches one of the acceptable outcomes documented in the issue, but is a behavioral note callers should be aware of.
| .build(); | ||
|
|
||
| StepVerifier.create(agent.stream(List.of(inputMsg))) | ||
| .expectNextMatches( |
There was a problem hiding this comment.
[nit] The test relies on at least one REASONING event being emitted by StreamingHook before completion. Per the issue analysis, an empty TextBlock does not satisfy TextAccumulator.hasContent(), so this assumption depends on hook implementation details (e.g. the start-of-reasoning event still firing). Consider making the assertion more defensive, e.g. .thenConsumeWhile(event -> true).expectComplete() without the upfront expectNextMatches, since the fix's contract is purely about completion, not about which events fire. As written the test couples the regression assertion to the StreamingHook event sequence and may become flaky if hook emission semantics change.
| }, | ||
| sink::error); | ||
| sink::error, | ||
| sink::complete); |
There was a problem hiding this comment.
[minor] Consider also emitting a synthetic AGENT_RESULT (or at least a placeholder empty assistant Msg) when the upstream Mono completes empty, so downstream SSE / AG-UI adapters that key off AGENT_RESULT as the terminal event still receive a final marker. The current fix correctly unblocks completion, but consumers that loop until they observe an AGENT_RESULT will now silently terminate without one. If keeping the current behavior intentionally, please document it in the Javadoc of createEventStream (e.g. "if the agent call produces no final message, the stream completes without emitting an AGENT_RESULT event").
AgentScope-Java Version
1.1.0-SNAPSHOT
Description
This PR fixes an edge case where
ReActAgent.stream(...)can hang when the model returns an empty assistant text response.Previously,
AgentBase.createEventStream()completed the outerFluxSinkonly inside theonNext(finalMsg)callback. When the agent call completed asMono.empty(),onNextwas not invoked, sosink.complete()was never called and consumers such asstream().blockLast()could wait indefinitely.Changes made:
Monocompletion callback.Test command:
mvn -pl agentscope-core test
Local result:
BUILD SUCCESS
Fixes #1503
Checklist
Please check the following items before code is ready to be reviewed.
mvn spotless:applymvn -pl agentscope-core test)