[spring-ai] Streaming responses ending with CJK punctuation (。!?) are misclassified as partial and never persisted to the session
メンテナーはふだん 1 日以内に返信
評価
- 難易度
- 2/5
- 見積もり時間
- 1〜3時間
- 初心者へのやさしさ
- 84/100
調査の方向性
contrib/spring-ai/src/main/java/com/google/adk/models/springai/MessageConverter.java のメソッド isPartialResponse から始めてください。これは ASCII の . ! ? と改行のみをチェックします。Issue の最小再現は、ChatResponse を 。 で終わらせて MessageConverter#toLlmResponse を直接呼び出すので、それを contrib/spring-ai/src/test 配下のユニットテストに変換し、現在 partial=true を返すことを確認してください。完了とは、。!? で終わる応答が partial=false を生成し、新しいテストとモジュールの既存テストの両方がパスすることを意味します。
索引モデルが issue の本文から書いたものです。
説明
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.
- 主要言語
- Java
- スター
- 1.7k
- フォーク
- 431
- 平均マージ
- 2日 20時間
- マージ済み PR(30日)
- 42
環境構築
このプロジェクトの開発コンテナを、あなたの GitHub アカウントでブラウザ上に起動します。
- Dockerfile・Docker Compose ファイルなし
- プルリクエストのテンプレートあり
- コントリビューションガイドを読む
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
google/adk-java のほかの issue
-
[spring-ai] ToolConverter silently drops enum and items from tool parameter schemas対応中かも このイシューにリンクされたプルリクエストがオープン中、またはマージ済みです。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 76/100
google/adk-java#1609 · コメント 1 件 · 担当者 1 名 ·
メンテナーはふだん 1 日以内に返信
-
[spring-ai] Bridge drops reasoning_content (thinking) — surface it as partial events and/or persist it対応中かも @hemasekhar-p が 1 日前に担当しました。 オープンneeds review
google/adk-java#1616 · コメント 1 件 · 担当者 1 名 ·
メンテナーはふだん 1 日以内に返信
-
needs review
難易度 5/5 1週間以上 初心者へのやさしさ 35/100
google/adk-java#1598 · コメント 1 件 · 担当者 1 名 ·
メンテナーはふだん 1 日以内に返信
-
BaseLlmFlow nests each step inside the previous one and overflows the stack after a few hundred LLM calls対応中かも @hemasekhar-p が 8 日前に担当しました。 オープンneeds review
難易度 4/5 3〜5日 初心者へのやさしさ 68/100
google/adk-java#1564 · コメント 1 件 · 担当者 1 名 ·
メンテナーはふだん 1 日以内に返信
-
Approved tool call re-runs on every later user turn if it never got a function response対応中かも @hemasekhar-p が 11 日前に担当しました。 オープンneeds review
google/adk-java#1556 · コメント 1 件 · 担当者 1 名 ·
メンテナーはふだん 1 日以内に返信
google/adk-java の issue をすべて見る
似ている issue
-
難易度 2/5 1〜3時間 初心者へのやさしさ 70/100
commonmark/commonmark-java#460 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 86/100
-
1.21.1见幽匿感测体就崩溃オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 75/100
-
Make branch and label autocomplete matching locale-independent対応中かも このイシューにリンクされたプルリクエストがオープン中、またはマージ済みです。 オープン
難易度 2/5 1〜3時間 初心者へのやさしさ 83/100
jenkinsci/gitlab-plugin#1950 ·
-
難易度 2/5 1〜3時間 初心者へのやさしさ 65/100
commons-app/apps-android-commons#6984 ·
メンテナーはふだん 2 日以内に返信