GEMINI LABEN
NANOLITE — Nano Banana 2 Liteが登場しました。Googleで最も速く、最もコスト効率の高いGemini Imageモデルで、軽量な画像生成を安く回したい用途に向いていますOMNIFLASH — Gemini Omni Flashがpublic previewになりました。ネイティブにマルチモーダルなモデルで、企業や開発者が独自の動的な動画ワークフローを構築できますAGENTS — Managed Agentsが拡張されました。background: trueでサーバー側の非同期実行とポーリング、リモートMCPサーバー連携、対話をまたぐ認証情報のリフレッシュに対応しますMEMORY — Memory BankのIngestEvents APIが一般提供になりました。イベントの取り込みとメモリ生成を分離し、コンテンツを継続的にストリームできますTHROUGHPUT — Provisioned Throughputで、同一モデル・同一リージョンに対して最大7件の保留オーダーを提出できるようになりましたDEPRECATE — 画像生成モデルは8月17日に、Gemini Enterprise Agent PlatformのGrok 4.1系は8月20日に停止される予定ですNANOLITE — Nano Banana 2 Liteが登場しました。Googleで最も速く、最もコスト効率の高いGemini Imageモデルで、軽量な画像生成を安く回したい用途に向いていますOMNIFLASH — Gemini Omni Flashがpublic previewになりました。ネイティブにマルチモーダルなモデルで、企業や開発者が独自の動的な動画ワークフローを構築できますAGENTS — Managed Agentsが拡張されました。background: trueでサーバー側の非同期実行とポーリング、リモートMCPサーバー連携、対話をまたぐ認証情報のリフレッシュに対応しますMEMORY — Memory BankのIngestEvents APIが一般提供になりました。イベントの取り込みとメモリ生成を分離し、コンテンツを継続的にストリームできますTHROUGHPUT — Provisioned Throughputで、同一モデル・同一リージョンに対して最大7件の保留オーダーを提出できるようになりましたDEPRECATE — 画像生成モデルは8月17日に、Gemini Enterprise Agent PlatformのGrok 4.1系は8月20日に停止される予定です
記事一覧/API / SDK
API / SDK/2026-04-19中級

Gemini APIのマルチターン会話が壊れる:チャット履歴管理の落とし穴と解決策

Gemini APIでマルチターン会話を実装すると会話が長くなるにつれてトークン超過・応答遅延・文脈消失が起きます。ChatSessionの正しい使い方と履歴管理の実践的な解決策をコード付きで解説します。

gemini-api279troubleshooting57chat4multi-turn2python103context-window3ChatSession

Gemini APIでチャットボットや会話形式のアシスタントを作っていると、最初の数ターンはスムーズなのに、会話が続くにつれてこんな問題が出てきます。

google.api_core.exceptions.InvalidArgument: 400
Request contains too many tokens. Please reduce the input size.

あるいはエラーは出なくても、「10ターンあたりから急に応答が遅くなった」「しばらく話していたら、最初に伝えた情報を忘れた」という経験をされた方は多いのではないでしょうか。

これらはすべて チャット履歴の管理方法 に起因する問題です。ここではマルチターン会話の実装でよく起きる3つの問題と、それぞれの実践的な解決策をコード付きでご紹介します。

なぜ会話が長くなると問題が起きるのか

原因から理解しておきましょう。Gemini API の ChatSession は、会話のたびに 過去のすべてのやりとりを毎回送信 しています。

import google.generativeai as genai
 
genai.configure(api_key="YOUR_GEMINI_API_KEY")
model = genai.GenerativeModel("gemini-2.5-flash")
chat = model.start_chat()
 
# 1ターン目:「こんにちは」だけ送信
response = chat.send_message("こんにちは")
 
# 10ターン目:過去9回分のやりとりを含む大量のテキストが送信される
# 100ターン目:過去99回分……コストも処理時間も線形に増加

