Google GenAI SDK のストリーミングでマルチターン画像編集🍌が不安定になる問題と対処法

Google GenAI SDK のストリーミングでマルチターン画像編集🍌が不安定になる問題と対処法

こんにちは!

Gemini 3 Pro Image (Nano banana Pro)を使ったマルチターン画像編集機能を実装していたところ、動いたり動かなかったりするという厄介な問題に遭遇しました。

本記事では、この問題の現象、原因調査の過程、そして解決策を共有します。


問題の現象

実行環境

Google GenAI SDKライブラリ(pip): google-genai 1.56.0

期待する動作

  1. ユーザー: 「かわいい子猫の画像を生成して」
  2. Gemini: 子猫の画像を生成
  3. ユーザー: 「この子にメガネをかけて」
  4. Gemini: 同じ子猫にメガネをかけた画像を生成

実際に起きた現象

  1. ユーザー: 「かわいい子猫の画像を生成して」
  2. Gemini: 茶色の子猫の画像を生成
  3. ユーザー: 「この子にメガネをかけて」
  4. Gemini: メガネをかけた女の子の画像を生成
あれれ、メガネをかけた子猫になるはずが、メガネをかけた女の子の画像が生成されてしまった

つまり、前回生成した画像を「覚えていない」状態になっていました。

厄介だったのは「再現性のなさ」

この問題が特に厄介だったのは、動いたり動かなかったりするという点でした。

  • 同じコードなのに、タイミングによって成功したり失敗したり、と挙動が変わる
  • サーバー再起動したら、タイミングからは長時間動作しなくなる
  • 開発環境では動いたが、ステージングでは動かない

同一コードで急に動かなくなると、「いったん再起動しよう」などとりあえずやってしまうと、環境の固定が崩れてしまい、問題の切り分け難しくなり「さっきまで動いてたのに...」という状況が発生し、原因特定に時間がかかりました。


原因調査

thought_signature の仕組み

Gemini 3 Pro Image のマルチターン画像編集は、thought_signature という仕組みに依存しています。

  • 画像生成時に、モデルは thought_signature を返す
  • これは生成した画像の情報(構図、色、内容など)を保持する約2MBのデータ
  • 次のターンでこれを渡すことで、前回の画像を「覚えている」状態になる

Google の公式ドキュメントによると

If you use the official Google Gen AI SDKs and use the chat feature, thought signatures are handled automatically.

ということで、つまり、SDK のチャット機能を使えば自動管理されるはず...でした。
この thought_signature という仕組みをつかえば、テキストチャットで行う毎回それまでのすべての履歴を送信する、ということを避けることができます。

SDK のチャットセッション

私たちのアプローチでは Google GenAI SDK の client.aio.chats.create() でチャットセッションを作成し、chat.send_message_stream() でメッセージを送信していました。

# チャットセッション作成
chat = client.aio.chats.create(model="gemini-3-pro-preview", config=config)

# メッセージ送信(ストリーミング)
response_stream = await chat.send_message_stream(content_parts)
async for response in response_stream:
    # レスポンス処理
    ...

ドキュメント通りなら、これで thought_signature は自動管理されるはず。しかし実際には動作しませんでした。

GitHub Issue #1791 の発見

調査を進める中で、GitHub で関連する issue を発見しました。

[Bug] ChatSession history fragmentation when using send_message_stream with Thinking (Gemini 3 Pro)
https://github.com/googleapis/python-genai/issues/1791

当該issue

この issue によると

When using Gemini 3 Pro Preview with thinking_config enabled, the ChatSession history becomes fragmented when using send_message_stream. Instead of appending a single model turn with the complete response, the SDK appends multiple model turns corresponding to the streaming chunks.

つまり、send_message_stream() を使うと、チャット履歴が断片化されてしまうというバグが報告されていました。

期待値

[User, Model]  # 2エントリ

実際

[User, Model, Model, Model, Model, ...]  # 複数のModelエントリ

ストリーミングのチャンクごとに履歴エントリが追加されてしまい、会話構造が壊れるとのこと。

「動いたり動かなかったり」の理由

この issue を読んで、「動いたり動かなかったり」の理由が推測できました。

  1. 同じサーバーインスタンス内で連続してリクエストすると、セッションがメモリ上に残っているため動くことがある
  2. サーバー再起動新しいセッションでは、壊れた履歴から再開しようとして動かない
  3. タイミングやネットワーク状況によって、履歴の断片化の程度が変わる

これが再現性のない挙動の原因でした。


解決策

非ストリーミング版を使う

issue #1791 を参考に、send_message_stream() の代わりに send_message() を使うことにしました。

# 修正前(ストリーミング)
response_stream = await chat.send_message_stream(content_parts)
async for response in response_stream:
    # 処理
    ...

# 修正後(非ストリーミング)
response = await chat.send_message(content_parts)
# 処理
...

結果

非ストリーミング版に変更したところ、マルチターン画像編集が安定して動作するようになりました。

  • 子猫を生成 → 同じ子猫にメガネを追加
今度は、ちゃんと、元の子猫にメガネが装着されました
  • 何度試しても同じように​
  • サーバー再起動後も動作

まとめ

問題

Google GenAI SDK の send_message_stream() を使うと、チャット履歴が断片化され、thought_signature が正しく管理されない。

影響

Gemini 3 Pro Image のマルチターン画像編集が不安定になる(動いたり動かなかったりする)。

解決策

send_message_stream() の代わりに send_message() を使う。

副作用

  • リアルタイムのストリーミング表示ができなくなる(文章+SVG出力などでの逐次表示に影響)
  • 画像生成完了まで結果が返ってこない
  • ただし、進捗表示(「処理中です...」など)のサブメッセージを別途実装すれば UX への影響は最小限で済む

