◉GEMINI LABEN
●ARGON — gemini-4-argon が 10/13 に GA。複雑なコーディング・エージェント・数学・科学向けです●DEEP RES — deep-research-pro-preview-12-2025 は 10/23 に停止、残り6日。移行先は deep-research-preview-04-2026●NANO — gemini-nano-banana-2.1 が GA(10/06)。gemini-3.1-flash-image は非推奨で停止日は未発表です●MCP — ツール結果が 8.5万トークンに膨らんだ事例を、ADK で 310 トークンに包んで抑えた報告が出ています●FLASH — gemini-3.7-flash と 3.5-flash は新しい Flash モデルに自動で回されます(10/08)●NEW — クラッシュ報告を毎回ゼロから読ませない — 既知バグ早見表を先に grep し…●ARGON — gemini-4-argon が 10/13 に GA。複雑なコーディング・エージェント・数学・科学向けです●DEEP RES — deep-research-pro-preview-12-2025 は 10/23 に停止、残り6日。移行先は deep-research-preview-04-2026●NANO — gemini-nano-banana-2.1 が GA(10/06)。gemini-3.1-flash-image は非推奨で停止日は未発表です●MCP — ツール結果が 8.5万トークンに膨らんだ事例を、ADK で 310 トークンに包んで抑えた報告が出ています●FLASH — gemini-3.7-flash と 3.5-flash は新しい Flash モデルに自動で回されます(10/08)●NEW — クラッシュ報告を毎回ゼロから読ませない — 既知バグ早見表を先に grep し…
記事一覧/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・ストリーミング双方の例で解説します。

troubleshooting59thought-signaturesthinking7function-calling20gemini-api288

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

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-10-16
models は 200 なのに generateContent だけ 403 になる日 — 切り分けの順番を先に決めておく
モデル一覧の取得は 200 で通るのに、生成リクエストだけが 403 permission_denied で止まるときの切り分け手順です。キー・プロジェクト・請求の順に確かめる小さな診断スクリプトと、リトライしてはいけない理由、サポートへ渡す材料の揃え方をまとめました。
◈ API / SDK2026-06-23
Gemini 3.2 Pro の Function Calling を iOS/Android アプリに統合する実装パターン
Gemini 3.2 Pro の Function Calling を iOS/Android アプリに統合する実装ガイド。SwiftUI・Kotlin 両対応の動くコード例と、個人開発の壁紙アプリで実証した本番設計パターン(コスト・レイテンシ・段階ロールアウト・回帰テスト)を解説します。
◈ API / SDK2026-05-20
Gemini API のストリーミングが本番だけまとめて返ってくる — Cloud Run・Vercel・Cloudflare のバッファリング対処
ローカル開発では一文字ずつ流れるのに本番デプロイ後だけ応答が一括で返ってくる — Cloud Run / Vercel / Cloudflare Workers / Nginx で発生するストリーミングバッファリング問題を、原因と対処パターン別に整理しました。