chat.history に会話が蓄積されていくため、ターン数が増えるほどトークン数も増加し続けます。軽い雑談ならほとんど問題になりませんが、技術的な説明や長いテキストを含む会話では、予想外に早くトークン上限に近づきます。

問題1:トークン超過エラーが突然発生する

症状

会話の途中で 400 Request contains too many tokens エラーが発生します。直前のやりとりは問題なかったのに、というケースがほとんどです。

解決策:送信前にトークン数を確認する習慣をつける

メッセージを送る前に count_tokens() でチェックすることで、エラーを未然に防げます。

import google.generativeai as genai
 
genai.configure(api_key="YOUR_GEMINI_API_KEY")
model = genai.GenerativeModel("gemini-2.5-flash")
 
def safe_send_message(chat, message, max_tokens=400000):
    """トークン数を確認してからメッセージを送信する"""
    token_info = model.count_tokens(chat.history)
    current_tokens = token_info.total_tokens
    print(f"現在のトークン数: {current_tokens:,}")
 
    if current_tokens > max_tokens:
        print("⚠️ トークン上限に近づいています。古い履歴を削除します。")
        trim_history(chat, max_tokens)
 
    return chat.send_message(message)
 
def trim_history(chat, max_tokens):
    """古いメッセージのペアを削除してトークン数を削減する"""
    while (model.count_tokens(chat.history).total_tokens > max_tokens
           and len(chat.history) >= 2):
        # 最も古いやりとり(user + model の2件)を削除
        # 注意:1件だけ削除すると履歴の構造が壊れるためペアで削除する
        chat.history.pop(0)
        chat.history.pop(0)
 
# 使用例
chat = model.start_chat()
for turn in range(50):
    response = safe_send_message(
        chat, f"ターン{turn + 1}:Gemini APIについて教えてください"
    )
    print(f"Turn {turn + 1}: {response.text[:60]}...")

このコードで重要なのは pop(0) を必ず2回呼ぶことです。履歴は「ユーザーの発言」と「AIの返答」がペアで構成されているため、1件だけ削除すると構造が壊れてエラーになります。

トークン上限超過エラーの詳しい対処法もあわせてご確認ください。

問題2:古いメッセージを削除すると文脈を忘れる

症状

trim_history() で古いメッセージを削除したところ、会話の最初に伝えたはずの情報(名前・目的・条件など)をAIが忘れてしまいました。

これはシンプルな削除戦略の限界です。「会話の最初に伝えた重要情報」が削除対象になってしまうためです。

解決策A:重要な情報は system_instruction に固定する

ユーザー固有の情報やコンテキストは、会話履歴ではなく system_instruction に含めることで、履歴を整理しても失われません。

import google.generativeai as genai
 
genai.configure(api_key="YOUR_GEMINI_API_KEY")
 
# ユーザーの固定情報はsystem_instructionに記述する
system_instruction = """
あなたは丁寧なプログラミングアシスタントです。
 
ユーザー情報:
- 名前: Taro
- 職業: ソフトウェアエンジニア(Python歴3年)
- 目標: Gemini APIを使ったチャットボット開発
 
回答は日本語で、初心者にも分かりやすく説明してください。
"""
 
model = genai.GenerativeModel(
    "gemini-2.5-flash",
    system_instruction=system_instruction
)
chat = model.start_chat()
 
# 会話履歴を整理しても、system_instructionの情報は常に保持される
response = chat.send_message("私の名前と職業を教えてください")
print(response.text)
# → "Taroさん、ソフトウェアエンジニアとして..." と答えてくれる

ユーザー名・目標・言語設定・AIのキャラクター設定など、会話全体を通じて変わらない情報はすべて system_instruction に置くのが基本です。

解決策B:古い会話を要約して圧縮する

変化するコンテキスト情報を保ちながらトークン数を削減したい場合は、古い会話を「要約」に置き換える方法が有効です。

import google.generativeai as genai
 
genai.configure(api_key="YOUR_GEMINI_API_KEY")
model = genai.GenerativeModel("gemini-2.5-flash")
 
