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-29中級

Gemini APIで『thoughts not present in conversation history』エラーが出る - thought_signatures 欠落の対処手順

Gemini 2.5/3.x のThinkingモードでマルチターンFunction Callingを実装すると突然出る『thoughts not present in conversation history』エラーの原因と、最短5分で動く修正手順をPython SDK・ストリーミング双方の例で解説します。

troubleshooting57thought-signaturesthinking7function-calling20gemini-api279

このエラーに遭遇した方へ

Gemini 2.5 Pro 以降の Thinking モードでマルチターンの Function Calling を実装している方なら、こんなメッセージに足を引っ張られた経験があるのではないでしょうか。

google.api_core.exceptions.InvalidArgument: 400 ...
Thoughts must be present in conversation history when using thinking signature.

公式ドキュメントには「thought_signature を会話履歴に含めて返してください」と一行書かれているだけで、どこをどう直せばよいのか掴みにくい部分です。私自身もエージェントの本番化作業中、ツール呼び出しの2ターン目で突然これに当たり、原因の切り分けに半日溶かしました。

エラーが出る本当の原因

Gemini 2.5/3.x の Thinking モデルは、Function Calling の最中に内部的な「思考」を行い、その結果を parts[].thought_signature(Base64 エンコードされた不透明トークン)として応答に含めます。このトークンには、モデルが次のターンで応答を継続するための内部状態が暗号化されて入っています。

マルチターン会話で次のリクエストを送るときに、この thought_signature を会話履歴の中の関数呼び出しパーツに紐付けたまま API へ送り返す必要があります。逆に言えば、そのまま渡せば良いだけなのですが、以下の場面で「うっかり剥がす」事故が起きます。

  • function_call だけを取り出して履歴を再構築している
  • 自前で Content オブジェクトを dict から組み立て直して送信している
  • レスポンスを JSON で別言語の SDK に渡し、不要そうなフィールドを null で削っている
  • LangChain など外部ラッパーが古いバージョンで parts を再加工している

つまり「履歴を加工しすぎた」ことが原因です。これを最初に頭に入れておくと、調査の方向が定まります。

最短の修正:応答 Content をそのまま append する

google-genai Python SDK での正しい実装は、たった一行の意識で十分です。

import os
from google import genai
from google.genai import types
 
client = genai.Client(api_key=os.environ["GEMINI_API_KEY"])
 
tools = [{
    "function_declarations": [{
        "name": "get_weather",
        "description": "都市の天気を取得します",
        "parameters": {
            "type": "OBJECT",
            "properties": {"city": {"type": "STRING"}},
            "required": ["city"],
        },
    }]
}]
 
config = types.GenerateContentConfig(
    tools=tools,
    thinking_config=types.ThinkingConfig(thinking_budget=1024),
)
 
# 1ターン目
contents = [types.Content(
    role="user",
    parts=[types.Part.from_text(text="東京の天気を調べて要点だけ教えてください")],
)]
res1 = client.models.generate_content(
    model="gemini-2.5-pro", contents=contents, config=config,
)
 
# ⭐ ここがキモ:応答の content をそのまま履歴に追加する
contents.append(res1.candidates[0].content)
 
# function_call を実行して結果を返す
fc = res1.candidates[0].content.parts[0].function_call
tool_result = {"city": fc.args["city"], "temp_c": 18, "summary": "晴れ"}
 
contents.append(types.Content(
    role="user",
    parts=[types.Part.from_function_response(
        name=fc.name, response=tool_result,
    )],
))
 
# 2ターン目(thought_signature が保持されているので成功する)
res2 = client.models.generate_content(
    model="gemini-2.5-pro", contents=contents, config=config,
)
print(res2.text)
# 期待出力: "東京は晴れ、気温は18度です。" のような最終回答

ポイントは contents.append(res1.candidates[0].content) の一行です。応答の Content オブジェクトを分解せずまるごと履歴に渡すことで、parts[].thought_signature が自動的に保持されます。

やりがちな間違いと修正パターン

逆に、以下のように書くと確実にエラーが再現します。

# Bad: function_call だけを抜き出して再構築している
fc = res1.candidates[0].content.parts[0].function_call
contents.append(types.Content(
    role="model",
    parts=[types.Part(function_call=fc)],  # thought_signature が消える
))
# → 次のリクエストで Thoughts must be present ... が出る

修正は単純で、応答全体をそのまま使うだけです。

# Good
contents.append(res1.candidates[0].content)

履歴を JSON にシリアライズしてキャッシュや Redis に保存する設計を取っている場合も、同じ罠にハマります。Pydantic の model_dump_json(exclude_none=True) などで None を一律落としていると、thought_signature 以外でも壊れる場合があります。私は exclude_none=False を使うか、保持すべきフィールドを明示的に列挙する設計に切り替えました。再出現したらまずシリアライズ層を疑うのが早いです。

ストリーミングで詰まったときの集約処理

generate_content_stream でストリームを受け取る構成では、最後のチャンクだけを履歴に積むとほぼ確実に再発します。全チャンクのパーツを集約して最終 Content を組み立ててください。

final_parts = []
function_call = None
 
for chunk in client.models.generate_content_stream(
    model="gemini-2.5-pro", contents=contents, config=config,
):
    if not chunk.candidates or not chunk.candidates[0].content.parts:
        continue
    for part in chunk.candidates[0].content.parts:
        final_parts.append(part)  # thought_signature を含むパーツも漏らさず集める
        if part.function_call:
            function_call = part.function_call
 
contents.append(types.Content(role="model", parts=final_parts))

Gemini 3.x では thought_signature が応答の途中チャンクに入ってくるケースが増えており、最後のチャンクだけ拾うと欠落します。集約処理を抜いた瞬間に再発するので、ストリーミング実装を疑うときはここを最初に確認してください。

REST API を直接叩いている場合の落とし穴

Curl や fetch で generateContent を呼んでいる方も、考え方は同じです。レスポンスの candidates[0].contentそのまま次回リクエストの contents 配列の末尾に積みます。

# 1ターン目の結果から content を取り出して次のリクエストに混ぜる
RESP1=$(curl -s -H "x-goog-api-key: $GEMINI_API_KEY" \
  -H "Content-Type: application/json" \
  -d @req1.json \
  "https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-pro:generateContent")
 
# jq で content をそのまま抜き、次の contents に追加する
MODEL_TURN=$(echo "$RESP1" | jq '.candidates[0].content')
jq --argjson m "$MODEL_TURN" '.contents += [$m] | .contents += [{
  "role":"user",
  "parts":[{"functionResponse":{"name":"get_weather","response":{"temp_c":18}}}]
}]' req1.json > req2.json

ここで jq 'del(.candidates[0].content.parts[].thoughtSignature)' のように削ってしまうと、まさに今回のエラーが顔を出します。生 JSON を扱う時こそ thoughtSignature フィールドの存在を意識してください。

それでも消えないときのチェックリスト

最後に、上記のいずれを試してもエラーが残る場合のチェックポイントを並べておきます。

  • SDK のバージョンが古くないか(google-genai>=1.0.0 推奨。google-generativeai の旧 SDK は EOL)
  • thinking_config.thinking_budget=0 になっていないか(0 だと thought_signature 自体が発行されないため、履歴に入っていなくてもエラーは出ませんが Thinking の恩恵も消えます)
  • LangChain / LlamaIndex のラッパー側で parts を再構築していないか(両者とも最新版で thought_signature 保持に対応済み。古いバージョンは要更新)
  • Vertex AI と Google AI Studio で履歴を相互流用していないか(thought_signature は発行元エンドポイントでのみ有効で、混在すると署名検証で弾かれます)
  • リトライ層で同じリクエストを別ターンとして扱っていないか(リトライ時は contents をそのまま再送するのが原則)

最後の Vertex AI / Studio 切り替えは意外な落とし穴です。開発中に Studio で取った会話履歴をそのまま本番の Vertex AI に流すと弾かれます。履歴は同一エンドポイントで完結させるのが安全です。

次の一手

このエラー、結局のところ「履歴を素直に渡せば消える」のが本質です。応答の content をそのまま contents 配列に append するという一行さえ守れば、9 割の事例は解消します。残りの 1 割はストリーミング集約とエンドポイント混在の 2 点を順番に潰してください。

Function Calling 自体で他にも詰まっている箇所がある方は、症状別に整理した Gemini Function Calling が機能しない原因と解決策 も合わせて読むと早く片付くかもしれません。ツール呼び出しが無限ループに陥る別系統の問題は Gemini API でツール呼び出しが無限ループする時の対処 で扱っています。

シェア

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

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

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

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

関連記事

API / SDK2026-06-01
Gemini 2.5/3 で本文が空なのに finish_reason が MAX_TOKENS になるときの原因と対処
プロンプトはほんの数行なのに、maxOutputTokens を絞った gemini-2.5-flash が空文字を返し finish_reason が MAX_TOKENS になる — 犯人は思考トークンです。原因と3通りの対処を実装コードで整理します。
API / SDK2026-05-30
Gemini 2.5 Pro で thinkingBudget を 0 にすると INVALID_ARGUMENT になる原因と対処
Gemini 2.5 Pro で thinkingBudget を 0 にすると 400 INVALID_ARGUMENT が返る原因を、モデルごとの思考予算レンジの違いから解説します。Pro でレイテンシを抑える正しい書き方と Flash への切り替え判断を Python・JavaScript のコード付きで紹介します。
API / SDK2026-05-09
Gemini APIで tools と responseSchema を同時指定すると400になる — Function Callingと構造化出力を両立する3つの設計
Function Callingで外部APIを呼びつつ、最終回答は決まった形のJSONで受け取りたい。tools と responseSchema を両方指定すると400エラーになる仕様の理由と、本番で安定して動く3つの回避設計を実装例つきで解説します。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます
もっと見る →