Skip to content

fix(core): apply transformed model-call text deltas - #2469

Open
zouyx wants to merge 4 commits into
agentscope-ai:mainfrom
zouyx:featrue/fix-2385-model-call-text-delta
Open

fix(core): apply transformed model-call text deltas#2469
zouyx wants to merge 4 commits into
agentscope-ai:mainfrom
zouyx:featrue/fix-2385-model-call-text-delta

Conversation

@zouyx

@zouyx zouyx commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

AgentScope-Java Version

2.0.1-SNAPSHOT

Description

Fixes #2385.

MiddlewareBase.onModelCall can transform emitted TextBlockDeltaEvents, but those changes previously affected only the external event stream. The final Msg was still built from unmodified model chunks, causing native structured-output parsing to consume stale text.

This change rebuilds accumulated response text from the transformed model-call event stream before producing the final message. As a result, middleware can normalize text—such as removing Markdown JSON fences—before native structured-output parsing.

Added regression coverage for a model that streams fenced JSON across multiple chunks. The middleware removes the fence deltas, and the test verifies the final response contains valid JSON and deserializes into structured data.

Validation:

  • mvn -pl agentscope-core -Dtest=ReActAgentStructuredOutputWithToolsTest,ReActAgentMiddlewareIntegrationTest test
  • 12 tests passed.

Checklist

  • Code has been formatted with mvn spotless:apply
  • All tests are passing (mvn test)
  • Javadoc comments are complete and follow project conventions
  • Related documentation has been updated
  • Code is ready for review

Copilot AI review requested due to automatic review settings July 28, 2026 15:39
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@oss-maintainer oss-maintainer left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Review: fix(core): apply transformed model-call text deltas

Verdict: Approved ✅

Summary

Fixes #2385 — middleware onModelCall text delta transformations (e.g. removing Markdown JSON fences) were only reflected in the external event stream, not in the final Msg used for structured-output parsing. This PR rebuilds accumulated response text from the post-middleware event stream before producing the final message.

Code Quality

  • Well-targeted fix — minimal change (+123/-1) touching exactly the right layers: ReActAgent, ReasoningContext, TextAccumulator, and MiddlewareBase Javadoc.
  • Correct approachdoOnNext collects transformed text deltas, doOnComplete replaces accumulated text only when middleware actually produced deltas (sawTransformedTextDelta guard).
  • Good test coverageMarkdownFenceRemovingMiddleware test simulates a model streaming fenced JSON across 3 chunks, verifies the fence removal propagates to structured output parsing. 12 tests pass.
  • Thread safety noteStringBuilder in doOnNext closure is safe here because model call streams are single-subscriber sequential SSE streams, consistent with existing TextAccumulator patterns.

Observations

  • CLA not yet signed — contributor needs to sign before merge.
  • CI builds still pending — will confirm once green.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a mismatch between middleware-transformed TextBlockDeltaEvent streams and the final accumulated response text used to construct the terminal Msg (notably impacting native structured-output parsing).

Changes:

  • Rebuild accumulated response text from the (potentially middleware-transformed) TextBlockDeltaEvent stream before producing the final message.
  • Document the middleware contract that TextBlockDeltaEvent transformations are reflected in the final response message.
  • Add a regression test covering streamed fenced JSON where middleware removes Markdown fence deltas and structured output still deserializes correctly.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
agentscope-core/src/main/java/io/agentscope/core/ReActAgent.java Accumulates transformed text deltas during onModelCall and replaces the reasoning context’s accumulated text at stream end.
agentscope-core/src/main/java/io/agentscope/core/middleware/MiddlewareBase.java Updates onModelCall Javadoc to state that TextBlockDeltaEvent transformations are reflected in the final response.
agentscope-core/src/main/java/io/agentscope/core/agent/accumulator/TextAccumulator.java Adds replace(String) to allow overwriting accumulated text from reconstructed transformed deltas.
agentscope-core/src/main/java/io/agentscope/core/agent/accumulator/ReasoningContext.java Exposes replaceAccumulatedText(String) to swap accumulated text post-middleware transformation.
agentscope-core/src/test/java/io/agentscope/core/agent/ReActAgentStructuredOutputWithToolsTest.java Adds a regression test ensuring middleware delta replacement updates native structured output parsing results.

Comment on lines +2154 to +2160
.doOnComplete(
() -> {
if (sawTransformedTextDelta.get()
|| !context.getAccumulatedText().isEmpty()) {
context.replaceAccumulatedText(transformedText.toString());
}
});
Comment on lines +2136 to +2144
StringBuilder transformedText = new StringBuilder();
AtomicBoolean sawTransformedTextDelta = new AtomicBoolean(false);
return MiddlewareChain.build(
middlewares,
ReActAgent.this,
rc,
MiddlewareBase::onModelCall,
modelCallCore)
.apply(new ModelCallInput(messages, tools, options, modelForCall()));
.apply(new ModelCallInput(messages, tools, options, modelForCall()))
@codecov

codecov Bot commented Jul 28, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@oss-maintainer oss-maintainer left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LGTM. Clean, well-scoped fix for #2385.

Summary: Rebuilds accumulated response text from the post-middleware TextBlockDeltaEvent stream, so onModelCall transformations (e.g. stripping Markdown fences) are reflected in the final Msg used by native structured-output parsing.

Key observations:

  • doOnNext/doOnComplete pattern is idiomatic Reactor — no shared mutable state, StringBuilder and AtomicBoolean are local per invocation.
  • TextAccumulator.replace() has proper null safety.
  • MiddlewareBase Javadoc update correctly documents the new contract.
  • Regression test (fencedNativeResponder) is realistic: multi-chunk fenced JSON → middleware strips fences → final Msg contains valid JSON that deserializes.
  • CI all green (build + codecov + CLA).

No concerns.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature]: Support replacing TextBlockDeltaEvent in Middleware.onModelCall to fix structured output parsing

3 participants