def summarize_old_history(history):
    """古い会話を要約する(Flashモデルで低コスト処理)"""
    conversation_text = "\n".join([
        f"{'ユーザー' if msg.role == 'user' else 'AI'}: {msg.parts[0].text}"
        for msg in history
    ])
    # 要約は安価なモデルで処理するとコスト効率が良い
    summary_model = genai.GenerativeModel("gemini-2.5-flash")
    response = summary_model.generate_content(
        f"以下の会話を3〜5文で要約してください。"
        f"ユーザーの重要な情報・決定事項・文脈を必ず含めてください:\n\n{conversation_text}"
    )
    return response.text
 
def compress_history(chat, keep_last_n=4):
    """直近N件以外の古い会話を要約に置き換える"""
    # 直近4ターン(8件)は保持
    if len(chat.history) <= keep_last_n * 2:
        return
 
    old_history = chat.history[:-keep_last_n * 2]
    recent_history = chat.history[-keep_last_n * 2:]
 
    summary = summarize_old_history(old_history)
    print(f"📝 要約完了: {summary[:80]}...")
 
    # 履歴を「要約メッセージ + 直近の会話」で再構成
    chat.history = [
        genai.protos.Content(role="user", parts=[
            genai.protos.Part(text=f"[これまでの会話の要約]\n{summary}")
        ]),
        genai.protos.Content(role="model", parts=[
            genai.protos.Part(text="了解しました。これまでの文脈を踏まえて続けます。")
        ])
    ] + recent_history
 
# 使用例
chat = model.start_chat()
for i in range(20):
    response = chat.send_message(f"質問{i + 1}: Gemini APIのベストプラクティスを教えてください")
    print(f"Turn {i + 1}: {response.text[:50]}...")
 
    # 8ターンごとに圧縮
    if (i + 1) % 8 == 0:
        compress_history(chat, keep_last_n=4)
        tokens = model.count_tokens(chat.history).total_tokens
        print(f"✅ 圧縮後トークン数: {tokens:,}")

問題3:複数ユーザー環境でセッションが混在する

症状

複数のユーザーをサポートするアプリを作ったところ、あるユーザーの会話内容が別のユーザーに見えてしまう(会話が混在する)バグが発生しました。

原因

ChatSession を複数のリクエストで使い回してしまっているケースです。

# ❌ 間違いの例:chat を全ユーザーで共有している
chat = model.start_chat()  # アプリ起動時に一度だけ作成
 
@app.route("/chat", methods=["POST"])
def handle_chat():
    message = request.json["message"]
    response = chat.send_message(message)  # 全ユーザーの会話が混在!
    return {"response": response.text}

解決策:ユーザーごとにセッションを分離する

import google.generativeai as genai
from flask import Flask, request, jsonify
 
genai.configure(api_key="YOUR_GEMINI_API_KEY")
model = genai.GenerativeModel("gemini-2.5-flash")
 
# ユーザーIDをキーにセッションを管理
# 本番環境ではRedisやデータベースへの永続化を推奨
user_sessions: dict[str, genai.ChatSession] = {}
 
def get_or_create_chat(user_id: str) -> genai.ChatSession:
    """ユーザーIDに対応するチャットセッションを返す"""
    if user_id not in user_sessions:
        user_sessions[user_id] = model.start_chat()
        print(f"✅ 新しいセッション作成: {user_id}")
    return user_sessions[user_id]
 
app = Flask(__name__)
 
@app.route("/chat", methods=["POST"])
def handle_chat():
    user_id = request.json.get("user_id")
    message = request.json.get("message")
 
    if not user_id or not message:
        return jsonify({"error": "user_id と message は必須です"}), 400
 
    chat = get_or_create_chat(user_id)
 
    # トークン数が多くなったら自動圧縮
    token_count = model.count_tokens(chat.history).total_tokens
    if token_count > 300000:
        compress_history(chat, keep_last_n=6)
 
    response = chat.send_message(message)
 
    return jsonify({
        "response": response.text,
        "session_turns": len(chat.history) // 2,
        "current_tokens": model.count_tokens(chat.history).total_tokens
    })
 
