Skip to content

fix(agent): complete stream when result is empty#1508

Open
JGoP-L wants to merge 2 commits into
agentscope-ai:mainfrom
JGoP-L:fix/issue-1503-empty-stream-completion
Open

fix(agent): complete stream when result is empty#1508
JGoP-L wants to merge 2 commits into
agentscope-ai:mainfrom
JGoP-L:fix/issue-1503-empty-stream-completion

Conversation

@JGoP-L
Copy link
Copy Markdown
Contributor

@JGoP-L JGoP-L commented May 27, 2026

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 outer FluxSink only inside the onNext(finalMsg) callback. When the agent call completed as Mono.empty(), onNext was not invoked, so sink.complete() was never called and consumers such as stream().blockLast() could wait indefinitely.

Changes made:

  • Complete the stream from the upstream Mono completion callback.
  • Preserve existing behavior for normal final messages and error propagation.
  • Add a regression test for an empty assistant text response.

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.

  • Code has been formatted with mvn spotless:apply
  • All relevant tests are passing (mvn -pl agentscope-core test)
  • Javadoc comments are complete and follow project conventions
  • Related documentation has been updated (not applicable: internal stream lifecycle bug fix covered by regression test)
  • Code is ready for review

@JGoP-L JGoP-L requested review from a team and Copilot May 27, 2026 09:59
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 the onNext handler with sink::complete as the explicit onComplete callback in createEventStream.
  • Add a streaming test case that verifies ReActAgent.stream completes 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
Copy link
Copy Markdown

codecov Bot commented May 27, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@AgentScopeJavaBot AgentScopeJavaBot added bug Something isn't working area/core/agent Agent runtime, pipeline, hooks, plan labels May 28, 2026
Copy link
Copy Markdown
Collaborator

@AgentScopeJavaBot AgentScopeJavaBot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 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(
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[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").

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/core/agent Agent runtime, pipeline, hooks, plan bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]:ReActAgent.stream(...) never completes when the model returns empty assistant content

3 participants