fix(core): apply transformed model-call text deltas - #2469
Conversation
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
oss-maintainer
left a comment
There was a problem hiding this comment.
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, andMiddlewareBaseJavadoc. - Correct approach —
doOnNextcollects transformed text deltas,doOnCompletereplaces accumulated text only when middleware actually produced deltas (sawTransformedTextDeltaguard). - Good test coverage —
MarkdownFenceRemovingMiddlewaretest simulates a model streaming fenced JSON across 3 chunks, verifies the fence removal propagates to structured output parsing. 12 tests pass. - Thread safety note —
StringBuilderindoOnNextclosure is safe here because model call streams are single-subscriber sequential SSE streams, consistent with existingTextAccumulatorpatterns.
Observations
- CLA not yet signed — contributor needs to sign before merge.
- CI builds still pending — will confirm once green.
There was a problem hiding this comment.
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)
TextBlockDeltaEventstream before producing the final message. - Document the middleware contract that
TextBlockDeltaEventtransformations 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. |
| .doOnComplete( | ||
| () -> { | ||
| if (sawTransformedTextDelta.get() | ||
| || !context.getAccumulatedText().isEmpty()) { | ||
| context.replaceAccumulatedText(transformedText.toString()); | ||
| } | ||
| }); |
| 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 Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
oss-maintainer
left a comment
There was a problem hiding this comment.
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/doOnCompletepattern is idiomatic Reactor — no shared mutable state,StringBuilderandAtomicBooleanare local per invocation.TextAccumulator.replace()has proper null safety.MiddlewareBaseJavadoc update correctly documents the new contract.- Regression test (
fencedNativeResponder) is realistic: multi-chunk fenced JSON → middleware strips fences → finalMsgcontains valid JSON that deserializes. - CI all green (build + codecov + CLA).
No concerns.
AgentScope-Java Version
2.0.1-SNAPSHOT
Description
Fixes #2385.
MiddlewareBase.onModelCallcan transform emittedTextBlockDeltaEvents, but those changes previously affected only the external event stream. The finalMsgwas 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 testChecklist
mvn spotless:applymvn test)