if __name__ == "__main__":
    app.run(debug=True)

インメモリ管理はサーバー再起動で消えてしまうため、本番では Redis の SET user:{id}:history などに JSON でシリアライズして保存することをおすすめします。

会話履歴をファイルに永続化する

開発段階では、会話をJSONファイルに保存・復元する方法も便利です。

import json
import google.generativeai as genai
 
genai.configure(api_key="YOUR_GEMINI_API_KEY")
model = genai.GenerativeModel("gemini-2.5-flash")
 
def save_history(chat: genai.ChatSession, filepath: str) -> None:
    """会話履歴をJSONファイルに保存する"""
    history_data = [
        {
            "role": msg.role,
            "parts": [p.text for p in msg.parts if hasattr(p, "text")]
        }
        for msg in chat.history
    ]
    with open(filepath, "w", encoding="utf-8") as f:
        json.dump(history_data, f, ensure_ascii=False, indent=2)
    print(f"💾 保存完了: {len(history_data)}件のメッセージ")
 
def load_history(filepath: str) -> genai.ChatSession:
    """保存した履歴からチャットセッションを復元する"""
    try:
        with open(filepath, "r", encoding="utf-8") as f:
            history_data = json.load(f)
 
        history = [
            genai.protos.Content(
                role=msg["role"],
                parts=[genai.protos.Part(text=p) for p in msg["parts"]]
            )
            for msg in history_data
        ]
        chat = model.start_chat(history=history)
        print(f"✅ 復元完了: {len(history)}件のメッセージ")
        return chat
    except FileNotFoundError:
        print("📝 新規会話を開始します")
        return model.start_chat()
 
# 使用例
HISTORY_FILE = "chat_history.json"
 
chat = load_history(HISTORY_FILE)
response = chat.send_message("前回の続きを教えてください")
print(response.text)
 
# セッション終了時に保存
save_history(chat, HISTORY_FILE)

次のステップ

本記事でご紹介した対処法のまとめです。

  • トークン超過エラー → 送信前に count_tokens() でチェックし、上限に近づいたら古いメッセージのペアを削除する
  • 文脈の消失 → 変わらない情報は system_instruction に固定し、変化する文脈は要約で圧縮して保持する
  • セッション混在 → ユーザーIDをキーに ChatSession を分離し、トークン数が増えたら自動圧縮する

同じシステムプロンプトを繰り返し使う場合は、Context Cachingによるコスト最適化も有効です。キャッシュを使えば、共通部分のトークンコストを大幅に削減できます。

また、会話数が増えるとレート制限の問題に直面することもあります。その際はGemini APIのクォータ・429エラーの対処法をあわせてご参照ください。

シェア

お読みいただきありがとうございます

Gemini Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • コピー&ペーストで使える実装コード付き
  • 毎日新しい上級ガイドを追加
  • ¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

API / SDK2026-05-28
Gemini API の chat で send_message ごとに温度やトークン上限が変わらない原因
google-genai SDK の chat セッションで send_message ごとに generationConfig や temperature を渡しているのに、応答の挙動が変わらない・前の設定が引きずられる場合の切り分け手順をまとめました。SDK の設計意図を踏まえた回避策と、現場で使っているラッパー実装まで動くコードで整理しています。
API / SDK2026-05-01
Gemini APIの『contents must alternate between user and model』エラーが消えない理由と直し方
Gemini API で頻発する『contents must alternate between user and model』エラーの正体と、role 順序・assistant 表記・Function Calling 履歴の落とし穴を、すぐ使える修正コード付きで解説します。
API / SDK2026-04-16
Gemini API の System Instructions が効かない・無視される — よくある原因と解決法
Gemini API でSystem Instructionsを設定したのにモデルが無視する、マルチターンで途中から効かなくなる、思い通りの応答が得られない——よくある原因を4パターンにまとめ、コード例と一緒に解消法を紹介します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →