[spring-ai] Streaming responses ending with CJK punctuation (。!?) are misclassified as partial and never persisted to the session
Maintainer antworten meist innerhalb von 2 Tagen
Bewertung
- Schwierigkeit
- 2/5
- Geschätzter Aufwand
- 1-3 Stunden
- Anfängerfreundlichkeit
- 84/100
Rechercherichtung
Beginne bei contrib/spring-ai/src/main/java/com/google/adk/models/springai/MessageConverter.java, Methode isPartialResponse, die nur ASCII . ! ? und Zeilenumbruch prüft. Die minimale Reproduktion des Issues ruft MessageConverter#toLlmResponse direkt mit einer ChatResponse auf, die auf 。 endet, also verwandle das in einen Unit-Test unter contrib/spring-ai/src/test und bestätige, dass derzeit partial=true zurückgegeben wird. Fertig bedeutet, dass eine Antwort, die auf 。!? endet, partial=false ergibt, und sowohl der neue Test als auch die vorhandenen Tests des Moduls bestehen.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Beschreibung
Describe the Bug
In the Spring AI bridge (contrib/spring-ai), MessageConverter#isPartialResponse
decides whether a streaming chunk is partial by checking only ASCII sentence-ending
punctuation: ., !, ?, \n. CJK terminal punctuation (。, !, ?) is not
included.
As a result, when a model's final streaming response ends with a Chinese sentence
mark (which is the normal case for Chinese users), it is classified as partial=true
and therefore never persisted to the session — the reply silently disappears from
conversation history, breaking multi-turn context and session replay.
Steps to Reproduce
- Use the official
com.google.adk.models.springai.SpringAImodel bridge with any
OpenAI-compatible model, streaming mode (RunConfigSSE). - Send a question that produces a Chinese answer, e.g.
根因是库存扣减为 0 未拦截。 - Observe the final
LlmResponse:partialistrue, and the session event store
never receives this response.
A minimal unit-level repro (calling the converter directly) is provided below.
No exception/stacktrace is produced — the failure is silent, which is what makes it
hard to notice in production.
Expected Behavior
A response ending with 。 (or ! / ?) is a complete final response in Chinese
and should be partial=false, persisted to the session like its ASCII-. counterpart.
Observed Behavior
Verified against google-adk-spring-ai:1.9.0 on our classpath:
[PROBE] chinese-ending partial = true <-- bug: final reply treated as partial
[PROBE] ascii-ending partial = false
Downstream effect: Runner-driven sessions lose the assistant's final answer for
CJK conversations; Session.events() replay shows the question without the reply.
Live streaming looks fine (partials are forwarded to the client), so the loss only
shows up in history, multi-turn follow-ups and session replay.
Environment Details
- ADK Library Version: verified on 1.9.0, and confirmed still present in
1.11.0 (latest release) and on currentmain
(contrib/spring-ai/src/main/java/com/google/adk/models/springai/MessageConverter.java#isPartialResponse) - OS: macOS (behavior is OS-independent — pure string logic)
- TS Version: N/A (Java)
Model Information
Model-independent. Observed with GLM via an OpenAI-compatible gateway; the
classification happens in MessageConverter before any model-specific handling.
Regression
N/A — the heuristic has been present since we started using the bridge (1.9.0),
and is unchanged through 1.11.0.
Additional Context
Suggested fix, from minimal to better:
- Add CJK terminal punctuation to the check:
。!?(plus…,)). - Better: don't infer completeness from punctuation at all. Text legitimately ends
without sentence-ending characters (lists, code blocks, tables). Partial-ness
could be derived from chunk position / stream state instead of the last character.
This affects every CJK-language user of the official SpringAI bridge — and because
partials are still forwarded live, the missing persistence only surfaces later
(history / follow-up turns), which makes it easy to misdiagnose as a session-store
problem.
Minimal Reproduction Code
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.adk.models.springai.MessageConverter;
import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.model.Generation;
import java.util.List;
MessageConverter converter = new MessageConverter(new ObjectMapper());
ChatResponse chineseEnding = new ChatResponse(List.of(new Generation(
AssistantMessage.builder().content("根因是库存扣减为 0 未拦截。").build())));
ChatResponse asciiEnding = new ChatResponse(List.of(new Generation(
AssistantMessage.builder().content("Root cause: zero-stock deduction is not blocked.").build())));
boolean chinesePartial = converter.toLlmResponse(chineseEnding, true).partial().orElse(false); // true (bug)
boolean asciiPartial = converter.toLlmResponse(asciiEnding, true).partial().orElse(false); // false
How often has this issue occurred?
Always (100%) — every Chinese final response ending with 。, which is essentially
every conversation.
- Vorherrschende Sprache
- Java
- Sterne
- 1.8k
- Forks
- 446
- Ø Merge
- 4 T. 20 Std.
- Gemergte PRs (30 T.)
- 59
Entwicklungsumgebung
Startet den Dev-Container des Projekts im Browser, mit Ihrem eigenen GitHub-Konto.
- Kein Dockerfile und keine Docker-Compose-Datei
- Hat eine Pull-Request-Vorlage
- Beitragsleitfaden lesen
Erste Schritte
- Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
- Forken Sie das Repository und arbeiten Sie in einem Branch.
- Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.
Mehr aus google/adk-java
-
GeminiUtil placeholder user turn ("Continue output. DO NOT look at this line ...") is flagged by prompt injection filtersEvtl. vergeben @hemasekhar-p hat das vor 11 Tagen übernommen. Offenneeds review
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 76/100
google/adk-java#1628 · 1 Kommentar · 1 zugewiesene Person ·
Maintainer antworten meist innerhalb von 2 Tagen
-
runLive passes partial events to appendEvent and never aggregates text chunks, unlike ADK PythonEvtl. vergeben @hemasekhar-p hat das vor 7 Tagen übernommen. Offenneeds review
Schwierigkeit 4/5 3-5 Tage Anfängerfreundlichkeit 45/100
google/adk-java#1648 · 3 Kommentare · 1 zugewiesene Person ·
Maintainer antworten meist innerhalb von 2 Tagen
-
FirestoreSessionService compares event timestamps as text, reordering or skipping same-second eventsEvtl. vergeben @hemasekhar-p hat das vor 7 Tagen übernommen. Offenneeds review
Schwierigkeit 3/5 1-2 Tage Anfängerfreundlichkeit 50/100
google/adk-java#1646 · 3 Kommentare · 1 zugewiesene Person ·
Maintainer antworten meist innerhalb von 2 Tagen
-
FirestoreSessionService ignores `app:` / `user:` state prefixes, so app and user state are never shared across sessionsEvtl. vergeben @hemasekhar-p hat das vor 7 Tagen übernommen. Offenwaiting on reporter
Schwierigkeit 4/5 3-5 Tage Anfängerfreundlichkeit 45/100
google/adk-java#1645 · 4 Kommentare · 1 zugewiesene Person ·
Maintainer antworten meist innerhalb von 2 Tagen
-
FirestoreSessionService loses event fields on reload, breaking later turns and tool confirmationsEvtl. vergeben @hirematha hat das vor 7 Tagen übernommen. Offenneeds review
Schwierigkeit 4/5 3-5 Tage Anfängerfreundlichkeit 22/100
google/adk-java#1640 · 1 Kommentar · 1 zugewiesene Person ·
Maintainer antworten meist innerhalb von 2 Tagen
Alle Issues in google/adk-java
Ähnliche Issues
-
status: waiting-for-triage
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 79/100
spring-projects/spring-ai#7206 ·
Maintainer antworten meist innerhalb von 2 Tagen
-
area/cli
Schwierigkeit 2/5 Ein halber Tag Anfängerfreundlichkeit 78/100
quarkusio/quarkus#57623 · 1 Kommentar ·
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 72/100
jhipster/prettier-java#1113 ·
Maintainer antworten meist innerhalb von 2 Tagen
-
bug follow-up lane:backlog
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 82/100
Sanne/incus-spawn#1576 · 1 Kommentar ·
Maintainer antworten meist innerhalb von 1 Tag
-
Pasting code into a comment adds imports for the pasted codeEvtl. vergeben @ravnskjaer hat das heute übernommen. Offen
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 79/100
eclipse-jdt/eclipse.jdt.ui#3275 · 1 Kommentar ·
Maintainer antworten meist innerhalb von 1 Tag