今後

  • SDK のバグ修正を待つ
  • issue #1791 の進捗を監視
  • 修正されたらストリーミング版に戻すことを検討

参考リンク


最後に

同一コードで「動いたり動かなかったりする」バグは、原因特定が非常に難しいですね。今回のケースでは、SDK の内部動作を疑うまでに時間がかかりました。

同じ問題で困っている方の参考になれば幸いです。

それでは、次回またお会いしましょう!

Read more

Python と JavaScript で絵文字の文字数が違う!サロゲートペアが引き起こす位置ずれバグの話

Python と JavaScript で絵文字の文字数が違う!サロゲートペアが引き起こす位置ずれバグの話

こんにちは! Qualitegプロダクト開発部です! PII(個人情報)検出のデモアプリを開発していて、検出したエンティティの位置をハイライト表示する機能を実装していました。 バックエンドは Python(FastAPI)、フロントエンドは JavaScript という構成です。 ある日、テストデータにこんなメール文面を使ったところ、ハイライトの位置が途中から微妙にずれるバグに遭遇しました。 鈴木一郎 様 いつもお世話になっております。 サンプル商事の佐藤でございます。 先日の件、確認が取れましたのでご連絡いたします。 お忙しいところ恐縮ですが、ご確認のほど宜しくお願い致します。 💻 #オンラインでのお打ち合わせ、お気軽に声がけください! ―――――――――――――――――――――――――――――― サンプル商事株式会社 営業部 第一課 山田 太郎 (Yamada Taro) 〒100-0001 東京都千代田区千代田1-1-1 サンプルビル 3F tel: 03-1234-5678 https://example.com/contact 検出結果をハイライト表示

By Qualiteg プロダクト開発部
大企業のAIセキュリティを支える基盤技術 - 今こそ理解するActive Directory 第5回 ブラウザ設定と認証

大企業のAIセキュリティを支える基盤技術 - 今こそ理解するActive Directory 第5回 ブラウザ設定と認証

こんにちは、今回はシリーズ第5回「ブラウザ設定と認証」について解説いたします! さて、前回(第4回)では、プロキシサーバーをドメインに参加させることで、ChatGPTやClaudeへのアクセスを「誰が」行ったかを確実に特定する仕組みを解説しました。「信頼の連鎖」の概念や、Windows版Squidなら1時間で構築できる環境、Negotiate/NTLM/Basicという3段階の認証フォールバック機構について理解いただけたかと思います。 しかし、せっかくサーバー側で完璧な統合Windows認証環境を構築しても、ブラウザ側の設定が適切でなければ、ユーザーには毎回パスワード入力ダイアログが表示されてしまいます。 「Edgeだと自動でログインできるのに、Chromeだとパスワードを聞かれる」 「同じサーバーなのにURLの書き方で動作が違う」 これらはヘルプデスクに寄せられる典型的な問い合わせです。(ただ、業務に好きなブラウザ使っていいよ、という企業はそんなに多くはないとおもいます) 今回は、統合Windows認証がブラウザでどのように動作するのか、その仕組みから各ブラウザ(Edge/

By Qualiteg AIセキュリティチーム, Qualiteg コンサルティング
スライドパズルを解くAIから学ぶ、「考える」の正体

スライドパズルを解くAIから学ぶ、「考える」の正体

こんにちは! 「このパズル、AIの教科書に載ってるらしいよ」 子供の頃に遊んだスライドパズル。いや、大人が遊んでも楽しいです。 数字のタイルをカチャカチャ動かして揃えるあれです。実はこのシンプルなパズルが、AI研究の出発点のひとつだったって知ってました? 今回は、このパズルを題材に「AIがどうやって考えているのか」を解き明かしていきます。しかも、ここで使われている手法は、Google Mapsの経路探索からChatGPTまで、現代の様々な技術のベースになっているんです。 まず遊んでみよう 理屈の前に、まずは感覚を思い出してみてください。 最初に shuffle をクリックすると、配置がシャッフルされゲームを開始できます。 ちなみに必ず解くことができるようになっていますが、慣れていないとそれなりに難しいかもしれません。 どうでしょう? 何手でクリアできましたか? クリアできなくても大丈夫です。記事後半で、実際にAIが解いてくれる機能つきゲームも掲載しています^^ 以下は動画です。本ブログで紹介するアルゴリズムで実際にパズルを解く様子をご覧いただけます

By Qualiteg 研究部
楽観的ロック vs 悲観的ロック:実際のトラブルから学ぶ排他制御

楽観的ロック vs 悲観的ロック:実際のトラブルから学ぶ排他制御

こんにちは! Qualitegプロダクト開発部です! 「楽観的ロックを実装したのに、まだ競合エラーが出るんですけど...」 これは私たちが実際に経験したことです。 本記事では、楽観的ロックと悲観的ロックの違いを、実際に発生したトラブルを通じて解説します。 抽象的な説明ではなく、 「なぜそれが必要なのか」「どんな問題を解決できるのか」 を実感できる内容を目指します。 目次 1. 問題の背景:並列処理で謎のエラー 2. ロックなしの世界:なぜ競合が起きるのか 3. 楽観的ロックの導入:期待と現実 4. 楽観的ロックの限界:解決できなかった問題 5. 悲観的ロックによる解決 6. 実装時のハマりポイント 7. どちらを選ぶべきか:判断基準 8. まとめ 1. 問題の背景:並列処理で謎のエラー 1.1 システムの概要 私たちが開発していたのは、 複数のワークスペースを切り替えて使用するAPIサーバー でした。 当社AI関係のプロダクトの一部だったのですが、結合テスト兼負荷テストを実行すると、まれに発生してしまっていました。 ユーザーは複数のワーキン

By Qualiteg プロダクト開発部